From 707bb6f66ed8077fb98e79fd7fc4de657bdd90e1 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 23 Nov 2025 13:29:24 +0300 Subject: [PATCH 01/32] embed the simple shader --- src/render/lines.rs | 3 +-- src/render/mod.rs | 2 ++ shaders/line.wgsl => src/render/simple.wgsl | 0 3 files changed, 3 insertions(+), 2 deletions(-) rename shaders/line.wgsl => src/render/simple.wgsl (100%) diff --git a/src/render/lines.rs b/src/render/lines.rs index 1b5f3fc..493023e 100644 --- a/src/render/lines.rs +++ b/src/render/lines.rs @@ -57,10 +57,9 @@ impl Pipeline { mapped_at_creation: false, }); - let shader = std::fs::read_to_string("shaders/line.wgsl").unwrap(); let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: None, - source: wgpu::ShaderSource::Wgsl(shader.into()), + source: wgpu::ShaderSource::Wgsl(super::SIMPLE_SHADER.into()), }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, diff --git a/src/render/mod.rs b/src/render/mod.rs index ef14730..249b8bb 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1 +1,3 @@ pub mod lines; + +static SIMPLE_SHADER: &str = include_str!("simple.wgsl"); diff --git a/shaders/line.wgsl b/src/render/simple.wgsl similarity index 100% rename from shaders/line.wgsl rename to src/render/simple.wgsl -- 2.47.3 From fb8ea92ce869865617fee0ca863b5510f2aa8ad8 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 23 Nov 2025 21:02:38 +0300 Subject: [PATCH 02/32] fix double-accounting for surface slope --- src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8b7384d..a6a1101 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -309,9 +309,7 @@ impl Core { assert!(hit.incident.dir.is_normalized()); let reflector = Lambertian; let in_lm = 1.0; - let out_cd = in_lm - * hit.normal.dot(-hit.incident.dir) - * reflector.brdf(hit.normal, hit.incident.dir, -ray.dir); + let out_cd = in_lm * reflector.brdf(hit.normal, hit.incident.dir, -ray.dir); let weight = accum_normalizator * (-0.5 * d2 / sigma2).exp(); total_cd += weight * out_cd; } -- 2.47.3 From a51f46a038762a949c5a4ef7f12cff9f879f8dc2 Mon Sep 17 00:00:00 2001 From: numzero Date: Mon, 24 Nov 2025 00:19:28 +0300 Subject: [PATCH 03/32] fix a bug it's invisible with Lambertian model though --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a6a1101..f2f9c39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -309,7 +309,8 @@ impl Core { assert!(hit.incident.dir.is_normalized()); let reflector = Lambertian; let in_lm = 1.0; - let out_cd = in_lm * reflector.brdf(hit.normal, hit.incident.dir, -ray.dir); + let out_cd = + in_lm * reflector.brdf(light_hit.normal, light_hit.incident.dir, -ray.dir); let weight = accum_normalizator * (-0.5 * d2 / sigma2).exp(); total_cd += weight * out_cd; } -- 2.47.3 From 4b138deb34ef04df546dd1ebb49d3f2c98709fa1 Mon Sep 17 00:00:00 2001 From: numzero Date: Mon, 24 Nov 2025 00:21:06 +0300 Subject: [PATCH 04/32] allow choosing which normals to use --- src/lib.rs | 19 +++++++++++--- src/main.rs | 3 ++- ui/src/api.hxx | 9 ++++++- ui/src/main_window.cxx | 13 +++++++++- ui/src/main_window.hxx | 1 + ui/src/main_window.ui | 57 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 95 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f2f9c39..18937dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,13 @@ pub struct SphericalPosition { pub distance: f32, } +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum UseNormal { + Light, + Camera, +} + #[derive(Debug, Clone, Copy)] #[repr(C)] pub struct RedrawArgs { @@ -36,7 +43,8 @@ pub struct RedrawArgs { pub light_spread: f32, pub accum_sigma: f32, pub accum_scale: f32, - pub reflections: u32, + pub reflections: u8, + pub use_normal: UseNormal, pub show_axes: bool, pub show_shapes: bool, pub show_hit_emission: bool, @@ -305,12 +313,15 @@ impl Core { if d2 > 9. * sigma2 { continue; } - assert!(hit.normal.is_normalized()); + let normal = match args.use_normal { + UseNormal::Light => light_hit.normal, + UseNormal::Camera => hit.normal, + }; + assert!(normal.is_normalized()); assert!(hit.incident.dir.is_normalized()); let reflector = Lambertian; let in_lm = 1.0; - let out_cd = - in_lm * reflector.brdf(light_hit.normal, light_hit.incident.dir, -ray.dir); + let out_cd = in_lm * reflector.brdf(normal, light_hit.incident.dir, -ray.dir); let weight = accum_normalizator * (-0.5 * d2 / sigma2).exp(); total_cd += weight * out_cd; } diff --git a/src/main.rs b/src/main.rs index f08bf16..cb11049 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::{f32::consts::PI, sync::Arc}; use glam::uvec2; -use photon_light::{Core, RedrawArgs, SphericalPosition, init_gpu_inner}; +use photon_light::{Core, RedrawArgs, SphericalPosition, UseNormal, init_gpu_inner}; use winit::{ application::ApplicationHandler, event::WindowEvent, @@ -53,6 +53,7 @@ impl MainWindow { accum_sigma: 0.025, accum_scale: 0.01, reflections: 2, + use_normal: UseNormal::Light, show_axes: true, show_shapes: true, show_hit_emission: false, diff --git a/ui/src/api.hxx b/ui/src/api.hxx index caf32cd..bc96bc8 100644 --- a/ui/src/api.hxx +++ b/ui/src/api.hxx @@ -13,6 +13,11 @@ struct SphericalPosition { float distance = 1.; }; +enum class UseNormal: std::uint8_t { + Light, + Camera, +}; + struct RedrawArgs { SphericalPosition camera_position; SphericalPosition light_position; @@ -20,7 +25,8 @@ struct RedrawArgs { float light_spread = 0.; float accum_sigma = 1.; float accum_scale = 1.; - std::uint32_t reflections = 0; + std::uint8_t reflections = 0; + UseNormal use_normal = UseNormal::Light; bool show_axes = true; bool show_shapes = true; bool show_hit_emission = true; @@ -31,6 +37,7 @@ struct RedrawArgs { }; } // namespace ffi +using ffi::UseNormal; using ffi::RedrawArgs; using ffi::SphericalPosition; diff --git a/ui/src/main_window.cxx b/ui/src/main_window.cxx index dff2f7d..11ae0f2 100644 --- a/ui/src/main_window.cxx +++ b/ui/src/main_window.cxx @@ -16,6 +16,11 @@ float deg_to_rad(float val) { } void PhotonLight::updateView() { + UseNormal use_normal = {}; + if (m_ui->normalFromLight->isChecked()) + use_normal = UseNormal::Light; + else if (m_ui->normalFromCamera->isChecked()) + use_normal = UseNormal::Camera; RedrawArgs args{ .camera_position = SphericalPosition{ .yaw = deg_to_rad(m_ui->cameraYaw->value()), @@ -31,7 +36,8 @@ void PhotonLight::updateView() { .light_spread = 0.125, .accum_sigma = exp10f(m_ui->accumSigma->value() / 25.0), .accum_scale = exp10f(m_ui->accumScale->value() / 25.0), - .reflections = std::uint32_t(m_ui->reflections->value()), + .reflections = std::uint8_t(m_ui->reflections->value()), + .use_normal = use_normal, .show_axes = m_ui->displayAxes->isChecked(), .show_shapes = m_ui->displayShapes->isChecked(), .show_hit_emission = m_ui->displayEmitted->isChecked(), @@ -51,4 +57,9 @@ void PhotonLight::updateView() { m_ui->viewport->setView(args); } +void PhotonLight::updateViewIf(bool update) { + if (update) + updateView(); +} + #include "moc_main_window.cpp" diff --git a/ui/src/main_window.hxx b/ui/src/main_window.hxx index 1ca73cc..27b63ff 100644 --- a/ui/src/main_window.hxx +++ b/ui/src/main_window.hxx @@ -16,6 +16,7 @@ public: public slots: void updateView(); + void updateViewIf(bool update); // for radio buttons private: const std::unique_ptr m_ui; diff --git a/ui/src/main_window.ui b/ui/src/main_window.ui index 697c566..5802a1d 100644 --- a/ui/src/main_window.ui +++ b/ui/src/main_window.ui @@ -298,6 +298,30 @@ + + + + Use normal at + + + + + + + Hit position + + + true + + + + + + + Show position + + + @@ -637,8 +661,41 @@ + + normalFromLight + toggled(bool) + MainWindow + updateViewIf(bool) + + + 1429 + 787 + + + 1381 + 783 + + + + + normalFromCamera + toggled(bool) + MainWindow + updateViewIf(bool) + + + 1932 + 816 + + + 1600 + 874 + + + updateView() + updateViewIf(bool) -- 2.47.3 From 77cab94a6e50e8247c4e1a9c050facfeaadbb59c Mon Sep 17 00:00:00 2001 From: numzero Date: Mon, 24 Nov 2025 17:10:12 +0300 Subject: [PATCH 05/32] add basic shape generation --- src/lib.rs | 1 + src/shape.rs | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 src/shape.rs diff --git a/src/lib.rs b/src/lib.rs index 18937dd..ce55aa2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ use crate::{ mod camera; mod ray; mod render; +mod shape; mod trace; const OUTPUT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb; diff --git a/src/shape.rs b/src/shape.rs new file mode 100644 index 0000000..e1b5865 --- /dev/null +++ b/src/shape.rs @@ -0,0 +1,140 @@ +use std::f32::consts::PI; + +use glam::{Vec3, vec3}; + +pub type Face = [u16; 3]; + +#[derive(Debug, Clone)] +pub struct Mesh { + pub vertices: Vec, + pub indices: Vec, +} + +pub fn sphere(subdiv: usize) -> Mesh { + assert!(subdiv >= 2); + assert!(2 * subdiv * (subdiv - 1) + 2 <= 1 << 16); + + let mut vertices = Vec::new(); + vertices.push(Vec3::Z); + for j in 1..subdiv { + let v = j as f32 / subdiv as f32; + let theta = PI * v; + let (xy, z) = theta.sin_cos(); + for i in 0..2 * subdiv { + let u = i as f32 / (2 * subdiv) as f32; + let phi = 2. * PI * u; + let (y, x) = phi.sin_cos(); + vertices.push(vec3(xy * x, xy * y, z)); + } + } + vertices.push(-Vec3::Z); + assert_eq!(vertices.len(), 2 * subdiv * (subdiv - 1) + 2); + + let mut indices = Vec::new(); + let subdiv = subdiv as u16; + for i in 0..2 * subdiv - 1 { + indices.push([0, i + 1, i + 2]); + } + indices.push([0, 2 * subdiv, 1]); + for j in 0..subdiv - 2 { + let k1 = j * 2 * subdiv + 1; + let k2 = k1 + 2 * subdiv; + for i in 0..2 * subdiv - 1 { + let a = k1 + i; + let b = k2 + i; + indices.push([a, b, b + 1]); + indices.push([a, b + 1, a + 1]); + } + let a = k1 + 2 * subdiv - 1; + let b = k2 + 2 * subdiv - 1; + indices.push([a, b, k2]); + indices.push([a, k2, k1]); + } + let k1 = 2 * subdiv * (subdiv - 2) + 1; + let k2 = 2 * subdiv * (subdiv - 1) + 1; + for i in 0..2 * subdiv - 1 { + indices.push([k1 + i, k2, k1 + i + 1]); + } + indices.push([k1 + 2 * subdiv - 1, k2, k1]); + + Mesh { vertices, indices } +} + +#[cfg(test)] +mod tests { + use approx::abs_diff_eq; + + use super::*; + + #[test] + fn test_sphere_2_topology() { + assert_eq!( + sphere(2).indices, + vec![ + [0, 1, 2], + [0, 2, 3], + [0, 3, 4], + [0, 4, 1], + [1, 5, 2], + [2, 5, 3], + [3, 5, 4], + [4, 5, 1], + ] + ); + } + + fn are_equal_eps(left: &[Vec3], right: &[Vec3], eps: f32) -> bool { + if left.len() != right.len() { + return false; + } + left.iter() + .zip(right.iter()) + .all(|(a, b)| abs_diff_eq!(a, b, epsilon = eps)) + } + + #[test] + fn test_sphere_2_geometry() { + let left = sphere(2).vertices; + let right = [Vec3::Z, Vec3::X, Vec3::Y, -Vec3::X, -Vec3::Y, -Vec3::Z]; + assert!( + are_equal_eps(&left, &right, 1e-6), + "assertion `left == right` failed\n left: {left:?}\n right: {right:?}" + ); + } + + #[test] + fn test_sphere_3_topology() { + assert_eq!( + sphere(3).indices, + vec![ + // top + [0, 1, 2], + [0, 2, 3], + [0, 3, 4], + [0, 4, 5], + [0, 5, 6], + [0, 6, 1], + // belt + [1, 7, 8], + [1, 8, 2], + [2, 8, 9], + [2, 9, 3], + [3, 9, 10], + [3, 10, 4], + [4, 10, 11], + [4, 11, 5], + [5, 11, 12], + [5, 12, 6], + [6, 12, 7], + [6, 7, 1], + // bottom + [7, 13, 8], + [8, 13, 9], + [9, 13, 10], + [10, 13, 11], + [11, 13, 12], + [12, 13, 7], + ] + ); + } +} -- 2.47.3 From 5a6b10965de4b6fb530df846ec76a92b0722b8c4 Mon Sep 17 00:00:00 2001 From: numzero Date: Mon, 24 Nov 2025 17:29:08 +0300 Subject: [PATCH 06/32] show spheres --- src/lib.rs | 29 ++++++++ src/render/faces.rs | 161 ++++++++++++++++++++++++++++++++++++++++++++ src/render/mod.rs | 1 + 3 files changed, 191 insertions(+) create mode 100644 src/render/faces.rs diff --git a/src/lib.rs b/src/lib.rs index ce55aa2..f2122d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,7 @@ pub struct Core { surface: wgpu::Surface<'static>, pipeline: Pipeline, + mesh_pipe: render::faces::Pipeline, tripod: Mesh, } @@ -130,6 +131,7 @@ impl Core { } = gpu; let pipeline = Pipeline::new(&device, OUTPUT_FORMAT); + let mesh_pipe = render::faces::Pipeline::new(&device, OUTPUT_FORMAT); let tripod = new_tripod(&device); queue.submit([]); // flush buffer updates @@ -139,6 +141,7 @@ impl Core { surface, pipeline, tripod, + mesh_pipe, } } @@ -161,6 +164,12 @@ impl Core { m: perspective * camera.transform(), }, ); + self.mesh_pipe.set_look( + &self.queue, + render::faces::LookParams { + m: perspective * camera.transform(), + }, + ); self.queue.submit([]); // flush buffer updates let view = output.create_view(&wgpu::TextureViewDescriptor::default()); @@ -223,6 +232,26 @@ impl Core { sphere(vec3(0.1, 0.3, 0.1)), ], }; + if args.show_shapes { + let mut meshes = Vec::new(); + for obj in &scene.objects { + let mesh = shape::sphere((obj.radius * 15.) as usize + 7); + let obj_mesh = render::faces::Mesh::new( + &self.device, + &mesh + .vertices + .iter() + .map(|v| render::faces::Vertex { + pos: obj.position + obj.radius * v, + color: 0.5 * (v + 1.), + }) + .collect::>(), + bytemuck::cast_slice(&mesh.indices), + ); + meshes.push(obj_mesh); + } + self.mesh_pipe.render(&mut pass, &meshes); + } let mut prng = rand_pcg::Pcg64::new(42, 0); let source_rays: Vec = (0..10240).map(|_| source.make_ray(&mut prng)).collect(); diff --git a/src/render/faces.rs b/src/render/faces.rs new file mode 100644 index 0000000..1776a39 --- /dev/null +++ b/src/render/faces.rs @@ -0,0 +1,161 @@ +use std::mem::offset_of; + +use bytemuck::{Pod, Zeroable, bytes_of, cast_slice}; +use glam::{Mat4, Vec3}; +use wgpu::util::DeviceExt as _; + +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct LookParams { + pub m: Mat4, +} + +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct Vertex { + pub pos: Vec3, + pub color: Vec3, +} + +impl Vertex { + pub fn new(pos: Vec3, color: Vec3) -> Self { + Self { pos, color } + } +} + +pub struct Mesh { + vertex_buffer: wgpu::Buffer, + index_buffer: wgpu::Buffer, + index_count: u32, +} + +impl Mesh { + pub fn new(device: &wgpu::Device, vertices: &[Vertex], indices: &[u16]) -> Self { + if vertices.len() >= 1 << 16 { + panic!("too many vertices"); + } + for index in indices { + if *index as usize >= vertices.len() { + panic!( + "vertex index out of bounds: {index} out of {}", + vertices.len() + ); + } + } + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + usage: wgpu::BufferUsages::VERTEX, + contents: cast_slice(vertices), + }); + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + usage: wgpu::BufferUsages::INDEX, + contents: cast_slice(indices), + }); + Self { + vertex_buffer, + index_buffer, + index_count: indices.len().try_into().expect("too many indices"), + } + } +} + +pub struct Pipeline { + look_buf: wgpu::Buffer, + bindings: wgpu::BindGroup, + pipeline: wgpu::RenderPipeline, +} + +impl Pipeline { + pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self { + let look_buf = device.create_buffer(&wgpu::BufferDescriptor { + label: None, + size: size_of::() as u64, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(super::SIMPLE_SHADER.into()), + }); + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: None, + vertex: wgpu::VertexState { + module: &shader, + entry_point: None, + compilation_options: wgpu::PipelineCompilationOptions::default(), + buffers: &[wgpu::VertexBufferLayout { + array_stride: size_of::() as u64, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + shader_location: 0, + offset: offset_of!(Vertex, pos) as u64, + format: wgpu::VertexFormat::Float32x3, + }, + wgpu::VertexAttribute { + shader_location: 1, + offset: offset_of!(Vertex, color) as u64, + format: wgpu::VertexFormat::Float32x3, + }, + ], + }], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + ..Default::default() + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: None, + compilation_options: wgpu::PipelineCompilationOptions::default(), + targets: &[Some(wgpu::ColorTargetState { + format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + multiview: None, + cache: None, + }); + let bindings = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &pipeline.get_bind_group_layout(0), + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: look_buf.as_entire_binding(), + }], + }); + Self { + look_buf, + bindings, + pipeline, + } + } + + pub fn set_look(&self, queue: &wgpu::Queue, look: LookParams) { + queue.write_buffer(&self.look_buf, 0, bytes_of(&look)); + } + + pub fn render<'a>( + &self, + pass: &mut wgpu::RenderPass, + meshes: impl IntoIterator, + ) { + pass.set_pipeline(&self.pipeline); + pass.set_bind_group(0, &self.bindings, &[]); + for mesh in meshes { + pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..)); + pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint16); + pass.draw_indexed(0..mesh.index_count, 0, 0..1); + } + } +} diff --git a/src/render/mod.rs b/src/render/mod.rs index 249b8bb..291e015 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,3 +1,4 @@ +pub mod faces; pub mod lines; static SIMPLE_SHADER: &str = include_str!("simple.wgsl"); -- 2.47.3 From c40ff3e324b5cd55767acbd191f232c6a8aa8277 Mon Sep 17 00:00:00 2001 From: numzero Date: Mon, 24 Nov 2025 17:54:42 +0300 Subject: [PATCH 07/32] fix output format --- src/lib.rs | 11 ++++++----- src/render/faces.rs | 6 ++++-- src/render/lines.rs | 6 ++++-- src/render/mod.rs | 2 ++ 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f2122d3..79e027d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,10 @@ use rand_distr::Distribution as _; use crate::{ camera::OrbitalCamera, ray::Ray, - render::lines::{LookParams, Mesh, Pipeline, Vertex}, + render::{ + OUTPUT_FORMAT, + lines::{LookParams, Mesh, Pipeline, Vertex}, + }, trace::{Hit, Lambertian, Reflector, Scene, Source, Sphere}, }; @@ -18,8 +21,6 @@ mod render; mod shape; mod trace; -const OUTPUT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb; - #[derive(Debug, Clone, Copy, PartialEq)] #[repr(C)] pub struct SphericalPosition { @@ -130,8 +131,8 @@ impl Core { surface, } = gpu; - let pipeline = Pipeline::new(&device, OUTPUT_FORMAT); - let mesh_pipe = render::faces::Pipeline::new(&device, OUTPUT_FORMAT); + let pipeline = Pipeline::new(&device); + let mesh_pipe = render::faces::Pipeline::new(&device); let tripod = new_tripod(&device); queue.submit([]); // flush buffer updates diff --git a/src/render/faces.rs b/src/render/faces.rs index 1776a39..e52e3e7 100644 --- a/src/render/faces.rs +++ b/src/render/faces.rs @@ -4,6 +4,8 @@ use bytemuck::{Pod, Zeroable, bytes_of, cast_slice}; use glam::{Mat4, Vec3}; use wgpu::util::DeviceExt as _; +use crate::render::OUTPUT_FORMAT; + #[derive(Debug, Clone, Copy, Pod, Zeroable)] #[repr(C)] pub struct LookParams { @@ -67,7 +69,7 @@ pub struct Pipeline { } impl Pipeline { - pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self { + pub fn new(device: &wgpu::Device) -> Self { let look_buf = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: size_of::() as u64, @@ -118,7 +120,7 @@ impl Pipeline { entry_point: None, compilation_options: wgpu::PipelineCompilationOptions::default(), targets: &[Some(wgpu::ColorTargetState { - format, + format: OUTPUT_FORMAT, blend: Some(wgpu::BlendState::REPLACE), write_mask: wgpu::ColorWrites::ALL, })], diff --git a/src/render/lines.rs b/src/render/lines.rs index 493023e..8a7ef94 100644 --- a/src/render/lines.rs +++ b/src/render/lines.rs @@ -4,6 +4,8 @@ use bytemuck::{Pod, Zeroable, bytes_of, cast_slice}; use glam::{Mat4, Vec3}; use wgpu::util::DeviceExt as _; +use crate::render::OUTPUT_FORMAT; + #[derive(Debug, Clone, Copy, Pod, Zeroable)] #[repr(C)] pub struct LookParams { @@ -49,7 +51,7 @@ pub struct Pipeline { } impl Pipeline { - pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self { + pub fn new(device: &wgpu::Device) -> Self { let look_buf = device.create_buffer(&wgpu::BufferDescriptor { label: None, size: size_of::() as u64, @@ -100,7 +102,7 @@ impl Pipeline { entry_point: None, compilation_options: wgpu::PipelineCompilationOptions::default(), targets: &[Some(wgpu::ColorTargetState { - format, + format: OUTPUT_FORMAT, blend: Some(wgpu::BlendState::REPLACE), write_mask: wgpu::ColorWrites::ALL, })], diff --git a/src/render/mod.rs b/src/render/mod.rs index 291e015..435dc0d 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -2,3 +2,5 @@ pub mod faces; pub mod lines; static SIMPLE_SHADER: &str = include_str!("simple.wgsl"); + +pub const OUTPUT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb; -- 2.47.3 From a8f72096d8805fb25e46920af9600b5c74412cfb Mon Sep 17 00:00:00 2001 From: numzero Date: Mon, 24 Nov 2025 18:08:03 +0300 Subject: [PATCH 08/32] pass size on creation --- src/lib.rs | 8 +++++--- src/main.rs | 3 +-- ui/src/api.cxx | 6 +++--- ui/src/api.hxx | 2 +- ui/src/lib.rs | 9 ++++++--- ui/src/viewport.cxx | 4 ++-- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 79e027d..87c1922 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,7 +124,7 @@ impl OrbitalCamera { } impl Core { - pub fn new(gpu: Gpu) -> Self { + pub fn new(gpu: Gpu, pixel_size: UVec2) -> Self { let Gpu { device, queue, @@ -136,14 +136,16 @@ impl Core { let tripod = new_tripod(&device); queue.submit([]); // flush buffer updates - Self { + let mut this = Self { device, queue, surface, pipeline, tripod, mesh_pipe, - } + }; + this.configure(pixel_size); + this } fn render(&self, output: &wgpu::Texture, args: &RedrawArgs) { diff --git a/src/main.rs b/src/main.rs index cb11049..adce210 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,8 +26,7 @@ impl MainWindow { instance.create_surface(Arc::clone(&window)) })) .unwrap(); - let mut core = Core::new(gpu); - core.configure(uvec2(1, 1)); + let core = Core::new(gpu, uvec2(1, 1)); Self { window, core } } diff --git a/ui/src/api.cxx b/ui/src/api.cxx index d65a690..aaa2b64 100644 --- a/ui/src/api.cxx +++ b/ui/src/api.cxx @@ -4,7 +4,7 @@ #include namespace ffi { -extern "C" Core* rt4_viewport_create(xcb_connection_t* connection, std::uint32_t window); +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); @@ -26,13 +26,13 @@ BoxCore& BoxCore::operator=(BoxCore&& b) { return *this; } -BoxCore BoxCore::from_xcb(xcb_connection_t* connection, std::uint32_t window) { +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); + out.ptr.ptr = ffi::rt4_viewport_create(connection, window, width, height); return out; } diff --git a/ui/src/api.hxx b/ui/src/api.hxx index bc96bc8..d46272e 100644 --- a/ui/src/api.hxx +++ b/ui/src/api.hxx @@ -69,7 +69,7 @@ public: void reset(); - static BoxCore from_xcb(xcb_connection_t* connection, std::uint32_t window); + 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 index 9ee96ef..c7a85f8 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -1,12 +1,13 @@ use std::{ffi::c_void, num::NonZero, ptr::NonNull}; -use glam::uvec2; +use glam::{UVec2, uvec2}; use photon_light::{Core, RedrawArgs, init_gpu_inner}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle}; unsafe fn create_viewport( display: impl Into, window: impl Into, + size: UVec2, ) -> Box { let target = wgpu::SurfaceTargetUnsafe::RawHandle { raw_display_handle: display.into(), @@ -16,17 +17,19 @@ unsafe fn create_viewport( instance.create_surface_unsafe(target) })) .unwrap(); - Box::new(Core::new(gpu)) + 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) } + unsafe { create_viewport(display, window, uvec2(width, height)) } } #[unsafe(no_mangle)] diff --git a/ui/src/viewport.cxx b/ui/src/viewport.cxx index 483ba07..e681bd9 100644 --- a/ui/src/viewport.cxx +++ b/ui/src/viewport.cxx @@ -61,9 +61,9 @@ void Viewport::recreate() try { fprintf(stderr, "connection %p, window %#08x\n", xcb_connection, x11_window); + const QSize device_size = size() * devicePixelRatio(); core.reset(); - core = BoxCore::from_xcb(xcb_connection, x11_window); - updateSize(); + core = BoxCore::from_xcb(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()); } -- 2.47.3 From 295037edc6a22774ae76cb3a9b26d97766e0db5d Mon Sep 17 00:00:00 2001 From: numzero Date: Mon, 24 Nov 2025 18:15:48 +0300 Subject: [PATCH 09/32] add depth buffer --- src/lib.rs | 56 +++++++++++++++++++++++++++++++++++++-------- src/render/faces.rs | 10 ++++++-- src/render/lines.rs | 14 ++++++++++-- src/render/mod.rs | 1 + 4 files changed, 67 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 87c1922..0beb0aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ use crate::{ camera::OrbitalCamera, ray::Ray, render::{ - OUTPUT_FORMAT, + DEPTH_FORMAT, OUTPUT_FORMAT, lines::{LookParams, Mesh, Pipeline, Vertex}, }, trace::{Hit, Lambertian, Reflector, Scene, Source, Sphere}, @@ -67,6 +67,8 @@ pub struct Core { queue: wgpu::Queue, surface: wgpu::Surface<'static>, + depth: wgpu::Texture, + pipeline: Pipeline, mesh_pipe: render::faces::Pipeline, tripod: Mesh, @@ -134,18 +136,20 @@ impl Core { let pipeline = Pipeline::new(&device); let mesh_pipe = render::faces::Pipeline::new(&device); let tripod = new_tripod(&device); + let depth = Self::create_depth_buffer(&device, pixel_size); queue.submit([]); // flush buffer updates - let mut this = Self { + Self::configure_surface(&surface, &device, pixel_size); + + Self { device, queue, surface, + depth, pipeline, tripod, mesh_pipe, - }; - this.configure(pixel_size); - this + } } fn render(&self, output: &wgpu::Texture, args: &RedrawArgs) { @@ -176,6 +180,9 @@ impl Core { self.queue.submit([]); // flush buffer updates let view = output.create_view(&wgpu::TextureViewDescriptor::default()); + let depth_view = self + .depth + .create_view(&wgpu::TextureViewDescriptor::default()); let mut encoder = self .device .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); @@ -189,7 +196,14 @@ impl Core { store: wgpu::StoreOp::Store, }, })], - depth_stencil_attachment: None, + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &depth_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.), + store: wgpu::StoreOp::Discard, + }), + stencil_ops: None, + }), ..Default::default() }); if args.show_axes { @@ -389,10 +403,26 @@ impl Core { self.queue.submit(std::iter::once(encoder.finish())); } - /// Configures the renderer for a given target size. - pub fn configure(&mut self, pixel_size: UVec2) { - self.surface.configure( - &self.device, + fn create_depth_buffer(device: &wgpu::Device, pixel_size: UVec2) -> wgpu::Texture { + device.create_texture(&wgpu::TextureDescriptor { + label: Some("depth buffer"), + size: wgpu::Extent3d { + width: pixel_size.x, + height: pixel_size.y, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: DEPTH_FORMAT, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }) + } + + fn configure_surface(surface: &wgpu::Surface, device: &wgpu::Device, pixel_size: UVec2) { + surface.configure( + device, &wgpu::SurfaceConfiguration { usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST, format: OUTPUT_FORMAT, @@ -406,6 +436,12 @@ impl Core { ); } + /// Configures the renderer for a given target size. + pub fn configure(&mut self, pixel_size: UVec2) { + Self::configure_surface(&self.surface, &self.device, pixel_size); + self.depth = Self::create_depth_buffer(&self.device, pixel_size); + } + /// Redraws the entire surface. /// /// [`Self::configure`] must be called at least once before this. diff --git a/src/render/faces.rs b/src/render/faces.rs index e52e3e7..b6a2a20 100644 --- a/src/render/faces.rs +++ b/src/render/faces.rs @@ -4,7 +4,7 @@ use bytemuck::{Pod, Zeroable, bytes_of, cast_slice}; use glam::{Mat4, Vec3}; use wgpu::util::DeviceExt as _; -use crate::render::OUTPUT_FORMAT; +use crate::render::{DEPTH_FORMAT, OUTPUT_FORMAT}; #[derive(Debug, Clone, Copy, Pod, Zeroable)] #[repr(C)] @@ -109,7 +109,13 @@ impl Pipeline { topology: wgpu::PrimitiveTopology::TriangleList, ..Default::default() }, - depth_stencil: None, + depth_stencil: Some(wgpu::DepthStencilState { + format: DEPTH_FORMAT, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::LessEqual, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState::default(), + }), multisample: wgpu::MultisampleState { count: 1, mask: !0, diff --git a/src/render/lines.rs b/src/render/lines.rs index 8a7ef94..b68fb86 100644 --- a/src/render/lines.rs +++ b/src/render/lines.rs @@ -4,7 +4,7 @@ use bytemuck::{Pod, Zeroable, bytes_of, cast_slice}; use glam::{Mat4, Vec3}; use wgpu::util::DeviceExt as _; -use crate::render::OUTPUT_FORMAT; +use crate::render::{DEPTH_FORMAT, OUTPUT_FORMAT}; #[derive(Debug, Clone, Copy, Pod, Zeroable)] #[repr(C)] @@ -91,7 +91,17 @@ impl Pipeline { topology: wgpu::PrimitiveTopology::LineList, ..Default::default() }, - depth_stencil: None, + depth_stencil: Some(wgpu::DepthStencilState { + format: DEPTH_FORMAT, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::LessEqual, + stencil: wgpu::StencilState::default(), + bias: wgpu::DepthBiasState { + constant: -2, + slope_scale: 0., + clamp: 0., + }, + }), multisample: wgpu::MultisampleState { count: 1, mask: !0, diff --git a/src/render/mod.rs b/src/render/mod.rs index 435dc0d..2f629ee 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -4,3 +4,4 @@ pub mod lines; static SIMPLE_SHADER: &str = include_str!("simple.wgsl"); pub const OUTPUT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb; +pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth24Plus; -- 2.47.3 From 722e5d4b6eee1ad5907af5e3a3ce85cf5985b6b7 Mon Sep 17 00:00:00 2001 From: numzero Date: Mon, 24 Nov 2025 18:21:31 +0300 Subject: [PATCH 10/32] split light calculations and camera rays --- src/lib.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0beb0aa..7376d38 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -346,16 +346,13 @@ impl Core { hits1 = hits2; } } - let mut camera_ray_display: Vec = Vec::with_capacity(camera_rays.len()); - if args.show_light { + let light_at = { let sigma2 = args.accum_sigma.powi(2); let accum_normalizator = (2. * PI * sigma2).sqrt().recip(); - for ray in camera_rays { - let Some(hit) = scene.trace_ray(ray) else { - continue; - }; + let hits = &hits; + move |hit: Hit| -> f32 { let mut total_cd = 0.0f32; - for light_hit in &hits { + for light_hit in hits { let d2 = hit.incident.base.distance_squared(light_hit.incident.base); if d2 > 9. * sigma2 { continue; @@ -368,11 +365,22 @@ impl Core { assert!(hit.incident.dir.is_normalized()); let reflector = Lambertian; let in_lm = 1.0; - let out_cd = in_lm * reflector.brdf(normal, light_hit.incident.dir, -ray.dir); + let out_cd = + in_lm * reflector.brdf(normal, light_hit.incident.dir, -hit.incident.dir); let weight = accum_normalizator * (-0.5 * d2 / sigma2).exp(); total_cd += weight * out_cd; } - let brightness = 3. * (1. - (1. + total_cd * args.accum_scale).recip()); + total_cd * args.accum_scale + } + }; + let mut camera_ray_display: Vec = Vec::with_capacity(camera_rays.len()); + if args.show_light { + for ray in camera_rays { + let Some(hit) = scene.trace_ray(ray) else { + continue; + }; + let light = light_at(hit); + let brightness = 3. * (1. - (1. + light).recip()); let r = args.accum_sigma; let color = vec3(brightness, brightness - 1., brightness - 2.) .clamp(Vec3::splat(0.), Vec3::splat(1.)); -- 2.47.3 From 41579bbb5d44f6a455fa1b45876c2ee962a48db8 Mon Sep 17 00:00:00 2001 From: numzero Date: Mon, 24 Nov 2025 18:23:35 +0300 Subject: [PATCH 11/32] split colormap --- src/lib.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7376d38..d3713d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -373,17 +373,20 @@ impl Core { total_cd * args.accum_scale } }; + let colormap = |light: f32| { + let brightness = 3. * (1. - (1. + light).recip()); + vec3(brightness, brightness - 1., brightness - 2.) + .clamp(Vec3::splat(0.), Vec3::splat(1.)) + }; let mut camera_ray_display: Vec = Vec::with_capacity(camera_rays.len()); if args.show_light { + let r = args.accum_sigma; for ray in camera_rays { let Some(hit) = scene.trace_ray(ray) else { continue; }; let light = light_at(hit); - let brightness = 3. * (1. - (1. + light).recip()); - let r = args.accum_sigma; - let color = vec3(brightness, brightness - 1., brightness - 2.) - .clamp(Vec3::splat(0.), Vec3::splat(1.)); + let color = colormap(light); let vertex = |off: Vec3| Vertex { pos: hit.incident.base + r * off, color, -- 2.47.3 From 59bc5b640f515318c0af780921bcf9eeb2dac0e8 Mon Sep 17 00:00:00 2001 From: numzero Date: Mon, 24 Nov 2025 18:31:23 +0300 Subject: [PATCH 12/32] per-vertex lighting! --- src/lib.rs | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d3713d7..4393e0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -249,26 +249,6 @@ impl Core { sphere(vec3(0.1, 0.3, 0.1)), ], }; - if args.show_shapes { - let mut meshes = Vec::new(); - for obj in &scene.objects { - let mesh = shape::sphere((obj.radius * 15.) as usize + 7); - let obj_mesh = render::faces::Mesh::new( - &self.device, - &mesh - .vertices - .iter() - .map(|v| render::faces::Vertex { - pos: obj.position + obj.radius * v, - color: 0.5 * (v + 1.), - }) - .collect::>(), - bytemuck::cast_slice(&mesh.indices), - ); - meshes.push(obj_mesh); - } - self.mesh_pipe.render(&mut pass, &meshes); - } let mut prng = rand_pcg::Pcg64::new(42, 0); let source_rays: Vec = (0..10240).map(|_| source.make_ray(&mut prng)).collect(); @@ -401,6 +381,33 @@ impl Core { ]); } } + if args.show_shapes { + let mut meshes = Vec::new(); + for obj in &scene.objects { + let mesh = shape::sphere((obj.radius * 45.) as usize + 7); + let obj_mesh = render::faces::Mesh::new( + &self.device, + &mesh + .vertices + .iter() + .map(|&v| { + let pos = obj.position + obj.radius * v; + let normal = v; + let dir = (pos - camera.position()).normalize(); + let light = light_at(Hit { + incident: Ray::new(pos, dir), + normal, + }); + let color = colormap(light); + render::faces::Vertex { pos, color } + }) + .collect::>(), + bytemuck::cast_slice(&mesh.indices), + ); + meshes.push(obj_mesh); + } + self.mesh_pipe.render(&mut pass, &meshes); + } if !source_ray_display.is_empty() { self.pipeline .render(&mut pass, [&Mesh::new(&self.device, &source_ray_display)]); -- 2.47.3 From 98f3c44ab3e944c331791675f8afe31dbe933868 Mon Sep 17 00:00:00 2001 From: numzero Date: Tue, 25 Nov 2025 12:40:26 +0300 Subject: [PATCH 13/32] add super-duper complicated sphere mesh generator --- src/lib.rs | 2 +- src/shape.rs | 208 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 4393e0e..ddd026c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -384,7 +384,7 @@ impl Core { if args.show_shapes { let mut meshes = Vec::new(); for obj in &scene.objects { - let mesh = shape::sphere((obj.radius * 45.) as usize + 7); + let mesh = shape::octo_sphere((obj.radius * 45.) as usize + 7); let obj_mesh = render::faces::Mesh::new( &self.device, &mesh diff --git a/src/shape.rs b/src/shape.rs index e1b5865..311076d 100644 --- a/src/shape.rs +++ b/src/shape.rs @@ -60,6 +60,114 @@ pub fn sphere(subdiv: usize) -> Mesh { Mesh { vertices, indices } } +pub fn octo_sphere(subdiv: usize) -> Mesh { + assert!(subdiv >= 2); + + let mut vertices = Vec::new(); + let theta_step = PI / subdiv as f32; + let v_belt = |j: usize, n: usize| { + let phi_step = 2. * PI / n as f32; + let theta = j as f32 * theta_step; + let (xy, z) = theta.sin_cos(); + (0..n).map(move |i| { + let phi = i as f32 * phi_step; + let (y, x) = phi.sin_cos(); + vec3(xy * x, xy * y, z) + }) + }; + vertices.push(Vec3::Z); + for j in 1..subdiv / 2 { + vertices.extend(v_belt(j, 4 * j)); + } + { + let j = subdiv / 2; + vertices.extend(v_belt(j, 4 * j)); + } + for j in (subdiv + 2) / 2..subdiv { + vertices.extend(v_belt(j, 4 * (subdiv - j))); + } + vertices.push(-Vec3::Z); + + fn i_cap(top: usize, start: usize) -> impl Iterator { + gen move { + yield [top, start, start + 1]; + yield [top, start + 1, start + 2]; + yield [top, start + 2, start + 3]; + yield [top, start + 3, start]; + } + .map(face) + } + fn i_skew_belt( + short_start: usize, + long_start: usize, + seg_short_face: usize, + ) -> impl Iterator { + gen move { + let seg_long_face = seg_short_face + 1; + let n_short_total = 4 * seg_short_face; + let n_long_total = 4 * seg_long_face; + for face in 0..4 { + let short = move |k| short_start + (face * seg_short_face + k) % n_short_total; + let long = move |k| long_start + (face * seg_long_face + k) % n_long_total; + yield [short(0), long(0), long(1)]; + for k in 0..seg_short_face { + yield [short(k), long(k + 1), short(k + 1)]; + yield [short(k + 1), long(k + 1), long(k + 2)]; + } + } + } + .map(face) + } + fn i_belt(start1: usize, start2: usize, seg_per_face: usize) -> impl Iterator { + gen move { + let n = 4 * seg_per_face; + for k in 0..n { + let k2 = (k + 1) % n; + let a = start1 + k; + let b = start2 + k; + let c = start1 + k2; + let d = start2 + k2; + yield [a, b, c]; + yield [c, b, d]; + } + } + .map(face) + } + fn face(f: [usize; 3]) -> Face { + f.map(|i| i as u16) + } + fn flip(f: Face) -> Face { + [f[1], f[0], f[2]] + } + + let mut indices = Vec::new(); + indices.extend(i_cap(0, 1)); + let mut k = 1; + for j in 1..subdiv - 1 { + let j2 = (subdiv - 1) - j; + match j.cmp(&j2) { + std::cmp::Ordering::Less => { + let n = 4 * j; + indices.extend(i_skew_belt(k, k + n, j)); + k += n; + } + std::cmp::Ordering::Equal => { + let n = 4 * j; + indices.extend(i_belt(k, k + n, j)); + k += n; + } + std::cmp::Ordering::Greater => { + let n = 4 * (j2 + 1); + indices.extend(i_skew_belt(k + n, k, j2).map(flip)); + k += n; + } + } + } + indices.extend(i_cap(k + 4, k).map(flip)); + + Mesh { vertices, indices } +} + #[cfg(test)] mod tests { use approx::abs_diff_eq; @@ -137,4 +245,104 @@ mod tests { ] ); } + + #[test] + fn test_octo_sphere_2_topology() { + assert_eq!( + octo_sphere(2).indices, + vec![ + [0, 1, 2], + [0, 2, 3], + [0, 3, 4], + [0, 4, 1], + [1, 5, 2], + [2, 5, 3], + [3, 5, 4], + [4, 5, 1], + ] + ); + } + + #[test] + fn test_octo_sphere_2_geometry() { + let left = octo_sphere(2).vertices; + let right = [Vec3::Z, Vec3::X, Vec3::Y, -Vec3::X, -Vec3::Y, -Vec3::Z]; + assert!( + are_equal_eps(&left, &right, 1e-6), + "assertion `left == right` failed\n left: {left:?}\n right: {right:?}" + ); + } + + #[test] + fn test_octo_sphere_3_topology() { + assert_eq!( + octo_sphere(3).indices, + vec![ + // top + [0, 1, 2], + [0, 2, 3], + [0, 3, 4], + [0, 4, 1], + // belt + [1, 5, 2], + [2, 5, 6], + [2, 6, 3], + [3, 6, 7], + [3, 7, 4], + [4, 7, 8], + [4, 8, 1], + [1, 8, 5], + // bottom + [5, 9, 6], + [6, 9, 7], + [7, 9, 8], + [8, 9, 5], + ] + ); + } + + #[test] + fn test_octo_sphere_4_topology() { + assert_eq!( + octo_sphere(4).indices, + vec![ + // top + [0, 1, 2], + [0, 2, 3], + [0, 3, 4], + [0, 4, 1], + // belt 1 + [1, 5, 6], + [1, 6, 2], + [2, 6, 7], + [2, 7, 8], + [2, 8, 3], + [3, 8, 9], + [3, 9, 10], + [3, 10, 4], + [4, 10, 11], + [4, 11, 12], + [4, 12, 1], + [1, 12, 5], + // belt 2 + [5, 13, 6], + [6, 13, 14], + [6, 14, 7], + [7, 14, 8], + [8, 14, 15], + [8, 15, 9], + [9, 15, 10], + [10, 15, 16], + [10, 16, 11], + [11, 16, 12], + [12, 16, 13], + [12, 13, 5], + // bottom + [13, 17, 14], + [14, 17, 15], + [15, 17, 16], + [16, 17, 13], + ] + ); + } } -- 2.47.3 From 68b4041c886be4a2b530330a87a80e8a2c15e091 Mon Sep 17 00:00:00 2001 From: numzero Date: Tue, 25 Nov 2025 12:59:28 +0300 Subject: [PATCH 14/32] fix normalization --- src/lib.rs | 2 +- ui/src/main_window.cxx | 4 ++-- ui/src/main_window.ui | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ddd026c..432b3b7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -328,7 +328,7 @@ impl Core { } let light_at = { let sigma2 = args.accum_sigma.powi(2); - let accum_normalizator = (2. * PI * sigma2).sqrt().recip(); + let accum_normalizator = (2. * PI * sigma2).recip(); let hits = &hits; move |hit: Hit| -> f32 { let mut total_cd = 0.0f32; diff --git a/ui/src/main_window.cxx b/ui/src/main_window.cxx index 11ae0f2..75861e6 100644 --- a/ui/src/main_window.cxx +++ b/ui/src/main_window.cxx @@ -52,8 +52,8 @@ void PhotonLight::updateView() { m_ui->lightYawLabel->setText(tr("Yaw: %1 deg").arg(QString::number(qRadiansToDegrees(args.light_position.yaw)))); m_ui->lightPitchLabel->setText(tr("Pitch: %1 deg").arg(QString::number(qRadiansToDegrees(args.light_position.pitch)))); m_ui->lightDistanceLabel->setText(tr("Distance: %1").arg(QString::number(args.light_position.distance))); - m_ui->accumSigmaLabel->setText(tr("Averaging radius: %1").arg(QString::number(args.accum_sigma, 'f', 3))); - m_ui->accumScaleLabel->setText(tr("Brightness: %1").arg(QString::number(args.accum_scale, 'f', 3))); + m_ui->accumSigmaLabel->setText(tr("Averaging radius: %1").arg(QString::number(args.accum_sigma, 'f', 5))); + m_ui->accumScaleLabel->setText(tr("Brightness: %1").arg(QString::number(args.accum_scale, 'f', 5))); m_ui->viewport->setView(args); } diff --git a/ui/src/main_window.ui b/ui/src/main_window.ui index 5802a1d..150d5e0 100644 --- a/ui/src/main_window.ui +++ b/ui/src/main_window.ui @@ -245,7 +245,7 @@ 0 - -40 + -45 Qt::Horizontal @@ -271,7 +271,7 @@ 0 - -50 + -85 Qt::Horizontal -- 2.47.3 From 653788d456bdf929f8f275b4946de235cdc1c96c Mon Sep 17 00:00:00 2001 From: numzero Date: Tue, 25 Nov 2025 13:00:26 +0300 Subject: [PATCH 15/32] change default view --- ui/src/main_window.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/main_window.ui b/ui/src/main_window.ui index 150d5e0..9c4f169 100644 --- a/ui/src/main_window.ui +++ b/ui/src/main_window.ui @@ -337,7 +337,7 @@ Axes - true + false @@ -387,7 +387,7 @@ Average light - true + false -- 2.47.3 From 0065a45e7df429a27d8a6d23f30827f3a925de95 Mon Sep 17 00:00:00 2001 From: numzero Date: Tue, 25 Nov 2025 14:40:53 +0300 Subject: [PATCH 16/32] avoid crashes on dir=0 --- src/camera.rs | 6 ++++++ src/lib.rs | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/camera.rs b/src/camera.rs index a91e1a7..06542e2 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -20,6 +20,12 @@ impl OrbitalCamera { self.distance * vec3(xy * x, xy * y, z) } + pub fn direction(&self) -> Vec3 { + let (y, x) = self.position_yaw.sin_cos(); + let (z, xy) = self.position_pitch.sin_cos(); + -vec3(xy * x, xy * y, z) + } + pub fn transform(&self) -> Mat4 { // for yaw=0, pitch=0: // X -> -Z diff --git a/src/lib.rs b/src/lib.rs index 432b3b7..746879b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -393,7 +393,10 @@ impl Core { .map(|&v| { let pos = obj.position + obj.radius * v; let normal = v; - let dir = (pos - camera.position()).normalize(); + let mut dir = (pos - camera.position()).normalize(); + if !dir.is_finite() { + dir = camera.direction(); + } let light = light_at(Hit { incident: Ray::new(pos, dir), normal, -- 2.47.3 From 059e98c3e352065a42f8330746ee829660004798 Mon Sep 17 00:00:00 2001 From: numzero Date: Tue, 25 Nov 2025 14:41:10 +0300 Subject: [PATCH 17/32] do color mapping per-pixel --- src/lib.rs | 2 +- src/render/colormap.wgsl | 32 ++++++++++++++++++++++++++++++++ src/render/faces.rs | 2 +- src/render/mod.rs | 1 + 4 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 src/render/colormap.wgsl diff --git a/src/lib.rs b/src/lib.rs index 746879b..048a7cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -401,7 +401,7 @@ impl Core { incident: Ray::new(pos, dir), normal, }); - let color = colormap(light); + let color = Vec3::splat(light); render::faces::Vertex { pos, color } }) .collect::>(), diff --git a/src/render/colormap.wgsl b/src/render/colormap.wgsl new file mode 100644 index 0000000..e2af29d --- /dev/null +++ b/src/render/colormap.wgsl @@ -0,0 +1,32 @@ +struct LookParams { + m: mat4x4f, +} + +struct Vertex { + @location(0) pos: vec3f, + @location(1) color: vec3f, +} + +struct Varying { + @builtin(position) screen: vec4f, + @location(0) color: vec4f, +} + +@group(0) @binding(0) var look: LookParams; + +fn colormap(light: f32) -> vec3f { + let brightness = 3. * (1. - 1. / (1. + light)); + return vec3(brightness, brightness - 1., brightness - 2.); +} + +@vertex +fn on_vertex(in: Vertex) -> Varying { + let pos = look.m * vec4f(in.pos, 1.0); + let color = vec4f(in.color, 1.0); + return Varying(pos, color); +} + +@fragment +fn on_fragment(in: Varying) -> @location(0) vec4f { + return vec4f(colormap(in.color.x), 1.0); +} diff --git a/src/render/faces.rs b/src/render/faces.rs index b6a2a20..7879c30 100644 --- a/src/render/faces.rs +++ b/src/render/faces.rs @@ -79,7 +79,7 @@ impl Pipeline { let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: None, - source: wgpu::ShaderSource::Wgsl(super::SIMPLE_SHADER.into()), + source: wgpu::ShaderSource::Wgsl(super::COLORMAP_SHADER.into()), }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: None, diff --git a/src/render/mod.rs b/src/render/mod.rs index 2f629ee..3601ec0 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -2,6 +2,7 @@ pub mod faces; pub mod lines; static SIMPLE_SHADER: &str = include_str!("simple.wgsl"); +static COLORMAP_SHADER: &str = include_str!("colormap.wgsl"); pub const OUTPUT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb; pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth24Plus; -- 2.47.3 From 3e713b13a8d83465aa00b941fefc876538783060 Mon Sep 17 00:00:00 2001 From: numzero Date: Sat, 24 Jan 2026 21:07:33 +0300 Subject: [PATCH 18/32] Qt+WGPU --- .gitignore | 2 + CMakeLists.txt | 19 + Cargo.lock | 2446 ++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 24 + README.md | 5 + src/lib.rs | 169 +++ src/main.rs | 82 ++ ui/CMakeLists.txt | 14 + ui/Cargo.toml | 15 + ui/impl.cmake | 22 + ui/src/api.cxx | 57 + ui/src/api.hxx | 53 + ui/src/lib.rs | 48 + ui/src/main.cxx | 15 + ui/src/main_window.cxx | 27 + ui/src/main_window.hxx | 23 + ui/src/main_window.ui | 114 ++ ui/src/viewport.cxx | 74 ++ ui/src/viewport.hxx | 28 + 19 files changed, 3237 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 ui/CMakeLists.txt create mode 100644 ui/Cargo.toml create mode 100644 ui/impl.cmake create mode 100644 ui/src/api.cxx create mode 100644 ui/src/api.hxx create mode 100644 ui/src/lib.rs create mode 100644 ui/src/main.cxx create mode 100644 ui/src/main_window.cxx create mode 100644 ui/src/main_window.hxx create mode 100644 ui/src/main_window.ui create mode 100644 ui/src/viewport.cxx create mode 100644 ui/src/viewport.hxx diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..82448cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f30e705 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.18) + +project(PROJECT-NAME VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 REQUIRED COMPONENTS Gui Widgets) +find_program(CARGO cargo REQUIRED) +set(CARGO_TARGET_DIR "${CMAKE_BINARY_DIR}/cargo") + +# KDE-specific +find_package(ECM REQUIRED NO_MODULE) +set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) +find_package(KF6 REQUIRED COMPONENTS WidgetsAddons) + +qt_standard_project_setup() + +add_subdirectory(ui) diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9db39c2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2446 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "PROJECT-NAME" +version = "0.1.0" +dependencies = [ + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags 2.10.0", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.10.0", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "codespan-reporting" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.3", + "windows-link", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glam" +version = "0.30.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9" + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" +dependencies = [ + "bitflags 2.10.0", + "gpu-alloc-types", +] + +[[package]] +name = "gpu-alloc-types" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "gpu-allocator" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.10.0", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags 2.10.0", + "libc", + "redox_syscall 0.7.0", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + +[[package]] +name = "metal" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" +dependencies = [ + "bitflags 2.10.0", + "block", + "core-graphics-types 0.2.0", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "naga" +version = "27.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap", + "libm", + "log", + "num-traits", + "once_cell", + "rustc-hash", + "spirv", + "thiserror 2.0.18", + "unicode-ident", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.10.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.10.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.10.0", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "orbclient" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ad2c6bae700b7aa5d1cc30c59bdd3a1c180b09dbaea51e2ae2b8e1cf211fdd" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "ordered-float" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +dependencies = [ + "num-traits", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "portable-atomic" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "range-alloc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sctk-adwaita" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +dependencies = [ + "ab_glyph", + "log", + "memmap2", + "smithay-client-toolkit", + "tiny-skia", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.10.0", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wayland-backend" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.3", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec" +dependencies = [ + "bitflags 2.10.0", + "rustix 1.1.3", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.10.0", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5864c4b5b6064b06b1e8b74ead4a98a6c45a285fe7a0e784d24735f011fdb078" +dependencies = [ + "rustix 1.1.3", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baeda9ffbcfc8cd6ddaade385eaf2393bd2115a69523c735f12242353c3df4f3" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa98634619300a535a9a97f338aed9a5ff1e01a461943e8346ff4ae26007306b" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9597cdf02cf0c34cd5823786dce6b5ae8598f05c2daf5621b6e178d4f7345f3" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "27.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77" +dependencies = [ + "arrayvec", + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "27.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.10.0", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror 2.0.18", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-apple" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0772ae958e9be0c729561d5e3fd9a19679bcdfb945b8b1a1969d9bfe8056d233" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06ac3444a95b0813ecfd81ddb2774b66220b264b3e2031152a4a29fda4da6b5" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71197027d61a71748e4120f05a9242b2ad142e3c01f8c1b47707945a879a03c3" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "27.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.10.0", + "block", + "bytemuck", + "cfg-if", + "cfg_aliases", + "core-graphics-types 0.2.0", + "glow", + "glutin_wgl_sys", + "gpu-alloc", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.16.1", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "metal", + "naga", + "ndk-sys", + "objc", + "once_cell", + "ordered-float", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "smallvec", + "thiserror 2.0.18", + "wasm-bindgen", + "web-sys", + "wgpu-types", + "windows", + "windows-core", +] + +[[package]] +name = "wgpu-types" +version = "27.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb" +dependencies = [ + "bitflags 2.10.0", + "bytemuck", + "js-sys", + "log", + "thiserror 2.0.18", + "web-sys", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winit" +version = "0.30.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.10.0", + "block2", + "bytemuck", + "calloop", + "cfg_aliases", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 1.1.3", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcursor" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.10.0", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "zerocopy" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0d64950 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[workspace] +members = ["ui"] + +[package] +name = "PROJECT-NAME" +version = "0.1.0" +edition = "2024" + +[profile.dev] +panic = 'abort' + +[profile.dev.package."*"] +debug = false +opt-level = 3 + +[profile.test.package."*"] +debug = false +opt-level = 3 + +[dependencies] +glam = { version = "0.30.9" } +pollster = "0.4.0" +wgpu = "27.0.1" +winit = "0.30.12" diff --git a/README.md b/README.md new file mode 100644 index 0000000..a179901 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Qt6+WGPU skeleton program + +This example combines Qt6 GUI (C++) with WGPU rendering (Rust). It doesn’t do much of either but it’s the bridging what’s relevant. + +Currently only X11 (XCB) is supported but it should be easy to extend to other platforms. diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2518f00 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,169 @@ +use std::error::Error; + +use glam::{UVec2, Vec4}; + +const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth16Unorm; +const OUTPUT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb; + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct RedrawArgs { + pub background: Vec4, +} + +pub struct Gpu { + device: wgpu::Device, + queue: wgpu::Queue, + surface: wgpu::Surface<'static>, +} + +pub struct Core { + device: wgpu::Device, + queue: wgpu::Queue, + surface: wgpu::Surface<'static>, + + depth: wgpu::Texture, +} + +impl Core { + pub fn new(gpu: Gpu, pixel_size: UVec2) -> Self { + let Gpu { + device, + queue, + surface, + } = gpu; + + let depth = Self::create_depth_buffer(&device, pixel_size); + queue.submit([]); // flush buffer updates + + Self::configure_surface(&surface, &device, pixel_size); + + Self { + device, + queue, + surface, + depth, + } + } + + fn render(&self, output: &wgpu::Texture, args: &RedrawArgs) { + let aspect = { + let size = output.size(); + let w = size.width as f32; + let h = size.height as f32; + w / h + }; + self.queue.submit([]); // flush buffer updates + + let view = output.create_view(&wgpu::TextureViewDescriptor::default()); + let depth_view = self + .depth + .create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + depth_slice: None, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: args.background.x.into(), + g: args.background.y.into(), + b: args.background.z.into(), + a: args.background.w.into(), + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &depth_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.), + store: wgpu::StoreOp::Discard, + }), + stencil_ops: None, + }), + ..Default::default() + }); + + drop(pass); + self.queue.submit(std::iter::once(encoder.finish())); + } + + fn create_depth_buffer(device: &wgpu::Device, pixel_size: UVec2) -> wgpu::Texture { + device.create_texture(&wgpu::TextureDescriptor { + label: Some("depth buffer"), + size: wgpu::Extent3d { + width: pixel_size.x, + height: pixel_size.y, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: DEPTH_FORMAT, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }) + } + + fn configure_surface(surface: &wgpu::Surface, device: &wgpu::Device, pixel_size: UVec2) { + surface.configure( + device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST, + format: OUTPUT_FORMAT, + width: pixel_size.x, + height: pixel_size.y, + present_mode: wgpu::PresentMode::Fifo, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + view_formats: vec![], + desired_maximum_frame_latency: 2, + }, + ); + } + + /// Configures the renderer for a given target size. + pub fn configure(&mut self, pixel_size: UVec2) { + Self::configure_surface(&self.surface, &self.device, pixel_size); + self.depth = Self::create_depth_buffer(&self.device, pixel_size); + } + + /// Redraws the entire surface. + /// + /// [`Self::configure`] must be called at least once before this. + pub fn redraw(&mut self, args: &RedrawArgs) { + let output = self.surface.get_current_texture().unwrap(); + self.render(&output.texture, args); + output.present(); + } +} + +pub async fn init_gpu_inner( + make_surface: impl FnOnce(&wgpu::Instance) -> Result, E>, +) -> Result> { + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::PRIMARY, + ..Default::default() + }); + let surface = make_surface(&instance)?; + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .unwrap(); + let (device, queue) = adapter + .request_device(&wgpu::DeviceDescriptor::default()) + .await + .unwrap(); + Ok(Gpu { + device, + queue, + surface, + }) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..aa0bec3 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,82 @@ +use std::sync::Arc; + +use PROJECT_NAME::{Core, RedrawArgs, init_gpu_inner}; +use glam::{uvec2, vec4}; +use winit::{ + application::ApplicationHandler, + event::WindowEvent, + event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, + window::Window, +}; + +const TITLE: &str = "PROJECT NAME"; + +struct MainWindow { + window: Arc, + core: Core, +} + +impl MainWindow { + fn new(event_loop: &ActiveEventLoop) -> Self { + let window = event_loop + .create_window(Window::default_attributes().with_title(TITLE)) + .unwrap(); + let window = Arc::new(window); + let gpu = pollster::block_on(init_gpu_inner(|instance| { + instance.create_surface(Arc::clone(&window)) + })) + .unwrap(); + let core = Core::new(gpu, uvec2(1, 1)); + Self { window, core } + } + + fn event(&mut self, event_loop: &ActiveEventLoop, event: WindowEvent) { + match event { + WindowEvent::CloseRequested => event_loop.exit(), + WindowEvent::Resized(physical_size) => self + .core + .configure(uvec2(physical_size.width, physical_size.height)), + WindowEvent::RedrawRequested => self.core.redraw(&RedrawArgs { + background: vec4(0.05, 0.20, 0.85, 1.00), + }), + _ => {} + } + } +} + +struct Application { + main_window: Option, +} + +impl Application { + fn new() -> Self { + Self { main_window: None } + } +} + +impl ApplicationHandler for Application { + fn resumed(&mut self, event_loop: &ActiveEventLoop) { + self.main_window = Some(MainWindow::new(event_loop)); + } + + fn window_event( + &mut self, + event_loop: &ActiveEventLoop, + window_id: winit::window::WindowId, + event: WindowEvent, + ) { + let window = self + .main_window + .as_mut() + .expect("window must exist to recieve events"); + assert_eq!(window.window.id(), window_id); + window.event(event_loop, event); + } +} + +fn main() { + let event_loop = EventLoop::new().unwrap(); + event_loop.set_control_flow(ControlFlow::Wait); + let mut app = Application::new(); + event_loop.run_app(&mut app).unwrap(); +} diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt new file mode 100644 index 0000000..5c3f9f3 --- /dev/null +++ b/ui/CMakeLists.txt @@ -0,0 +1,14 @@ +include(impl.cmake) + +qt_add_executable(PROJECT-NAME + src/api.cxx + src/main.cxx + src/main_window.cxx + src/main_window.ui + src/viewport.cxx +) + +target_link_libraries(PROJECT-NAME PRIVATE Qt6::Gui Qt6::Widgets) +target_link_libraries(PROJECT-NAME PRIVATE KF6::WidgetsAddons) +target_link_libraries(PROJECT-NAME PRIVATE PROJECT_NAME_impl) +target_include_directories(PROJECT-NAME PRIVATE src) diff --git a/ui/Cargo.toml b/ui/Cargo.toml new file mode 100644 index 0000000..6565f4f --- /dev/null +++ b/ui/Cargo.toml @@ -0,0 +1,15 @@ +[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" diff --git a/ui/impl.cmake b/ui/impl.cmake new file mode 100644 index 0000000..e2f84e1 --- /dev/null +++ b/ui/impl.cmake @@ -0,0 +1,22 @@ +set(impl_basename "${CARGO_TARGET_DIR}/release/libPROJECT_NAME_impl") + +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 + JOB_SERVER_AWARE + DEPENDS_EXPLICIT_ONLY +) + +# 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 +) diff --git a/ui/src/api.cxx b/ui/src/api.cxx new file mode 100644 index 0000000..aaa2b64 --- /dev/null +++ b/ui/src/api.cxx @@ -0,0 +1,57 @@ +#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 new file mode 100644 index 0000000..bb946e9 --- /dev/null +++ b/ui/src/api.hxx @@ -0,0 +1,53 @@ +#pragma once + +#include + +struct xcb_connection_t; + +struct alignas(16) Vec4 { + float x, y, z, w; +}; + +namespace ffi { +struct Core; + +struct RedrawArgs { + Vec4 background; +}; +} // 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; +}; diff --git a/ui/src/lib.rs b/ui/src/lib.rs new file mode 100644 index 0000000..571ecd9 --- /dev/null +++ b/ui/src/lib.rs @@ -0,0 +1,48 @@ +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, + 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/main.cxx b/ui/src/main.cxx new file mode 100644 index 0000000..a0405ec --- /dev/null +++ b/ui/src/main.cxx @@ -0,0 +1,15 @@ +#include "main_window.hxx" + +#include + +int main(int argc, char* argv[]) { + // only X11 is supported so, use XCB unless overriden + setenv("QT_QPA_PLATFORM", "xcb", 0); + + QApplication app(argc, argv); + + auto w = new PROJECTNAME; + w->show(); + + return app.exec(); +} diff --git a/ui/src/main_window.cxx b/ui/src/main_window.cxx new file mode 100644 index 0000000..723dd57 --- /dev/null +++ b/ui/src/main_window.cxx @@ -0,0 +1,27 @@ +#include "main_window.hxx" + +#include "ui_main_window.h" + +PROJECTNAME::PROJECTNAME(QWidget* parent) + : QMainWindow(parent), + m_ui(new Ui::MainWindow) { + m_ui->setupUi(this); + updateView(); +} + +PROJECTNAME::~PROJECTNAME() = default; + +void PROJECTNAME::updateView() { + const auto color = m_ui->inBackground->color(); + RedrawArgs args{ + .background = { color.redF(), color.greenF(), color.blueF(), 1.00 }, + }; + m_ui->viewport->setView(args); +} + +void PROJECTNAME::updateViewIf(bool update) { + if (update) + updateView(); +} + +#include "moc_main_window.cpp" diff --git a/ui/src/main_window.hxx b/ui/src/main_window.hxx new file mode 100644 index 0000000..1b75e58 --- /dev/null +++ b/ui/src/main_window.hxx @@ -0,0 +1,23 @@ +#pragma once +#include + +#include + +namespace Ui { +class MainWindow; +} + +class PROJECTNAME : public QMainWindow { + Q_OBJECT + +public: + explicit PROJECTNAME(QWidget* parent = nullptr); + ~PROJECTNAME() override; + +public slots: + void updateView(); + void updateViewIf(bool update); // for radio buttons + +private: + const std::unique_ptr m_ui; +}; diff --git a/ui/src/main_window.ui b/ui/src/main_window.ui new file mode 100644 index 0000000..36facd0 --- /dev/null +++ b/ui/src/main_window.ui @@ -0,0 +1,114 @@ + + + MainWindow + + + + 0 + 0 + 1600 + 1200 + + + + PROJECT NAME + + + + + + + + + + + + 0 + 0 + 1600 + 34 + + + + + + + QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable + + + 2 + + + + + + + Background color + + + + + + + + 25 + 220 + 0 + + + + + + + + Qt::Vertical + + + + 20 + 385 + + + + + + + + + + + KColorCombo + QComboBox +
kcolorcombo.h
+
+ + Viewport + QWidget +
viewport.hxx
+ 1 +
+
+ + + + inBackground + highlighted(QColor) + MainWindow + updateView() + + + 1531 + 115 + + + 799 + 599 + + + + + + updateView() + updateViewIf(bool) + +
diff --git a/ui/src/viewport.cxx b/ui/src/viewport.cxx new file mode 100644 index 0000000..e681bd9 --- /dev/null +++ b/ui/src/viewport.cxx @@ -0,0 +1,74 @@ +#include "viewport.hxx" + +#include +#include +#include + +Viewport::Viewport(QWidget* parent, Qt::WindowFlags f) + : QWidget(parent, f) { + setAttribute(Qt::WA_DontCreateNativeAncestors); + setAttribute(Qt::WA_NativeWindow); + setAttribute(Qt::WA_PaintOnScreen); + setAttribute(Qt::WA_NoSystemBackground); +} + +Viewport::~Viewport() = default; + +void Viewport::setView(RedrawArgs new_args) { + args = new_args; + update(); +} + +QPaintEngine* Viewport::paintEngine() const { + return nullptr; +} + +bool Viewport::event(QEvent* event) { + switch (event->type()) { + case QEvent::Type::WinIdChange: + recreate(); + break; + default: + break; + } + return QWidget::event(event); +} + +void Viewport::paintEvent(QPaintEvent* event) { + if (!core) + recreate(); + core->redraw(args); +} + +void Viewport::resizeEvent(QResizeEvent* event) { + if (!core) + return; + updateSize(); + QWidget::resizeEvent(event); +} + +void Viewport::recreate() try { + auto* app = qobject_cast(QApplication::instance()); + if (!app) + throw std::runtime_error("not a GUI application (WTF?)"); + + auto* native = app->nativeInterface(); + if (!native) + throw std::runtime_error("X11 interface is not available"); + + auto* xcb_connection = native->connection(); + std::uint32_t x11_window = winId(); + + fprintf(stderr, "connection %p, window %#08x\n", xcb_connection, x11_window); + + const QSize device_size = size() * devicePixelRatio(); + core.reset(); + core = BoxCore::from_xcb(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()); +} + +void Viewport::updateSize() { + const QSize device_size = size() * devicePixelRatio(); + core->configure(device_size.width(), device_size.height()); +} diff --git a/ui/src/viewport.hxx b/ui/src/viewport.hxx new file mode 100644 index 0000000..95839b9 --- /dev/null +++ b/ui/src/viewport.hxx @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "api.hxx" + +class Viewport : public QWidget { + Q_OBJECT + +public: + explicit Viewport(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + ~Viewport() override; + QPaintEngine* paintEngine() const override; + + void setView(RedrawArgs new_args); + +protected: + bool event(QEvent* event) override; + void paintEvent(QPaintEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + +private: + BoxCore core; + RedrawArgs args = {}; + + void recreate(); + void updateSize(); +}; -- 2.47.3 From 24f3bc5d710bec393e792cbb11b921cb29b22ea6 Mon Sep 17 00:00:00 2001 From: numzero Date: Fri, 3 Apr 2026 14:57:51 +0300 Subject: [PATCH 19/32] add usage notes --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index a179901..4c819e4 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,20 @@ This example combines Qt6 GUI (C++) with WGPU rendering (Rust). It doesn’t do much of either but it’s the bridging what’s relevant. Currently only X11 (XCB) is supported but it should be easy to extend to other platforms. + +## Usage + +This program is intended as a starting point rather than a dependency, so just copy it. + +1. Fetch this code: + * clone the repo: `git clone https://gitea.noteuclid.ru/noteuclid/qt-wgpu-skel.git` + * or, download and unpack the [tarball](https://gitea.noteuclid.ru/noteuclid/qt-wgpu-skel/archive/master.tar.gz). +2. Rename the directory to your liking +3. Change the name in all files: + * `PROJECT NAME` to the user-visible project name, + * `PROJECT-NAME` to the technical project name (ASCII, no spaces), + * `PROJECT_NAME` to the same with dashes (`-`) replaced with underscores (`_`), + * `PROJECTNAME` to the same in PascalCase. + +If you want to use Qt5, change the version in both `CMakeLists.txt`, as well as the KF version. +If you don’t want to depend on KDE, remove the ECM and KF5 dependencies, as well as `KColorCombo inBackground` from the UI. You’ll need to replace `inBackground->color()` with something else though. -- 2.47.3 From caa75b5fcfb89bc113bec7b8174e916419302b9f Mon Sep 17 00:00:00 2001 From: numzero Date: Fri, 3 Apr 2026 16:42:21 +0300 Subject: [PATCH 20/32] add init script --- README.md | 10 ++++---- init.sh | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 5 deletions(-) create mode 100755 init.sh diff --git a/README.md b/README.md index 4c819e4..5949dc3 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,11 @@ This program is intended as a starting point rather than a dependency, so just c * clone the repo: `git clone https://gitea.noteuclid.ru/noteuclid/qt-wgpu-skel.git` * or, download and unpack the [tarball](https://gitea.noteuclid.ru/noteuclid/qt-wgpu-skel/archive/master.tar.gz). 2. Rename the directory to your liking -3. Change the name in all files: - * `PROJECT NAME` to the user-visible project name, - * `PROJECT-NAME` to the technical project name (ASCII, no spaces), - * `PROJECT_NAME` to the same with dashes (`-`) replaced with underscores (`_`), - * `PROJECTNAME` to the same in PascalCase. +3. Run `init.sh` with the project name, like: + * `./init.sh 'My FANCY Project'`, or + * `./init.sh 'My FANCY Project' 'fancy-project'`. + +Running `init.sh` without arguments prints the help. If you want to use Qt5, change the version in both `CMakeLists.txt`, as well as the KF version. If you don’t want to depend on KDE, remove the ECM and KF5 dependencies, as well as `KColorCombo inBackground` from the UI. You’ll need to replace `inBackground->color()` with something else though. diff --git a/init.sh b/init.sh new file mode 100755 index 0000000..450f39c --- /dev/null +++ b/init.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +# set -x # debug +set -E # abort on errors +cd "${0%/*}" # where this script is located + +usage() { + { + echo "Usage:" + echo + echo " ./init.sh 'Project Name' [ internal-name ]" + echo + echo "Project name may be anything you like, in any language, with spaces (but not other control characters), etc." + echo + echo "Technical name must be ASCII alphanumeric (a-z, 0-9, also _ and - are allowed), preferably all-lowercase. If omitted, it will be generated from the project name (but that only works if the latter is ASCII too)." + echo + echo "Running this script with no arguments shows this message" + } | fold -s 2>&1 +} + +name="$1" +[ -n "$name" ] || { usage; exit 0; } 2>&1 +echo -n "$name" | LC_ALL=C grep '[[:cntrl:]]' && { echo 'Project name must not contain any control characters'; exit 2; } 2>&1 +name="$(echo -n $name)" # normalize whitespace + +techname="$2" +if [ -z "$techname" ]; then + echo "Technical name not specified; trying to generate from the visible name" >&2 + techname=$( + echo -n $name | + LC_ALL=C tr ' ' '-' | + LC_ALL=C tr -d -c '[:alnum:]-_' | + LC_ALL=C tr '[:upper:]' '[:lower:]' | + cat + ) +fi + +echo -n $techname | LC_ALL=C grep '[^0-9A-Za-z_-]' && { echo 'Technical name must be ASCII alphanumeric (a-z, 0-9, also _ and - are allowed); got:' $techname; exit 2; } 2>&1 +echo -n $techname | LC_ALL=C grep -q '^[A-Za-z]' || { echo 'Technical name must start with an ASCII letter (a-z); got:' $techname; exit 2; } 2>&1 +echo -n $techname | LC_ALL=C grep -q '[A-Za-z0-9]$' || { echo 'Technical name must end with an ASCII letter or digit (a-z, 0-9); got:' $techname; exit 2; } 2>&1 + +echo "Project name: $name" +echo "Internal name: $techname" + +# detach from the skeleton repository +# oldurl="$(git remote -v get-url origin)" +git remote remove origin + +# remove this script from the project (but keep on disk for now) +git rm --cached init.sh + +# escape HTML special characters (&, <, and >), but also sed special characters (& again, \, and /) +htmlname=$(echo -n "$name" | sed 's/&/\&/g; s//\>/g; s:[\\/&]:\\&:g') +sed -i "s/PROJECT NAME/$htmlname/" ui/src/main_window.ui + +# escape Rust special characters (" and \; don't worry of the others as they are unlikely enough and easy to fix manually), and also sed special characters (&, \, and /) +quotname=$(echo -n "$name" | sed 's/"/\\"/g; s:[\\/&]:\\&:g') +sed -i "s/PROJECT NAME/$quotname/" src/main.rs + +# $techname is alphanumeric, nothing to escape +grep -l -r PROJECT-NAME | xargs sed -i "s/PROJECT-NAME/$techname/g" + +# replace - with _ to form a valid snake_case identifier +ident=$(echo -n $techname | LC_ALL=C tr '-' '_') +grep -l -r PROJECT_NAME | xargs sed -i "s/PROJECT_NAME/$ident/g" + +# black magic to convert snake_case to PascalCase +pascal=$(echo -n $ident | LC_ALL=C tr '_' '\n' | LC_ALL=C awk -v FIELDWIDTHS='1 1000' -v ORS='' '{print toupper($1) $2}') +grep -l -r PROJECTNAME | xargs sed -i "s/PROJECTNAME/$pascal/g" -- 2.47.3 From b6899e8537689010066dfa6cd51eb026e86527f6 Mon Sep 17 00:00:00 2001 From: numzero Date: Fri, 3 Apr 2026 16:45:08 +0300 Subject: [PATCH 21/32] init.sh: simplify error reporting --- init.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/init.sh b/init.sh index 450f39c..2c4cebc 100755 --- a/init.sh +++ b/init.sh @@ -25,7 +25,6 @@ name="$(echo -n $name)" # normalize whitespace techname="$2" if [ -z "$techname" ]; then - echo "Technical name not specified; trying to generate from the visible name" >&2 techname=$( echo -n $name | LC_ALL=C tr ' ' '-' | @@ -33,11 +32,12 @@ if [ -z "$techname" ]; then LC_ALL=C tr '[:upper:]' '[:lower:]' | cat ) + echo "Technical name not specified; autogenerated: \"$techname\"" >&2 fi -echo -n $techname | LC_ALL=C grep '[^0-9A-Za-z_-]' && { echo 'Technical name must be ASCII alphanumeric (a-z, 0-9, also _ and - are allowed); got:' $techname; exit 2; } 2>&1 -echo -n $techname | LC_ALL=C grep -q '^[A-Za-z]' || { echo 'Technical name must start with an ASCII letter (a-z); got:' $techname; exit 2; } 2>&1 -echo -n $techname | LC_ALL=C grep -q '[A-Za-z0-9]$' || { echo 'Technical name must end with an ASCII letter or digit (a-z, 0-9); got:' $techname; exit 2; } 2>&1 +echo -n $techname | LC_ALL=C grep '[^0-9A-Za-z_-]' && { echo 'Technical name must be ASCII alphanumeric'; exit 2; } 2>&1 +echo -n $techname | LC_ALL=C grep -q '^[A-Za-z]' || { echo 'Technical name must start with an ASCII letter'; exit 2; } 2>&1 +echo -n $techname | LC_ALL=C grep -q '[A-Za-z0-9]$' || { echo 'Technical name must end with an ASCII letter or digit'; exit 2; } 2>&1 echo "Project name: $name" echo "Internal name: $techname" -- 2.47.3 From 6de824c9c2bde2c43adbacae8f58181ee6a48ed0 Mon Sep 17 00:00:00 2001 From: numzero Date: Fri, 3 Apr 2026 16:56:05 +0300 Subject: [PATCH 22/32] init.sh: fix error handling --- init.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init.sh b/init.sh index 2c4cebc..428f66b 100755 --- a/init.sh +++ b/init.sh @@ -1,7 +1,7 @@ #!/bin/sh # set -x # debug -set -E # abort on errors +set -e # abort on errors cd "${0%/*}" # where this script is located usage() { -- 2.47.3 From 4c8dfa06d44a87b3fe21bf4777f7e475a43a1afb Mon Sep 17 00:00:00 2001 From: numzero Date: Fri, 3 Apr 2026 16:56:25 +0300 Subject: [PATCH 23/32] init.sh: fix working with no git --- init.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/init.sh b/init.sh index 428f66b..aad52a1 100755 --- a/init.sh +++ b/init.sh @@ -42,6 +42,10 @@ echo -n $techname | LC_ALL=C grep -q '[A-Za-z0-9]$' || { echo 'Technical name mu echo "Project name: $name" echo "Internal name: $techname" +if ! [ -e .git ]; then + alias git='echo skipping:' +fi + # detach from the skeleton repository # oldurl="$(git remote -v get-url origin)" git remote remove origin -- 2.47.3 From ff3ec96819c5a3835a92ad552de3efd13acc76ba Mon Sep 17 00:00:00 2001 From: numzero Date: Fri, 3 Apr 2026 16:58:10 +0300 Subject: [PATCH 24/32] init.sh: mark new project beginning with a commit --- init.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/init.sh b/init.sh index aad52a1..98a011b 100755 --- a/init.sh +++ b/init.sh @@ -71,3 +71,5 @@ grep -l -r PROJECT_NAME | xargs sed -i "s/PROJECT_NAME/$ident/g" # black magic to convert snake_case to PascalCase pascal=$(echo -n $ident | LC_ALL=C tr '_' '\n' | LC_ALL=C awk -v FIELDWIDTHS='1 1000' -v ORS='' '{print toupper($1) $2}') grep -l -r PROJECTNAME | xargs sed -i "s/PROJECTNAME/$pascal/g" + +git commit -a -m "MORPH INTO: $name" -- 2.47.3 From 7baefc958dc26f83430680d05aa3285baf87f1b0 Mon Sep 17 00:00:00 2001 From: numzero Date: Fri, 3 Apr 2026 17:01:12 +0300 Subject: [PATCH 25/32] init.sh: add tests (for manual running only for now) --- test/cargo.sh | 5 +++++ test/cmake.sh | 8 ++++++++ test/test-nogit.sh | 17 +++++++++++++++++ test/test-tar.sh | 16 ++++++++++++++++ test/test.sh | 18 ++++++++++++++++++ 5 files changed, 64 insertions(+) create mode 100755 test/cargo.sh create mode 100755 test/cmake.sh create mode 100755 test/test-nogit.sh create mode 100755 test/test-tar.sh create mode 100755 test/test.sh diff --git a/test/cargo.sh b/test/cargo.sh new file mode 100755 index 0000000..4c0e2cc --- /dev/null +++ b/test/cargo.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -E + +exec cargo check diff --git a/test/cmake.sh b/test/cmake.sh new file mode 100755 index 0000000..6d2b548 --- /dev/null +++ b/test/cmake.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +set -x +set -E + +mkdir build +cmake -S . -B build -G Ninja -D CMAKE_BUILD_TYPE=Debug +ninja -C build diff --git a/test/test-nogit.sh b/test/test-nogit.sh new file mode 100755 index 0000000..1aa4a74 --- /dev/null +++ b/test/test-nogit.sh @@ -0,0 +1,17 @@ +#!/bin/zsh + +set -e +trap 'echo Test failed!' ERR +name=' "Example"' +dir=${0%/*} +root=$dir/.. +tmp=$(mktemp -d) +trap 'rm -rf $tmp' EXIT +rsync -a --exclude=/.git --exclude-from=$root/.gitignore $root/. $tmp/ +cd $tmp +echo "Initializing in $tmp" +./init.sh $name +echo "Starting the shell in $tmp" +echo "Review the files, then exit the shell" +${SHELL:-/bin/sh} -i +echo "Review successful, cleaning up" diff --git a/test/test-tar.sh b/test/test-tar.sh new file mode 100755 index 0000000..8c60732 --- /dev/null +++ b/test/test-tar.sh @@ -0,0 +1,16 @@ +#!/bin/zsh + +set -e +trap 'echo Test failed!' ERR +name=' "Example"' +root=$(realpath ${0%/*}/..) +tmp=$(mktemp -d) +trap 'rm -rf $tmp' EXIT +cd $tmp +git -C $root archive HEAD | tar -x +echo "Initializing in $tmp" +./init.sh $name +echo "Starting the shell in $tmp" +echo "Review the files, then exit the shell" +${SHELL:-/bin/sh} -i +echo "Review successful, cleaning up" diff --git a/test/test.sh b/test/test.sh new file mode 100755 index 0000000..f80950f --- /dev/null +++ b/test/test.sh @@ -0,0 +1,18 @@ +#!/bin/zsh + +set -e +trap 'echo Test failed!' ERR +name=' "Example"' +dir=${0%/*} +root=$dir/.. +tmp=$(mktemp -d) +trap 'rm -rf $tmp' EXIT +rsync -a --exclude-from=$root/.gitignore $root/. $tmp/ +cd $tmp +git clean -fxdq +echo "Initializing in $tmp" +./init.sh $name +echo "Starting the shell in $tmp" +echo "Review the files, then exit the shell" +${SHELL:-/bin/sh} -i +echo "Review successful, cleaning up" -- 2.47.3 From af79b01fff61e5ebd425402df20ebd0ef66f6c49 Mon Sep 17 00:00:00 2001 From: numzero Date: Sat, 18 Apr 2026 16:23:47 +0300 Subject: [PATCH 26/32] make sure skeleton tests are separate for (future) project tests --- init.sh | 1 + skel-test/README.md | 3 +++ {test => skel-test}/cargo.sh | 0 {test => skel-test}/cmake.sh | 0 {test => skel-test}/test-nogit.sh | 0 {test => skel-test}/test-tar.sh | 0 {test => skel-test}/test.sh | 0 7 files changed, 4 insertions(+) create mode 100644 skel-test/README.md rename {test => skel-test}/cargo.sh (100%) rename {test => skel-test}/cmake.sh (100%) rename {test => skel-test}/test-nogit.sh (100%) rename {test => skel-test}/test-tar.sh (100%) rename {test => skel-test}/test.sh (100%) diff --git a/init.sh b/init.sh index 98a011b..7e098e7 100755 --- a/init.sh +++ b/init.sh @@ -52,6 +52,7 @@ git remote remove origin # remove this script from the project (but keep on disk for now) git rm --cached init.sh +git rm -r skel-test # escape HTML special characters (&, <, and >), but also sed special characters (& again, \, and /) htmlname=$(echo -n "$name" | sed 's/&/\&/g; s//\>/g; s:[\\/&]:\\&:g') diff --git a/skel-test/README.md b/skel-test/README.md new file mode 100644 index 0000000..c0d80e5 --- /dev/null +++ b/skel-test/README.md @@ -0,0 +1,3 @@ +# Self-tests + +This directory contains self-tests for the skeleton itself. It should be removed when starting a new project. diff --git a/test/cargo.sh b/skel-test/cargo.sh similarity index 100% rename from test/cargo.sh rename to skel-test/cargo.sh diff --git a/test/cmake.sh b/skel-test/cmake.sh similarity index 100% rename from test/cmake.sh rename to skel-test/cmake.sh diff --git a/test/test-nogit.sh b/skel-test/test-nogit.sh similarity index 100% rename from test/test-nogit.sh rename to skel-test/test-nogit.sh diff --git a/test/test-tar.sh b/skel-test/test-tar.sh similarity index 100% rename from test/test-tar.sh rename to skel-test/test-tar.sh diff --git a/test/test.sh b/skel-test/test.sh similarity index 100% rename from test/test.sh rename to skel-test/test.sh -- 2.47.3 From f2b904b5f913938674b25842514bb9d4299448bd Mon Sep 17 00:00:00 2001 From: numzero Date: Sat, 4 Jul 2026 22:45:54 +0300 Subject: [PATCH 27/32] replace README on morph --- init.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/init.sh b/init.sh index 98a011b..c93c8b7 100755 --- a/init.sh +++ b/init.sh @@ -53,6 +53,9 @@ git remote remove origin # remove this script from the project (but keep on disk for now) git rm --cached init.sh +# replace the README with a stub +echo "# $name" >| README.md + # escape HTML special characters (&, <, and >), but also sed special characters (& again, \, and /) htmlname=$(echo -n "$name" | sed 's/&/\&/g; s//\>/g; s:[\\/&]:\\&:g') sed -i "s/PROJECT NAME/$htmlname/" ui/src/main_window.ui -- 2.47.3 From 187019a318a0e1f14b199cce36225d68cc4408e0 Mon Sep 17 00:00:00 2001 From: numzero Date: Sun, 19 Jul 2026 21:42:05 +0300 Subject: [PATCH 28/32] use Default to avoid breaking fallback main --- src/lib.rs | 10 +++++++++- src/main.rs | 6 ++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2518f00..294edfd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ use std::error::Error; -use glam::{UVec2, Vec4}; +use glam::{UVec2, Vec4, vec4}; const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth16Unorm; const OUTPUT_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8UnormSrgb; @@ -11,6 +11,14 @@ pub struct RedrawArgs { pub background: Vec4, } +impl Default for RedrawArgs { + fn default() -> Self { + Self { + background: vec4(0.05, 0.20, 0.85, 1.00), + } + } +} + pub struct Gpu { device: wgpu::Device, queue: wgpu::Queue, diff --git a/src/main.rs b/src/main.rs index aa0bec3..3021a6a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use PROJECT_NAME::{Core, RedrawArgs, init_gpu_inner}; -use glam::{uvec2, vec4}; +use glam::uvec2; use winit::{ application::ApplicationHandler, event::WindowEvent, @@ -36,9 +36,7 @@ impl MainWindow { WindowEvent::Resized(physical_size) => self .core .configure(uvec2(physical_size.width, physical_size.height)), - WindowEvent::RedrawRequested => self.core.redraw(&RedrawArgs { - background: vec4(0.05, 0.20, 0.85, 1.00), - }), + WindowEvent::RedrawRequested => self.core.redraw(&RedrawArgs::default()), _ => {} } } -- 2.47.3 From 9a695c06b585f8939fdd248fa7694d5fa6e9d756 Mon Sep 17 00:00:00 2001 From: numzero Date: Tue, 4 Aug 2026 22:13:11 +0300 Subject: [PATCH 29/32] update Cargo.lock on morph --- init.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/init.sh b/init.sh index c93c8b7..276fb13 100755 --- a/init.sh +++ b/init.sh @@ -75,4 +75,7 @@ grep -l -r PROJECT_NAME | xargs sed -i "s/PROJECT_NAME/$ident/g" pascal=$(echo -n $ident | LC_ALL=C tr '_' '\n' | LC_ALL=C awk -v FIELDWIDTHS='1 1000' -v ORS='' '{print toupper($1) $2}') grep -l -r PROJECTNAME | xargs sed -i "s/PROJECTNAME/$pascal/g" +# another hack, to update Cargo.lock +cargo tree >/dev/null + git commit -a -m "MORPH INTO: $name" -- 2.47.3 From a5e8e3e2e800c70eb8723ffda8e5cb0032956dc2 Mon Sep 17 00:00:00 2001 From: numzero Date: Tue, 27 Oct 2026 17:14:46 +0300 Subject: [PATCH 30/32] MORPH INTO: Photon Light --- CMakeLists.txt | 2 +- Cargo.lock | 42 ++++++++++----------- Cargo.toml | 2 +- README.md | 23 +----------- init.sh | 82 ----------------------------------------- skel-test/README.md | 3 -- skel-test/cargo.sh | 5 --- skel-test/cmake.sh | 8 ---- skel-test/test-nogit.sh | 17 --------- skel-test/test-tar.sh | 16 -------- skel-test/test.sh | 18 --------- src/main.rs | 4 +- ui/CMakeLists.txt | 10 ++--- ui/Cargo.toml | 4 +- ui/impl.cmake | 8 ++-- ui/src/lib.rs | 2 +- ui/src/main.cxx | 2 +- ui/src/main_window.cxx | 8 ++-- ui/src/main_window.hxx | 6 +-- ui/src/main_window.ui | 2 +- 20 files changed, 47 insertions(+), 217 deletions(-) delete mode 100755 init.sh delete mode 100644 skel-test/README.md delete mode 100755 skel-test/cargo.sh delete mode 100755 skel-test/cmake.sh delete mode 100755 skel-test/test-nogit.sh delete mode 100755 skel-test/test-tar.sh delete mode 100755 skel-test/test.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index f30e705..49b4606 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.18) -project(PROJECT-NAME VERSION 1.0.0 LANGUAGES CXX) +project(photon-light VERSION 1.0.0 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/Cargo.lock b/Cargo.lock index 9db39c2..add1bfd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,27 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "PROJECT-NAME" -version = "0.1.0" -dependencies = [ - "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" @@ -1184,6 +1163,27 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "photon-light" +version = "0.1.0" +dependencies = [ + "glam", + "pollster", + "wgpu", + "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" diff --git a/Cargo.toml b/Cargo.toml index 0d64950..b43459f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ members = ["ui"] [package] -name = "PROJECT-NAME" +name = "photon-light" version = "0.1.0" edition = "2024" diff --git a/README.md b/README.md index 5949dc3..69f2a64 100644 --- a/README.md +++ b/README.md @@ -1,22 +1 @@ -# Qt6+WGPU skeleton program - -This example combines Qt6 GUI (C++) with WGPU rendering (Rust). It doesn’t do much of either but it’s the bridging what’s relevant. - -Currently only X11 (XCB) is supported but it should be easy to extend to other platforms. - -## Usage - -This program is intended as a starting point rather than a dependency, so just copy it. - -1. Fetch this code: - * clone the repo: `git clone https://gitea.noteuclid.ru/noteuclid/qt-wgpu-skel.git` - * or, download and unpack the [tarball](https://gitea.noteuclid.ru/noteuclid/qt-wgpu-skel/archive/master.tar.gz). -2. Rename the directory to your liking -3. Run `init.sh` with the project name, like: - * `./init.sh 'My FANCY Project'`, or - * `./init.sh 'My FANCY Project' 'fancy-project'`. - -Running `init.sh` without arguments prints the help. - -If you want to use Qt5, change the version in both `CMakeLists.txt`, as well as the KF version. -If you don’t want to depend on KDE, remove the ECM and KF5 dependencies, as well as `KColorCombo inBackground` from the UI. You’ll need to replace `inBackground->color()` with something else though. +# Photon Light diff --git a/init.sh b/init.sh deleted file mode 100755 index 6fe9b04..0000000 --- a/init.sh +++ /dev/null @@ -1,82 +0,0 @@ -#!/bin/sh - -# set -x # debug -set -e # abort on errors -cd "${0%/*}" # where this script is located - -usage() { - { - echo "Usage:" - echo - echo " ./init.sh 'Project Name' [ internal-name ]" - echo - echo "Project name may be anything you like, in any language, with spaces (but not other control characters), etc." - echo - echo "Technical name must be ASCII alphanumeric (a-z, 0-9, also _ and - are allowed), preferably all-lowercase. If omitted, it will be generated from the project name (but that only works if the latter is ASCII too)." - echo - echo "Running this script with no arguments shows this message" - } | fold -s 2>&1 -} - -name="$1" -[ -n "$name" ] || { usage; exit 0; } 2>&1 -echo -n "$name" | LC_ALL=C grep '[[:cntrl:]]' && { echo 'Project name must not contain any control characters'; exit 2; } 2>&1 -name="$(echo -n $name)" # normalize whitespace - -techname="$2" -if [ -z "$techname" ]; then - techname=$( - echo -n $name | - LC_ALL=C tr ' ' '-' | - LC_ALL=C tr -d -c '[:alnum:]-_' | - LC_ALL=C tr '[:upper:]' '[:lower:]' | - cat - ) - echo "Technical name not specified; autogenerated: \"$techname\"" >&2 -fi - -echo -n $techname | LC_ALL=C grep '[^0-9A-Za-z_-]' && { echo 'Technical name must be ASCII alphanumeric'; exit 2; } 2>&1 -echo -n $techname | LC_ALL=C grep -q '^[A-Za-z]' || { echo 'Technical name must start with an ASCII letter'; exit 2; } 2>&1 -echo -n $techname | LC_ALL=C grep -q '[A-Za-z0-9]$' || { echo 'Technical name must end with an ASCII letter or digit'; exit 2; } 2>&1 - -echo "Project name: $name" -echo "Internal name: $techname" - -if ! [ -e .git ]; then - alias git='echo skipping:' -fi - -# detach from the skeleton repository -# oldurl="$(git remote -v get-url origin)" -git remote remove origin - -# remove this script from the project (but keep on disk for now) -git rm --cached init.sh -git rm -r skel-test - -# replace the README with a stub -echo "# $name" >| README.md - -# escape HTML special characters (&, <, and >), but also sed special characters (& again, \, and /) -htmlname=$(echo -n "$name" | sed 's/&/\&/g; s//\>/g; s:[\\/&]:\\&:g') -sed -i "s/PROJECT NAME/$htmlname/" ui/src/main_window.ui - -# escape Rust special characters (" and \; don't worry of the others as they are unlikely enough and easy to fix manually), and also sed special characters (&, \, and /) -quotname=$(echo -n "$name" | sed 's/"/\\"/g; s:[\\/&]:\\&:g') -sed -i "s/PROJECT NAME/$quotname/" src/main.rs - -# $techname is alphanumeric, nothing to escape -grep -l -r PROJECT-NAME | xargs sed -i "s/PROJECT-NAME/$techname/g" - -# replace - with _ to form a valid snake_case identifier -ident=$(echo -n $techname | LC_ALL=C tr '-' '_') -grep -l -r PROJECT_NAME | xargs sed -i "s/PROJECT_NAME/$ident/g" - -# black magic to convert snake_case to PascalCase -pascal=$(echo -n $ident | LC_ALL=C tr '_' '\n' | LC_ALL=C awk -v FIELDWIDTHS='1 1000' -v ORS='' '{print toupper($1) $2}') -grep -l -r PROJECTNAME | xargs sed -i "s/PROJECTNAME/$pascal/g" - -# another hack, to update Cargo.lock -cargo tree >/dev/null - -git commit -a -m "MORPH INTO: $name" diff --git a/skel-test/README.md b/skel-test/README.md deleted file mode 100644 index c0d80e5..0000000 --- a/skel-test/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Self-tests - -This directory contains self-tests for the skeleton itself. It should be removed when starting a new project. diff --git a/skel-test/cargo.sh b/skel-test/cargo.sh deleted file mode 100755 index 4c0e2cc..0000000 --- a/skel-test/cargo.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -set -E - -exec cargo check diff --git a/skel-test/cmake.sh b/skel-test/cmake.sh deleted file mode 100755 index 6d2b548..0000000 --- a/skel-test/cmake.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -set -x -set -E - -mkdir build -cmake -S . -B build -G Ninja -D CMAKE_BUILD_TYPE=Debug -ninja -C build diff --git a/skel-test/test-nogit.sh b/skel-test/test-nogit.sh deleted file mode 100755 index 1aa4a74..0000000 --- a/skel-test/test-nogit.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/zsh - -set -e -trap 'echo Test failed!' ERR -name=' "Example"' -dir=${0%/*} -root=$dir/.. -tmp=$(mktemp -d) -trap 'rm -rf $tmp' EXIT -rsync -a --exclude=/.git --exclude-from=$root/.gitignore $root/. $tmp/ -cd $tmp -echo "Initializing in $tmp" -./init.sh $name -echo "Starting the shell in $tmp" -echo "Review the files, then exit the shell" -${SHELL:-/bin/sh} -i -echo "Review successful, cleaning up" diff --git a/skel-test/test-tar.sh b/skel-test/test-tar.sh deleted file mode 100755 index 8c60732..0000000 --- a/skel-test/test-tar.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/zsh - -set -e -trap 'echo Test failed!' ERR -name=' "Example"' -root=$(realpath ${0%/*}/..) -tmp=$(mktemp -d) -trap 'rm -rf $tmp' EXIT -cd $tmp -git -C $root archive HEAD | tar -x -echo "Initializing in $tmp" -./init.sh $name -echo "Starting the shell in $tmp" -echo "Review the files, then exit the shell" -${SHELL:-/bin/sh} -i -echo "Review successful, cleaning up" diff --git a/skel-test/test.sh b/skel-test/test.sh deleted file mode 100755 index f80950f..0000000 --- a/skel-test/test.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/zsh - -set -e -trap 'echo Test failed!' ERR -name=' "Example"' -dir=${0%/*} -root=$dir/.. -tmp=$(mktemp -d) -trap 'rm -rf $tmp' EXIT -rsync -a --exclude-from=$root/.gitignore $root/. $tmp/ -cd $tmp -git clean -fxdq -echo "Initializing in $tmp" -./init.sh $name -echo "Starting the shell in $tmp" -echo "Review the files, then exit the shell" -${SHELL:-/bin/sh} -i -echo "Review successful, cleaning up" diff --git a/src/main.rs b/src/main.rs index 3021a6a..26c3e2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use PROJECT_NAME::{Core, RedrawArgs, init_gpu_inner}; +use photon_light::{Core, RedrawArgs, init_gpu_inner}; use glam::uvec2; use winit::{ application::ApplicationHandler, @@ -9,7 +9,7 @@ use winit::{ window::Window, }; -const TITLE: &str = "PROJECT NAME"; +const TITLE: &str = "Photon Light"; struct MainWindow { window: Arc, diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 5c3f9f3..2759ac7 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -1,6 +1,6 @@ include(impl.cmake) -qt_add_executable(PROJECT-NAME +qt_add_executable(photon-light src/api.cxx src/main.cxx src/main_window.cxx @@ -8,7 +8,7 @@ qt_add_executable(PROJECT-NAME src/viewport.cxx ) -target_link_libraries(PROJECT-NAME PRIVATE Qt6::Gui Qt6::Widgets) -target_link_libraries(PROJECT-NAME PRIVATE KF6::WidgetsAddons) -target_link_libraries(PROJECT-NAME PRIVATE PROJECT_NAME_impl) -target_include_directories(PROJECT-NAME PRIVATE src) +target_link_libraries(photon-light PRIVATE Qt6::Gui Qt6::Widgets) +target_link_libraries(photon-light PRIVATE KF6::WidgetsAddons) +target_link_libraries(photon-light PRIVATE photon_light_impl) +target_include_directories(photon-light PRIVATE src) diff --git a/ui/Cargo.toml b/ui/Cargo.toml index 6565f4f..e12ba36 100644 --- a/ui/Cargo.toml +++ b/ui/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "PROJECT-NAME-impl" +name = "photon-light-impl" version = "0.1.0" edition = "2024" @@ -7,7 +7,7 @@ edition = "2024" crate-type = ["staticlib"] [dependencies] -PROJECT-NAME = {path = "../"} +photon-light = {path = "../"} glam = { version = "0.30" } pollster = "0.4.0" diff --git a/ui/impl.cmake b/ui/impl.cmake index e2f84e1..6abf56e 100644 --- a/ui/impl.cmake +++ b/ui/impl.cmake @@ -1,8 +1,8 @@ -set(impl_basename "${CARGO_TARGET_DIR}/release/libPROJECT_NAME_impl") +set(impl_basename "${CARGO_TARGET_DIR}/release/libphoton_light_impl") add_custom_command( OUTPUT ${impl_basename}.a - COMMAND env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} ${CARGO} build --release --package PROJECT-NAME-impl + COMMAND env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} ${CARGO} build --release --package photon-light-impl WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPFILE ${impl_basename}.d USES_TERMINAL @@ -15,8 +15,8 @@ add_custom_target(build_impl DEPENDS ${impl_basename}.a ) -add_library(PROJECT_NAME_impl STATIC IMPORTED) +add_library(photon_light_impl STATIC IMPORTED) -set_target_properties(PROJECT_NAME_impl PROPERTIES +set_target_properties(photon_light_impl PROPERTIES IMPORTED_LOCATION ${impl_basename}.a ) diff --git a/ui/src/lib.rs b/ui/src/lib.rs index 571ecd9..b9f9137 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -1,6 +1,6 @@ use std::{ffi::c_void, num::NonZero, ptr::NonNull}; -use PROJECT_NAME::{Core, RedrawArgs, init_gpu_inner}; +use photon_light::{Core, RedrawArgs, init_gpu_inner}; use glam::{UVec2, uvec2}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle, XcbDisplayHandle, XcbWindowHandle}; diff --git a/ui/src/main.cxx b/ui/src/main.cxx index a0405ec..03115ce 100644 --- a/ui/src/main.cxx +++ b/ui/src/main.cxx @@ -8,7 +8,7 @@ int main(int argc, char* argv[]) { QApplication app(argc, argv); - auto w = new PROJECTNAME; + auto w = new PhotonLight; w->show(); return app.exec(); diff --git a/ui/src/main_window.cxx b/ui/src/main_window.cxx index 723dd57..208325e 100644 --- a/ui/src/main_window.cxx +++ b/ui/src/main_window.cxx @@ -2,16 +2,16 @@ #include "ui_main_window.h" -PROJECTNAME::PROJECTNAME(QWidget* parent) +PhotonLight::PhotonLight(QWidget* parent) : QMainWindow(parent), m_ui(new Ui::MainWindow) { m_ui->setupUi(this); updateView(); } -PROJECTNAME::~PROJECTNAME() = default; +PhotonLight::~PhotonLight() = default; -void PROJECTNAME::updateView() { +void PhotonLight::updateView() { const auto color = m_ui->inBackground->color(); RedrawArgs args{ .background = { color.redF(), color.greenF(), color.blueF(), 1.00 }, @@ -19,7 +19,7 @@ void PROJECTNAME::updateView() { m_ui->viewport->setView(args); } -void PROJECTNAME::updateViewIf(bool update) { +void PhotonLight::updateViewIf(bool update) { if (update) updateView(); } diff --git a/ui/src/main_window.hxx b/ui/src/main_window.hxx index 1b75e58..27b63ff 100644 --- a/ui/src/main_window.hxx +++ b/ui/src/main_window.hxx @@ -7,12 +7,12 @@ namespace Ui { class MainWindow; } -class PROJECTNAME : public QMainWindow { +class PhotonLight : public QMainWindow { Q_OBJECT public: - explicit PROJECTNAME(QWidget* parent = nullptr); - ~PROJECTNAME() override; + explicit PhotonLight(QWidget* parent = nullptr); + ~PhotonLight() override; public slots: void updateView(); diff --git a/ui/src/main_window.ui b/ui/src/main_window.ui index 36facd0..8e9ae8b 100644 --- a/ui/src/main_window.ui +++ b/ui/src/main_window.ui @@ -11,7 +11,7 @@ - PROJECT NAME + Photon Light -- 2.47.3 From 991d672f3a754c25273b52ea0ceb3b959768a4de Mon Sep 17 00:00:00 2001 From: numzero Date: Sat, 12 Sep 2026 16:25:14 +0300 Subject: [PATCH 31/32] remove the KDE dependency --- CMakeLists.txt | 5 ----- ui/CMakeLists.txt | 1 - 2 files changed, 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 49b4606..097b292 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,11 +9,6 @@ find_package(Qt6 REQUIRED COMPONENTS Gui Widgets) find_program(CARGO cargo REQUIRED) set(CARGO_TARGET_DIR "${CMAKE_BINARY_DIR}/cargo") -# KDE-specific -find_package(ECM REQUIRED NO_MODULE) -set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) -find_package(KF6 REQUIRED COMPONENTS WidgetsAddons) - qt_standard_project_setup() add_subdirectory(ui) diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 2759ac7..191380f 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -9,6 +9,5 @@ qt_add_executable(photon-light ) target_link_libraries(photon-light PRIVATE Qt6::Gui Qt6::Widgets) -target_link_libraries(photon-light PRIVATE KF6::WidgetsAddons) target_link_libraries(photon-light PRIVATE photon_light_impl) target_include_directories(photon-light PRIVATE src) -- 2.47.3 From 9a8dc8fe34069756b1874479ed96ea286a2c289d Mon Sep 17 00:00:00 2001 From: numzero Date: Tue, 27 Oct 2026 18:24:04 +0300 Subject: [PATCH 32/32] cargo fmt --- src/camera.rs | 13 +++++++------ src/main.rs | 3 ++- ui/src/lib.rs | 3 ++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/camera.rs b/src/camera.rs index a91e1a7..72560ea 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -31,12 +31,13 @@ impl OrbitalCamera { [-1., 0., 0., 0.], [0., 1., 0., 0.], [0., 0., 0., 1.], - ]) * Mat4::from_euler( - glam::EulerRot::ZYZ, - 0., - self.position_pitch, - -self.position_yaw, - ) + ]) + * Mat4::from_euler( + glam::EulerRot::ZYZ, + 0., + self.position_pitch, + -self.position_yaw, + ) } } diff --git a/src/main.rs b/src/main.rs index 26c3e2d..da72f20 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use photon_light::{Core, RedrawArgs, init_gpu_inner}; use glam::uvec2; use winit::{ application::ApplicationHandler, @@ -9,6 +8,8 @@ use winit::{ window::Window, }; +use photon_light::{Core, RedrawArgs, init_gpu_inner}; + const TITLE: &str = "Photon Light"; struct MainWindow { diff --git a/ui/src/lib.rs b/ui/src/lib.rs index b9f9137..ed5dce1 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -1,9 +1,10 @@ use std::{ffi::c_void, num::NonZero, ptr::NonNull}; -use photon_light::{Core, RedrawArgs, init_gpu_inner}; 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, -- 2.47.3