summary refs log tree commit diff
diff options
context:
space:
mode:
authorIrene Knapp <ireneista@irenes.space>2026-08-06 16:43:02 -0700
committerIrene Knapp <ireneista@irenes.space>2026-08-06 16:43:02 -0700
commit2c8110d93e04a1bfd976fe20c8c7493a41d51eae (patch)
treeac7b4271e07e58bdc61e009a06f56164186c08aa
parent5b5f7bb4b3cac1fa96b2325e7c3d0784ade28ebf (diff)
state needed when rendering is now in its own struct
subdividing the lifetimes of all our various objects may feel questionable in general, but this particular division is a very natural one

Force-Push: yes
Change-Id: I499a32875a4d75965944af720e7bfd6ec5965c15
-rw-r--r--src/graphics_scene.rs39
-rw-r--r--src/graphics_window_dressing.rs309
-rw-r--r--src/main.rs36
3 files changed, 225 insertions, 159 deletions
diff --git a/src/graphics_scene.rs b/src/graphics_scene.rs
index 771255a..f3694f7 100644
--- a/src/graphics_scene.rs
+++ b/src/graphics_scene.rs
@@ -1,5 +1,6 @@
 #![deny(unsafe_code)]
 use crate::error::*;
+use crate::graphics_window_dressing::RenderState;
 use crate::linear_algebra::{ Vec3, Vec4, Transformation };
 use crate::shader_data::VertexPushBlock;
 
