diff options
| author | Irene Knapp <ireneista@irenes.space> | 2026-08-09 17:10:03 -0700 |
|---|---|---|
| committer | Irene Knapp <ireneista@irenes.space> | 2026-08-09 17:10:03 -0700 |
| commit | 4158fed109ae789cb3ca7ff53c2790797a3b4087 (patch) | |
| tree | 4e264225e211faaacc8259997dff207378c50e39 | |
| parent | 4b877079872248d3a4c17312b18599c6084e1e2d (diff) | |
refactor Texture into its own thing
as part of this, the command pools are moved from window dressing to permanent note also how mip count is a value computed by loading the texture, and used when creating the sampler, which is part of the window dressing. the assumption about how it's computed will have to change when we support multiple textures, but for now we settle for just pushing the assumption to the top level. Force-Push: yes Change-Id: I545c53feedc99628fbe943120798fe66fe9e065b
| -rw-r--r-- | src/graphics/mod.rs | 1 | ||||
| -rw-r--r-- | src/graphics/model.rs | 6 | ||||
| -rw-r--r-- | src/graphics/permanent.rs | 52 | ||||
| -rw-r--r-- | src/graphics/render_state.rs | 33 | ||||
| -rw-r--r-- | src/graphics/texture.rs | 376 | ||||
| -rw-r--r-- | src/graphics/util.rs | 78 | ||||
| -rw-r--r-- | src/graphics/window_dressing.rs | 458 | ||||
| -rw-r--r-- | src/main.rs | 22 |
8 files changed, 538 insertions, 488 deletions
diff --git a/src/graphics/mod.rs b/src/graphics/mod.rs index 7007429..3d03f9c 100644 --- a/src/graphics/mod.rs +++ b/src/graphics/mod.rs @@ -3,6 +3,7 @@ pub mod model; pub mod permanent; pub mod render_state; +pub mod texture; pub mod scene; pub mod util; pub mod window_dressing; diff --git a/src/graphics/model.rs b/src/graphics/model.rs index 01e8731..7b912b6 100644 --- a/src/graphics/model.rs +++ b/src/graphics/model.rs @@ -3,7 +3,6 @@ use crate::error::*; use crate::graphics::permanent::PermanentGraphicsState; use crate::graphics::render_state::RenderState; use crate::graphics::util::init_buffer; -use crate::graphics::window_dressing::WindowDressing; use crate::linear_algebra::{ Vec3, Vec4, Transformation }; use crate::shader_data::{ Vertex, VertexPushBlock }; @@ -26,14 +25,13 @@ pub struct Model { impl Model { pub fn new(vertices: Vec<Vertex<f32>>, indices: Vec<u32>, - permanent: &PermanentGraphicsState, - window_dressing: &WindowDressing) + permanent: &PermanentGraphicsState) -> Result<Self> { let device = &permanent.device; let instance = &permanent.instance; let graphics_queue = &permanent.graphics_queue; - let transient_command_pool = &window_dressing.transient_command_pool; + let transient_command_pool = &permanent.transient_command_pool; let index_count = indices.len(); diff --git a/src/graphics/permanent.rs b/src/graphics/permanent.rs index beda3fe..f359dbd 100644 --- a/src/graphics/permanent.rs +++ b/src/graphics/permanent.rs @@ -75,6 +75,14 @@ pub struct PermanentGraphicsState { // concerns, which have a tendency to defeat optimizations. Alas. pub graphics_queue: vk::Queue, pub presentation_queue: vk::Queue, + + // A command pool is an object from which command buffers are allocated. + // Our command pools are permanent, but none of the actual buffers are, + // those are all managed elsewhere. Lifecycle operations on Permanent + // require all the command-buffer lifecycle stuff to have already been dealt + // with. + pub primary_command_pool: vk::CommandPool, + pub transient_command_pool: vk::CommandPool, } @@ -130,9 +138,13 @@ impl PermanentGraphicsState { let descriptor_set_layout = init_descriptor_set_layout(&device)?; + let (primary_command_pool, transient_command_pool) + = init_command_pools(&device, &indices)?; + Ok((PermanentGraphicsState { window, entry, instance, debug_messager, surface, device, - graphics_queue, presentation_queue + graphics_queue, presentation_queue, + primary_command_pool, transient_command_pool, }, GraphicsStateForReinit { indices, sample_count, descriptor_set_layout, }, enable_anisotropy, enable_swapchain)) @@ -140,9 +152,17 @@ impl PermanentGraphicsState { #[allow(unsafe_code)] pub fn destroy(self) -> () { - unsafe { self.device.destroy_device(None) }; + let device = self.device; + let instance = self.instance; + + // Notice that we rely on the assumption any command buffers in the + // pools have already been freed. + unsafe { device.destroy_command_pool(self.primary_command_pool, None) }; + unsafe { device.destroy_command_pool(self.transient_command_pool, None) }; + + unsafe { device.destroy_device(None) }; - unsafe { self.instance.destroy_surface_khr(self.surface, None) }; + unsafe { instance.destroy_surface_khr(self.surface, None) }; // Everything but the instance itself should already be destroyed, // before we destroy the debug messager. The special hook to get debug @@ -151,11 +171,11 @@ impl PermanentGraphicsState { // shouldn't after this point, we'd miss out on diagnostics. if let Some(debug_messager) = self.debug_messager { unsafe { - self.instance.destroy_debug_utils_messenger_ext(debug_messager, None); + instance.destroy_debug_utils_messenger_ext(debug_messager, None); } } - unsafe { self.instance.destroy_instance(None) }; + unsafe { instance.destroy_instance(None) }; } // We expect our caller to have already verified that the device supports @@ -573,6 +593,28 @@ fn init_descriptor_set_layout(device: &Device) +#[allow(unsafe_code)] +fn init_command_pools(device: &Device, indices: &QueueFamilyIndices) + -> Result<(vk::CommandPool, vk::CommandPool)> +{ + let command_pool_info = vk::CommandPoolCreateInfo::builder() + .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) + }?; + + command_pool_info.flags(vk::CommandPoolCreateFlags::TRANSIENT); + let transient = unsafe { + device.create_command_pool(&command_pool_info, None) + }?; + + Ok((primary, transient)) +} + + // To Vulkan, a "physical" device is the actual GPU, and a "logical" // device is per-process state that represents a connection to the GPU. // Before we can create a logical device, we must choose which physical diff --git a/src/graphics/render_state.rs b/src/graphics/render_state.rs index d918e9a..5848ac8 100644 --- a/src/graphics/render_state.rs +++ b/src/graphics/render_state.rs @@ -4,6 +4,7 @@ use crate::graphics::permanent::{ PermanentGraphicsState, GraphicsStateForReinit }; use crate::graphics::model::Model; +use crate::graphics::texture::Texture; use crate::graphics::window_dressing::WindowDressing; use crate::shader_data::{ Vertex, UniformBlock, VertexPushBlock }; @@ -34,18 +35,17 @@ pub struct RenderState { impl RenderState { pub fn new(permanent: &PermanentGraphicsState, for_reinit: &GraphicsStateForReinit, - window_dressing: &WindowDressing) + window_dressing: &WindowDressing, texture: &Texture) -> Result<Self> { let device = &permanent.device; + let primary_command_pool = &permanent.primary_command_pool; 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; @@ -68,7 +68,7 @@ impl RenderState { = init_descriptor_sets(device, descriptor_set_layout, &uniform_buffers, &descriptor_pool, swapchain.images.len(), - &texture_image_view, &sampler)?; + &texture.image_view, &sampler)?; let model = None; @@ -87,24 +87,23 @@ impl RenderState { // idle. pub fn reinit(&mut self, permanent: &PermanentGraphicsState, for_reinit: &GraphicsStateForReinit, - window_dressing: &WindowDressing) + window_dressing: &WindowDressing, texture: &Texture) -> Result<()> { + self.destroy_replaceable(permanent); + let device = &permanent.device; + let primary_command_pool = &permanent.primary_command_pool; 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)?; @@ -124,7 +123,7 @@ impl RenderState { = init_descriptor_sets(device, descriptor_set_layout, &uniform_buffers, &descriptor_pool, swapchain.images.len(), - texture_image_view, sampler)?; + &texture.image_view, sampler)?; self.render_pass = render_pass; self.pipeline = pipeline; @@ -139,20 +138,20 @@ impl RenderState { // 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) + pub fn destroy(mut self, permanent: &PermanentGraphicsState) { - self.destroy_replaceable(device, &window_dressing.primary_command_pool); + self.destroy_replaceable(permanent); if let Some(model) = self.model { - model.destroy(device); + model.destroy(&permanent.device); } } #[allow(unsafe_code)] - fn destroy_replaceable(&mut self, device: &Device, - primary_command_pool: &vk::CommandPool) + fn destroy_replaceable(&mut self, permanent: &PermanentGraphicsState) { + let device = &permanent.device; + for framebuffer in &self.framebuffers { unsafe { device.destroy_framebuffer(*framebuffer, None) }; } @@ -163,7 +162,7 @@ impl RenderState { // buffers. We promise ourselves to free buffers in the transient pool // immediately after using them. unsafe { - device.free_command_buffers(*primary_command_pool, + device.free_command_buffers(permanent.primary_command_pool, &self.command_buffers) }; diff --git a/src/graphics/texture.rs b/src/graphics/texture.rs new file mode 100644 index 0000000..cd4dd7a --- /dev/null +++ b/src/graphics/texture.rs @@ -0,0 +1,376 @@ +#![deny(unsafe_code)] +use crate::error::*; +use crate::graphics::permanent::PermanentGraphicsState; +use crate::graphics::util::{ + stage_in_buffer, allocate_image, init_image_view, + begin_transient_commands, end_transient_commands +}; + +use std::io::Cursor; + +use png::Decoder; +use vulkanalia::{ Device, Instance }; +use vulkanalia::vk::{ self, HasBuilder, InstanceV1_0, DeviceV1_0 }; + + +#[derive(Debug)] +pub struct Texture { + image: vk::Image, + image_memory: vk::DeviceMemory, + pub image_view: vk::ImageView, +} + + +impl Texture { + pub fn new(permanent: &PermanentGraphicsState) -> Result<(Self, u32)> { + let graphics_queue = &permanent.graphics_queue; + let instance = &permanent.instance; + let device = &permanent.device; + let transient_command_pool = &permanent.transient_command_pool; + + let (image, image_memory, image_view, mip_count) + = init_texture(instance, device, graphics_queue, + &transient_command_pool)?; + + Ok((Texture { + image, + image_memory, + image_view, + }, mip_count)) + } + + // This relies on its caller to have already waited for the device to be + // idle. + #[allow(unsafe_code)] + pub fn destroy(self, device: &Device) { + unsafe { device.destroy_image(self.image, None) }; + unsafe { device.free_memory(self.image_memory, None) }; + unsafe { device.destroy_image_view(self.image_view, None) }; + } +} + + +#[allow(unsafe_code)] +fn init_texture(instance: &Instance, device: &Device, queue: &vk::Queue, + command_pool: &vk::CommandPool) + -> Result<(vk::Image, vk::DeviceMemory, vk::ImageView, u32)> +{ + let physical_device = device.physical_device(); + + let png = include_bytes!("../../textures/forest_leaves_04_diff.png"); + + let decoder = Decoder::new(Cursor::new(png)); + let mut reader = decoder.read_info()?; + + let (width, height) = reader.info().size(); + + let format_properties = unsafe { + instance.get_physical_device_format_properties(physical_device, + vk::Format::R8G8B8A8_SRGB) + }; + let has_linear_filter = format_properties + .optimal_tiling_features + .contains(vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR); + let mip_count = if has_linear_filter { + // This will generate mips all the way down to 1x1. It is not clear + // whether there's a benefit to that. + (width.max(height)).ilog2() + 1 + } else { + 1 + }; + + let mut pixels = vec![0; reader.info().raw_bytes()]; + reader.next_frame(&mut pixels)?; + + let (staging_buffer, staging_memory, _byte_size) + = stage_in_buffer(instance, device, &pixels)?; + + let (image, image_memory) + = allocate_image(instance, device, + width, height, mip_count, vk::SampleCountFlags::_1, + vk::Format::R8G8B8A8_SRGB, + vk::ImageTiling::OPTIMAL, + vk::ImageUsageFlags::SAMPLED + | vk::ImageUsageFlags::TRANSFER_SRC + | vk::ImageUsageFlags::TRANSFER_DST, + vk::MemoryPropertyFlags::DEVICE_LOCAL)?; + + change_image_layout(device, queue, command_pool, &image, mip_count, + vk::ImageLayout::UNDEFINED, + vk::ImageLayout::TRANSFER_DST_OPTIMAL)?; + + copy_buffer_to_image(device, queue, command_pool, &staging_buffer, &image, + width, height)?; + + // This will also change the layout to SHADER_READ_ONLY_OPTIMAL. + + fill_mip_levels(device, queue, command_pool, &image, + width, height, mip_count)?; + + let view = init_image_view(device, &image, mip_count, + vk::Format::R8G8B8A8_SRGB, + vk::ImageAspectFlags::COLOR)?; + + unsafe { device.destroy_buffer(staging_buffer, None) }; + unsafe { device.free_memory(staging_memory, None) }; + + Ok((image, image_memory, view, mip_count)) +} + + +#[allow(unsafe_code)] +fn change_image_layout(device: &Device, queue: &vk::Queue, + command_pool: &vk::CommandPool, image: &vk::Image, + mip_count: u32, old: vk::ImageLayout, + new: vk::ImageLayout) + -> Result<()> +{ + let command_buffer = begin_transient_commands(device, command_pool)?; + + let subresource_range = vk::ImageSubresourceRange::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(mip_count) + .base_array_layer(0) + .layer_count(1); + + // Notionally this is a property that our caller is in a better position + // to know than we are, but in practice the nature of the transition + // strongly implies a particular phase of the image's lifecycle, so we just + // compute it here. + let (source_access, source_stage, destination_access, destination_stage) + = match (old, new) + { + (vk::ImageLayout::UNDEFINED, vk::ImageLayout::TRANSFER_DST_OPTIMAL) + => (vk::AccessFlags::empty(), + vk::PipelineStageFlags::TOP_OF_PIPE, + vk::AccessFlags::TRANSFER_WRITE, + vk::PipelineStageFlags::TRANSFER), + (vk::ImageLayout::TRANSFER_DST_OPTIMAL, + vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) + => (vk::AccessFlags::TRANSFER_WRITE, + vk::PipelineStageFlags::TRANSFER, + vk::AccessFlags::SHADER_READ, + vk::PipelineStageFlags::FRAGMENT_SHADER), + _ => return Err(Error { + message: + format!("Don't know how to change from image layout {:?} to {:?}", + old, new) + }) + }; + + let barrier_info = vk::ImageMemoryBarrier::builder() + .image(*image) + .subresource_range(subresource_range) + .old_layout(old) + .new_layout(new) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .src_access_mask(source_access) + .dst_access_mask(destination_access); + + unsafe { + device.cmd_pipeline_barrier(command_buffer, + source_stage, destination_stage, + vk::DependencyFlags::empty(), + &[] as &[vk::MemoryBarrier], + &[] as &[vk::BufferMemoryBarrier], + &[barrier_info]) + }; + + end_transient_commands(command_buffer, device, queue, command_pool)?; + + Ok(()) +} + + + +#[allow(unsafe_code)] +fn copy_buffer_to_image(device: &Device, queue: &vk::Queue, + command_pool: &vk::CommandPool, source: &vk::Buffer, + destination: &vk::Image, width: u32, height: u32) + -> Result<()> +{ + let command_buffer = begin_transient_commands(device, command_pool)?; + + let subresource_layers = vk::ImageSubresourceLayers::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .mip_level(0) + .base_array_layer(0) + .layer_count(1); + + let copy_info = vk::BufferImageCopy::builder() + .buffer_offset(0) + .buffer_row_length(0) + .buffer_image_height(0) + .image_subresource(subresource_layers) + .image_offset(vk::Offset3D { x: 0, y: 0, z: 0 }) + .image_extent(vk::Extent3D { width, height, depth: 1 }); + + unsafe { + device.cmd_copy_buffer_to_image(command_buffer, *source, *destination, + vk::ImageLayout::TRANSFER_DST_OPTIMAL, + &[copy_info]) + }; + + end_transient_commands(command_buffer, device, queue, command_pool)?; + + Ok(()) +} + + +// An Image can store multiple mip levels within it, as one of several kinds +// of subresource it has. We deal with this by +#[allow(unsafe_code)] +fn fill_mip_levels(device: &Device, queue: &vk::Queue, + command_pool: &vk::CommandPool, image: &vk::Image, + original_width: u32, original_height: u32, + mip_count: u32) + -> Result<()> +{ + let command_buffer = begin_transient_commands(device, command_pool)?; + + // We'll be mutating these two builders as we loop through the mip levels, + // because we need to construct a lot of similar things. Remember, the + // builder methods don't mutate in-place, they return a new builder; to + // avoid confusion we always assign that result back to the same variable. + let mut barrier_subresource_range = vk::ImageSubresourceRange::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .level_count(1) + .base_array_layer(0) + .layer_count(1); + + let mut blit_barrier_info = vk::ImageMemoryBarrier::builder() + .image(*image) + .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) + .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED); + + // Now we loop through the mip levels from largest (low numbers) to + // smallest (high numbers). Conceptually, the only thing we're doing is a + // blit that copies each mip level from the one immediately before. Recall + // though that we don't just want to fill in the pixels, we also care about + // pixel format and memory sharing. There are additional operations to deal + // with that. These are best done together, as detailed below. + // + // This loop has a lot of code in it, so we make the "paragraphs" a little + // more dense than usual to make sure the logical grouping is clear. + let mut source_width = original_width; + let mut source_height = original_height; + for destination_mip_level in 1 .. mip_count { + let source_mip_level = destination_mip_level - 1; + let destination_width = (source_width / 2).max(1); + let destination_height = (source_height / 2).max(1); + + // So. The name pipeline_barrier is a little misleading; it does indeed + // mean "barrier" in the concurrency sense, but it isn't just initiating + // a wait, it's also performing any needed mutation. We do one of them + // here, acting on this iteration's source level, to set it up for + // reading. + barrier_subresource_range = barrier_subresource_range + .base_mip_level(source_mip_level as u32); + blit_barrier_info = blit_barrier_info + .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) + .new_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL) + .src_access_mask(vk::AccessFlags::TRANSFER_WRITE) + .dst_access_mask(vk::AccessFlags::TRANSFER_READ) + .subresource_range(barrier_subresource_range); + unsafe { + device.cmd_pipeline_barrier(command_buffer, + vk::PipelineStageFlags::TRANSFER, + vk::PipelineStageFlags::TRANSFER, + vk::DependencyFlags::empty(), + &[] as &[vk::MemoryBarrier], + &[] as &[vk::BufferMemoryBarrier], + &[blit_barrier_info]) + }; + + // Now we do the actual blit. Nice and easy, though specifying the + // coordinates is a bit verbose. + let blit_source_layer_info = vk::ImageSubresourceLayers::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .mip_level(source_mip_level as u32) + .base_array_layer(0) + .layer_count(1); + let blit_destination_layer_info = vk::ImageSubresourceLayers::builder() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .mip_level(destination_mip_level as u32) + .base_array_layer(0) + .layer_count(1); + let blit_info = vk::ImageBlit::builder() + .src_offsets([vk::Offset3D { x: 0, y: 0, z: 0 }, + vk::Offset3D { + x: source_width as i32, + y: source_height as i32, + z: 1 + }]) + .src_subresource(blit_source_layer_info) + .dst_offsets([vk::Offset3D { x: 0, y: 0, z: 0 }, + vk::Offset3D { + x: destination_width as i32, + y: destination_height as i32, + z: 1 + }]) + .dst_subresource(blit_destination_layer_info); + unsafe { + device.cmd_blit_image(command_buffer, + *image, vk::ImageLayout::TRANSFER_SRC_OPTIMAL, + *image, vk::ImageLayout::TRANSFER_DST_OPTIMAL, + &[blit_info], + vk::Filter::LINEAR) + }; + + // Now we do another pipeline_barrier. We're still acting on this + // iteration's source level, not on the destination. We'll never need to + // use it again except from the shader, so we set it appropriately for + // that. + blit_barrier_info = blit_barrier_info + .old_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL) + .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .src_access_mask(vk::AccessFlags::TRANSFER_READ) + .dst_access_mask(vk::AccessFlags::SHADER_READ); + unsafe { + device.cmd_pipeline_barrier(command_buffer, + vk::PipelineStageFlags::TRANSFER, + vk::PipelineStageFlags::FRAGMENT_SHADER, + vk::DependencyFlags::empty(), + &[] as &[vk::MemoryBarrier], + &[] as &[vk::BufferMemoryBarrier], + &[blit_barrier_info]) + }; + + source_width = destination_width; + source_height = destination_height; + } + + let final_mip_level = mip_count - 1; + + // We need to do one final pipeline_barrier, because the loop didn't do it + // to the smallest (last) mip level. We change it to have the same settings + // the loop left the rest of them in. The barrier source properties for this + // barrier are different from the others because this level was never useds + // as a blit source, only as a blit destination. The barrier destination + // properties are the same as the rest, so after this all the subresourcess + // will be in their fully-ready state. + barrier_subresource_range = barrier_subresource_range + .base_mip_level(final_mip_level as u32); + blit_barrier_info = blit_barrier_info + .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) + .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .src_access_mask(vk::AccessFlags::TRANSFER_WRITE) + .dst_access_mask(vk::AccessFlags::SHADER_READ) + .subresource_range(barrier_subresource_range); + unsafe { + device.cmd_pipeline_barrier(command_buffer, + vk::PipelineStageFlags::TRANSFER, + vk::PipelineStageFlags::FRAGMENT_SHADER, + vk::DependencyFlags::empty(), + &[] as &[vk::MemoryBarrier], + &[] as &[vk::BufferMemoryBarrier], + &[blit_barrier_info]) + }; + + end_transient_commands(command_buffer, device, queue, command_pool)?; + + Ok(()) +} + diff --git a/src/graphics/util.rs b/src/graphics/util.rs index c970d18..d09d144 100644 --- a/src/graphics/util.rs +++ b/src/graphics/util.rs @@ -151,6 +151,84 @@ pub fn pick_memory_type(instance: &Instance, #[allow(unsafe_code)] +pub fn allocate_image(instance: &Instance, device: &Device, width: u32, + height: u32, mip_count: u32, + sample_count: vk::SampleCountFlags, format: vk::Format, + tiling: vk::ImageTiling, usage: vk::ImageUsageFlags, + memory_flags: vk::MemoryPropertyFlags) + -> Result<(vk::Image, vk::DeviceMemory)> +{ + let physical_device = device.physical_device(); + + let image_info = vk::ImageCreateInfo::builder() + .image_type(vk::ImageType::_2D) + .extent(vk::Extent3D { width, height, depth: 1 }) + .mip_levels(mip_count) + .samples(sample_count) + .array_layers(1) + .format(format) + .tiling(tiling) + .initial_layout(vk::ImageLayout::UNDEFINED) + .usage(usage) + .sharing_mode(vk::SharingMode::EXCLUSIVE) + .flags(vk::ImageCreateFlags::empty()); + let image = unsafe { device.create_image(&image_info, None) }?; + + let requirements = unsafe { device.get_image_memory_requirements(image) }; + + let type_index = pick_memory_type(instance, &physical_device, + &memory_flags, &requirements)?; + + let image_memory_info = vk::MemoryAllocateInfo::builder() + .allocation_size(requirements.size) + .memory_type_index(type_index); + let image_memory = unsafe { + device.allocate_memory(&image_memory_info, None) + }?; + + unsafe { device.bind_image_memory(image, image_memory, 0) }?; + + Ok((image, image_memory)) +} + + + +#[allow(unsafe_code)] +pub fn init_image_view(device: &Device, image: &vk::Image, mip_count: u32, + format: vk::Format, aspects: vk::ImageAspectFlags) + -> Result<vk::ImageView> +{ + // Component mapping is only for color components (not, for example, depth + // or stencil components), so we always just want it like this. + let components = vk::ComponentMapping::builder() + .r(vk::ComponentSwizzle::IDENTITY) + .g(vk::ComponentSwizzle::IDENTITY) + .b(vk::ComponentSwizzle::IDENTITY) + .a(vk::ComponentSwizzle::IDENTITY); + + let subresource_range = vk::ImageSubresourceRange::builder() + .aspect_mask(aspects) + .base_mip_level(0) + .level_count(mip_count) + .base_array_layer(0) + .layer_count(1); + + let view_info = vk::ImageViewCreateInfo::builder() + .image(*image) + .view_type(vk::ImageViewType::_2D) + .format(format) + .components(components) + .subresource_range(subresource_range); + + let view = unsafe { + device.create_image_view(&view_info, None) + }?; + + Ok(view) +} + + +#[allow(unsafe_code)] pub fn begin_transient_commands(device: &Device, command_pool: &vk::CommandPool) -> Result<vk::CommandBuffer> diff --git a/src/graphics/window_dressing.rs b/src/graphics/window_dressing.rs index 09410bc..9afb03e 100644 --- a/src/graphics/window_dressing.rs +++ b/src/graphics/window_dressing.rs @@ -5,17 +5,13 @@ use crate::graphics::permanent::{ EnableAnisotropy }; use crate::graphics::util::{ - allocate_buffer, stage_in_buffer, - pick_memory_type, - begin_transient_commands, end_transient_commands + allocate_buffer, allocate_image, init_image_view }; use crate::shader_data::UniformBlock; use std::collections::BTreeSet; -use std::io::Cursor; use std::mem::size_of; -use png::Decoder; use vulkanalia::{ Device, Instance }; use vulkanalia::vk::{ self, Handle, HasBuilder, InstanceV1_0, DeviceV1_0, KhrSwapchainExtensionDeviceCommands }; @@ -46,12 +42,6 @@ pub struct WindowDressing { pub depth_image_view: vk::ImageView, pub depth_format: vk::Format, - pub primary_command_pool: vk::CommandPool, - pub transient_command_pool: vk::CommandPool, - - texture_image: vk::Image, - texture_image_memory: vk::DeviceMemory, - pub texture_image_view: vk::ImageView, pub sampler: vk::Sampler, pub uniform_buffers: Vec<vk::Buffer>, @@ -101,14 +91,13 @@ pub struct Concurrency { impl WindowDressing { pub fn new(permanent: &PermanentGraphicsState, for_reinit: &GraphicsStateForReinit, - enable_anisotropy: EnableAnisotropy) + enable_anisotropy: EnableAnisotropy, mip_count: u32) -> Result<Self> { let window = &permanent.window; let instance = &permanent.instance; let surface = &permanent.surface; let device = &permanent.device; - let graphics_queue = &permanent.graphics_queue; let sample_count = for_reinit.sample_count; let indices = &for_reinit.indices; @@ -122,13 +111,6 @@ impl WindowDressing { let (depth_image, depth_image_memory, depth_image_view, depth_format) = init_depth(instance, device, &swapchain.extent, sample_count)?; - let (primary_command_pool, transient_command_pool) - = init_command_pools(device, indices)?; - - let (texture_image, texture_image_memory, texture_image_view, mip_count) - = init_texture(instance, device, graphics_queue, - &transient_command_pool)?; - let sampler = init_sampler(&device, &enable_anisotropy, mip_count)?; let (uniform_buffers, uniform_buffer_memory) @@ -148,15 +130,10 @@ impl WindowDressing { depth_image_memory, depth_image_view, depth_format, - texture_image, - texture_image_memory, - texture_image_view, sampler, uniform_buffers, uniform_buffer_memory, descriptor_pool, - primary_command_pool, - transient_command_pool, concurrency, }) } @@ -220,9 +197,6 @@ impl WindowDressing { pub fn destroy(mut self, device: &Device) { self.destroy_replaceable(device); - 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) }; unsafe { device.destroy_sampler(self.sampler, None) }; for semaphore in self.concurrency.image_available_semaphores { @@ -236,11 +210,6 @@ impl WindowDressing { for fence in self.concurrency.frame_fences { unsafe { device.destroy_fence(fence, None) }; } - - // Notice that destroy_replaceable() freed the buffers in the pools, but - // did not destroy the pools. - unsafe { device.destroy_command_pool(self.primary_command_pool, None) }; - unsafe { device.destroy_command_pool(self.transient_command_pool, None) }; } @@ -400,74 +369,6 @@ fn init_depth(instance: &Instance, device: &Device, extent: &vk::Extent2D, } -#[allow(unsafe_code)] -fn init_texture(instance: &Instance, device: &Device, queue: &vk::Queue, - command_pool: &vk::CommandPool) - -> Result<(vk::Image, vk::DeviceMemory, vk::ImageView, u32)> -{ - let physical_device = device.physical_device(); - - let png = include_bytes!("../../textures/forest_leaves_04_diff.png"); - - let decoder = Decoder::new(Cursor::new(png)); - let mut reader = decoder.read_info()?; - - let (width, height) = reader.info().size(); - - let format_properties = unsafe { - instance.get_physical_device_format_properties(physical_device, - vk::Format::R8G8B8A8_SRGB) - }; - let has_linear_filter = format_properties - .optimal_tiling_features - .contains(vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR); - let mip_count = if has_linear_filter { - // This will generate mips all the way down to 1x1. It is not clear - // whether there's a benefit to that. - (width.max(height)).ilog2() + 1 - } else { - 1 - }; - - let mut pixels = vec![0; reader.info().raw_bytes()]; - reader.next_frame(&mut pixels)?; - - let (staging_buffer, staging_memory, _byte_size) - = stage_in_buffer(instance, device, &pixels)?; - - let (image, image_memory) - = allocate_image(instance, device, - width, height, mip_count, vk::SampleCountFlags::_1, - vk::Format::R8G8B8A8_SRGB, - vk::ImageTiling::OPTIMAL, - vk::ImageUsageFlags::SAMPLED - | vk::ImageUsageFlags::TRANSFER_SRC - | vk::ImageUsageFlags::TRANSFER_DST, - vk::MemoryPropertyFlags::DEVICE_LOCAL)?; - - change_image_layout(device, queue, command_pool, &image, mip_count, - vk::ImageLayout::UNDEFINED, - vk::ImageLayout::TRANSFER_DST_OPTIMAL)?; - - copy_buffer_to_image(device, queue, command_pool, &staging_buffer, &image, - width, height)?; - - // This will also change the layout to SHADER_READ_ONLY_OPTIMAL. - - fill_mip_levels(device, queue, command_pool, &image, - width, height, mip_count)?; - - let view = init_image_view(device, &image, mip_count, - vk::Format::R8G8B8A8_SRGB, - vk::ImageAspectFlags::COLOR)?; - - unsafe { device.destroy_buffer(staging_buffer, None) }; - unsafe { device.free_memory(staging_memory, None) }; - - Ok((image, image_memory, view, mip_count)) -} - - fn init_uniform_buffers(instance: &Instance, device: &Device, count: usize) -> Result<(Vec<vk::Buffer>, Vec<vk::DeviceMemory>)> { @@ -545,28 +446,6 @@ fn init_descriptor_pool(device: &Device, count: usize) #[allow(unsafe_code)] -fn init_command_pools(device: &Device, indices: &QueueFamilyIndices) - -> Result<(vk::CommandPool, vk::CommandPool)> -{ - let command_pool_info = vk::CommandPoolCreateInfo::builder() - .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) - }?; - - command_pool_info.flags(vk::CommandPoolCreateFlags::TRANSIENT); - let transient = unsafe { - device.create_command_pool(&command_pool_info, None) - }?; - - Ok((primary, transient)) -} - - -#[allow(unsafe_code)] fn init_concurrency(device: &Device, swapchain_images: &Vec<vk::Image>) -> Result<Concurrency> @@ -606,41 +485,6 @@ fn init_concurrency(device: &Device, } -#[allow(unsafe_code)] -fn init_image_view(device: &Device, image: &vk::Image, mip_count: u32, - format: vk::Format, aspects: vk::ImageAspectFlags) - -> Result<vk::ImageView> -{ - // Component mapping is only for color components (not, for example, depth - // or stencil components), so we always just want it like this. - let components = vk::ComponentMapping::builder() - .r(vk::ComponentSwizzle::IDENTITY) - .g(vk::ComponentSwizzle::IDENTITY) - .b(vk::ComponentSwizzle::IDENTITY) - .a(vk::ComponentSwizzle::IDENTITY); - - let subresource_range = vk::ImageSubresourceRange::builder() - .aspect_mask(aspects) - .base_mip_level(0) - .level_count(mip_count) - .base_array_layer(0) - .layer_count(1); - - let view_info = vk::ImageViewCreateInfo::builder() - .image(*image) - .view_type(vk::ImageViewType::_2D) - .format(format) - .components(components) - .subresource_range(subresource_range); - - let view = unsafe { - device.create_image_view(&view_info, None) - }?; - - Ok(view) -} - - fn pick_surface_format(available_formats: &Vec<vk::SurfaceFormatKHR>) -> Result<vk::SurfaceFormatKHR> { @@ -713,301 +557,3 @@ fn pick_image_extent(window: &Window, } } - -#[allow(unsafe_code)] -fn allocate_image(instance: &Instance, device: &Device, width: u32, - height: u32, mip_count: u32, - sample_count: vk::SampleCountFlags, format: vk::Format, - tiling: vk::ImageTiling, usage: vk::ImageUsageFlags, - memory_flags: vk::MemoryPropertyFlags) - -> Result<(vk::Image, vk::DeviceMemory)> -{ - let physical_device = device.physical_device(); - - let image_info = vk::ImageCreateInfo::builder() - .image_type(vk::ImageType::_2D) - .extent(vk::Extent3D { width, height, depth: 1 }) - .mip_levels(mip_count) - .samples(sample_count) - .array_layers(1) - .format(format) - .tiling(tiling) - .initial_layout(vk::ImageLayout::UNDEFINED) - .usage(usage) - .sharing_mode(vk::SharingMode::EXCLUSIVE) - .flags(vk::ImageCreateFlags::empty()); - let image = unsafe { device.create_image(&image_info, None) }?; - - let requirements = unsafe { device.get_image_memory_requirements(image) }; - - let type_index = pick_memory_type(instance, &physical_device, - &memory_flags, &requirements)?; - - let image_memory_info = vk::MemoryAllocateInfo::builder() - .allocation_size(requirements.size) - .memory_type_index(type_index); - let image_memory = unsafe { - device.allocate_memory(&image_memory_info, None) - }?; - - unsafe { device.bind_image_memory(image, image_memory, 0) }?; - - Ok((image, image_memory)) -} - - -#[allow(unsafe_code)] -fn copy_buffer_to_image(device: &Device, queue: &vk::Queue, - command_pool: &vk::CommandPool, source: &vk::Buffer, - destination: &vk::Image, width: u32, height: u32) - -> Result<()> -{ - let command_buffer = begin_transient_commands(device, command_pool)?; - - let subresource_layers = vk::ImageSubresourceLayers::builder() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .mip_level(0) - .base_array_layer(0) - .layer_count(1); - - let copy_info = vk::BufferImageCopy::builder() - .buffer_offset(0) - .buffer_row_length(0) - .buffer_image_height(0) - .image_subresource(subresource_layers) - .image_offset(vk::Offset3D { x: 0, y: 0, z: 0 }) - .image_extent(vk::Extent3D { width, height, depth: 1 }); - - unsafe { - device.cmd_copy_buffer_to_image(command_buffer, *source, *destination, - vk::ImageLayout::TRANSFER_DST_OPTIMAL, - &[copy_info]) - }; - - end_transient_commands(command_buffer, device, queue, command_pool)?; - - Ok(()) -} - - -#[allow(unsafe_code)] -fn change_image_layout(device: &Device, queue: &vk::Queue, - command_pool: &vk::CommandPool, image: &vk::Image, - mip_count: u32, old: vk::ImageLayout, - new: vk::ImageLayout) - -> Result<()> -{ - let command_buffer = begin_transient_commands(device, command_pool)?; - - let subresource_range = vk::ImageSubresourceRange::builder() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .base_mip_level(0) - .level_count(mip_count) - .base_array_layer(0) - .layer_count(1); - - // Notionally this is a property that our caller is in a better position - // to know than we are, but in practice the nature of the transition - // strongly implies a particular phase of the image's lifecycle, so we just - // compute it here. - let (source_access, source_stage, destination_access, destination_stage) - = match (old, new) - { - (vk::ImageLayout::UNDEFINED, vk::ImageLayout::TRANSFER_DST_OPTIMAL) - => (vk::AccessFlags::empty(), - vk::PipelineStageFlags::TOP_OF_PIPE, - vk::AccessFlags::TRANSFER_WRITE, - vk::PipelineStageFlags::TRANSFER), - (vk::ImageLayout::TRANSFER_DST_OPTIMAL, - vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) - => (vk::AccessFlags::TRANSFER_WRITE, - vk::PipelineStageFlags::TRANSFER, - vk::AccessFlags::SHADER_READ, - vk::PipelineStageFlags::FRAGMENT_SHADER), - _ => return Err(Error { - message: - format!("Don't know how to change from image layout {:?} to {:?}", - old, new) - }) - }; - - let barrier_info = vk::ImageMemoryBarrier::builder() - .image(*image) - .subresource_range(subresource_range) - .old_layout(old) - .new_layout(new) - .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) - .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) - .src_access_mask(source_access) - .dst_access_mask(destination_access); - - unsafe { - device.cmd_pipeline_barrier(command_buffer, - source_stage, destination_stage, - vk::DependencyFlags::empty(), - &[] as &[vk::MemoryBarrier], - &[] as &[vk::BufferMemoryBarrier], - &[barrier_info]) - }; - - end_transient_commands(command_buffer, device, queue, command_pool)?; - - Ok(()) -} - - -// An Image can store multiple mip levels within it, as one of several kinds -// of subresource it has. We deal with this by -#[allow(unsafe_code)] -fn fill_mip_levels(device: &Device, queue: &vk::Queue, - command_pool: &vk::CommandPool, image: &vk::Image, - original_width: u32, original_height: u32, - mip_count: u32) - -> Result<()> -{ - let command_buffer = begin_transient_commands(device, command_pool)?; - - // We'll be mutating these two builders as we loop through the mip levels, - // because we need to construct a lot of similar things. Remember, the - // builder methods don't mutate in-place, they return a new builder; to - // avoid confusion we always assign that result back to the same variable. - let mut barrier_subresource_range = vk::ImageSubresourceRange::builder() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .level_count(1) - .base_array_layer(0) - .layer_count(1); - - let mut blit_barrier_info = vk::ImageMemoryBarrier::builder() - .image(*image) - .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) - .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED); - - // Now we loop through the mip levels from largest (low numbers) to - // smallest (high numbers). Conceptually, the only thing we're doing is a - // blit that copies each mip level from the one immediately before. Recall - // though that we don't just want to fill in the pixels, we also care about - // pixel format and memory sharing. There are additional operations to deal - // with that. These are best done together, as detailed below. - // - // This loop has a lot of code in it, so we make the "paragraphs" a little - // more dense than usual to make sure the logical grouping is clear. - let mut source_width = original_width; - let mut source_height = original_height; - for destination_mip_level in 1 .. mip_count { - let source_mip_level = destination_mip_level - 1; - let destination_width = (source_width / 2).max(1); - let destination_height = (source_height / 2).max(1); - - // So. The name pipeline_barrier is a little misleading; it does indeed - // mean "barrier" in the concurrency sense, but it isn't just initiating - // a wait, it's also performing any needed mutation. We do one of them - // here, acting on this iteration's source level, to set it up for - // reading. - barrier_subresource_range = barrier_subresource_range - .base_mip_level(source_mip_level as u32); - blit_barrier_info = blit_barrier_info - .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) - .new_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL) - .src_access_mask(vk::AccessFlags::TRANSFER_WRITE) - .dst_access_mask(vk::AccessFlags::TRANSFER_READ) - .subresource_range(barrier_subresource_range); - unsafe { - device.cmd_pipeline_barrier(command_buffer, - vk::PipelineStageFlags::TRANSFER, - vk::PipelineStageFlags::TRANSFER, - vk::DependencyFlags::empty(), - &[] as &[vk::MemoryBarrier], - &[] as &[vk::BufferMemoryBarrier], - &[blit_barrier_info]) - }; - - // Now we do the actual blit. Nice and easy, though specifying the - // coordinates is a bit verbose. - let blit_source_layer_info = vk::ImageSubresourceLayers::builder() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .mip_level(source_mip_level as u32) - .base_array_layer(0) - .layer_count(1); - let blit_destination_layer_info = vk::ImageSubresourceLayers::builder() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .mip_level(destination_mip_level as u32) - .base_array_layer(0) - .layer_count(1); - let blit_info = vk::ImageBlit::builder() - .src_offsets([vk::Offset3D { x: 0, y: 0, z: 0 }, - vk::Offset3D { - x: source_width as i32, - y: source_height as i32, - z: 1 - }]) - .src_subresource(blit_source_layer_info) - .dst_offsets([vk::Offset3D { x: 0, y: 0, z: 0 }, - vk::Offset3D { - x: destination_width as i32, - y: destination_height as i32, - z: 1 - }]) - .dst_subresource(blit_destination_layer_info); - unsafe { - device.cmd_blit_image(command_buffer, - *image, vk::ImageLayout::TRANSFER_SRC_OPTIMAL, - *image, vk::ImageLayout::TRANSFER_DST_OPTIMAL, - &[blit_info], - vk::Filter::LINEAR) - }; - - // Now we do another pipeline_barrier. We're still acting on this - // iteration's source level, not on the destination. We'll never need to - // use it again except from the shader, so we set it appropriately for - // that. - blit_barrier_info = blit_barrier_info - .old_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL) - .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) - .src_access_mask(vk::AccessFlags::TRANSFER_READ) - .dst_access_mask(vk::AccessFlags::SHADER_READ); - unsafe { - device.cmd_pipeline_barrier(command_buffer, - vk::PipelineStageFlags::TRANSFER, - vk::PipelineStageFlags::FRAGMENT_SHADER, - vk::DependencyFlags::empty(), - &[] as &[vk::MemoryBarrier], - &[] as &[vk::BufferMemoryBarrier], - &[blit_barrier_info]) - }; - - source_width = destination_width; - source_height = destination_height; - } - - let final_mip_level = mip_count - 1; - - // We need to do one final pipeline_barrier, because the loop didn't do it - // to the smallest (last) mip level. We change it to have the same settings - // the loop left the rest of them in. The barrier source properties for this - // barrier are different from the others because this level was never useds - // as a blit source, only as a blit destination. The barrier destination - // properties are the same as the rest, so after this all the subresourcess - // will be in their fully-ready state. - barrier_subresource_range = barrier_subresource_range - .base_mip_level(final_mip_level as u32); - blit_barrier_info = blit_barrier_info - .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) - .new_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) - .src_access_mask(vk::AccessFlags::TRANSFER_WRITE) - .dst_access_mask(vk::AccessFlags::SHADER_READ) - .subresource_range(barrier_subresource_range); - unsafe { - device.cmd_pipeline_barrier(command_buffer, - vk::PipelineStageFlags::TRANSFER, - vk::PipelineStageFlags::FRAGMENT_SHADER, - vk::DependencyFlags::empty(), - &[] as &[vk::MemoryBarrier], - &[] as &[vk::BufferMemoryBarrier], - &[blit_barrier_info]) - }; - - end_transient_commands(command_buffer, device, queue, command_pool)?; - - Ok(()) -} - diff --git a/src/main.rs b/src/main.rs index 9fb89bc..6601982 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ #![deny(unsafe_code)] use crate::error::*; use crate::graphics::model::Model; +use crate::graphics::texture::Texture; use crate::graphics::permanent::{ PermanentGraphicsState, GraphicsStateForReinit }; @@ -37,6 +38,7 @@ struct Surreality { for_reinit: RefCell<Option<GraphicsStateForReinit>>, window_dressing: RefCell<Option<WindowDressing>>, render_state: RefCell<Option<RenderState>>, + texture: RefCell<Option<Texture>>, is_minimized: bool, is_reinit_queued: bool, frame_index: usize, @@ -54,6 +56,7 @@ impl Surreality { for_reinit: RefCell::new(None), window_dressing: RefCell::new(None), render_state: RefCell::new(None), + texture: RefCell::new(None), is_minimized: false, is_reinit_queued: false, frame_index: 0, @@ -69,19 +72,21 @@ impl Surreality { let (permanent, for_reinit, enable_anisotropy, enable_swapchain) = PermanentGraphicsState::new(event_loop)?; + let (texture, mip_count) = Texture::new(&permanent)?; + if enable_swapchain.0 { let window_dressing = WindowDressing::new(&permanent, &for_reinit, - enable_anisotropy)?; + enable_anisotropy, mip_count)?; let mut render_state = RenderState::new(&permanent, &for_reinit, - &window_dressing)?; + &window_dressing, &texture)?; let (vertices, indices) = load_obj()?; - render_state.set_model(Model::new(vertices, indices, - &permanent, &window_dressing)?); + render_state.set_model(Model::new(vertices, indices, &permanent)?); *self.window_dressing.get_mut() = Some(window_dressing); *self.render_state.get_mut() = Some(render_state); } + *self.texture.get_mut() = Some(texture); *self.permanent.get_mut() = Some(permanent); *self.for_reinit.get_mut() = Some(for_reinit); @@ -94,9 +99,10 @@ impl Surreality { && let Some(window_dressing) = self.window_dressing.borrow_mut().as_mut() && let Some(render_state) = self.render_state.borrow_mut().as_mut() + && let Some(texture) = self.texture.borrow_mut().as_mut() { window_dressing.reinit(permanent, for_reinit)?; - render_state.reinit(permanent, for_reinit, &window_dressing)?; + render_state.reinit(permanent, for_reinit, &window_dressing, &texture)?; } Ok(()) @@ -221,12 +227,16 @@ impl Drop for Surreality { 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); + render_state.destroy(&permanent); } window_dressing.destroy(&permanent.device); } + if let Some(texture) = self.texture.replace(None) { + texture.destroy(&permanent.device); + } + if let Some(for_reinit) = self.for_reinit.replace(None) { for_reinit.destroy(&permanent.device); } |