Compare commits

...

5 Commits

Author SHA1 Message Date
784e99b31c move the GPU initialization out of the bridge 2026-10-27 19:18:23 +03:00
46ce52c2bf reduce the unsafe part to the GPU initialization 2026-10-27 19:18:23 +03:00
9742916bd6 move ui/impl.cmake to src/
Now that there is no separate UI-specific wrapper, it belongs to the implementation rather than to the UI, probably
2026-10-27 19:18:23 +03:00
751408ea92 replace custom FFI with Diplomat 2026-10-27 19:18:20 +03:00
1c28c636b7 quote arguments
when it comes to quotes, cmake is quite like bash...
2026-10-27 19:15:39 +03:00
14 changed files with 207 additions and 249 deletions

View File

@ -9,6 +9,9 @@ find_package(Qt6 REQUIRED COMPONENTS Gui Widgets)
find_program(CARGO cargo REQUIRED)
set(CARGO_TARGET_DIR "${CMAKE_SOURCE_DIR}/target" CACHE PATH "Path to the Cargo target directory")
include(diplomat.cmake)
qt_standard_project_setup()
add_subdirectory(src)
add_subdirectory(ui)

87
Cargo.lock generated
View File

@ -7,23 +7,14 @@ name = "PROJECT-NAME"
version = "0.1.0"
dependencies = [
"bytemuck",
"diplomat",
"diplomat-runtime",
"glam",
"pollster",
"wgpu",
"winit",
]
[[package]]
name = "PROJECT-NAME-impl"
version = "0.1.0"
dependencies = [
"PROJECT-NAME",
"glam",
"pollster",
"raw-window-handle",
"wgpu",
]
[[package]]
name = "ab_glyph"
version = "0.2.32"
@ -193,7 +184,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.114",
]
[[package]]
@ -367,6 +358,38 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
[[package]]
name = "diplomat"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a80510e6103d35c5cd031c0e3f21165b32742c301ee3dafdbb5904c6dca46a"
dependencies = [
"diplomat_core",
"proc-macro2",
"quote",
"syn 3.0.9",
]
[[package]]
name = "diplomat-runtime"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9d981121ba76150987fda68185b37853c0b928a55dfdc28897348cd068440be"
[[package]]
name = "diplomat_core"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa5c594efd1d097105255fc325c430a517c97b7b7c69f8727f653593e1ce726"
dependencies = [
"proc-macro2",
"quote",
"serde",
"smallvec",
"strck",
"syn 3.0.9",
]
[[package]]
name = "dispatch"
version = "0.2.0"
@ -455,7 +478,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.114",
]
[[package]]
@ -904,7 +927,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
"syn 2.0.114",
]
[[package]]
@ -1205,7 +1228,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.114",
]
[[package]]
@ -1459,7 +1482,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.114",
]
[[package]]
@ -1538,6 +1561,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strck"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42316e70da376f3d113a68d138a60d8a9883c604fe97942721ec2068dab13a9f"
dependencies = [
"unicode-ident",
]
[[package]]
name = "strict-num"
version = "0.1.1"
@ -1555,6 +1587,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "syn"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.4.1"
@ -1590,7 +1633,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.114",
]
[[package]]
@ -1601,7 +1644,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.114",
]
[[package]]
@ -1770,7 +1813,7 @@ dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"syn 2.0.114",
"wasm-bindgen-shared",
]
@ -2103,7 +2146,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.114",
]
[[package]]
@ -2114,7 +2157,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.114",
]
[[package]]
@ -2446,5 +2489,5 @@ checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.114",
]

View File

@ -1,11 +1,11 @@
[workspace]
members = ["ui"]
[package]
name = "PROJECT-NAME"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["lib", "staticlib"]
[profile.dev]
panic = 'abort'
@ -23,3 +23,6 @@ glam = { version = "0.30.9", features = ["bytemuck"] }
pollster = "0.4.0"
wgpu = "27.0.1"
winit = "0.30.12"
diplomat = "0.17.0"
diplomat-runtime = "0.16.1"

