diff --git a/CMakeLists.txt b/CMakeLists.txt index 5470e0c..b53f761 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,8 @@ 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(ui) diff --git a/Cargo.lock b/Cargo.lock index 6194248..1cd7292 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,7 +180,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -354,6 +354,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" @@ -442,7 +474,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -892,7 +924,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1182,6 +1214,8 @@ version = "0.1.0" dependencies = [ "approx", "bytemuck", + "diplomat", + "diplomat-runtime", "glam", "pollster", "rand", @@ -1191,17 +1225,6 @@ dependencies = [ "winit", ] -[[package]] -name = "photon-light-impl" -version = "0.1.0" -dependencies = [ - "glam", - "photon-light", - "pollster", - "raw-window-handle", - "wgpu", -] - [[package]] name = "pin-project" version = "1.1.10" @@ -1219,7 +1242,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1530,7 +1553,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1609,6 +1632,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" @@ -1626,6 +1658,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" @@ -1661,7 +1704,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1672,7 +1715,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -1841,7 +1884,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.114", "wasm-bindgen-shared", ] @@ -2174,7 +2217,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -2185,7 +2228,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] [[package]] @@ -2517,5 +2560,5 @@ checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.114", ] diff --git a/Cargo.toml b/Cargo.toml index 4fbd998..95fe38c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ -[workspace] -members = ["ui"] - [package] name = "photon-light" version = "0.1.0" edition = "2024" +[lib] +crate-type = ["lib", "staticlib"] + [profile.dev] panic = 'abort' @@ -26,6 +26,8 @@ rand_distr = "0.5.1" rand_pcg = "0.9.0" wgpu = "27.0.1" winit = "0.30.12" +diplomat = "0.17.0" +diplomat-runtime = "0.16.1" [dev-dependencies] approx = "0.5.1" diff --git a/diplomat.cmake b/diplomat.cmake new file mode 100644 index 0000000..70a752f --- /dev/null +++ b/diplomat.cmake @@ -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() diff --git a/src/lib.rs b/src/lib.rs index 99e79e9..942ae73 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ use std::{error::Error, f32::consts::PI}; use glam::{Mat4, UVec2, Vec2, Vec3, vec3}; use rand_distr::Distribution as _; +use wgpu::rwh::{RawDisplayHandle, RawWindowHandle}; use crate::{ camera::OrbitalCamera, @@ -16,73 +17,103 @@ use crate::{ trace::{Hit, Lambertian, Reflector, Scene, Source, Sphere}, }; +pub use crate::ffi::{RedrawArgs, SphericalPosition, UseNormal}; + mod camera; +mod iter; mod ray; mod render; mod shape; mod trace; -#[derive(Debug, Clone, Copy, PartialEq)] -#[repr(C)] -pub struct SphericalPosition { - pub yaw: f32, - pub pitch: f32, - pub distance: f32, -} +#[diplomat::bridge] +mod ffi { + use std::{f32::consts::PI, num::NonZero, ptr::NonNull}; -#[derive(Debug, Clone, Copy)] -#[repr(u8)] -pub enum UseNormal { - Light, - Camera, -} + use glam::uvec2; + use wgpu::rwh::{XcbDisplayHandle, XcbWindowHandle}; -#[derive(Debug, Clone, Copy)] -#[repr(C)] -pub struct RedrawArgs { - pub camera_position: SphericalPosition, - pub light_position: SphericalPosition, - pub light_radius: f32, - pub light_spread: f32, - pub accum_sigma: f32, - pub accum_scale: f32, - pub reflections: u8, - pub use_normal: UseNormal, - pub show_axes: bool, - pub show_shapes: bool, - pub show_hit_emission: bool, - pub show_miss_emission: bool, - pub show_direct_hit: bool, - pub show_indirect_hit: bool, - pub show_light: bool, -} + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct SphericalPosition { + pub yaw: f32, + pub pitch: f32, + pub distance: f32, + } -impl Default for RedrawArgs { - fn default() -> Self { - Self { - camera_position: SphericalPosition { - yaw: PI / 4., - pitch: 0.5f32.sqrt().atan(), - distance: 3.0, - }, - light_position: SphericalPosition { - yaw: 0.0, - pitch: PI / 3., - distance: 1.0, - }, - light_radius: 0.125, - light_spread: 0.125, - accum_sigma: 0.025, - accum_scale: 0.01, - reflections: 2, - use_normal: UseNormal::Light, - show_axes: true, - show_shapes: true, - show_hit_emission: false, - show_miss_emission: false, - show_direct_hit: false, - show_indirect_hit: false, - show_light: true, + #[derive(Debug)] + pub enum UseNormal { + Light, + Camera, + } + + #[derive(Debug, Clone, Copy)] + pub struct RedrawArgs { + pub camera_position: SphericalPosition, + pub light_position: SphericalPosition, + pub light_radius: f32, + pub light_spread: f32, + pub accum_sigma: f32, + pub accum_scale: f32, + pub reflections: u8, + pub use_normal: UseNormal, + pub show_axes: bool, + pub show_shapes: bool, + pub show_hit_emission: bool, + pub show_miss_emission: bool, + pub show_direct_hit: bool, + pub show_indirect_hit: bool, + pub show_light: bool, + } + + impl Default for RedrawArgs { + fn default() -> Self { + Self { + camera_position: SphericalPosition { + yaw: PI / 4., + pitch: 0.5f32.sqrt().atan(), + distance: 3.0, + }, + light_position: SphericalPosition { + yaw: 0.0, + pitch: PI / 3., + distance: 1.0, + }, + light_radius: 0.125, + light_spread: 0.125, + accum_sigma: 0.025, + accum_scale: 0.01, + reflections: 2, + use_normal: UseNormal::Light, + show_axes: true, + show_shapes: true, + show_hit_emission: false, + show_miss_emission: false, + show_direct_hit: false, + show_indirect_hit: false, + show_light: true, + } + } + } + + #[diplomat::opaque_mut] + pub struct Core(crate::Core); + + impl Core { + pub fn from_xcb(connection: usize, window: u32, width: u32, height: u32) -> Box { + 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); } } } @@ -93,6 +124,23 @@ pub struct Gpu { surface: wgpu::Surface<'static>, } +impl Gpu { + unsafe fn new_raw( + display: impl Into, + window: impl Into, + ) -> 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 + } +} + pub struct Core { device: wgpu::Device, queue: wgpu::Queue, diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 191380f..abc617e 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -1,7 +1,6 @@ include(impl.cmake) qt_add_executable(photon-light - src/api.cxx src/main.cxx src/main_window.cxx src/main_window.ui diff --git a/ui/Cargo.toml b/ui/Cargo.toml deleted file mode 100644 index e12ba36..0000000 --- a/ui/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "photon-light-impl" -version = "0.1.0" -edition = "2024" - -[lib] -crate-type = ["staticlib"] - -[dependencies] -photon-light = {path = "../"} - -glam = { version = "0.30" } -pollster = "0.4.0" -raw-window-handle = "0.6.2" -wgpu = "27.0.1" diff --git a/ui/impl.cmake b/ui/impl.cmake index de4d266..636739e 100644 --- a/ui/impl.cmake +++ b/ui/impl.cmake @@ -1,19 +1,25 @@ # build the Rust side as a static library -set(impl_basename "${CARGO_TARGET_DIR}/release/libphoton_light_impl") +set(impl_basename "${CARGO_TARGET_DIR}/release/libphoton_light") 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 photon-light-impl + COMMAND env "CARGO_TARGET_DIR=${CARGO_TARGET_DIR}" ${CARGO} build --release --package photon-light WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" DEPFILE "${impl_basename}.d" USES_TERMINAL @@ -31,3 +37,22 @@ add_library(photon_light_impl STATIC IMPORTED) set_target_properties(photon_light_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 "${CMAKE_SOURCE_DIR}/src/lib.rs" + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + VERBATIM + ${diplomat_cmake_args} +) + +target_sources(photon_light_impl INTERFACE ${bindings}) +target_include_directories(photon_light_impl INTERFACE "${bindings_dir}") diff --git a/ui/src/api.cxx b/ui/src/api.cxx deleted file mode 100644 index aaa2b64..0000000 --- a/ui/src/api.cxx +++ /dev/null @@ -1,57 +0,0 @@ -#include "api.hxx" - -#include -#include - -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); -} diff --git a/ui/src/api.hxx b/ui/src/api.hxx deleted file mode 100644 index d46272e..0000000 --- a/ui/src/api.hxx +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -#include - -struct xcb_connection_t; - -namespace ffi { -struct Core; - -struct SphericalPosition { - float yaw = 0.; - float pitch = 0.; - float distance = 1.; -}; - -enum class UseNormal: std::uint8_t { - Light, - Camera, -}; - -struct RedrawArgs { - SphericalPosition camera_position; - SphericalPosition light_position; - float light_radius = 1.; - float light_spread = 0.; - float accum_sigma = 1.; - float accum_scale = 1.; - std::uint8_t reflections = 0; - UseNormal use_normal = UseNormal::Light; - bool show_axes = true; - bool show_shapes = true; - bool show_hit_emission = true; - bool show_miss_emission = true; - bool show_direct_hit = true; - bool show_indirect_hit = true; - bool show_light = true; -}; -} // namespace ffi - -using ffi::UseNormal; -using ffi::RedrawArgs; -using ffi::SphericalPosition; - -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; -}; diff --git a/ui/src/lib.rs b/ui/src/lib.rs deleted file mode 100644 index ed5dce1..0000000 --- a/ui/src/lib.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::{ffi::c_void, num::NonZero, ptr::NonNull}; - -use glam::{UVec2, uvec2}; -use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle}; - -use photon_light::{Core, RedrawArgs, init_gpu_inner}; - -unsafe fn create_viewport( - display: impl Into, - window: impl Into, - size: UVec2, -) -> Box { - 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, - window: NonZero, - width: u32, - height: u32, -) -> Box { - 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) { - 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); -} diff --git a/ui/src/viewport.cxx b/ui/src/viewport.cxx index e681bd9..60d638c 100644 --- a/ui/src/viewport.cxx +++ b/ui/src/viewport.cxx @@ -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()); } diff --git a/ui/src/viewport.hxx b/ui/src/viewport.hxx index 95839b9..5d1d738 100644 --- a/ui/src/viewport.hxx +++ b/ui/src/viewport.hxx @@ -2,7 +2,7 @@ #include -#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; RedrawArgs args = {}; void recreate();