aboutsummaryrefslogtreecommitdiffstats
path: root/src
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 /src
parentadd trunk for web deployment (diff)
downloadnodetoy-d687ff022b35e1ef39be32efad755bcaf0b5a398.tar.gz
nodetoy-d687ff022b35e1ef39be32efad755bcaf0b5a398.zip
wgpu, logging integrations
Diffstat (limited to 'src')
-rw-r--r--src/main.rs42
-rw-r--r--src/preview.rs232
2 files changed, 254 insertions, 20 deletions
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);
+ }
+}