8
diplomat.cmake Normal file
View File

@ -0,0 +1,8 @@
set(crates "${CMAKE_BINARY_DIR}/crates")
set(diplomat "${crates}/bin/diplomat-tool")
if(NOT IS_EXECUTABLE "${diplomat}")
message(CONFIGURE_LOG "Downloading Diplomat...")
file(MAKE_DIRECTORY "${crates}")
execute_process(COMMAND ${CARGO} install --root "${crates}" diplomat-tool@0.17.0)
endif()

63
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,63 @@
# build the Rust side as a static library
set(impl_basename "${CARGO_TARGET_DIR}/release/libPROJECT_NAME")
set(cargo_cmake_args)
set(diplomat_cmake_args)
if("${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.27")
list(APPEND cargo_cmake_args DEPENDS_EXPLICIT_ONLY)
list(APPEND diplomat_cmake_args DEPENDS_EXPLICIT_ONLY)
endif()
if("${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.28")
list(APPEND cargo_cmake_args JOB_SERVER_AWARE TRUE)
endif()
if("${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.31")
list(APPEND diplomat_cmake_args CODEGEN)
endif()
add_custom_command(
OUTPUT "${impl_basename}.a"
COMMAND env "CARGO_TARGET_DIR=${CARGO_TARGET_DIR}" ${CARGO} build --release --package PROJECT-NAME
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
DEPFILE "${impl_basename}.d"
USES_TERMINAL
VERBATIM
${cargo_cmake_args}
)
# HACK ensure CMake *actually adds* the command above
add_custom_target(build_impl
DEPENDS "${impl_basename}.a"
)
add_library(PROJECT_NAME_impl STATIC IMPORTED GLOBAL)
set_target_properties(PROJECT_NAME_impl PROPERTIES
IMPORTED_LOCATION "${impl_basename}.a"
)
# generate the bindings
set(bindings_dir "${CMAKE_CURRENT_BINARY_DIR}/include")
file(MAKE_DIRECTORY "${bindings_dir}")
set(bindings
"${bindings_dir}/Core.hpp"
)
add_custom_command(
OUTPUT ${bindings}
COMMAND ${diplomat} cpp "${bindings_dir}"
MAIN_DEPENDENCY "lib.rs"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
VERBATIM
${diplomat_cmake_args}
)
# HACK ensure CMake *actually adds* the command above
add_custom_target(codegen_impl
DEPENDS ${bindings}
)
target_sources(PROJECT_NAME_impl INTERFACE ${bindings})
target_include_directories(PROJECT_NAME_impl INTERFACE "${bindings_dir}")

View File

@ -1,21 +1,15 @@
use std::error::Error;
use glam::{Mat4, UVec2, Vec3, Vec4, vec3, vec4};
use wgpu::rwh::{RawDisplayHandle, RawWindowHandle};
pub use crate::ffi::RedrawArgs;
mod camera;
mod render;
const BACKGROUND: Vec4 = vec4(0.00, 0.39, 0.52, 1.00);
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct RedrawArgs {
pub cam_yaw: f32,
pub cam_pitch: f32,
pub cam_distance: f32,
pub cam_fovy: f32,
}
impl Default for RedrawArgs {
fn default() -> Self {
Self {
@ -252,3 +246,59 @@ pub async fn init_gpu_inner<E: Error + 'static>(
surface,
})
}
impl Gpu {
unsafe fn new_raw(
display: impl Into<RawDisplayHandle>,
window: impl Into<RawWindowHandle>,
) -> Self {
let target = wgpu::SurfaceTargetUnsafe::RawHandle {
raw_display_handle: display.into(),
raw_window_handle: window.into(),
};
let gpu = pollster::block_on(init_gpu_inner(|instance| unsafe {
instance.create_surface_unsafe(target)
}))
.unwrap();
gpu
}
}
#[diplomat::bridge]
mod ffi {
use std::{num::NonZero, ptr::NonNull};
use glam::uvec2;
use wgpu::rwh::{XcbDisplayHandle, XcbWindowHandle};
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct RedrawArgs {
pub cam_yaw: f32,
pub cam_pitch: f32,
pub cam_distance: f32,
pub cam_fovy: f32,
}
#[diplomat::opaque_mut]
pub struct Core(crate::Core);
impl Core {
pub fn from_xcb(connection: usize, window: u32, width: u32, height: u32) -> Box<Core> {
let display =
XcbDisplayHandle::new(Some(NonNull::new(connection as *mut _).unwrap()), 0);
let window = XcbWindowHandle::new(NonZero::new(window).unwrap());
let gpu = unsafe { crate::Gpu::new_raw(display, window) };
let size = uvec2(width, height);
Box::new(Self(crate::Core::new(gpu, size)))
}
pub fn configure(&mut self, width: u32, height: u32) {
self.0.configure(uvec2(width, height));
}
pub fn redraw(&mut self, args: &RedrawArgs) {
self.0.redraw(args);
}
}
}