@@ -11,19 +12,15 @@ 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<()>
+pub fn generate_scene_commands<'a>(image_index: usize, time: f32,
+                                   render_state: &'a RenderState,
+                                   device: &Device, extent: &vk::Extent2D)
+    -> Result<&'a vk::CommandBuffer>
 {
+  let command_buffer = &render_state.command_buffers[image_index];
+  let framebuffer = &render_state.framebuffers[image_index];
+  let descriptor_set = &render_state.descriptor_sets[image_index];
+
   let inheritance_info = vk::CommandBufferInheritanceInfo::builder();
 
   let command_buffer_begin_info = vk::CommandBufferBeginInfo::builder()
@@ -52,7 +49,7 @@ pub fn generate_scene_commands(command_buffer: &vk::CommandBuffer,
   let clear_values = [color_clear_value, depth_clear_value];
 
   let begin_pass_info = vk::RenderPassBeginInfo::builder()
-          .render_pass(*render_pass)
+          .render_pass(render_state.render_pass)
           .framebuffer(*framebuffer)
           .render_area(render_area)
           .clear_values(&clear_values);
@@ -65,23 +62,23 @@ pub fn generate_scene_commands(command_buffer: &vk::CommandBuffer,
   unsafe {
     device.cmd_bind_pipeline(*command_buffer,
                              vk::PipelineBindPoint::GRAPHICS,
-                             *pipeline)
+                             render_state.pipeline)
   };
 
   unsafe {
     device.cmd_bind_vertex_buffers(*command_buffer, 0,
-                                   &[*vertex_buffer], &[0])
+                                   &[render_state.vertex_buffer], &[0])
   };
 
   unsafe {
-    device.cmd_bind_index_buffer(*command_buffer, *index_buffer, 0,
-                                 vk::IndexType::UINT32)
+    device.cmd_bind_index_buffer(*command_buffer, render_state.index_buffer,
+                                 0, vk::IndexType::UINT32)
   };
 
   unsafe {
     device.cmd_bind_descriptor_sets(*command_buffer,
                                     vk::PipelineBindPoint::GRAPHICS,
-                                    *pipeline_layout,
+                                    render_state.pipeline_layout,
                                     0,
                                     &[*descriptor_set],
                                     &[])
@@ -101,14 +98,14 @@ pub fn generate_scene_commands(command_buffer: &vk::CommandBuffer,
   };
 
   unsafe {
-    device.cmd_push_constants(*command_buffer, *pipeline_layout,
+    device.cmd_push_constants(*command_buffer, render_state.pipeline_layout,
                               vk::ShaderStageFlags::VERTEX,
                               0,
                               push_block_bytes)
   };
 
   unsafe {
-    device.cmd_draw_indexed(*command_buffer, index_count as u32,
+    device.cmd_draw_indexed(*command_buffer, render_state.index_count as u32,
                             1, 0, 0, 0)
   };
 
@@ -116,6 +113,6 @@ pub fn generate_scene_commands(command_buffer: &vk::CommandBuffer,
 
   unsafe { device.end_command_buffer(*command_buffer) }?;
 
-  Ok(())
+  Ok(command_buffer)
 }
 
diff --git a/src/graphics_window_dressing.rs b/src/graphics_window_dressing.rs
index 51483cf..1b5b602 100644
--- a/src/graphics_window_dressing.rs
+++ b/src/graphics_window_dressing.rs
@@ -27,8 +27,9 @@ pub const N_SIMULTANEOUS_FRAMES: usize = 5;
 
 //   The WindowDressing collects the Vulkan graphics objects which need to be
 // regenerated or modified when the window changes in certain ways, such as
-// resizing. The ones which don't need that are collected above, in
-// PermanentGraphicsState.
+// resizing, but are not needed during rendering. The ones which don't need to
+// be regenerated are collected in PermanentGraphicsState. The ones which are
+// needed during rendering are collected in RenderState, below.
 #[derive(Debug)]
 pub struct WindowDressing {
   pub swapchain: Swapchain,
@@ -42,23 +43,9 @@ pub struct WindowDressing {
   depth_image_view: vk::ImageView,
   depth_format: vk::Format,
 
-  pub render_pass: vk::RenderPass,
-
-  pub pipeline: vk::Pipeline,
-  pub pipeline_layout: vk::PipelineLayout,
-
-  pub framebuffers: Vec<vk::Framebuffer>,
-
   primary_command_pool: vk::CommandPool,
   transient_command_pool: vk::CommandPool,
 
-  pub vertex_buffer: vk::Buffer,
-  vertex_buffer_memory: vk::DeviceMemory,
-
-  pub index_buffer: vk::Buffer,
-  index_buffer_memory: vk::DeviceMemory,
-  pub index_count: usize,
-
   texture_image: vk::Image,
   texture_image_memory: vk::DeviceMemory,
   texture_image_view: vk::ImageView,
@@ -69,13 +56,32 @@ pub struct WindowDressing {
   pub uniform_buffer_memory: Vec<vk::DeviceMemory>,
 
   descriptor_pool: vk::DescriptorPool,
-  pub descriptor_sets: Vec<vk::DescriptorSet>,
-
-  pub command_buffers: Vec<vk::CommandBuffer>,
 
   pub concurrency: Concurrency,
 }
 
+//   The RenderState collects the Vulkan graphics objects which need to be
+// regenerated or modified when the window changes, as with WindowDressing,
+// and which are also used as part of rendering.
+#[derive(Debug)]
+pub struct RenderState {
+  pub render_pass: vk::RenderPass,
+
+  pub pipeline: vk::Pipeline,
+  pub pipeline_layout: vk::PipelineLayout,
+
+  pub vertex_buffer: vk::Buffer,
+  vertex_buffer_memory: vk::DeviceMemory,
+
+  pub index_buffer: vk::Buffer,
+  index_buffer_memory: vk::DeviceMemory,
+  pub index_count: usize,
+
+  pub framebuffers: Vec<vk::Framebuffer>,
+  pub command_buffers: Vec<vk::CommandBuffer>,
+  pub descriptor_sets: Vec<vk::DescriptorSet>,
+}
+
 //   A swapchain is the generalized facility that is used to implement
 // double buffering, triple buffering, rendering passes that feed into each
 // other, and other things of that nature. It's a first-class thing but for
@@ -125,7 +131,6 @@ impl WindowDressing {
     let physical_device = &for_reinit.physical_device;
     let sample_count = for_reinit.sample_count;
     let indices = &for_reinit.indices;
-    let descriptor_set_layout = &for_reinit.descriptor_set_layout;
 
     let swapchain = init_swapchain(
             window, instance, surface, &physical_device, device, &indices)?;
@@ -138,30 +143,9 @@ impl WindowDressing {
             = init_depth(instance, &physical_device, device,
                          &swapchain.extent, sample_count)?;
 
-    let render_pass = init_render_pass(device, sample_count,
-                                       &swapchain.format, &depth_format)?;
-
-    let (pipeline_layout, pipeline)
-            = init_pipeline(device, descriptor_set_layout, &swapchain.extent,
-                            sample_count, &render_pass)?;
-
-    let framebuffers = init_framebuffers(
-            device, &swapchain.extent, &swapchain.image_views,
-            &color_image_view, &depth_image_view, &render_pass)?;
-
     let (primary_command_pool, transient_command_pool)
             = init_command_pools(device, indices)?;
 
-    let (vertices, indices) = load_model()?;
-    let index_count = indices.len();
-
-    let (vertex_buffer, vertex_buffer_memory)
-            = init_vertex_buffer(vertices, instance, physical_device, device,
-                                 graphics_queue, &transient_command_pool)?;
-    let (index_buffer, index_buffer_memory)
-            = init_index_buffer(indices, instance, physical_device, device,
-                                graphics_queue, &transient_command_pool)?;
-
     let (texture_image, texture_image_memory, texture_image_view, mip_count)
             = init_texture(instance, physical_device, device,
                            graphics_queue, &transient_command_pool)?;
@@ -174,16 +158,6 @@ impl WindowDressing {
 
     let descriptor_pool
             = init_descriptor_pool(device, swapchain.images.len())?;
-    let descriptor_sets
-            = init_descriptor_sets(device, descriptor_set_layout,
-                                   &uniform_buffers, &descriptor_pool,
-                                   swapchain.images.len(),
-                                   &texture_image_view, &sampler)?;
-
-    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)?;
 
     let concurrency = init_concurrency(device, &swapchain.images)?;
 
@@ -196,15 +170,6 @@ impl WindowDressing {
       depth_image_memory,
       depth_image_view,
       depth_format,
-      render_pass,
-      pipeline,
-      pipeline_layout,
-      framebuffers,
-      vertex_buffer,
-      vertex_buffer_memory,
-      index_buffer,
-      index_buffer_memory,
-      index_count,
       texture_image,
       texture_image_memory,
       texture_image_view,
@@ -213,10 +178,8 @@ impl WindowDressing {
       uniform_buffers,
       uniform_buffer_memory,
       descriptor_pool,
-      descriptor_sets,
       primary_command_pool,
       transient_command_pool,
-      command_buffers,
       concurrency,
     })
   }
@@ -251,17 +214,6 @@ impl WindowDressing {
             = init_depth(instance, &physical_device, device,
                          &swapchain.extent, sample_count)?;
 
-    let render_pass = init_render_pass(device, sample_count,
-                                       &swapchain.format, &depth_format)?;
-
-    let (pipeline_layout, pipeline)
-            = init_pipeline(device, descriptor_set_layout, &swapchain.extent,
-                            sample_count, &render_pass)?;
-
-    let framebuffers = init_framebuffers(
-            device, &swapchain.extent, &swapchain.image_views,
-            &color_image_view, &depth_image_view, &render_pass)?;
-
     let (uniform_buffers, uniform_buffer_memory)
             = init_uniform_buffers(instance, physical_device, device,
                                    swapchain.images.len())?;
@@ -270,19 +222,6 @@ impl WindowDressing {
     let descriptor_pool
             = init_descriptor_pool(device, swapchain.images.len())?;
 
-    let descriptor_sets
-            = init_descriptor_sets(device, descriptor_set_layout,
-                                   &uniform_buffers, &descriptor_pool,
-                                   swapchain.images.len(),
-                                   &self.texture_image_view, &self.sampler)?;
-
-    // Notice that we reused the command pool.
-    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,
-            &self.primary_command_pool)?;
-
     self.concurrency.image_fences.resize(swapchain.images.len(),
                                          vk::Fence::null());
 
@@ -294,15 +233,9 @@ impl WindowDressing {
     self.depth_image_memory = depth_image_memory;
     self.depth_image_view = depth_image_view;
     self.depth_format = depth_format;
-    self.render_pass = render_pass;
-    self.pipeline = pipeline;
-    self.pipeline_layout = pipeline_layout;
-    self.framebuffers = framebuffers;
     self.uniform_buffers = uniform_buffers;
     self.uniform_buffer_memory = uniform_buffer_memory;
     self.descriptor_pool = descriptor_pool;
-    self.descriptor_sets = descriptor_sets;
-    self.command_buffers = command_buffers;
 
     Ok(())
   }
@@ -314,12 +247,6 @@ impl WindowDressing {
   pub fn destroy(mut self, device: &Device) {
     self.destroy_replaceable(device);
 
-    unsafe { device.destroy_buffer(self.vertex_buffer, None) };
-    unsafe { device.free_memory(self.vertex_buffer_memory, None) };
-
-    unsafe { device.destroy_buffer(self.index_buffer, None) };
-    unsafe { device.free_memory(self.index_buffer_memory, None) };
-
     unsafe { device.destroy_image(self.texture_image, None) };
     unsafe { device.free_memory(self.texture_image_memory, None) };
     unsafe { device.destroy_image_view(self.texture_image_view, None) };
@@ -346,20 +273,6 @@ impl WindowDressing {
 
   #[allow(unsafe_code)]
   fn destroy_replaceable(&mut self, device: &Device) {
-    for framebuffer in &self.framebuffers {
-      unsafe { device.destroy_framebuffer(*framebuffer, None) };
-    }
-
-    //   Notice that we free the buffers in the pool, but do not destroy the
-    // pool itself. Notice also that we only do this for the primary command
-    // pool, because that's the only one where we've kept track of the
-    // buffers. We promise ourselves to free buffers in the transient pool
-    // immediately after using them.
-    unsafe {
-      device.free_command_buffers(self.primary_command_pool,
-                                  &self.command_buffers)
-    };
-
     //   While the descriptor pool is also a pool, it has a preallocated size
     // which will be different next time. So, we destroy it all the way.
     unsafe { device.destroy_descriptor_pool(self.descriptor_pool, None) };
@@ -374,9 +287,6 @@ impl WindowDressing {
       unsafe { device.free_memory(*memory, None) };
     }
 
-    unsafe { device.destroy_pipeline(self.pipeline, None) };
-    unsafe { device.destroy_pipeline_layout(self.pipeline_layout, None) };
-    unsafe { device.destroy_render_pass(self.render_pass, None) };
     unsafe { device.destroy_image(self.color_image, None) };
     unsafe { device.free_memory(self.color_image_memory, None) };
     unsafe { device.destroy_image_view(self.color_image_view, None) };
@@ -393,6 +303,167 @@ impl WindowDressing {
 }
 
 
+impl RenderState {
+  pub fn new(permanent: &PermanentGraphicsState,
+             for_reinit: &GraphicsStateForReinit,
+             window_dressing: &WindowDressing)
+      -> Result<Self>
+  {
+    let device = &permanent.device;
+    let instance = &permanent.instance;
+    let graphics_queue = &permanent.graphics_queue;
+    let physical_device = &for_reinit.physical_device;
+    let sample_count = for_reinit.sample_count;
+    let descriptor_set_layout = &for_reinit.descriptor_set_layout;
+    let primary_command_pool = &window_dressing.primary_command_pool;
+    let transient_command_pool = &window_dressing.transient_command_pool;
+    let swapchain = &window_dressing.swapchain;
+    let depth_format = &window_dressing.depth_format;
+    let color_image_view = &window_dressing.color_image_view;
+    let depth_image_view = &window_dressing.depth_image_view;
+    let texture_image_view = &window_dressing.texture_image_view;
+    let uniform_buffers = &window_dressing.uniform_buffers;
+    let descriptor_pool = &window_dressing.descriptor_pool;
+    let sampler = &window_dressing.sampler;
+
+    let render_pass = init_render_pass(device, sample_count,
+                                       &swapchain.format, &depth_format)?;
+
+    let (pipeline_layout, pipeline)
+            = init_pipeline(device, descriptor_set_layout, &swapchain.extent,
+                            sample_count, &render_pass)?;
+
+    let framebuffers = init_framebuffers(
+            device, &swapchain.extent, &swapchain.image_views,
+            &color_image_view, &depth_image_view, &render_pass)?;
+
+    let (vertices, indices) = load_model()?;
+    let index_count = indices.len();
+
+    let command_buffers = init_command_buffers(device, &framebuffers,
+                                               primary_command_pool)?;
+
+    let (vertex_buffer, vertex_buffer_memory)
+            = init_vertex_buffer(vertices, instance, physical_device, device,
+                                 graphics_queue, &transient_command_pool)?;
+    let (index_buffer, index_buffer_memory)
+            = init_index_buffer(indices, instance, physical_device, device,
+                                graphics_queue, &transient_command_pool)?;
+
+    let descriptor_sets
+            = init_descriptor_sets(device, descriptor_set_layout,
+                                   &uniform_buffers, &descriptor_pool,
+                                   swapchain.images.len(),
+                                   &texture_image_view, &sampler)?;
+
+    Ok(RenderState {
+      render_pass,
+      pipeline,
+      pipeline_layout,
+      vertex_buffer,
+      vertex_buffer_memory,
+      index_buffer,
+      index_buffer_memory,
+      index_count,
+      framebuffers,
+      command_buffers,
+      descriptor_sets,
+    })
+  }
+
+  //   This relies on its caller to have already waited for the device to be
+  // idle.
+  pub fn reinit(&mut self, permanent: &PermanentGraphicsState,
+                for_reinit: &GraphicsStateForReinit,
+                window_dressing: &WindowDressing)
+      -> Result<()>
+  {
+    let device = &permanent.device;
+    let sample_count = for_reinit.sample_count;
+    let descriptor_set_layout = &for_reinit.descriptor_set_layout;
+    let primary_command_pool = &window_dressing.primary_command_pool;
+    let swapchain = &window_dressing.swapchain;
+    let depth_format = &window_dressing.depth_format;
+    let color_image_view = &window_dressing.color_image_view;
+    let depth_image_view = &window_dressing.depth_image_view;
+    let texture_image_view = &window_dressing.texture_image_view;
+    let uniform_buffers = &window_dressing.uniform_buffers;
+    let descriptor_pool = &window_dressing.descriptor_pool;
+    let sampler = &window_dressing.sampler;
+
+    self.destroy_replaceable(device, primary_command_pool);
+
+    let render_pass = init_render_pass(device, sample_count,
+                                       &swapchain.format, &depth_format)?;
+
+    let (pipeline_layout, pipeline)
+            = init_pipeline(device, descriptor_set_layout, &swapchain.extent,
+                            sample_count, &render_pass)?;
+
+    let framebuffers = init_framebuffers(
+            device, &swapchain.extent, &swapchain.image_views,
+            &color_image_view, &depth_image_view, &render_pass)?;
+
+    // Notice that we reused the command pool.
+    let command_buffers = init_command_buffers(device, &framebuffers,
+                                               primary_command_pool)?;
+
+    let descriptor_sets
+            = init_descriptor_sets(device, descriptor_set_layout,
+                                   &uniform_buffers, &descriptor_pool,
+                                   swapchain.images.len(),
+                                   texture_image_view, sampler)?;
+
+    self.render_pass = render_pass;
+    self.pipeline = pipeline;
+    self.pipeline_layout = pipeline_layout;
+    self.framebuffers = framebuffers;
+    self.command_buffers = command_buffers;
+    self.descriptor_sets = descriptor_sets;
+
+    Ok(())
+  }
+
+  //   This relies on its caller to have already waited for the device to be
+  // idle.
+  #[allow(unsafe_code)]
+  pub fn destroy(mut self, device: &Device,
+                 window_dressing: &WindowDressing)
+  {
+    self.destroy_replaceable(device, &window_dressing.primary_command_pool);
+
+    unsafe { device.destroy_buffer(self.vertex_buffer, None) };
+    unsafe { device.free_memory(self.vertex_buffer_memory, None) };
+
+    unsafe { device.destroy_buffer(self.index_buffer, None) };
+    unsafe { device.free_memory(self.index_buffer_memory, None) };
+  }
+
+  #[allow(unsafe_code)]
+  fn destroy_replaceable(&mut self, device: &Device,
+                         primary_command_pool: &vk::CommandPool)
+  {
+    for framebuffer in &self.framebuffers {
+      unsafe { device.destroy_framebuffer(*framebuffer, None) };
+    }
+
+    //   Notice that we free the buffers in the pool, but do not destroy the
+    // pool itself. Notice also that we only do this for the primary command
+    // pool, because that's the only one where we've kept track of the
+    // buffers. We promise ourselves to free buffers in the transient pool
+    // immediately after using them.
+    unsafe {
+      device.free_command_buffers(*primary_command_pool,
+                                  &self.command_buffers)
+    };
+
+    unsafe { device.destroy_pipeline(self.pipeline, None) };
+    unsafe { device.destroy_pipeline_layout(self.pipeline_layout, None) };
+    unsafe { device.destroy_render_pass(self.render_pass, None) };
+  }
+}
+
+
 #[allow(unsafe_code)]
 fn init_swapchain(window: &Window, instance: &Instance,
                   surface: &vk::SurfaceKHR,
@@ -1041,15 +1112,7 @@ fn init_command_pools(device: &Device, indices: &QueueFamilyIndices)
 
 #[allow(unsafe_code)]
 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>>
 {
diff --git a/src/main.rs b/src/main.rs
index 8a42147..10b203a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,7 +4,7 @@ use crate::graphics_permanent::{
   PermanentGraphicsState, GraphicsStateForReinit
 };
 use crate::graphics_window_dressing::{
-  WindowDressing, N_SIMULTANEOUS_FRAMES
+  WindowDressing, RenderState, N_SIMULTANEOUS_FRAMES
 };
 use crate::linear_algebra::{ Vec3, Mat4, Transformation };
 use crate::shader_data::UniformBlock;
@@ -34,6 +34,7 @@ struct Surreality {
   permanent: RefCell<Option<PermanentGraphicsState>>,
   for_reinit: RefCell<Option<GraphicsStateForReinit>>,
   window_dressing: RefCell<Option<WindowDressing>>,
+  render_state: RefCell<Option<RenderState>>,
   is_minimized: bool,
   is_reinit_queued: bool,
   frame_index: usize,
@@ -50,6 +51,7 @@ impl Surreality {
       permanent: RefCell::new(None),
       for_reinit: RefCell::new(None),
       window_dressing: RefCell::new(None),
+      render_state: RefCell::new(None),
       is_minimized: false,
       is_reinit_queued: false,
       frame_index: 0,
@@ -66,9 +68,13 @@ impl Surreality {
         = PermanentGraphicsState::new(event_loop)?;
 
     if enable_swapchain.0 {
-      *self.window_dressing.get_mut()
-          = Some(WindowDressing::new(&permanent, &for_reinit,
-                                     enable_anisotropy)?);
+      let window_dressing = WindowDressing::new(&permanent, &for_reinit,
+                                                enable_anisotropy)?;
+      let render_state = RenderState::new(&permanent, &for_reinit,
+                                          &window_dressing)?;
+
+      *self.window_dressing.get_mut() = Some(window_dressing);
+      *self.render_state.get_mut() = Some(render_state);
     }
 
     *self.permanent.get_mut() = Some(permanent);
@@ -82,8 +88,10 @@ impl Surreality {
        && let Some(for_reinit) = self.for_reinit.borrow().as_ref()
        && let Some(window_dressing)
               = self.window_dressing.borrow_mut().as_mut()
+       && let Some(render_state) = self.render_state.borrow_mut().as_mut()
     {
       window_dressing.reinit(permanent, for_reinit)?;
+      render_state.reinit(permanent, for_reinit, &window_dressing)?;
     }
 
     Ok(())
@@ -109,6 +117,7 @@ impl Surreality {
     if let Some(permanent) = self.permanent.borrow().as_ref()
        && let Some(window_dressing)
               = self.window_dressing.borrow_mut().as_mut()
+       && let Some(render_state) = self.render_state.borrow_mut().as_mut()
        && window_id == permanent.window.id()
     {
       let device = &permanent.device;
@@ -141,16 +150,9 @@ impl Surreality {
 
       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)?;
+      let command_buffer = crate::graphics_scene::generate_scene_commands(
+              image_index, time, &render_state, device,
+              &window_dressing.swapchain.extent)?;
 
       render_uniforms(time, device,
                       &window_dressing.uniform_buffer_memory[image_index],
@@ -160,7 +162,7 @@ impl Surreality {
       let second_semaphores = [*rendering_finished_semaphore];
 
       let wait_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT];
-      let command_buffers = [command_buffer];
+      let command_buffers = [*command_buffer];
       let submit_info = vk::SubmitInfo::builder()
                             .wait_semaphores(&first_semaphores)
                             .wait_dst_stage_mask(&wait_stages)
@@ -213,6 +215,10 @@ impl Drop for Surreality {
       unsafe { permanent.device.device_wait_idle() }.unwrap();
 
       if let Some(window_dressing) = self.window_dressing.replace(None) {
+        if let Some(render_state) = self.render_state.replace(None) {
+          render_state.destroy(&permanent.device, &window_dressing);
+        }
+
         window_dressing.destroy(&permanent.device);
       }