diff options
| -rw-r--r-- | shaders/shader.frag | 7 | ||||
| -rw-r--r-- | shaders/shader.vert | 5 | ||||
| -rw-r--r-- | src/graphics/frame.rs | 68 | ||||
| -rw-r--r-- | src/graphics/model.rs | 12 | ||||
| -rw-r--r-- | src/graphics/permanent.rs | 120 | ||||
| -rw-r--r-- | src/graphics/render.rs | 105 | ||||
| -rw-r--r-- | src/graphics/scene.rs | 18 | ||||
| -rw-r--r-- | src/graphics/texture.rs | 35 | ||||
| -rw-r--r-- | src/main.rs | 22 | ||||
| -rw-r--r-- | 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<Vec<Self>> { 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<Self>, 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::<UniformBlock<f32>>() 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::<UniformBlock<f32>>() 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<vk::DescriptorSetLayout> +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<Model>, + pub texture: Option<Texture>, + + // 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<Frame>, } @@ -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<Self> { 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::<VertexPushBlock<f32>>() 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<vk::DescriptorUpdateTemplate> +{ + 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::<TexturePushBlock>()); + + 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<Option<ForReinit>>, window_dressing: RefCell<Option<WindowDressing>>, render: RefCell<Option<Render>>, - texture: RefCell<Option<Texture>>, 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<T: Copy> { pub model: Transformation<T>, } +#[repr(C)] +#[derive(Clone, Debug)] +pub struct TexturePushBlock { + pub image_info: vk::DescriptorImageInfo, +} + impl<T: Copy> Vertex<T> { pub const fn new(position: Vec3<T>, color: Vec3<T>, texture_coordinates: Vec2<T>) |