View File

@ -1,7 +1,4 @@
include(impl.cmake)
qt_add_executable(PROJECT-NAME
src/api.cxx
src/main.cxx
src/main_window.cxx
src/main_window.ui

View File

@ -1,15 +0,0 @@
[package]
name = "PROJECT-NAME-impl"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["staticlib"]
[dependencies]
PROJECT-NAME = {path = "../"}
glam = { version = "0.30" }
pollster = "0.4.0"
raw-window-handle = "0.6.2"
wgpu = "27.0.1"

View File

@ -1,33 +0,0 @@
# build the Rust side as a static library
set(impl_basename "${CARGO_TARGET_DIR}/release/libPROJECT_NAME_impl")
set(cargo_cmake_args)
if("${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.27")
list(APPEND cargo_cmake_args DEPENDS_EXPLICIT_ONLY)
endif()
if("${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.28")
list(APPEND cargo_cmake_args JOB_SERVER_AWARE TRUE)
endif()
add_custom_command(
OUTPUT ${impl_basename}.a
COMMAND env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} ${CARGO} build --release --package PROJECT-NAME-impl
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPFILE ${impl_basename}.d
USES_TERMINAL
VERBATIM
${cargo_cmake_args}
)
# HACK ensure CMake *actually adds* the command above
add_custom_target(build_impl
DEPENDS ${impl_basename}.a
)
add_library(PROJECT_NAME_impl STATIC IMPORTED)
set_target_properties(PROJECT_NAME_impl PROPERTIES
IMPORTED_LOCATION ${impl_basename}.a
)

View File

@ -1,57 +0,0 @@
#include "api.hxx"
#include <stdexcept>
#include <utility>
namespace ffi {
extern "C" Core* rt4_viewport_create(xcb_connection_t* connection, std::uint32_t window, std::uint32_t width, std::uint32_t height);
extern "C" void rt4_viewport_destroy(Core* viewport);
extern "C" void rt4_viewport_configure(Core* viewport, std::uint32_t width, std::uint32_t height);
extern "C" void rt4_viewport_redraw(Core* viewport, const RedrawArgs* args);
} // namespace ffi
BoxCore::BoxCore(BoxCore&& b)
: ptr(std::exchange(b.ptr, {})) {
}
BoxCore::~BoxCore() {
reset();
}
BoxCore& BoxCore::operator=(BoxCore&& b) {
if (&b == this)
return *this;
std::swap(ptr, b.ptr);
b.reset();
return *this;
}
BoxCore BoxCore::from_xcb(xcb_connection_t* connection, std::uint32_t window, std::uint32_t width, std::uint32_t height) {
if (!connection)
throw std::logic_error("attempt to use a null connection");
if (!window)
throw std::logic_error("attempt to use a null window");
BoxCore out;
out.ptr.ptr = ffi::rt4_viewport_create(connection, window, width, height);
return out;
}
void BoxCore::reset() {
auto viewport = std::exchange(ptr, {});
if (viewport)
ffi::rt4_viewport_destroy(viewport.use());
}
ffi::Core* MutCore::use() const {
if (!ptr)
throw std::logic_error("attempt to use a null Core");
return ptr;
}
void MutCore::configure(std::uint32_t width, std::uint32_t height) const {
rt4_viewport_configure(use(), width, height);
}
void MutCore::redraw(const RedrawArgs& args) const {
rt4_viewport_redraw(use(), &args);
}

