Skip to content

Commit c14f754

Browse files
Merge pull request #6 from friendlymatthew/voodoo-gpu
Voodoo
2 parents c362c72 + d45b78c commit c14f754

8 files changed

Lines changed: 767 additions & 47 deletions

File tree

src/renderer/app_state.rs

Lines changed: 204 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ use crate::{
55
feature_uniform::{FeatureUniform, TransformAction},
66
gpu_state::GpuResourceAllocator,
77
mouse_state::MouseState,
8-
shader::Shader,
9-
shape::{compute_radius, Shape, ShapeStack},
8+
shader::{Shader, TextureResource},
9+
shape::{compute_distance, RevisionStack, Shape},
10+
shape_uniform::{CircleData, ShapeUniform, MAX_CIRCLES},
1011
},
1112
};
1213
use anyhow::Result;
@@ -29,9 +30,13 @@ pub struct AppState<'a> {
2930
pub feature_uniform: FeatureUniform,
3031
pub draw_uniform: DrawUniform,
3132
pub mouse_state: MouseState,
32-
pub shape_stack: ShapeStack,
33+
pub shape_stack: RevisionStack,
3334

3435
pub image_shader: Shader,
36+
pub shape_shader: Shader,
37+
pub shape_render_texture: TextureResource,
38+
pub shape_uniform: ShapeUniform,
39+
pub circle_storage_buffer: wgpu::Buffer,
3540
}
3641

