aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2025-12-23 13:50:48 +0000
committers-ol <s+removethis@s-ol.nu>2025-12-23 13:51:46 +0000
commitd687ff022b35e1ef39be32efad755bcaf0b5a398 (patch)
tree0766d04a3f0d63992e978d2a081e9fa3cfbbaf45
parentadd trunk for web deployment (diff)
downloadnodetoy-d687ff022b35e1ef39be32efad755bcaf0b5a398.tar.gz
nodetoy-d687ff022b35e1ef39be32efad755bcaf0b5a398.zip
wgpu, logging integrations
-rw-r--r--Cargo.lock75
-rw-r--r--Cargo.toml6
-rw-r--r--src/main.rs42
-rw-r--r--src/preview.rs232
4 files changed, 281 insertions, 74 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 7d5ac15..725608a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/Cargo.toml b/Cargo.toml
index 2f8bf71..81b029b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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);
+ }
+}