View File

@ -1,56 +0,0 @@
#pragma once
#include <cstdint>
struct xcb_connection_t;
struct alignas(16) Vec4 {
float x, y, z, w;
};
namespace ffi {
struct Core;
struct RedrawArgs {
float cam_yaw;
float cam_pitch;
float cam_distance;
float cam_fovy;
};
} // namespace ffi
using ffi::RedrawArgs;
class MutCore {
friend class BoxCore;
public:
explicit operator bool() const { return ptr; }
void configure(std::uint32_t width, std::uint32_t height) const;
void redraw(const RedrawArgs& args) const;
private:
ffi::Core* ptr = nullptr;
ffi::Core* use() const;
};
class BoxCore {
public:
BoxCore() = default;
BoxCore(const BoxCore&) = delete;
BoxCore(BoxCore&&);
BoxCore& operator=(const BoxCore&) = delete;
BoxCore& operator=(BoxCore&&);
~BoxCore();
explicit operator bool() const { return !!ptr; }
const MutCore* operator->() const { return &ptr; }
void reset();
static BoxCore from_xcb(xcb_connection_t* connection, std::uint32_t window, std::uint32_t width, std::uint32_t height);
private:
MutCore ptr;
};

View File

@ -1,48 +0,0 @@
use std::{ffi::c_void, num::NonZero, ptr::NonNull};
use PROJECT_NAME::{Core, RedrawArgs, init_gpu_inner};
use glam::{UVec2, uvec2};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle};
unsafe fn create_viewport(
display: impl Into<RawDisplayHandle>,
window: impl Into<RawWindowHandle>,
size: UVec2,
) -> Box<Core> {
let target = wgpu::SurfaceTargetUnsafe::RawHandle {
raw_display_handle: display.into(),
raw_window_handle: window.into(),
};
let gpu = pollster::block_on(init_gpu_inner(|instance| unsafe {
instance.create_surface_unsafe(target)
}))
.unwrap();
Box::new(Core::new(gpu, size))
}
#[unsafe(no_mangle)]
unsafe extern "C" fn rt4_viewport_create(
connection: NonNull<c_void>,
window: NonZero<u32>,
width: u32,
height: u32,
) -> Box<Core> {
let display = XcbDisplayHandle::new(Some(connection), 0);
let window = XcbWindowHandle::new(window);
unsafe { create_viewport(display, window, uvec2(width, height)) }
}
#[unsafe(no_mangle)]
unsafe extern "C" fn rt4_viewport_destroy(viewport: Box<Core>) {
drop(viewport);
}
#[unsafe(no_mangle)]
unsafe extern "C" fn rt4_viewport_configure(viewport: &mut Core, width: u32, height: u32) {
viewport.configure(uvec2(width, height));
}
#[unsafe(no_mangle)]
unsafe extern "C" fn rt4_viewport_redraw(viewport: &mut Core, args: &RedrawArgs) {
viewport.redraw(args);
}

View File

@ -63,7 +63,7 @@ void Viewport::recreate() try {
const QSize device_size = size() * devicePixelRatio();
core.reset();
core = BoxCore::from_xcb(xcb_connection, x11_window, device_size.width(), device_size.height());
core = Core::from_xcb((std::uintptr_t)xcb_connection, x11_window, device_size.width(), device_size.height());
} catch (const std::exception& e) {
fprintf(stderr, "failed to recreate the viewport: %s", e.what());
}

View File

@ -2,7 +2,7 @@
#include <QWidget>
#include "api.hxx"
#include "Core.hpp"
class Viewport : public QWidget {
Q_OBJECT
@ -20,7 +20,7 @@ protected:
void resizeEvent(QResizeEvent* event) override;
private:
BoxCore core;
std::unique_ptr<Core> core;
RedrawArgs args = {};
void recreate();