3742
impl<'a> AppState<'a> {
@@ -51,15 +56,42 @@ impl<'a> AppState<'a> {
5156
let draw_uniform_resource =
5257
gpu_allocator.create_uniform_resource("draw_uniform", draw_uniform)?;
5358

59+
// Create shape render texture
60+
let shape_render_texture =
61+
gpu_allocator.create_render_texture("shape_texture", size.width, size.height);
62+
63+
// Create shape uniform and storage buffer
64+
let shape_uniform = ShapeUniform::new(size.width, size.height);
65+
let shape_uniform_resource =
66+
gpu_allocator.create_uniform_resource("shape_uniform", shape_uniform)?;
67+
68+
// Create empty circle storage buffer
69+
let empty_circles = vec![CircleData::default(); MAX_CIRCLES];
70+
let circle_storage_buffer =
71+
gpu_allocator.create_storage_buffer("circle_storage", &empty_circles)?;
72+
73+
let shape_shader = gpu_allocator.create_shape_shader(
74+
"shape_shader",
75+
include_str!("shape_shader.wgsl"),
76+
shape_uniform_resource,
77+
&circle_storage_buffer,
78+
);
79+
80+
// Create a reference to the shape texture for the image shader
81+
let shape_texture_for_image = gpu_allocator.create_texture_resource_from_existing(
82+
"shape_texture_ref",
83+
&shape_render_texture.resource,
84+
);
85+
5486
let image_shader = gpu_allocator.create_shader(
5587
"image_shader",
5688
include_str!("image_shader.wgsl"),
57-
vec![image_texture_resource],
89+
vec![image_texture_resource, shape_texture_for_image],
5890
vec![feature_uniform_resource, draw_uniform_resource],
5991
);
6092

6193
let mouse_state = MouseState::default();
62-
let shape_stack = ShapeStack::new();
94+
let shape_stack = RevisionStack::new();
6395

6496
Ok(Self {
6597
gpu_allocator,
@@ -70,6 +102,10 @@ impl<'a> AppState<'a> {
70102
mouse_state,
71103
shape_stack,
72104
image_shader,
105+
shape_shader,
106+
shape_render_texture,
107+
shape_uniform,
108+
circle_storage_buffer,
73109
})
74110
}
75111

@@ -83,6 +119,8 @@ impl<'a> AppState<'a> {
83119
self.gpu_allocator.configure_surface(&new_size);
84120
self.feature_uniform
85121
.update_window_dimensions(new_size.width, new_size.height);
122+
self.shape_uniform
123+
.update_dimensions(new_size.width, new_size.height);
86124
}
87125
}
88126

@@ -94,50 +132,111 @@ impl<'a> AppState<'a> {
94132
WindowEvent::MouseInput { state, button, .. } => {
95133
if *button == MouseButton::Left {
96134
let prev_state = self.mouse_state.pressed();
97-
98135
self.mouse_state
99136
.set_pressed(matches!(state, ElementState::Pressed));
100137

101-
if !draw_uniform.crosshair() {
102-
return true;
103-
}
104-
105-
match (prev_state, self.mouse_state.pressed()) {
106-
(false, true) => {
107-
let (start_x, start_y) = self.mouse_state.position();
138+
if draw_uniform.crosshair() {
139+
// Crosshair mode: Draw new circles
140+
match (prev_state, self.mouse_state.pressed()) {
141+
(false, true) => {
142+
let (start_x, start_y) = self.mouse_state.position();
143+
self.mouse_state.set_start_drag(Some((start_x, start_y)));
144+
draw_uniform.set_circle_center(start_x, start_y);
145+
}
146+
(true, false) => {
147+
let initial_drag_position = self.mouse_state.start_drag();
148+
if initial_drag_position.is_none() {
149+
panic!("Logic error occurred. Mouse state once finished pressing doesn't have initial drag position set.");
150+
}
108151

109-
dbg!("start drag", start_x, start_y);
110-
self.mouse_state.set_start_drag(Some((start_x, start_y)));
111-
draw_uniform.set_circle_center(start_x, start_y);
152+
let (x, y) = initial_drag_position.unwrap();
153+
let (edge_x, edge_y) = self.mouse_state.position();
154+
let radius = compute_distance((x, y), (edge_x, edge_y));
155+
156+
// Convert to normalized coordinates (0-1 range)
157+
let normalized_x = x / self.size.width as f32;
158+
let normalized_y = y / self.size.height as f32;
159+
let normalized_radius =
160+
radius / (self.size.width.min(self.size.height) as f32);
161+
162+
self.shape_stack.push(Shape::Circle {
163+
x: normalized_x,
164+
y: normalized_y,
165+
radius: normalized_radius,
166+
});
167+
168+
// Clear state
169+
self.mouse_state.set_start_drag(None);
170+
draw_uniform.set_circle_radius(0.0);
171+
}
172+
_ => {}
112173
}
113-
(true, false) => {
114-
let initial_drag_position = self.mouse_state.start_drag();
115-
116-
if initial_drag_position.is_none() {
117-
panic!("Logic error occured. Mouse state once finished pressing doesn't have initial drag position set.");
174+
} else {
175+
// Selection mode: Select and move circles
176+
match (prev_state, self.mouse_state.pressed()) {
177+
(false, true) => {
178+
// Mouse press - check if we clicked on a circle
179+
let (mouse_x, mouse_y) = self.mouse_state.position();
180+
let normalized_x = mouse_x / self.size.width as f32;
181+
let normalized_y = mouse_y / self.size.height as f32;
182+
183+
if let Some(circle_index) = self.shape_stack.find_shape_at_point(
184+
normalized_x,
185+
normalized_y,
186+
self.size.width,
187+
self.size.height,
188+
) {
189+
// Found a circle - select it and start dragging
190+
self.mouse_state.set_selected_shape(Some(circle_index));
191+
self.mouse_state.set_dragging_shape(true);
192+
193+
// Calculate offset from circle center to mouse position
194+
if let Some(Shape::Circle { x, y, .. }) =
195+
self.shape_stack.shapes().get(circle_index)
196+
{
197+
let offset_x = normalized_x - x;
198+
let offset_y = normalized_y - y;
199+
self.mouse_state.set_drag_offset((offset_x, offset_y));
200+
}
201+
} else {
202+
// Clicked on empty space - deselect any selected circle
203+
self.mouse_state.set_selected_shape(None);
204+
}
118205
}
119-
120-
let (x, y) = initial_drag_position.unwrap();
121-
let (edge_x, edge_y) = self.mouse_state.position();
122-
let radius = compute_radius((x, y), (edge_x, edge_y));
123-
self.shape_stack.push(Shape::Circle { x, y, radius });
124-
125-
// clear state
126-
self.mouse_state.set_start_drag(None);
127-
dbg!("stop drag");
128-
draw_uniform.set_circle_radius(0.0);
206+
(true, false) => {
207+
// Mouse release - stop dragging
208+
self.mouse_state.set_dragging_shape(false);
209+
}
210+
_ => {}
129211
}
130-
_ => {}
131212
}
132213
}
133214
}
134215
WindowEvent::CursorMoved { position, .. } => {
135216
let (x, y) = (position.x as f32, position.y as f32);
136217

137-
if let Some(center) = self.mouse_state.start_drag() {
138-
let radius = compute_radius(center, (x, y));
139-
dbg!("dragging: radius", radius);
140-
self.draw_uniform.set_circle_radius(radius);
218+
if draw_uniform.crosshair() {
219+
// Crosshair mode: Update the preview circle radius
220+
if let Some(center) = self.mouse_state.start_drag() {
221+
let radius = compute_distance(center, (x, y));
222+
self.draw_uniform.set_circle_radius(radius);
223+
}
224+
} else {
225+
// Selection mode: Handle circle dragging
226+
if self.mouse_state.dragging_shape() {
227+
if let Some(selected_index) = self.mouse_state.selected_shape() {
228+
let normalized_x = x / self.size.width as f32;
229+
let normalized_y = y / self.size.height as f32;
230+
231+
// Apply the drag offset to maintain relative position
232+
let (offset_x, offset_y) = self.mouse_state.drag_offset();
233+
let new_x = normalized_x - offset_x;
234+
let new_y = normalized_y - offset_y;
235+
236+
// Move the circle to the new position
237+
self.shape_stack.move_shape(selected_index, new_x, new_y);
238+
}
239+
}
141240
}
142241

143242
self.mouse_state.update_position(x, y);
@@ -202,6 +301,15 @@ impl<'a> AppState<'a> {
202301
(KeyCode::KeyY, ElementState::Pressed) => {
203302
feature_uniform.apply_transform(TransformAction::FlipY);
204303
}
304+
(KeyCode::Delete, ElementState::Pressed)
305+
| (KeyCode::Backspace, ElementState::Pressed) => {
306+
// Delete the selected circle
307+
if let Some(selected_index) = self.mouse_state.selected_shape() {
308+
self.shape_stack.remove_shape(selected_index);
309+
self.mouse_state.set_selected_shape(None);
310+
self.mouse_state.set_dragging_shape(false);
311+
}
312+
}
205313
_ => return false,
206314
},
207315
_ => return false,
@@ -210,21 +318,76 @@ impl<'a> AppState<'a> {
210318
true
211319
}
212320

213-
pub(crate) fn update(&self) {
214-
// Make sure the uniform resource at index i correctly matches up to the uniform structure.
321+
pub(crate) fn update(&mut self) {
322+
// Update image shader uniforms
215323
let uniform_resources = &self.image_shader.uniform_resources;
216-
217324
self.gpu_allocator
218325
.write_uniform_buffer(&uniform_resources[0].resource, self.feature_uniform);
219-
220326
self.gpu_allocator
221327
.write_uniform_buffer(&uniform_resources[1].resource, self.draw_uniform);
328+
329+
// Update shape data
330+
self.update_shape_data();
331+
}
332+
333+
fn update_shape_data(&mut self) {
334+
let shapes = self.shape_stack.shapes();
335+
let num_circles = shapes.len().min(MAX_CIRCLES);
336+
337+
self.shape_uniform.set_num_circles(num_circles as u32);
338+
self.shape_uniform
339+
.set_selected_circle(self.mouse_state.selected_shape());
340+
341+
// Update shape uniform
342+
let shape_uniform_resources = &self.shape_shader.uniform_resources;
343+
self.gpu_allocator
344+
.write_uniform_buffer(&shape_uniform_resources[0].resource, self.shape_uniform);
345+
346+
// Update circle storage buffer
347+
let mut circle_data = vec![CircleData::default(); MAX_CIRCLES];
348+
for (i, shape) in shapes.iter().take(MAX_CIRCLES).enumerate() {
349+
circle_data[i] = CircleData::from(shape);
350+
}
351+
352+
self.gpu_allocator
353+
.write_storage_buffer(&self.circle_storage_buffer, &circle_data);
222354
}
223355

224356
pub(crate) fn render(&self) -> Result<(), wgpu::SurfaceError> {
225357
let (output, view, mut encoder) = self.gpu_allocator.begin_frame()?;
226358

227-
// Image shader render pass
359+
// First pass: Render shapes to shape texture
360+
{
361+
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
362+
label: Some("shape render pass"),
363+
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
364+
view: &self.shape_render_texture.resource.view,
365+
resolve_target: None,
366+
ops: wgpu::Operations {
367+
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
368+
store: wgpu::StoreOp::Store,
369+
},
370+
})],
371+
depth_stencil_attachment: None,
372+
occlusion_query_set: None,
373+
timestamp_writes: None,
374+
});
375+
376+
render_pass.set_pipeline(&self.shape_shader.render_pipeline);
377+
378+
// Set bind group for shape shader (uniform + storage buffer)
379+
render_pass.set_bind_group(0, &self.shape_shader.uniform_resources[0].bind_group, &[]);
380+
381+
render_pass.set_vertex_buffer(0, self.gpu_allocator.vertex_buffer.slice(..));
382+
render_pass.set_index_buffer(
383+
self.gpu_allocator.index_buffer.slice(..),
384+
wgpu::IndexFormat::Uint16,
385+
);
386+
387+
render_pass.draw_indexed(0..self.gpu_allocator.num_indices(), 0, 0..1);
388+
}
389+
390+
// Second pass: Render final image with shapes composited
228391
{
229392
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
230393
label: Some("content render pass"),

0 commit comments

Comments
 (0)