From b52d1be4302e452a45a03595f8c70411a061f920 Mon Sep 17 00:00:00 2001 From: Irene Knapp Date: Mon, 17 Aug 2026 01:41:16 -0700 Subject: move the texture binding to a push descriptor with a descriptor update template, even yay this also moves us to a minimum Vulkan version of 1.4, which we understand to be widely deployed. in theory, push descriptors and update templates were available via extensions in earlier versions, but it doesn't seem worth doing the work to switch out whether we use the extensions based on the version, so we don't. Force-Push: yes Change-Id: I26db8771abf5626d9614630c66e019ea99babcac --- shaders/shader.frag | 7 ++- shaders/shader.vert | 5 +- src/graphics/frame.rs | 68 ++++++++++++-------------- src/graphics/model.rs | 12 +---- src/graphics/permanent.rs | 120 +++++++++++++++++++++++++++++++++++++--------- src/graphics/render.rs | 105 +++++++++++++++++++++++++++++++++++----- src/graphics/scene.rs | 18 ++++++- src/graphics/texture.rs | 35 +++++++++++++- src/main.rs | 22 +++------ src/shader_data.rs | 6 +++ 10 files changed, 293 insertions(+), 105 deletions(-) diff --git a/shaders/shader.frag b/shaders/shader.frag index a2561d1..90d6ab0 100644 --- a/shaders/shader.frag +++ b/shaders/shader.frag @@ -1,7 +1,10 @@ #version 460 -layout(binding = 1) uniform sampler textureSampler; -layout(binding = 2) uniform texture2D textureImage; +// Set 0 is the default, so we could omit it, but we prefer to be explicit. +// It specifies which the descriptor set layout the binding index pertains to; +// see init_pipeline() in render.rs. +layout(set = 0, binding = 1) uniform sampler textureSampler; +layout(set = 1, binding = 0) uniform texture2D textureImage; layout(location = 0) in vec3 fragmentColor; layout(location = 1) in vec2 fragmentTextureCoordinate; diff --git a/shaders/shader.vert b/shaders/shader.vert index 009b38f..bd8cd94 100644 --- a/shaders/shader.vert +++ b/shaders/shader.vert @@ -5,7 +5,10 @@ struct Transformation { vec3 translation; }; -layout(binding = 0) uniform UniformBlock { +// Set 0 is the default, so we could omit it, but we prefer to be explicit. +// It specifies which the descriptor set layout the binding index pertains to; +// see init_pipeline() in render.rs. +layout(set = 0, binding = 0) uniform UniformBlock { Transformation view; mat4 projection; } uniform_block; diff --git a/src/graphics/frame.rs b/src/graphics/frame.rs index d8d4bcd..82e5c1c 100644 --- a/src/graphics/frame.rs +++ b/src/graphics/frame.rs @@ -1,6 +1,6 @@ #![deny(unsafe_code)] use crate::error::*; -use crate::graphics::{ Permanent, ForReinit, WindowDressing, Texture }; +use crate::graphics::{ Permanent, ForReinit, WindowDressing }; use crate::graphics::util::allocate_buffer; use crate::shader_data::UniformBlock; @@ -27,6 +27,10 @@ pub struct Frame { pub uniform_buffer: vk::Buffer, pub uniform_buffer_memory: vk::DeviceMemory, + // Notice that our descriptor logic is split between the descriptor sets, + // which are per-frame and defined here, and the descriptor update template + // for the push descriptors, which is shared by all frames and is kept in + // Render. pub descriptor_set: vk::DescriptorSet, } @@ -39,14 +43,16 @@ impl Frame { // an indidivual Frame. #[allow(unsafe_code)] pub fn new(permanent: &Permanent, for_reinit: &ForReinit, - window_dressing: &WindowDressing, texture: &Texture, - render_pass: &vk::RenderPass) + window_dressing: &WindowDressing, render_pass: &vk::RenderPass) -> Result> { let mut frames = Vec::new(); + // The amount of interesting work done in the top level methods is a bit + // more for Frame than it is for our various other state objects, so we + // consolidate the implementation details in reinit(). Frame::reinit(&mut frames, permanent, for_reinit, window_dressing, - texture, render_pass)?; + render_pass)?; Ok(frames) } @@ -55,7 +61,7 @@ impl Frame { #[allow(unsafe_code)] pub fn reinit(frames: &mut Vec, permanent: &Permanent, for_reinit: &ForReinit, window_dressing: &WindowDressing, - texture: &Texture, render_pass: &vk::RenderPass) + render_pass: &vk::RenderPass) -> Result<()> { Frame::destroy_replaceable(frames, permanent); @@ -63,7 +69,8 @@ impl Frame { let instance = &permanent.instance; let device = &permanent.device; let primary_command_pool = &permanent.primary_command_pool; - let descriptor_set_layout = &for_reinit.descriptor_set_layout; + let primary_descriptor_set_layout + = &for_reinit.primary_descriptor_set_layout; let swapchain = &window_dressing.swapchain; let color_image_view = &window_dressing.color_image_view; let depth_image_view = &window_dressing.depth_image_view; @@ -79,7 +86,7 @@ impl Frame { primary_command_pool)?; let descriptor_sets = allocate_descriptor_sets( - count, device, descriptor_set_layout, descriptor_pool)?; + count, device, primary_descriptor_set_layout, descriptor_pool)?; for (index, color_resolve_image_view) in swapchain.image_views.iter().enumerate() @@ -95,7 +102,7 @@ impl Frame { = init_uniform_buffer(instance, device)?; configure_descriptor_set(&mut descriptor_set, device, &uniform_buffer, - &texture.image_view, sampler)?; + sampler)?; frames.push(Frame { framebuffer, command_buffer, uniform_buffer, uniform_buffer_memory, @@ -189,6 +196,20 @@ fn allocate_command_buffers(count: usize, device: &Device, } +fn init_uniform_buffer(instance: &Instance, device: &Device) + -> Result<(vk::Buffer, vk::DeviceMemory)> +{ + let (buffer, memory) = allocate_buffer( + instance, device, + size_of::>() as vk::DeviceSize, + vk::BufferUsageFlags::UNIFORM_BUFFER, + vk::MemoryPropertyFlags::HOST_COHERENT + | vk::MemoryPropertyFlags::HOST_VISIBLE)?; + + Ok((buffer, memory)) +} + + #[allow(unsafe_code)] fn allocate_descriptor_sets(count: usize, device: &Device, descriptor_set_layout: &vk::DescriptorSetLayout, @@ -211,7 +232,6 @@ fn allocate_descriptor_sets(count: usize, device: &Device, fn configure_descriptor_set(descriptor_set: &mut vk::DescriptorSet, device: &Device, uniform_buffer: &vk::Buffer, - texture_image_view: &vk::ImageView, sampler: &vk::Sampler) -> Result<()> { @@ -238,20 +258,7 @@ fn configure_descriptor_set(descriptor_set: &mut vk::DescriptorSet, .descriptor_type(vk::DescriptorType::SAMPLER) .image_info(&sampler_image_info_list); - let texture_image_info = vk::DescriptorImageInfo::builder() - .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) - .image_view(*texture_image_view); - let texture_image_info_list = [texture_image_info]; - let texture_write_info = vk::WriteDescriptorSet::builder() - .dst_set(*descriptor_set) - .dst_binding(2) - .dst_array_element(0) - .descriptor_type(vk::DescriptorType::SAMPLED_IMAGE) - .image_info(&texture_image_info_list); - - let write_info_list = [ - uniform_block_write_info, sampler_write_info, texture_write_info - ]; + let write_info_list = [ uniform_block_write_info, sampler_write_info ]; let copy_info_list: [vk::CopyDescriptorSet; 0] = []; unsafe { @@ -261,18 +268,3 @@ fn configure_descriptor_set(descriptor_set: &mut vk::DescriptorSet, Ok(()) } - -fn init_uniform_buffer(instance: &Instance, device: &Device) - -> Result<(vk::Buffer, vk::DeviceMemory)> -{ - let (buffer, memory) = allocate_buffer( - instance, device, - size_of::>() as vk::DeviceSize, - vk::BufferUsageFlags::UNIFORM_BUFFER, - vk::MemoryPropertyFlags::HOST_COHERENT - | vk::MemoryPropertyFlags::HOST_VISIBLE)?; - - Ok((buffer, memory)) -} - - diff --git a/src/graphics/model.rs b/src/graphics/model.rs index 9c62d51..096de86 100644 --- a/src/graphics/model.rs +++ b/src/graphics/model.rs @@ -61,8 +61,7 @@ impl Model { #[allow(unsafe_code)] pub fn render(&self, time: f32, device: &Device, render: &Render, - command_buffer: &vk::CommandBuffer, - descriptor_set: &vk::DescriptorSet) + command_buffer: &vk::CommandBuffer) { unsafe { device.cmd_bind_vertex_buffers(*command_buffer, 0, @@ -74,15 +73,6 @@ impl Model { 0, vk::IndexType::UINT32) }; - unsafe { - device.cmd_bind_descriptor_sets(*command_buffer, - vk::PipelineBindPoint::GRAPHICS, - render.pipeline_layout, - 0, - &[*descriptor_set], - &[]) - }; - let scale = Vec3::new(1.0, 1.0, 1.0); let model = Transformation { rotation: Vec4::rotation_quaternion(&Vec3::new(0.0, 1.0, 0.0), diff --git a/src/graphics/permanent.rs b/src/graphics/permanent.rs index b39dbdd..6c4e02d 100644 --- a/src/graphics/permanent.rs +++ b/src/graphics/permanent.rs @@ -8,7 +8,7 @@ use vulkanalia::bytecode::Bytecode; use vulkanalia::loader::{ LibloadingLoader, LIBRARY }; use vulkanalia::vk::{ self, HasBuilder, ApplicationInfo, InstanceCreateInfo, - DeviceV1_0, EntryV1_0, InstanceV1_0, + DeviceV1_0, EntryV1_0, InstanceV1_0, InstanceV1_1, ExtDebugUtilsExtensionInstanceCommands, KhrSurfaceExtensionInstanceCommands }; use winit::dpi::LogicalSize; @@ -16,6 +16,7 @@ use winit::event_loop::ActiveEventLoop; use winit::window::{ Window, WindowAttributes }; +const VULKAN_MINIMUM_VERSION: Version = Version::new(1, 4, 0); const VULKAN_FIRST_PORTABILITY_VERSION: Version = Version::new(1, 3, 216); @@ -135,7 +136,8 @@ impl Permanent { = init_vulkan_device(&instance, &surface, enable_validation, enable_portability)?; - let descriptor_set_layout = init_descriptor_set_layout(&device)?; + let (primary_descriptor_set_layout, push_descriptor_set_layout) + = init_descriptor_set_layouts(&device)?; let (primary_command_pool, transient_command_pool) = init_command_pools(&device, &indices)?; @@ -145,7 +147,8 @@ impl Permanent { graphics_queue, presentation_queue, primary_command_pool, transient_command_pool, }, ForReinit { - indices, sample_count, descriptor_set_layout, + indices, sample_count, + primary_descriptor_set_layout, push_descriptor_set_layout, }, enable_anisotropy, enable_swapchain)) } @@ -236,7 +239,8 @@ impl Permanent { pub struct ForReinit { pub indices: QueueFamilyIndices, pub sample_count: vk::SampleCountFlags, - pub descriptor_set_layout: vk::DescriptorSetLayout, + pub primary_descriptor_set_layout: vk::DescriptorSetLayout, + pub push_descriptor_set_layout: vk::DescriptorSetLayout, } @@ -244,7 +248,13 @@ impl ForReinit { #[allow(unsafe_code)] pub fn destroy(self, device: &Device) -> () { unsafe { - device.destroy_descriptor_set_layout(self.descriptor_set_layout, None) + device.destroy_descriptor_set_layout(self.primary_descriptor_set_layout, + None) + }; + + unsafe { + device.destroy_descriptor_set_layout(self.push_descriptor_set_layout, + None) }; } } @@ -366,12 +376,28 @@ fn init_vulkan(window: &Window) this may mean other messages don't show up."); } + // The api_version field here is the version of Vulkan we actually get. + // The official documentation is a bit misleading, since it says "maximum", + // but on close reading what it means is that we guarantee that we as the + // application will not require anything newer than that. So from our + // perspective it is the MINIMUM we will accept. + // + // We define a constant for it to make it easy to find, since it's far + // more important operationally than any of the other versions declared + // here. + // + // The validation layer enforces this API version on us, interposing + // itself when we attempt to load functions. Vulkanalia will panic!() on us + // when loading fails, with a message in the form "could not load + // vkWhateverWhatever", which can be quite confusing, so this comment text + // is here to make that easy to diagnose. If you got here for that reason, + // give thanks to the Waiting and its foresight. let application_info = ApplicationInfo::builder() .application_name(b"Surreality\0") .application_version(vk::make_version(1, 0, 0)) .engine_name(b"Surreality\0") .engine_version(vk::make_version(1, 0, 0)) - .api_version(vk::make_version(1, 0, 0)); + .api_version(u32::from(VULKAN_MINIMUM_VERSION)); // Deceptively, this DOES get mutated later, but Vulkanalia doesn't see // it that way. @@ -454,9 +480,24 @@ fn init_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR, // Old versions of Vulkan want layers to be enabled at the device // level as well. Newer ones will ignore this and just use the instance // layers. - let available_features = unsafe { - instance.get_physical_device_features(physical_device) + // + // Dealing with this struct chain is kind of risky lifetime-wise, because + // Vulkanalia unsafely discards the reference for our "convenience". Alas. + let mut available_features_14 + = vk::PhysicalDeviceVulkan14Features::default(); + let mut available_features = vk::PhysicalDeviceFeatures2::builder() + .push_next(&mut available_features_14); + unsafe { + instance.get_physical_device_features2(physical_device, + &mut available_features) }; + + // Another struct chain with a risky lifetime. Vulkanalia's mutation + // features return a copy, so we have to avoid mutating features_14 once we + // add it to the chain. Fortunately we only need it for one thing right now, + // so we do that here at the top and treat it as non-mutable. + let mut features_14 = vk::PhysicalDeviceVulkan14Features::builder() + .push_descriptor(true); let mut features = vk::PhysicalDeviceFeatures::builder(); let mut extensions = Vec::new(); let mut layers = Vec::new(); @@ -506,7 +547,7 @@ fn init_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR, EnableSwapchain(false) }; - let enable_anisotropy = if available_features.sampler_anisotropy + let enable_anisotropy = if available_features.features.sampler_anisotropy == vk::TRUE { features = features.sampler_anisotropy(true); @@ -535,11 +576,14 @@ fn init_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR, .queue_priorities(&[1.0])); } + let mut features2 = vk::PhysicalDeviceFeatures2::builder() + .features(features); let device_info = vk::DeviceCreateInfo::builder() .queue_create_infos(&queues) .enabled_layer_names(&layers) .enabled_extension_names(&extensions) - .enabled_features(&features); + .push_next(&mut features2) + .push_next(&mut features_14); let device = unsafe { instance.create_device(physical_device, &device_info, None) @@ -564,8 +608,8 @@ fn init_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR, #[allow(unsafe_code)] -fn init_descriptor_set_layout(device: &Device) - -> Result +fn init_descriptor_set_layouts(device: &Device) + -> Result<(vk::DescriptorSetLayout, vk::DescriptorSetLayout)> { let uniform_block_binding = vk::DescriptorSetLayoutBinding::builder() .binding(0) @@ -579,21 +623,32 @@ fn init_descriptor_set_layout(device: &Device) .descriptor_count(1) .stage_flags(vk::ShaderStageFlags::FRAGMENT); + let primary_bindings = [uniform_block_binding, sampler_binding]; + let primary_descriptor_set_layout_info + = vk::DescriptorSetLayoutCreateInfo::builder() + .bindings(&primary_bindings); + let primary_descriptor_set_layout = unsafe { + device.create_descriptor_set_layout(&primary_descriptor_set_layout_info, + None) + }?; + let texture_binding = vk::DescriptorSetLayoutBinding::builder() - .binding(2) + .binding(0) .descriptor_type(vk::DescriptorType::SAMPLED_IMAGE) .descriptor_count(1) .stage_flags(vk::ShaderStageFlags::FRAGMENT); - let bindings = [uniform_block_binding, sampler_binding, texture_binding]; - let descriptor_set_layout_info + let push_bindings = [texture_binding]; + let push_descriptor_set_layout_info = vk::DescriptorSetLayoutCreateInfo::builder() - .bindings(&bindings); - let descriptor_set_layout = unsafe { - device.create_descriptor_set_layout(&descriptor_set_layout_info, None) + .bindings(&push_bindings) + .flags(vk::DescriptorSetLayoutCreateFlags::PUSH_DESCRIPTOR); + let push_descriptor_set_layout = unsafe { + device.create_descriptor_set_layout(&push_descriptor_set_layout_info, + None) }?; - Ok(descriptor_set_layout) + Ok((primary_descriptor_set_layout, push_descriptor_set_layout)) } @@ -770,10 +825,15 @@ fn score_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR, // selected. } - let features = unsafe { - instance.get_physical_device_features(*physical_device) + let mut features_14 + = vk::PhysicalDeviceVulkan14Features::default(); + let mut features = vk::PhysicalDeviceFeatures2::builder() + .push_next(&mut features_14); + unsafe { + instance.get_physical_device_features2(*physical_device, &mut features) }; - if features.sampler_anisotropy == vk::TRUE { + + if features.features.sampler_anisotropy == vk::TRUE { score += 1; } @@ -786,7 +846,17 @@ fn score_vulkan_device(instance: &Instance, surface: &vk::SurfaceKHR, score += shift; let sample_count = vk::SampleCountFlags::from_bits(1 << shift).unwrap(); - Ok(Acceptable::Accepted((score, indices, sample_count))) + // We prefer to report the most important reason, rather than + // less-fundamental ones. To make that easy, we try to keep this logic + // together at the end. Note that, before we get here, we may have already + // rejected the device thanks to find_device_queue_family_indices(), called + // above, but that's okay because all those reasons are in fact more + // important than the ones tested here. + if features_14.push_descriptor != vk::TRUE { + Ok(Acceptable::Rejected("Doesn't support push descriptors.".to_string())) + } else { + Ok(Acceptable::Accepted((score, indices, sample_count))) + } } @@ -819,6 +889,9 @@ fn find_device_queue_family_indices(instance: &Instance, } } + // We prefer to report the most important reason, rather than + // less-fundamental ones. To make that easy, we keep all this logic together + // at the end. if let Some(graphics) = graphics { if let Some(presentation) = presentation { Ok(Acceptable::Accepted(QueueFamilyIndices { @@ -885,3 +958,4 @@ extern "system" fn debug_messager_callback( // test so anything it does is fine with us. vk::FALSE } + diff --git a/src/graphics/render.rs b/src/graphics/render.rs index 30f4b62..368bb96 100644 --- a/src/graphics/render.rs +++ b/src/graphics/render.rs @@ -4,12 +4,12 @@ use crate::assets::asset; use crate::graphics::{ Permanent, ForReinit, WindowDressing, Frame, Model, Texture }; -use crate::shader_data::{ Vertex, VertexPushBlock }; +use crate::shader_data::{ Vertex, VertexPushBlock, TexturePushBlock }; use std::mem::size_of; use vulkanalia::Device; -use vulkanalia::vk::{ self, Handle, HasBuilder, DeviceV1_0 }; +use vulkanalia::vk::{ self, Handle, HasBuilder, DeviceV1_0, DeviceV1_1 }; // Render is a state object that collects the Vulkan graphics objects which @@ -23,6 +23,12 @@ pub struct Render { pub pipeline_layout: vk::PipelineLayout, pub model: Option, + pub texture: Option, + + // Notice that our descriptor logic is split between the descriptor update + // template for the push descriptors, which is defined here, and the + // descriptor sets, which are per-frame and are kept in Frame. + pub texture_descriptor_update_template: vk::DescriptorUpdateTemplate, pub per_frame: Vec, } @@ -30,12 +36,14 @@ pub struct Render { impl Render { pub fn new(permanent: &Permanent, for_reinit: &ForReinit, - window_dressing: &WindowDressing, texture: &Texture) + window_dressing: &WindowDressing) -> Result { let device = &permanent.device; let sample_count = for_reinit.sample_count; - let descriptor_set_layout = &for_reinit.descriptor_set_layout; + let primary_descriptor_set_layout + = &for_reinit.primary_descriptor_set_layout; + let push_descriptor_set_layout = &for_reinit.push_descriptor_set_layout; let swapchain = &window_dressing.swapchain; let depth_format = &window_dressing.depth_format; @@ -43,19 +51,26 @@ impl Render { &swapchain.format, &depth_format)?; let (pipeline_layout, pipeline) - = init_pipeline(device, descriptor_set_layout, &swapchain.extent, + = init_pipeline(device, primary_descriptor_set_layout, + push_descriptor_set_layout, &swapchain.extent, sample_count, &render_pass)?; + let texture_descriptor_update_template + = init_descriptor_update_template(device, &pipeline_layout)?; + let per_frame = Frame::new(permanent, for_reinit, window_dressing, - texture, &render_pass)?; + &render_pass)?; let model = None; + let texture = None; Ok(Render { render_pass, pipeline, pipeline_layout, model, + texture, + texture_descriptor_update_template, per_frame, }) } @@ -64,14 +79,16 @@ impl Render { // idle. pub fn reinit(&mut self, permanent: &Permanent, for_reinit: &ForReinit, - window_dressing: &WindowDressing, texture: &Texture) + window_dressing: &WindowDressing) -> Result<()> { self.destroy_replaceable(permanent); let device = &permanent.device; let sample_count = for_reinit.sample_count; - let descriptor_set_layout = &for_reinit.descriptor_set_layout; + let primary_descriptor_set_layout + = &for_reinit.primary_descriptor_set_layout; + let push_descriptor_set_layout = &for_reinit.push_descriptor_set_layout; let swapchain = &window_dressing.swapchain; let depth_format = &window_dressing.depth_format; @@ -79,15 +96,21 @@ impl Render { &swapchain.format, &depth_format)?; let (pipeline_layout, pipeline) - = init_pipeline(device, descriptor_set_layout, &swapchain.extent, + = init_pipeline(device, primary_descriptor_set_layout, + push_descriptor_set_layout, &swapchain.extent, sample_count, &render_pass)?; + let texture_descriptor_update_template + = init_descriptor_update_template(device, &pipeline_layout)?; + Frame::reinit(&mut self.per_frame, permanent, for_reinit, window_dressing, - texture, &render_pass)?; + &render_pass)?; self.render_pass = render_pass; self.pipeline = pipeline; self.pipeline_layout = pipeline_layout; + self.texture_descriptor_update_template + = texture_descriptor_update_template; Ok(()) } @@ -103,6 +126,10 @@ impl Render { if let Some(model) = self.model { model.destroy(&permanent.device); } + + if let Some(texture) = self.texture { + texture.destroy(&permanent.device); + } } #[allow(unsafe_code)] @@ -112,6 +139,11 @@ impl Render { let device = &permanent.device; + unsafe { + device.destroy_descriptor_update_template( + self.texture_descriptor_update_template, None) + }; + unsafe { device.destroy_pipeline(self.pipeline, None) }; unsafe { device.destroy_pipeline_layout(self.pipeline_layout, None) }; unsafe { device.destroy_render_pass(self.render_pass, None) }; @@ -120,6 +152,10 @@ impl Render { pub fn set_model(&mut self, model: Model) { self.model = Some(model); } + + pub fn set_texture(&mut self, texture: Texture) { + self.texture = Some(texture); + } } @@ -209,7 +245,8 @@ fn init_render_pass(device: &Device, sample_count: vk::SampleCountFlags, #[allow(unsafe_code)] fn init_pipeline(device: &Device, - descriptor_set_layout: &vk::DescriptorSetLayout, + primary_descriptor_set_layout: &vk::DescriptorSetLayout, + push_descriptor_set_layout: &vk::DescriptorSetLayout, extent: &vk::Extent2D, sample_count: vk::SampleCountFlags, render_pass: &vk::RenderPass) -> Result<(vk::PipelineLayout, vk::Pipeline)> @@ -308,7 +345,16 @@ fn init_pipeline(device: &Device, .offset(0) .size(size_of::>() as u32); - let layouts = [*descriptor_set_layout]; + // Indices into this layout array will appear as magic constants in + // several places: the call to cmd_bind_descriptor_sets() in + // scene.rs's generate_scene_commands(); the update template's definition + // in init_descriptor_update_template(), below; and the call to + // cmd_push_descriptor_set_with_template() in texture.rs's make_active(). + // + // When the list of layouts is changed, all those places need to be + // updated. Keep the breadcrumb comments on the other end in sync with this + // one, as well. + let layouts = [*primary_descriptor_set_layout, *push_descriptor_set_layout]; let push_constant_ranges = [vertex_push_constant_range]; let pipeline_layout_info = vk::PipelineLayoutCreateInfo::builder() .set_layouts(&layouts) @@ -345,3 +391,38 @@ fn init_pipeline(device: &Device, Ok((pipeline_layout, pipeline)) } + +#[allow(unsafe_code)] +fn init_descriptor_update_template( + device: &Device, + pipeline_layout: &vk::PipelineLayout) + -> Result +{ + let entry_info = vk::DescriptorUpdateTemplateEntry::builder() + .dst_binding(0) + .dst_array_element(0) + .descriptor_type(vk::DescriptorType::SAMPLED_IMAGE) + .descriptor_count(1) + .offset(0) + .stride(size_of::()); + + let entries = &[entry_info]; + + // Please notice the magic constant set(1). This is a zero-based index + // into the array of descriptor set layouts passed to + // create_pipeline_layout() in init_pipeline(), above. + let template_info = vk::DescriptorUpdateTemplateCreateInfo::builder() + .template_type(vk::DescriptorUpdateTemplateType::PUSH_DESCRIPTORS) + .descriptor_update_entries(entries) + .set(1) + .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) + .pipeline_layout(*pipeline_layout) + .flags(vk::DescriptorUpdateTemplateCreateFlags::empty()); + + let texture_descriptor_update_template = unsafe { + device.create_descriptor_update_template(&template_info, None) + }?; + + Ok(texture_descriptor_update_template) +} + diff --git a/src/graphics/scene.rs b/src/graphics/scene.rs index f73ebf2..9631dba 100644 --- a/src/graphics/scene.rs +++ b/src/graphics/scene.rs @@ -61,8 +61,24 @@ pub fn generate_scene_commands<'a>(image_index: usize, time: f32, render.pipeline) }; + // Please notice the magic constant 0. This is a zero-based index into + // the array of descriptor set layouts passed to create_pipeline_layout() + // in init_pipeline() in render.rs. + unsafe { + device.cmd_bind_descriptor_sets(*command_buffer, + vk::PipelineBindPoint::GRAPHICS, + render.pipeline_layout, + 0, + &[*descriptor_set], + &[]) + }; + + if let Some(texture) = &render.texture { + texture.make_active(device, render, command_buffer); + } + if let Some(model) = &render.model { - model.render(time, device, render, command_buffer, descriptor_set); + model.render(time, device, render, command_buffer); } unsafe { device.cmd_end_render_pass(*command_buffer) }; diff --git a/src/graphics/texture.rs b/src/graphics/texture.rs index b28d540..da40387 100644 --- a/src/graphics/texture.rs +++ b/src/graphics/texture.rs @@ -1,13 +1,18 @@ #![deny(unsafe_code)] use crate::error::*; -use crate::graphics::Permanent; +use crate::graphics::{ Permanent, Render }; use crate::graphics::util::{ stage_in_buffer, allocate_image, init_image_view, begin_transient_commands, end_transient_commands }; +use crate::shader_data::TexturePushBlock; + +use std::ffi::c_void; use vulkanalia::{ Device, Instance }; -use vulkanalia::vk::{ self, HasBuilder, InstanceV1_0, DeviceV1_0 }; +use vulkanalia::vk::{ + self, HasBuilder, InstanceV1_0, DeviceV1_0, DeviceV1_4 +}; #[derive(Debug)] @@ -47,6 +52,32 @@ impl Texture { unsafe { device.free_memory(self.image_memory, None) }; unsafe { device.destroy_image_view(self.image_view, None) }; } + + #[allow(unsafe_code)] + pub fn make_active(&self, device: &Device, render: &Render, + command_buffer: &vk::CommandBuffer) + { + let template = &render.texture_descriptor_update_template; + let layout = &render.pipeline_layout; + + // Constructing this every time may appear slow, but it's all CPU-side + // work, and the real cost we're trying to avoid with push descriptors is + // excessive CPU-GPU synchronziation. + let image_info = vk::DescriptorImageInfo::builder() + .image_view(self.image_view) + .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) + .build(); + let push_block = TexturePushBlock { image_info }; + let push_block_bytes = &raw const push_block as *const c_void; + + // Please notice the magic constant 1. This is a one-based index into + // the array of descriptor set layouts passed to create_pipeline_layout() + // in init_pipeline() in render.rs. + unsafe { + device.cmd_push_descriptor_set_with_template( + *command_buffer, *template, *layout, 1, push_block_bytes) + }; + } } diff --git a/src/main.rs b/src/main.rs index d461a63..4d0748f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ #![deny(unsafe_code)] use crate::error::*; use crate::assets::asset; -use crate::graphics::{ Permanent, ForReinit, Render, Texture }; +use crate::graphics::{ Permanent, ForReinit, Render }; use crate::graphics::scene::generate_scene_commands; use crate::graphics::window_dressing::{ WindowDressing, N_SIMULTANEOUS_FRAMES @@ -36,7 +36,6 @@ struct Surreality { for_reinit: RefCell>, window_dressing: RefCell>, render: RefCell>, - texture: RefCell>, is_minimized: bool, is_reinit_queued: bool, frame_index: usize, @@ -54,7 +53,6 @@ impl Surreality { for_reinit: RefCell::new(None), window_dressing: RefCell::new(None), render: RefCell::new(None), - texture: RefCell::new(None), is_minimized: false, is_reinit_queued: false, frame_index: 0, @@ -70,14 +68,14 @@ impl Surreality { let (permanent, for_reinit, enable_anisotropy, enable_swapchain) = Permanent::new(event_loop)?; - let png = asset("textures/forest_leaves_04_diff.png")?; - let (texture, mip_count) = load_png(png, &permanent)?; - if enable_swapchain.0 { + let png = asset("textures/forest_leaves_04_diff.png")?; + let (texture, mip_count) = load_png(png, &permanent)?; + let window_dressing = WindowDressing::new(&permanent, &for_reinit, enable_anisotropy, mip_count)?; - let mut render = Render::new(&permanent, &for_reinit, &window_dressing, - &texture)?; + let mut render = Render::new(&permanent, &for_reinit, &window_dressing)?; + render.set_texture(texture); let obj = asset("models/teapot.obj")?; let model = load_obj(obj, &permanent)?; @@ -87,7 +85,6 @@ impl Surreality { *self.render.get_mut() = Some(render); } - *self.texture.get_mut() = Some(texture); *self.permanent.get_mut() = Some(permanent); *self.for_reinit.get_mut() = Some(for_reinit); @@ -100,10 +97,9 @@ impl Surreality { && let Some(window_dressing) = self.window_dressing.borrow_mut().as_mut() && let Some(render) = self.render.borrow_mut().as_mut() - && let Some(texture) = self.texture.borrow_mut().as_mut() { window_dressing.reinit(permanent, for_reinit)?; - render.reinit(permanent, for_reinit, &window_dressing, &texture)?; + render.reinit(permanent, for_reinit, &window_dressing)?; } Ok(()) @@ -234,10 +230,6 @@ impl Drop for Surreality { 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); } diff --git a/src/shader_data.rs b/src/shader_data.rs index 892260d..96e6a1b 100644 --- a/src/shader_data.rs +++ b/src/shader_data.rs @@ -27,6 +27,12 @@ pub struct VertexPushBlock { pub model: Transformation, } +#[repr(C)] +#[derive(Clone, Debug)] +pub struct TexturePushBlock { + pub image_info: vk::DescriptorImageInfo, +} + impl Vertex { pub const fn new(position: Vec3, color: Vec3, texture_coordinates: Vec2) -- cgit 1.4.1