summary refs log tree commit diff
diff options
context:
space:
mode:
authorIrene Knapp <ireneista@irenes.space>2026-08-06 14:26:15 -0700
committerIrene Knapp <ireneista@irenes.space>2026-08-06 14:56:40 -0700
commit5b5f7bb4b3cac1fa96b2325e7c3d0784ade28ebf (patch)
treedf536be69c964533bf19c75119a8e406f9daad31
parent562693fe2d0afbe9c828fa1acb4473db9752eeed (diff)
factor out the render commands into a new scene module
use push constants

this concludes the tutorial; there's now a bit of fairly obvious refactoring to do to group objects in a way that reflects when they're needed, which can be followed by building a real scene graph

Change-Id: Id3b76d00d08ae915270095bd606e41767b7ec177
Force-Push: yes
-rw-r--r--shaders/shader.vert19
-rw-r--r--src/graphics_scene.rs121
-rw-r--r--src/graphics_window_dressing.rs135
-rw-r--r--src/main.rs38
-rw-r--r--src/shader_data.rs9
5 files changed, 195 insertions, 127 deletions
diff --git a/shaders/shader.vert b/shaders/shader.vert
index c07575a..009b38f 100644
--- a/shaders/shader.vert
+++ b/shaders/shader.vert
@@ -6,12 +6,15 @@ struct Transformation {
 };
 
 layout(binding = 0) uniform UniformBlock {
-   vec3 scale;
-   Transformation model;
-   Transformation view;
-   mat4 projection;
+  Transformation view;
+  mat4 projection;
 } uniform_block;
 
+layout(push_constant) uniform PushBlock {
+  vec3 scale;
+  Transformation model;
+} push_block;
+
 layout(location = 0) in vec3 inPosition;
 layout(location = 1) in vec3 inColor;
 layout(location = 2) in vec2 inTextureCoordinate;
