feat(render): bind materials per mesh batch
This commit is contained in:
@@ -68,6 +68,7 @@ pub fn project_msh_to_static_mesh(
|
||||
draw_ranges.push(VulkanStaticDrawRange {
|
||||
first_index,
|
||||
index_count: u32::from(batch.index_count),
|
||||
material_index: batch.material_index,
|
||||
});
|
||||
}
|
||||
if indices.is_empty() {
|
||||
@@ -181,6 +182,7 @@ mod tests {
|
||||
vec![VulkanStaticDrawRange {
|
||||
first_index: 0,
|
||||
index_count: 3,
|
||||
material_index: 0,
|
||||
}]
|
||||
);
|
||||
assert_eq!(mesh.vertices[1].position, [-0.8, -0.8]);
|
||||
@@ -203,4 +205,37 @@ mod tests {
|
||||
assert_eq!(mesh.vertices[1].uv, [0.0, 2.0]);
|
||||
assert_eq!(mesh.vertices[2].uv, [-1.0, 0.5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retains_each_source_batch_material_selector() {
|
||||
let mut second = batch(3, 3, 0);
|
||||
second.material_index = 7;
|
||||
let mesh = project_msh_to_static_mesh(&model(
|
||||
vec![
|
||||
[-2.0, 0.0, -1.0],
|
||||
[2.0, 0.0, -1.0],
|
||||
[-2.0, 0.0, 3.0],
|
||||
[2.0, 0.0, 3.0],
|
||||
],
|
||||
vec![0, 1, 2, 1, 3, 2],
|
||||
vec![batch(0, 3, 0), second],
|
||||
))
|
||||
.expect("representable MSH");
|
||||
|
||||
assert_eq!(
|
||||
mesh.draw_ranges,
|
||||
vec![
|
||||
VulkanStaticDrawRange {
|
||||
first_index: 0,
|
||||
index_count: 3,
|
||||
material_index: 0,
|
||||
},
|
||||
VulkanStaticDrawRange {
|
||||
first_index: 3,
|
||||
index_count: 3,
|
||||
material_index: 7,
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,9 @@ pub use self::runtime::{
|
||||
pub use self::smoke_types::{
|
||||
VulkanSmokeBootstrapProgress, VulkanSmokeBootstrapSnapshot, VulkanSmokeFrameOutcome,
|
||||
VulkanSmokeRenderer, VulkanSmokeRendererCreateInfo, VulkanSmokeRendererError,
|
||||
VulkanSmokeRendererReport, VulkanSmokeShutdownReport, VulkanStaticDrawRange, VulkanStaticMesh,
|
||||
VulkanStaticTexture, VulkanStaticVertex, VulkanValidationReport,
|
||||
VulkanSmokeRendererReport, VulkanSmokeShutdownReport, VulkanStaticDrawRange,
|
||||
VulkanStaticMaterial, VulkanStaticMesh, VulkanStaticTexture, VulkanStaticVertex,
|
||||
VulkanValidationReport,
|
||||
};
|
||||
#[cfg(test)]
|
||||
use self::surface::extension_name;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use ash::vk;
|
||||
|
||||
use super::smoke_types::resolve_draw_texture_indices;
|
||||
use super::{
|
||||
create_command_pool, create_frame_sync, create_static_mesh_index_buffer,
|
||||
create_static_mesh_vertex_buffer, create_static_texture_image, create_swapchain_resources,
|
||||
@@ -109,11 +110,9 @@ impl VulkanSmokeRenderer {
|
||||
.mesh
|
||||
.validate()
|
||||
.map_err(|context| VulkanSmokeRendererError::InvalidStaticMesh { context })?;
|
||||
if let Some(texture) = &create_info.texture {
|
||||
texture
|
||||
.validate()
|
||||
.map_err(|context| VulkanSmokeRendererError::InvalidStaticTexture { context })?;
|
||||
}
|
||||
let draw_texture_indices =
|
||||
resolve_draw_texture_indices(&create_info.mesh.draw_ranges, &create_info.materials)
|
||||
.map_err(|context| VulkanSmokeRendererError::InvalidStaticMesh { context })?;
|
||||
let bootstrap_progress = create_info.bootstrap_progress.as_ref();
|
||||
let shader_manifest = validate_shader_manifest(&triangle_shader_manifest())
|
||||
.map_err(VulkanSmokeRendererError::ShaderManifest)?;
|
||||
@@ -188,18 +187,31 @@ impl VulkanSmokeRenderer {
|
||||
height: 1,
|
||||
rgba8: vec![255, 255, 255, 255],
|
||||
};
|
||||
let texture_source = create_info.texture.as_ref().unwrap_or(&fallback_texture);
|
||||
let texture =
|
||||
let texture_sources = if create_info.materials.is_empty() {
|
||||
vec![&fallback_texture]
|
||||
} else {
|
||||
create_info
|
||||
.materials
|
||||
.iter()
|
||||
.map(|material| &material.texture)
|
||||
.collect()
|
||||
};
|
||||
let mut textures = Vec::with_capacity(texture_sources.len());
|
||||
for texture_source in texture_sources {
|
||||
match create_static_texture_image(&instance, &device, command_pool, texture_source) {
|
||||
Ok(image) => Some(image),
|
||||
Ok(image) => textures.push(image),
|
||||
Err(error) => {
|
||||
for texture in &textures {
|
||||
destroy_allocated_image(&device, texture);
|
||||
}
|
||||
// SAFETY: These resources belong to this live device and are rolled back before it drops.
|
||||
unsafe { device.device().destroy_command_pool(command_pool, None) };
|
||||
destroy_allocated_buffer(&device, &index_buffer);
|
||||
destroy_allocated_buffer(&device, &vertex_buffer);
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
let mut renderer = Self {
|
||||
instance: Some(instance),
|
||||
validation,
|
||||
@@ -210,8 +222,9 @@ impl VulkanSmokeRenderer {
|
||||
swapchain_resources: None,
|
||||
vertex_buffer: Some(vertex_buffer),
|
||||
index_buffer: Some(index_buffer),
|
||||
texture,
|
||||
textures,
|
||||
draw_ranges: create_info.mesh.draw_ranges.clone(),
|
||||
draw_texture_indices,
|
||||
frame_sync: Vec::new(),
|
||||
images_in_flight: Vec::new(),
|
||||
current_frame: 0,
|
||||
@@ -322,12 +335,14 @@ impl VulkanSmokeRenderer {
|
||||
})
|
||||
}
|
||||
|
||||
fn texture_ref(&self) -> Result<&super::VulkanAllocatedImage, VulkanSmokeRendererError> {
|
||||
self.texture
|
||||
.as_ref()
|
||||
.ok_or(VulkanSmokeRendererError::InvariantViolation {
|
||||
context: "static material texture",
|
||||
fn textures_ref(&self) -> Result<&[super::VulkanAllocatedImage], VulkanSmokeRendererError> {
|
||||
if self.textures.is_empty() {
|
||||
Err(VulkanSmokeRendererError::InvariantViolation {
|
||||
context: "static material textures",
|
||||
})
|
||||
} else {
|
||||
Ok(&self.textures)
|
||||
}
|
||||
}
|
||||
|
||||
fn surface_ref(&self) -> Result<&VulkanSurfaceProbe, VulkanSmokeRendererError> {
|
||||
@@ -544,7 +559,7 @@ impl VulkanSmokeRenderer {
|
||||
self.command_pool,
|
||||
self.vertex_buffer_ref()?,
|
||||
self.index_buffer_ref()?,
|
||||
self.texture_ref()?,
|
||||
self.textures_ref()?,
|
||||
reuse_command_pool,
|
||||
)?,
|
||||
|resources| destroy_swapchain_resources(device, self.command_pool, resources),
|
||||
@@ -621,14 +636,6 @@ impl VulkanSmokeRenderer {
|
||||
vk::PipelineBindPoint::GRAPHICS,
|
||||
resources.pipeline,
|
||||
);
|
||||
device.device().cmd_bind_descriptor_sets(
|
||||
command_buffer,
|
||||
vk::PipelineBindPoint::GRAPHICS,
|
||||
resources.pipeline_layout,
|
||||
0,
|
||||
&[resources.descriptor_set],
|
||||
&[],
|
||||
);
|
||||
let vertex_buffers = [self.vertex_buffer_ref()?.buffer];
|
||||
let offsets = [0_u64];
|
||||
device
|
||||
@@ -640,7 +647,20 @@ impl VulkanSmokeRenderer {
|
||||
0,
|
||||
vk::IndexType::UINT16,
|
||||
);
|
||||
for range in &self.draw_ranges {
|
||||
for (range, &texture_index) in self.draw_ranges.iter().zip(&self.draw_texture_indices) {
|
||||
let descriptor_set = resources.descriptor_sets.get(texture_index).ok_or(
|
||||
VulkanSmokeRendererError::InvariantViolation {
|
||||
context: "static material descriptor set",
|
||||
},
|
||||
)?;
|
||||
device.device().cmd_bind_descriptor_sets(
|
||||
command_buffer,
|
||||
vk::PipelineBindPoint::GRAPHICS,
|
||||
resources.pipeline_layout,
|
||||
0,
|
||||
std::slice::from_ref(descriptor_set),
|
||||
&[],
|
||||
);
|
||||
device.device().cmd_draw_indexed(
|
||||
command_buffer,
|
||||
range.index_count,
|
||||
@@ -689,7 +709,7 @@ impl VulkanSmokeRenderer {
|
||||
fn destroy_device_owned_resources(&mut self) {
|
||||
self.destroy_swapchain_resources();
|
||||
if let Some(device) = self.device.as_ref() {
|
||||
if let Some(texture) = self.texture.take() {
|
||||
for texture in self.textures.drain(..) {
|
||||
destroy_allocated_image(device, &texture);
|
||||
}
|
||||
if let Some(buffer) = self.index_buffer.take() {
|
||||
|
||||
@@ -29,8 +29,12 @@ pub struct VulkanSmokeRendererCreateInfo {
|
||||
/// This initial bridge keeps positions in clip-space. MSH transforms,
|
||||
/// materials, and textures are deliberately higher-level Stage 3 work.
|
||||
pub mesh: VulkanStaticMesh,
|
||||
/// Optional RGBA8 texture uploaded before the first live frame.
|
||||
pub texture: Option<VulkanStaticTexture>,
|
||||
/// Material textures uploaded before the first live frame.
|
||||
///
|
||||
/// An empty list retains the compatibility white fallback. A singleton list
|
||||
/// is a deliberately explicit one-material compatibility mode; otherwise
|
||||
/// every source batch selector must resolve to one entry in this list.
|
||||
pub materials: Vec<VulkanStaticMaterial>,
|
||||
/// Optional shared bootstrap progress tracker for failure evidence.
|
||||
pub bootstrap_progress: Option<Arc<VulkanSmokeBootstrapProgress>>,
|
||||
}
|
||||
@@ -64,6 +68,17 @@ pub struct VulkanStaticDrawRange {
|
||||
pub first_index: u32,
|
||||
/// Number of indices in this draw.
|
||||
pub index_count: u32,
|
||||
/// Original positional `Batch20.material_index` selector.
|
||||
pub material_index: u16,
|
||||
}
|
||||
|
||||
/// One diffuse material texture keyed by an original MSH batch selector.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct VulkanStaticMaterial {
|
||||
/// Positional material selector used by one or more source batches.
|
||||
pub material_index: u16,
|
||||
/// Decoded RGBA8 diffuse texture for this selector.
|
||||
pub texture: VulkanStaticTexture,
|
||||
}
|
||||
|
||||
/// Decoded RGBA8 image accepted by the initial Vulkan texture upload path.
|
||||
@@ -98,6 +113,39 @@ impl VulkanStaticTexture {
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves each source range to its descriptor-set material index.
|
||||
///
|
||||
/// Empty material input selects the renderer's white fallback. A singleton is
|
||||
/// the explicit direct-TEXM compatibility mode. Multiple materials must map
|
||||
/// each `Batch20.material_index` exactly and never duplicate a selector.
|
||||
pub(super) fn resolve_draw_texture_indices(
|
||||
draw_ranges: &[VulkanStaticDrawRange],
|
||||
materials: &[VulkanStaticMaterial],
|
||||
) -> Result<Vec<usize>, &'static str> {
|
||||
for (index, material) in materials.iter().enumerate() {
|
||||
material.texture.validate()?;
|
||||
if materials[..index]
|
||||
.iter()
|
||||
.any(|previous| previous.material_index == material.material_index)
|
||||
{
|
||||
return Err("static material selectors must be unique");
|
||||
}
|
||||
}
|
||||
draw_ranges
|
||||
.iter()
|
||||
.map(|range| {
|
||||
if materials.len() <= 1 {
|
||||
Ok(0)
|
||||
} else {
|
||||
materials
|
||||
.iter()
|
||||
.position(|material| material.material_index == range.material_index)
|
||||
.ok_or("static mesh draw range has no material texture")
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl VulkanStaticMesh {
|
||||
/// Returns the compatibility triangle used by the native Stage 0 smoke app.
|
||||
#[must_use]
|
||||
@@ -124,6 +172,7 @@ impl VulkanStaticMesh {
|
||||
draw_ranges: vec![VulkanStaticDrawRange {
|
||||
first_index: 0,
|
||||
index_count: 3,
|
||||
material_index: 0,
|
||||
}],
|
||||
}
|
||||
}
|
||||
@@ -189,6 +238,7 @@ mod static_mesh_tests {
|
||||
draw_ranges: vec![VulkanStaticDrawRange {
|
||||
first_index: 0,
|
||||
index_count: 2,
|
||||
material_index: 0,
|
||||
}],
|
||||
};
|
||||
let out_of_range_index = VulkanStaticMesh {
|
||||
@@ -207,6 +257,42 @@ mod static_mesh_tests {
|
||||
Err("static mesh index exceeds vertex count")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn material_descriptors_follow_source_batch_selectors() {
|
||||
let ranges = [
|
||||
VulkanStaticDrawRange {
|
||||
first_index: 0,
|
||||
index_count: 3,
|
||||
material_index: 7,
|
||||
},
|
||||
VulkanStaticDrawRange {
|
||||
first_index: 3,
|
||||
index_count: 3,
|
||||
material_index: 2,
|
||||
},
|
||||
];
|
||||
let texture = || VulkanStaticTexture {
|
||||
width: 1,
|
||||
height: 1,
|
||||
rgba8: vec![255; 4],
|
||||
};
|
||||
let materials = [
|
||||
VulkanStaticMaterial {
|
||||
material_index: 2,
|
||||
texture: texture(),
|
||||
},
|
||||
VulkanStaticMaterial {
|
||||
material_index: 7,
|
||||
texture: texture(),
|
||||
},
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
resolve_draw_texture_indices(&ranges, &materials),
|
||||
Ok(vec![1, 0])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Shared bootstrap progress used to report partial renderer startup evidence.
|
||||
@@ -421,8 +507,9 @@ pub struct VulkanSmokeRenderer {
|
||||
pub(super) swapchain_resources: Option<VulkanSwapchainResources>,
|
||||
pub(super) vertex_buffer: Option<VulkanAllocatedBuffer>,
|
||||
pub(super) index_buffer: Option<VulkanAllocatedBuffer>,
|
||||
pub(super) texture: Option<VulkanAllocatedImage>,
|
||||
pub(super) textures: Vec<VulkanAllocatedImage>,
|
||||
pub(super) draw_ranges: Vec<VulkanStaticDrawRange>,
|
||||
pub(super) draw_texture_indices: Vec<usize>,
|
||||
pub(super) frame_sync: Vec<VulkanFrameSync>,
|
||||
pub(super) images_in_flight: Vec<vk::Fence>,
|
||||
pub(super) current_frame: usize,
|
||||
|
||||
@@ -14,7 +14,7 @@ pub(super) struct VulkanSwapchainResources {
|
||||
pub(super) pipeline_layout: vk::PipelineLayout,
|
||||
pub(super) descriptor_set_layout: vk::DescriptorSetLayout,
|
||||
pub(super) descriptor_pool: vk::DescriptorPool,
|
||||
pub(super) descriptor_set: vk::DescriptorSet,
|
||||
pub(super) descriptor_sets: Vec<vk::DescriptorSet>,
|
||||
pub(super) sampler: vk::Sampler,
|
||||
pub(super) pipeline: vk::Pipeline,
|
||||
pub(super) framebuffers: Vec<vk::Framebuffer>,
|
||||
@@ -39,7 +39,7 @@ pub(super) fn create_swapchain_resources(
|
||||
command_pool: vk::CommandPool,
|
||||
vertex_buffer: &VulkanAllocatedBuffer,
|
||||
index_buffer: &VulkanAllocatedBuffer,
|
||||
texture: &VulkanAllocatedImage,
|
||||
textures: &[VulkanAllocatedImage],
|
||||
reuse_command_pool: bool,
|
||||
) -> Result<VulkanSwapchainResources, VulkanSmokeRendererError> {
|
||||
// SAFETY: The swapchain is live and owned by this renderer for the duration of the query.
|
||||
@@ -73,13 +73,13 @@ pub(super) fn create_swapchain_resources(
|
||||
pipeline,
|
||||
descriptor_set_layout,
|
||||
descriptor_pool,
|
||||
descriptor_set,
|
||||
descriptor_sets,
|
||||
sampler,
|
||||
) = match create_swapchain_pipeline_bundle(
|
||||
device,
|
||||
swapchain.report.plan.format.format,
|
||||
swapchain.report.plan.extent,
|
||||
texture,
|
||||
textures,
|
||||
) {
|
||||
Ok(bundle) => bundle,
|
||||
Err(error) => {
|
||||
@@ -126,7 +126,7 @@ pub(super) fn create_swapchain_resources(
|
||||
pipeline_layout,
|
||||
descriptor_set_layout,
|
||||
descriptor_pool,
|
||||
descriptor_set,
|
||||
descriptor_sets,
|
||||
sampler,
|
||||
pipeline,
|
||||
framebuffers: partial.framebuffers,
|
||||
@@ -151,7 +151,7 @@ fn create_swapchain_pipeline_bundle(
|
||||
device: &VulkanLogicalDeviceProbe,
|
||||
format: i32,
|
||||
extent: (u32, u32),
|
||||
texture: &VulkanAllocatedImage,
|
||||
textures: &[VulkanAllocatedImage],
|
||||
) -> Result<
|
||||
(
|
||||
vk::RenderPass,
|
||||
@@ -159,14 +159,14 @@ fn create_swapchain_pipeline_bundle(
|
||||
vk::Pipeline,
|
||||
vk::DescriptorSetLayout,
|
||||
vk::DescriptorPool,
|
||||
vk::DescriptorSet,
|
||||
Vec<vk::DescriptorSet>,
|
||||
vk::Sampler,
|
||||
),
|
||||
VulkanSmokeRendererError,
|
||||
> {
|
||||
let render_pass = create_render_pass(device, format)?;
|
||||
let (descriptor_set_layout, descriptor_pool, descriptor_set, sampler) =
|
||||
create_texture_descriptor_bundle(device, texture).inspect_err(|_| {
|
||||
let (descriptor_set_layout, descriptor_pool, descriptor_sets, sampler) =
|
||||
create_texture_descriptor_bundle(device, textures).inspect_err(|_| {
|
||||
// SAFETY: The render pass was created above on this live logical device and is destroyed on setup failure.
|
||||
unsafe { device.device().destroy_render_pass(render_pass, None) };
|
||||
})?;
|
||||
@@ -207,7 +207,7 @@ fn create_swapchain_pipeline_bundle(
|
||||
pipeline,
|
||||
descriptor_set_layout,
|
||||
descriptor_pool,
|
||||
descriptor_set,
|
||||
descriptor_sets,
|
||||
sampler,
|
||||
))
|
||||
}
|
||||
@@ -334,12 +334,12 @@ fn create_pipeline_layout(
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn create_texture_descriptor_bundle(
|
||||
device: &VulkanLogicalDeviceProbe,
|
||||
texture: &VulkanAllocatedImage,
|
||||
textures: &[VulkanAllocatedImage],
|
||||
) -> Result<
|
||||
(
|
||||
vk::DescriptorSetLayout,
|
||||
vk::DescriptorPool,
|
||||
vk::DescriptorSet,
|
||||
Vec<vk::DescriptorSet>,
|
||||
vk::Sampler,
|
||||
),
|
||||
VulkanSmokeRendererError,
|
||||
@@ -361,13 +361,23 @@ fn create_texture_descriptor_bundle(
|
||||
context: "vkCreateDescriptorSetLayout",
|
||||
result,
|
||||
})?;
|
||||
let texture_count = u32::try_from(textures.len()).map_err(|_| {
|
||||
VulkanSmokeRendererError::InvariantViolation {
|
||||
context: "static material texture count exceeds Vulkan descriptor limit",
|
||||
}
|
||||
})?;
|
||||
if texture_count == 0 {
|
||||
return Err(VulkanSmokeRendererError::InvariantViolation {
|
||||
context: "static material texture list is empty",
|
||||
});
|
||||
}
|
||||
let pool_size = vk::DescriptorPoolSize::default()
|
||||
.ty(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||
.descriptor_count(1);
|
||||
.descriptor_count(texture_count);
|
||||
let pool_sizes = [pool_size];
|
||||
let pool_info = vk::DescriptorPoolCreateInfo::default()
|
||||
.pool_sizes(&pool_sizes)
|
||||
.max_sets(1);
|
||||
.max_sets(texture_count);
|
||||
let pool =
|
||||
// SAFETY: The pool description is stack-owned and reserves exactly one descriptor.
|
||||
unsafe { device.device().create_descriptor_pool(&pool_info, None) }.map_err(|result| {
|
||||
@@ -378,12 +388,12 @@ fn create_texture_descriptor_bundle(
|
||||
result,
|
||||
}
|
||||
})?;
|
||||
let layouts = [layout];
|
||||
let layouts = vec![layout; textures.len()];
|
||||
let allocate_info = vk::DescriptorSetAllocateInfo::default()
|
||||
.descriptor_pool(pool)
|
||||
.set_layouts(&layouts);
|
||||
// SAFETY: The pool and layout are live on this logical device.
|
||||
let descriptor_set = unsafe { device.device().allocate_descriptor_sets(&allocate_info) }
|
||||
let descriptor_sets = unsafe { device.device().allocate_descriptor_sets(&allocate_info) }
|
||||
.map_err(|result| {
|
||||
// SAFETY: Resources were created above on this device and are rolled back once.
|
||||
unsafe {
|
||||
@@ -394,7 +404,7 @@ fn create_texture_descriptor_bundle(
|
||||
context: "vkAllocateDescriptorSets",
|
||||
result,
|
||||
}
|
||||
})?[0];
|
||||
})?;
|
||||
let sampler_info = vk::SamplerCreateInfo::default()
|
||||
.mag_filter(vk::Filter::LINEAR)
|
||||
.min_filter(vk::Filter::LINEAR)
|
||||
@@ -416,19 +426,30 @@ fn create_texture_descriptor_bundle(
|
||||
result,
|
||||
}
|
||||
})?;
|
||||
let image_info = vk::DescriptorImageInfo::default()
|
||||
.sampler(sampler)
|
||||
.image_view(texture.view)
|
||||
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
|
||||
let image_infos = [image_info];
|
||||
let write = vk::WriteDescriptorSet::default()
|
||||
.dst_set(descriptor_set)
|
||||
.dst_binding(0)
|
||||
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||
.image_info(&image_infos);
|
||||
// SAFETY: The descriptor set, sampler and image view are live and the texture upload completed its shader-read transition.
|
||||
unsafe { device.device().update_descriptor_sets(&[write], &[]) };
|
||||
Ok((layout, pool, descriptor_set, sampler))
|
||||
let image_infos = textures
|
||||
.iter()
|
||||
.map(|texture| {
|
||||
vk::DescriptorImageInfo::default()
|
||||
.sampler(sampler)
|
||||
.image_view(texture.view)
|
||||
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let writes = descriptor_sets
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(image_infos.iter())
|
||||
.map(|(descriptor_set, image_info)| {
|
||||
vk::WriteDescriptorSet::default()
|
||||
.dst_set(descriptor_set)
|
||||
.dst_binding(0)
|
||||
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||
.image_info(std::slice::from_ref(image_info))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// SAFETY: Descriptor sets, sampler and image views are live; every texture upload completed its shader-read transition.
|
||||
unsafe { device.device().update_descriptor_sets(&writes, &[]) };
|
||||
Ok((layout, pool, descriptor_sets, sampler))
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
|
||||
Reference in New Issue
Block a user