diff options
| author | s-ol <s+removethis@s-ol.nu> | 2025-12-23 13:50:48 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2025-12-23 13:51:46 +0000 |
| commit | d687ff022b35e1ef39be32efad755bcaf0b5a398 (patch) | |
| tree | 0766d04a3f0d63992e978d2a081e9fa3cfbbaf45 | |
| parent | add trunk for web deployment (diff) | |
| download | nodetoy-d687ff022b35e1ef39be32efad755bcaf0b5a398.tar.gz nodetoy-d687ff022b35e1ef39be32efad755bcaf0b5a398.zip | |
wgpu, logging integrations
| -rw-r--r-- | Cargo.lock | 75 | ||||
| -rw-r--r-- | Cargo.toml | 6 | ||||
| -rw-r--r-- | src/main.rs | 42 | ||||
| -rw-r--r-- | src/preview.rs | 232 |
4 files changed, 281 insertions, 74 deletions
@@ -189,12 +189,6 @@ dependencies = [ ] [[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" @@ -817,7 +811,6 @@ dependencies = [ "egui-wgpu", "egui-winit", "egui_glow", - "glow", "glutin", "glutin-winit", "home", @@ -829,6 +822,7 @@ dependencies = [ "objc2-foundation 0.2.2", "parking_lot", "percent-encoding", + "pollster", "profiling", "raw-window-handle", "ron", @@ -838,6 +832,7 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "web-time", + "wgpu", "windows-sys 0.61.2", "winit", ] @@ -1754,6 +1749,9 @@ name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +dependencies = [ + "serde_core", +] [[package]] name = "malloc_buf" @@ -1915,11 +1913,13 @@ dependencies = [ name = "nodetoy" version = "0.1.0" dependencies = [ + "bytemuck", "eframe", "egui", "egui-probe", "egui-snarl", "egui_extras", + "log", "serde", "serde_json", "syn", @@ -2451,6 +2451,12 @@ dependencies = [ ] [[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.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2718,19 +2724,6 @@ 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 0.19.2", - "tiny-skia", -] - -[[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2929,12 +2922,6 @@ 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.111" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3033,31 +3020,6 @@ dependencies = [ ] [[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 = "tinystr" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3530,6 +3492,7 @@ dependencies = [ "thiserror 2.0.17", "wgpu-core-deps-apple", "wgpu-core-deps-emscripten", + "wgpu-core-deps-wasm", "wgpu-core-deps-windows-linux-android", "wgpu-hal", "wgpu-types", @@ -3554,6 +3517,15 @@ dependencies = [ ] [[package]] +name = "wgpu-core-deps-wasm" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1027dcf3b027a877e44819df7ceb0e2e98578830f8cd34cd6c3c7c2a7a50b7" +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" @@ -4101,7 +4073,6 @@ dependencies = [ "raw-window-handle", "redox_syscall 0.4.1", "rustix 0.38.44", - "sctk-adwaita", "smithay-client-toolkit 0.19.2", "smol_str", "tracing", @@ -4,15 +4,17 @@ version = "0.1.0" edition = "2024" [dependencies] -eframe = { version = "0.33", features = ["serde", "persistence"] } +bytemuck = "1.24.0" +eframe = { version = "0.33", features = ["accesskit", "default_fonts", "wayland", "x11", "serde", "persistence", "wgpu"], default-features = false } egui = "0.33" egui-probe = { version = "0.10", features = ["derive"] } egui-snarl = { version = "0.9", features = ["serde", "egui-probe"] } egui_extras = { version = "0.33" } +log = { version = "0.4.29", features = ["serde", "std"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" syn = { version = "2.0", features = ["extra-traits"] } -wgpu = { version = "27.0", features = ["glsl"] } +wgpu = { version = "27.0", features = ["glsl", "webgl"] } [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen-futures = "0.4" diff --git a/src/main.rs b/src/main.rs index 2258fba..75a5df4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ #![allow(clippy::use_self)] +use log::*; use std::collections::HashMap; use eframe::{App, CreationContext}; @@ -12,6 +13,8 @@ use egui_snarl::{ }, }; +mod preview; + const STRING_COLOR: Color32 = Color32::from_rgb(0x00, 0xb0, 0x00); const NUMBER_COLOR: Color32 = Color32::from_rgb(0xb0, 0x00, 0x00); const IMAGE_COLOR: Color32 = Color32::from_rgb(0xb0, 0x00, 0xb0); @@ -928,7 +931,7 @@ impl Expr { pub struct DemoApp { snarl: Snarl<DemoNode>, - style: SnarlStyle, + preview: preview::Custom3d, } const fn default_style() -> SnarlStyle { @@ -975,15 +978,12 @@ impl DemoApp { }); // let snarl = Snarl::new(); - let style = cx.storage.map_or_else(default_style, |storage| { - storage - .get_string("style") - .and_then(|style| serde_json::from_str(&style).ok()) - .unwrap_or_else(default_style) - }); - // let style = SnarlStyle::new(); + info!("about to init"); - DemoApp { snarl, style } + DemoApp { + snarl, + preview: preview::Custom3d::new(cx).expect("Failed to init preview"), + } } } @@ -1011,13 +1011,7 @@ impl App for DemoApp { }); }); - egui::SidePanel::left("style").show(ctx, |ui| { - egui::ScrollArea::vertical().show(ui, |ui| { - egui_probe::Probe::new(&mut self.style).show(ui); - }); - }); - - egui::SidePanel::right("selected-list").show(ctx, |ui| { + egui::SidePanel::left("selected-list").show(ctx, |ui| { egui::ScrollArea::vertical().show(ui, |ui| { ui.strong("Selected nodes"); @@ -1049,10 +1043,18 @@ impl App for DemoApp { }); }); + egui::SidePanel::right("preview").show(ctx, |ui| { + egui::ScrollArea::vertical().show(ui, |ui| { + egui::Frame::canvas(ui.style()).show(ui, |ui| { + self.preview.custom_painting(ui); + }); + }); + }); + egui::CentralPanel::default().show(ctx, |ui| { SnarlWidget::new() .id(Id::new("snarl-demo")) - .style(self.style) + .style(default_style()) .show(&mut self.snarl, &mut DemoViewer, ui); }); } @@ -1060,9 +1062,6 @@ impl App for DemoApp { fn save(&mut self, storage: &mut dyn eframe::Storage) { let snarl = serde_json::to_string(&self.snarl).unwrap(); storage.set_string("snarl", snarl); - - let style = serde_json::to_string(&self.style).unwrap(); - storage.set_string("style", style); } } @@ -1073,6 +1072,7 @@ fn main() -> eframe::Result<()> { viewport: egui::ViewportBuilder::default() .with_inner_size([400.0, 300.0]) .with_min_inner_size([300.0, 220.0]), + renderer: eframe::Renderer::Wgpu, ..Default::default() }; @@ -1095,6 +1095,8 @@ fn get_canvas_element() -> Option<web_sys::HtmlCanvasElement> { // When compiling to web using trunk: #[cfg(target_arch = "wasm32")] fn main() { + eframe::WebLogger::init(log::LevelFilter::Debug).ok(); + let canvas = get_canvas_element().expect("Failed to find canvas with id 'egui_snarl_demo'"); let web_options = eframe::WebOptions::default(); diff --git a/src/preview.rs b/src/preview.rs new file mode 100644 index 0000000..33c6b77 --- /dev/null +++ b/src/preview.rs @@ -0,0 +1,232 @@ +#![expect(clippy::unwrap_used)] // TODO(emilk): avoid unwraps + +use log::*; +use std::num::NonZeroU64; + +use eframe::{ + egui_wgpu::wgpu::util::DeviceExt as _, + egui_wgpu::{self, wgpu}, +}; + +pub struct Custom3d { + angle: f32, +} + +impl Custom3d { + pub fn new<'a>(cc: &'a eframe::CreationContext<'a>) -> Option<Self> { + // Get the WGPU render state from the eframe creation context. This can also be retrieved + // from `eframe::Frame` when you don't have a `CreationContext` available. + info!("getting wgpu state"); + let wgpu_render_state = cc.wgpu_render_state.as_ref()?; + + let device = &wgpu_render_state.device; + + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("custom3d"), + source: wgpu::ShaderSource::Wgsl( + " +struct VertexOut { + @location(0) color: vec4<f32>, + @builtin(position) position: vec4<f32>, +}; + +struct Uniforms { + @size(16) angle: f32, // pad to 16 bytes +}; + +@group(0) @binding(0) +var<uniform> uniforms: Uniforms; + +var<private> v_positions: array<vec2<f32>, 3> = array<vec2<f32>, 3>( + vec2<f32>(0.0, 1.0), + vec2<f32>(1.0, -1.0), + vec2<f32>(-1.0, -1.0), +); + +var<private> v_colors: array<vec4<f32>, 3> = array<vec4<f32>, 3>( + vec4<f32>(1.0, 0.0, 0.0, 1.0), + vec4<f32>(0.0, 1.0, 0.0, 1.0), + vec4<f32>(0.0, 0.0, 1.0, 1.0), +); + +@vertex +fn vs_main(@builtin(vertex_index) v_idx: u32) -> VertexOut { + var out: VertexOut; + + out.position = vec4<f32>(v_positions[v_idx], 0.0, 1.0); + out.position.x = out.position.x * cos(uniforms.angle); + out.color = v_colors[v_idx]; + + return out; +} + +@fragment +fn fs_main(in: VertexOut) -> @location(0) vec4<f32> { + return in.color; +} +" + .into(), + ), + }); + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("custom3d"), + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: NonZeroU64::new(16), + }, + count: None, + }], + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("custom3d"), + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("custom3d"), + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: None, + buffers: &[], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu_render_state.target_format.into())], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + + let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("custom3d"), + contents: bytemuck::cast_slice(&[0.0_f32; 4]), // 16 bytes aligned! + // Mapping at creation (as done by the create_buffer_init utility) doesn't require us to to add the MAP_WRITE usage + // (this *happens* to workaround this bug ) + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM, + }); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("custom3d"), + layout: &bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: uniform_buffer.as_entire_binding(), + }], + }); + + // Because the graphics pipeline must have the same lifetime as the egui render pass, + // instead of storing the pipeline in our `Custom3d` struct, we insert it into the + // `paint_callback_resources` type map, which is stored alongside the render pass. + wgpu_render_state + .renderer + .write() + .callback_resources + .insert(TriangleRenderResources { + pipeline, + bind_group, + uniform_buffer, + }); + + Some(Self { angle: 0.0 }) + } +} + +// Callbacks in egui_wgpu have 3 stages: +// * prepare (per callback impl) +// * finish_prepare (once) +// * paint (per callback impl) +// +// The prepare callback is called every frame before paint and is given access to the wgpu +// Device and Queue, which can be used, for instance, to update buffers and uniforms before +// rendering. +// If [`egui_wgpu::Renderer`] has [`egui_wgpu::FinishPrepareCallback`] registered, +// it will be called after all `prepare` callbacks have been called. +// You can use this to update any shared resources that need to be updated once per frame +// after all callbacks have been processed. +// +// On both prepare methods you can use the main `CommandEncoder` that is passed-in, +// return an arbitrary number of user-defined `CommandBuffer`s, or both. +// The main command buffer, as well as all user-defined ones, will be submitted together +// to the GPU in a single call. +// +// The paint callback is called after finish prepare and is given access to egui's main render pass, +// which can be used to issue draw commands. +struct CustomTriangleCallback { + angle: f32, +} + +impl egui_wgpu::CallbackTrait for CustomTriangleCallback { + fn prepare( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + _screen_descriptor: &egui_wgpu::ScreenDescriptor, + _egui_encoder: &mut wgpu::CommandEncoder, + resources: &mut egui_wgpu::CallbackResources, + ) -> Vec<wgpu::CommandBuffer> { + let resources: &TriangleRenderResources = resources.get().unwrap(); + resources.prepare(device, queue, self.angle); + Vec::new() + } + + fn paint( + &self, + _info: egui::PaintCallbackInfo, + render_pass: &mut wgpu::RenderPass<'static>, + resources: &egui_wgpu::CallbackResources, + ) { + let resources: &TriangleRenderResources = resources.get().unwrap(); + resources.paint(render_pass); + } +} + +impl Custom3d { + pub fn custom_painting(&mut self, ui: &mut egui::Ui) { + let (rect, response) = + ui.allocate_exact_size(egui::Vec2::splat(300.0), egui::Sense::drag()); + + self.angle += response.drag_motion().x * 0.01; + ui.painter().add(egui_wgpu::Callback::new_paint_callback( + rect, + CustomTriangleCallback { angle: self.angle }, + )); + } +} + +struct TriangleRenderResources { + pipeline: wgpu::RenderPipeline, + bind_group: wgpu::BindGroup, + uniform_buffer: wgpu::Buffer, +} + +impl TriangleRenderResources { + fn prepare(&self, _device: &wgpu::Device, queue: &wgpu::Queue, angle: f32) { + // Update our uniform buffer with the angle from the UI + queue.write_buffer( + &self.uniform_buffer, + 0, + bytemuck::cast_slice(&[angle, 0.0, 0.0, 0.0]), + ); + } + + fn paint(&self, render_pass: &mut wgpu::RenderPass<'_>) { + // Draw our triangle! + render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group(0, &self.bind_group, &[]); + render_pass.draw(0..3, 0..1); + } +} |