@@ -74,10 +77,10 @@ vec3 transform(vec3 a, Transformation transformation) {
 
 
 vec4 apply_all_transforms(vec3 position) {
-  vec3 scaled = vec3(position.x * uniform_block.scale.x,
-                     position.y * uniform_block.scale.y,
-                     position.z * uniform_block.scale.z);
-  vec3 model = transform(scaled, uniform_block.model);
+  vec3 scaled = vec3(position.x * push_block.scale.x,
+                     position.y * push_block.scale.y,
+                     position.z * push_block.scale.z);
+  vec3 model = transform(scaled, push_block.model);
   vec3 view = transform(model, uniform_block.view);
   vec4 projected = vec4(view, 1.0) * uniform_block.projection;
   return projected;
diff --git a/src/graphics_scene.rs b/src/graphics_scene.rs
new file mode 100644
index 0000000..771255a
--- /dev/null
+++ b/src/graphics_scene.rs
@@ -0,0 +1,121 @@
+#![deny(unsafe_code)]
+use crate::error::*;
+use crate::linear_algebra::{ Vec3, Vec4, Transformation };
+use crate::shader_data::VertexPushBlock;
+
+use std::f32::consts::{ TAU };
+use std::mem::size_of;
+
+use vulkanalia::Device;
+use vulkanalia::vk::{ self, HasBuilder, DeviceV1_0 };
+
+
+#[allow(unsafe_code)]
+pub fn generate_scene_commands(command_buffer: &vk::CommandBuffer,
+                               time: f32, device: &Device,
+                               extent: &vk::Extent2D,
+                               framebuffer: &vk::Framebuffer,
+                               render_pass: &vk::RenderPass,
+                               pipeline_layout: &vk::PipelineLayout,
+                               pipeline: &vk::Pipeline,
+                               vertex_buffer: &vk::Buffer,
+                               index_buffer: &vk::Buffer,
+                               index_count: usize,
+                               descriptor_set: &vk::DescriptorSet)
+    -> Result<()>
+{
+  let inheritance_info = vk::CommandBufferInheritanceInfo::builder();
+
+  let command_buffer_begin_info = vk::CommandBufferBeginInfo::builder()
+          .flags(vk::CommandBufferUsageFlags::empty())
+          .inheritance_info(&inheritance_info);
+
+  unsafe {
+    device.begin_command_buffer(*command_buffer, &command_buffer_begin_info)
+  }?;
+
+  let render_area = vk::Rect2D::builder()
+          .offset(vk::Offset2D::default())
+          .extent(*extent);
+
+  let color_clear_value = vk::ClearValue {
+    color: vk::ClearColorValue {
+      float32: [0.0, 0.0, 0.0, 1.0]
+    }
+  };
+  let depth_clear_value = vk::ClearValue {
+    depth_stencil: vk::ClearDepthStencilValue {
+      depth: 1.0,
+      stencil: 0,
+    }
+  };
+  let clear_values = [color_clear_value, depth_clear_value];
+
+  let begin_pass_info = vk::RenderPassBeginInfo::builder()
+          .render_pass(*render_pass)
+          .framebuffer(*framebuffer)
+          .render_area(render_area)
+          .clear_values(&clear_values);
+
+  unsafe {
+    device.cmd_begin_render_pass(*command_buffer, &begin_pass_info,
+                                 vk::SubpassContents::INLINE)
+  };
+
+  unsafe {
+    device.cmd_bind_pipeline(*command_buffer,
+                             vk::PipelineBindPoint::GRAPHICS,
+                             *pipeline)
+  };
+
+  unsafe {
+    device.cmd_bind_vertex_buffers(*command_buffer, 0,
+                                   &[*vertex_buffer], &[0])
+  };
+
+  unsafe {
+    device.cmd_bind_index_buffer(*command_buffer, *index_buffer, 0,
+                                 vk::IndexType::UINT32)
+  };
+
+  unsafe {
+    device.cmd_bind_descriptor_sets(*command_buffer,
+                                    vk::PipelineBindPoint::GRAPHICS,
+                                    *pipeline_layout,
+                                    0,
+                                    &[*descriptor_set],
+                                    &[])
+  };
+
+  let scale = Vec3::new(1.0, 1.0, 1.0);
+  let model = Transformation {
+    rotation: Vec4::rotation_quaternion(&Vec3::new(0.0, 1.0, 0.0), time % TAU),
+    translation: Vec3::new(0.0, 0.0, 0.0),
+  };
+  let push_block = VertexPushBlock::<f32> { scale, model };
+  let size = size_of::<VertexPushBlock::<f32>>();
+  let push_block_bytes = unsafe {
+    std::slice::from_raw_parts(&push_block as *const VertexPushBlock<f32>
+                                           as *const u8,
+                               size)
+  };
+
+  unsafe {
+    device.cmd_push_constants(*command_buffer, *pipeline_layout,
+                              vk::ShaderStageFlags::VERTEX,
+                              0,
+                              push_block_bytes)
+  };
+
+  unsafe {
+    device.cmd_draw_indexed(*command_buffer, index_count as u32,
+                            1, 0, 0, 0)
+  };
+
+  unsafe { device.cmd_end_render_pass(*command_buffer) };
+
+  unsafe { device.end_command_buffer(*command_buffer) }?;
+
+  Ok(())
+}
+
diff --git a/src/graphics_window_dressing.rs b/src/graphics_window_dressing.rs
index 3c9291c..51483cf 100644
--- a/src/graphics_window_dressing.rs
+++ b/src/graphics_window_dressing.rs
@@ -5,10 +5,11 @@ use crate::graphics_permanent::{
   EnableAnisotropy
 };
 use crate::model_loader::load_model;
-use crate::shader_data::{ Vertex, UniformBlock };
+use crate::shader_data::{ Vertex, UniformBlock, VertexPushBlock };
 
 use std::collections::BTreeSet;
 use std::io::Cursor;
+use std::mem::size_of;
 use std::ptr::copy_nonoverlapping;
 
 use png::Decoder;
@@ -41,22 +42,22 @@ pub struct WindowDressing {
   depth_image_view: vk::ImageView,
   depth_format: vk::Format,
 
-  render_pass: vk::RenderPass,
+  pub render_pass: vk::RenderPass,
 
-  pipeline: vk::Pipeline,
-  pipeline_layout: vk::PipelineLayout,
+  pub pipeline: vk::Pipeline,
+  pub pipeline_layout: vk::PipelineLayout,
 
-  framebuffers: Vec<vk::Framebuffer>,
+  pub framebuffers: Vec<vk::Framebuffer>,
 
   primary_command_pool: vk::CommandPool,
   transient_command_pool: vk::CommandPool,
 
-  vertex_buffer: vk::Buffer,
+  pub vertex_buffer: vk::Buffer,
   vertex_buffer_memory: vk::DeviceMemory,
 
-  index_buffer: vk::Buffer,
+  pub index_buffer: vk::Buffer,
   index_buffer_memory: vk::DeviceMemory,
-  index_count: usize,
+  pub index_count: usize,
 
   texture_image: vk::Image,
   texture_image_memory: vk::DeviceMemory,
@@ -68,7 +69,7 @@ pub struct WindowDressing {
   pub uniform_buffer_memory: Vec<vk::DeviceMemory>,
 
   descriptor_pool: vk::DescriptorPool,
-  descriptor_sets: Vec<vk::DescriptorSet>,
+  pub descriptor_sets: Vec<vk::DescriptorSet>,
 
   pub command_buffers: Vec<vk::CommandBuffer>,
 
@@ -179,7 +180,7 @@ impl WindowDressing {
                                    swapchain.images.len(),
                                    &texture_image_view, &sampler)?;
 
-    let command_buffers = init_commands(
+    let command_buffers = init_command_buffers(
             device, &swapchain.extent, &framebuffers, &render_pass,
             &pipeline_layout, &pipeline, &vertex_buffer, &index_buffer,
             index_count, &descriptor_sets, &primary_command_pool)?;
@@ -276,7 +277,7 @@ impl WindowDressing {
                                    &self.texture_image_view, &self.sampler)?;
 
     // Notice that we reused the command pool.
-    let command_buffers = init_commands(
+    let command_buffers = init_command_buffers(
             device, &swapchain.extent, &framebuffers, &render_pass,
             &pipeline_layout, &pipeline, &self.vertex_buffer,
             &self.index_buffer, self.index_count, &descriptor_sets,
@@ -696,9 +697,16 @@ fn init_pipeline(device: &Device,
           .attachments(&blend_attachments)
           .blend_constants([0.0, 0.0, 0.0, 0.0]);
 
+  let vertex_push_constant_range = vk::PushConstantRange::builder()
+          .stage_flags(vk::ShaderStageFlags::VERTEX)
+          .offset(0)
+          .size(size_of::<VertexPushBlock<f32>>() as u32);
+
   let layouts = [*descriptor_set_layout];
+  let push_constant_ranges = [vertex_push_constant_range];
   let pipeline_layout_info = vk::PipelineLayoutCreateInfo::builder()
-                                 .set_layouts(&layouts);
+                                 .set_layouts(&layouts)
+                                 .push_constant_ranges(&push_constant_ranges);
 
   let pipeline_layout = unsafe {
     device.create_pipeline_layout(&pipeline_layout_info, None)
@@ -1014,8 +1022,9 @@ fn init_command_pools(device: &Device, indices: &QueueFamilyIndices)
     -> Result<(vk::CommandPool, vk::CommandPool)>
 {
   let command_pool_info = vk::CommandPoolCreateInfo::builder()
-                              .flags(vk::CommandPoolCreateFlags::empty())
-                              .queue_family_index(indices.graphics);
+          .flags(vk::CommandPoolCreateFlags::TRANSIENT
+                 | vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER)
+          .queue_family_index(indices.graphics);
 
   let primary = unsafe {
     device.create_command_pool(&command_pool_info, None)
@@ -1031,17 +1040,17 @@ fn init_command_pools(device: &Device, indices: &QueueFamilyIndices)
 
 
 #[allow(unsafe_code)]
-fn init_commands(device: &Device,
-                 extent: &vk::Extent2D,
-                 framebuffers: &Vec<vk::Framebuffer>,
-                 render_pass: &vk::RenderPass,
-                 pipeline_layout: &vk::PipelineLayout,
-                 pipeline: &vk::Pipeline,
-                 vertex_buffer: &vk::Buffer,
-                 index_buffer: &vk::Buffer,
-                 index_count: usize,
-                 descriptor_sets: &Vec<vk::DescriptorSet>,
-                 command_pool: &vk::CommandPool)
+fn init_command_buffers(device: &Device,
+                        extent: &vk::Extent2D,
+                        framebuffers: &Vec<vk::Framebuffer>,
+                        render_pass: &vk::RenderPass,
+                        pipeline_layout: &vk::PipelineLayout,
+                        pipeline: &vk::Pipeline,
+                        vertex_buffer: &vk::Buffer,
+                        index_buffer: &vk::Buffer,
+                        index_count: usize,
+                        descriptor_sets: &Vec<vk::DescriptorSet>,
+                        command_pool: &vk::CommandPool)
     -> Result<Vec<vk::CommandBuffer>>
 {
   let command_buffer_allocation_info
@@ -1053,82 +1062,6 @@ fn init_commands(device: &Device,
     device.allocate_command_buffers(&command_buffer_allocation_info)
   }?;
 
-  for (index, framebuffer) in framebuffers.iter().enumerate() {
-    let command_buffer = command_buffers[index];
-
-    let inheritance_info = vk::CommandBufferInheritanceInfo::builder();
-
-    let command_buffer_begin_info = vk::CommandBufferBeginInfo::builder()
-            .flags(vk::CommandBufferUsageFlags::empty())
-            .inheritance_info(&inheritance_info);
-
-    unsafe {
-      device.begin_command_buffer(command_buffer, &command_buffer_begin_info)
-    }?;
-
-    let render_area = vk::Rect2D::builder()
-            .offset(vk::Offset2D::default())
-            .extent(*extent);
-
-    let color_clear_value = vk::ClearValue {
-      color: vk::ClearColorValue {
-        float32: [0.0, 0.0, 0.0, 1.0]
-      }
-    };
-    let depth_clear_value = vk::ClearValue {
-      depth_stencil: vk::ClearDepthStencilValue {
-        depth: 1.0,
-        stencil: 0,
-      }
-    };
-    let clear_values = [color_clear_value, depth_clear_value];
-
-    let begin_pass_info = vk::RenderPassBeginInfo::builder()
-            .render_pass(*render_pass)
-            .framebuffer(*framebuffer)
-            .render_area(render_area)
-            .clear_values(&clear_values);
-
-    unsafe {
-      device.cmd_begin_render_pass(command_buffer, &begin_pass_info,
-                                   vk::SubpassContents::INLINE)
-    };
-
-    unsafe {
-      device.cmd_bind_pipeline(command_buffer,
-                               vk::PipelineBindPoint::GRAPHICS,
-                               *pipeline)
-    };
-
-    unsafe {
-      device.cmd_bind_vertex_buffers(command_buffer, 0,
-                                     &[*vertex_buffer], &[0])
-    };
-
-    unsafe {
-      device.cmd_bind_index_buffer(command_buffer, *index_buffer, 0,
-                                   vk::IndexType::UINT32)
-    };
-
-    unsafe {
-      device.cmd_bind_descriptor_sets(command_buffer,
-                                      vk::PipelineBindPoint::GRAPHICS,
-                                      *pipeline_layout,
-                                      0,
-                                      &[descriptor_sets[index]],
-                                      &[])
-    };
-
-    unsafe {
-      device.cmd_draw_indexed(command_buffer, index_count as u32,
-                              1, 0, 0, 0)
-    };
-
-    unsafe { device.cmd_end_render_pass(command_buffer) };
-
-    unsafe { device.end_command_buffer(command_buffer) }?;
-  }
-
   Ok(command_buffers)
 }
 
diff --git a/src/main.rs b/src/main.rs
index 3267d0b..8a42147 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -6,11 +6,11 @@ use crate::graphics_permanent::{
 use crate::graphics_window_dressing::{
   WindowDressing, N_SIMULTANEOUS_FRAMES
 };
-use crate::linear_algebra::{ Vec3, Vec4, Mat4, Transformation };
+use crate::linear_algebra::{ Vec3, Mat4, Transformation };
 use crate::shader_data::UniformBlock;
 
 use std::cell::RefCell;
-use std::f32::consts::{ FRAC_PI_4, TAU };
+use std::f32::consts::FRAC_PI_4;
 use std::ptr::copy_nonoverlapping;
 use std::time::Instant;
 use vulkanalia::Device;
@@ -23,6 +23,7 @@ use winit::window::WindowId;
 
 mod error;
 mod graphics_permanent;
+mod graphics_scene;
 mod graphics_window_dressing;
 mod linear_algebra;
 mod model_loader;
@@ -138,16 +139,28 @@ impl Surreality {
 
       concurrency.image_fences[image_index] = *frame_fence;
 
-      render_uniforms(device,
+      let time = self.simulation_start.elapsed().as_secs_f32();
+
+      let command_buffer = window_dressing.command_buffers[image_index];
+      let framebuffer = window_dressing.framebuffers[image_index];
+      let descriptor_set = window_dressing.descriptor_sets[image_index];
+
+      crate::graphics_scene::generate_scene_commands(
+          &command_buffer, time, device, &window_dressing.swapchain.extent,
+          &framebuffer, &window_dressing.render_pass,
+          &window_dressing.pipeline_layout, &window_dressing.pipeline,
+          &window_dressing.vertex_buffer, &window_dressing.index_buffer,
+          window_dressing.index_count, &descriptor_set)?;
+
+      render_uniforms(time, device,
                       &window_dressing.uniform_buffer_memory[image_index],
-                      &window_dressing.swapchain.extent,
-                      self.simulation_start)?;
+                      &window_dressing.swapchain.extent)?;
 
       let first_semaphores = [*image_available_semaphore];
       let second_semaphores = [*rendering_finished_semaphore];
 
       let wait_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT];
-      let command_buffers = [window_dressing.command_buffers[image_index]];
+      let command_buffers = [command_buffer];
       let submit_info = vk::SubmitInfo::builder()
                             .wait_semaphores(&first_semaphores)
                             .wait_dst_stage_mask(&wait_stages)
@@ -282,23 +295,16 @@ fn main() -> std::process::ExitCode {
 
 
 #[allow(unsafe_code)]
-fn render_uniforms(device: &Device, device_memory: &vk::DeviceMemory,
-                   extent: &vk::Extent2D, simulation_start: Instant)
+fn render_uniforms(time: f32, device: &Device,
+                   device_memory: &vk::DeviceMemory, extent: &vk::Extent2D)
     -> Result<()>
 {
-  let time = simulation_start.elapsed().as_secs_f32();
-
-  let scale = Vec3::new(1.0, 1.0, 1.0);
-  let model = Transformation {
-    rotation: Vec4::rotation_quaternion(&Vec3::new(0.0, 1.0, 0.0), time % TAU),
-    translation: Vec3::new(0.0, 0.0, 0.0),
-  };
   let view = Transformation::look_at(&Vec3::new(0.0, -3.0, -10.0),
                                      &Vec3::new(0.0, -1.0, 0.0),
                                      &Vec3::new(0.0, -1.0, 0.0));
   let aspect_ratio = extent.width as f32 / extent.height as f32;
   let projection = Mat4::perspective(FRAC_PI_4, aspect_ratio, 0.1, 100.0);
-  let block = UniformBlock::<f32> { scale, model, view, projection };
+  let block = UniformBlock::<f32> { view, projection };
 
   let size = size_of::<UniformBlock<f32>>() as u64;
   let host_memory = unsafe {
diff --git a/src/shader_data.rs b/src/shader_data.rs
index 8197bfc..892260d 100644
--- a/src/shader_data.rs
+++ b/src/shader_data.rs
@@ -16,12 +16,17 @@ pub struct Vertex<T: Copy> {
 #[repr(C)]
 #[derive(Clone, Debug)]
 pub struct UniformBlock<T: Copy> {
-  pub scale: Vec3<T>,
-  pub model: Transformation<T>,
   pub view: Transformation<T>,
   pub projection: Mat4<T>,
 }
 
+#[repr(C)]
+#[derive(Clone, Debug)]
+pub struct VertexPushBlock<T: Copy> {
+  pub scale: Vec3<T>,
+  pub model: Transformation<T>,
+}
+
 impl<T: Copy> Vertex<T> {
   pub const fn new(position: Vec3<T>, color: Vec3<T>,
                    texture_coordinates: Vec2<T>)