diff options
| -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); } |