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 /src/graphics/window_dressing.rs | |
| 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
Diffstat (limited to 'src/graphics/window_dressing.rs')
| -rw-r--r-- | src/graphics/window_dressing.rs | 458 |
1 files changed, 2 insertions, 456 deletions
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(()) -} - |