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 {
|
draw_ranges.push(VulkanStaticDrawRange {
|
||||||
first_index,
|
first_index,
|
||||||
index_count: u32::from(batch.index_count),
|
index_count: u32::from(batch.index_count),
|
||||||
|
material_index: batch.material_index,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if indices.is_empty() {
|
if indices.is_empty() {
|
||||||
@@ -181,6 +182,7 @@ mod tests {
|
|||||||
vec![VulkanStaticDrawRange {
|
vec![VulkanStaticDrawRange {
|
||||||
first_index: 0,
|
first_index: 0,
|
||||||
index_count: 3,
|
index_count: 3,
|
||||||
|
material_index: 0,
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
assert_eq!(mesh.vertices[1].position, [-0.8, -0.8]);
|
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[1].uv, [0.0, 2.0]);
|
||||||
assert_eq!(mesh.vertices[2].uv, [-1.0, 0.5]);
|
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::{
|
pub use self::smoke_types::{
|
||||||
VulkanSmokeBootstrapProgress, VulkanSmokeBootstrapSnapshot, VulkanSmokeFrameOutcome,
|
VulkanSmokeBootstrapProgress, VulkanSmokeBootstrapSnapshot, VulkanSmokeFrameOutcome,
|
||||||
VulkanSmokeRenderer, VulkanSmokeRendererCreateInfo, VulkanSmokeRendererError,
|
VulkanSmokeRenderer, VulkanSmokeRendererCreateInfo, VulkanSmokeRendererError,
|
||||||
VulkanSmokeRendererReport, VulkanSmokeShutdownReport, VulkanStaticDrawRange, VulkanStaticMesh,
|
VulkanSmokeRendererReport, VulkanSmokeShutdownReport, VulkanStaticDrawRange,
|
||||||
VulkanStaticTexture, VulkanStaticVertex, VulkanValidationReport,
|
VulkanStaticMaterial, VulkanStaticMesh, VulkanStaticTexture, VulkanStaticVertex,
|
||||||
|
VulkanValidationReport,
|
||||||
};
|
};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use self::surface::extension_name;
|
use self::surface::extension_name;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use ash::vk;
|
use ash::vk;
|
||||||
|
|
||||||
|
use super::smoke_types::resolve_draw_texture_indices;
|
||||||
use super::{
|
use super::{
|
||||||
create_command_pool, create_frame_sync, create_static_mesh_index_buffer,
|
create_command_pool, create_frame_sync, create_static_mesh_index_buffer,
|
||||||
create_static_mesh_vertex_buffer, create_static_texture_image, create_swapchain_resources,
|
create_static_mesh_vertex_buffer, create_static_texture_image, create_swapchain_resources,
|
||||||
@@ -109,11 +110,9 @@ impl VulkanSmokeRenderer {
|
|||||||
.mesh
|
.mesh
|
||||||
.validate()
|
.validate()
|
||||||
.map_err(|context| VulkanSmokeRendererError::InvalidStaticMesh { context })?;
|
.map_err(|context| VulkanSmokeRendererError::InvalidStaticMesh { context })?;
|
||||||
if let Some(texture) = &create_info.texture {
|
let draw_texture_indices =
|
||||||
texture
|
resolve_draw_texture_indices(&create_info.mesh.draw_ranges, &create_info.materials)
|
||||||
.validate()
|
.map_err(|context| VulkanSmokeRendererError::InvalidStaticMesh { context })?;
|
||||||
.map_err(|context| VulkanSmokeRendererError::InvalidStaticTexture { context })?;
|
|
||||||
}
|
|
||||||
let bootstrap_progress = create_info.bootstrap_progress.as_ref();
|
let bootstrap_progress = create_info.bootstrap_progress.as_ref();
|
||||||
let shader_manifest = validate_shader_manifest(&triangle_shader_manifest())
|
let shader_manifest = validate_shader_manifest(&triangle_shader_manifest())
|
||||||
.map_err(VulkanSmokeRendererError::ShaderManifest)?;
|
.map_err(VulkanSmokeRendererError::ShaderManifest)?;
|
||||||
@@ -188,18 +187,31 @@ impl VulkanSmokeRenderer {
|
|||||||
height: 1,
|
height: 1,
|
||||||
rgba8: vec![255, 255, 255, 255],
|
rgba8: vec![255, 255, 255, 255],
|
||||||
};
|
};
|
||||||
let texture_source = create_info.texture.as_ref().unwrap_or(&fallback_texture);
|
let texture_sources = if create_info.materials.is_empty() {
|
||||||
let texture =
|
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) {
|
match create_static_texture_image(&instance, &device, command_pool, texture_source) {
|
||||||
Ok(image) => Some(image),
|
Ok(image) => textures.push(image),
|
||||||
Err(error) => {
|
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.
|
// SAFETY: These resources belong to this live device and are rolled back before it drops.
|
||||||
unsafe { device.device().destroy_command_pool(command_pool, None) };
|
unsafe { device.device().destroy_command_pool(command_pool, None) };
|
||||||
destroy_allocated_buffer(&device, &index_buffer);
|
destroy_allocated_buffer(&device, &index_buffer);
|
||||||
destroy_allocated_buffer(&device, &vertex_buffer);
|
destroy_allocated_buffer(&device, &vertex_buffer);
|
||||||
return Err(error);
|
return Err(error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
}
|
||||||
let mut renderer = Self {
|
let mut renderer = Self {
|
||||||
instance: Some(instance),
|
instance: Some(instance),
|
||||||
validation,
|
validation,
|
||||||
@@ -210,8 +222,9 @@ impl VulkanSmokeRenderer {
|
|||||||
swapchain_resources: None,
|
swapchain_resources: None,
|
||||||
vertex_buffer: Some(vertex_buffer),
|
vertex_buffer: Some(vertex_buffer),
|
||||||
index_buffer: Some(index_buffer),
|
index_buffer: Some(index_buffer),
|
||||||
texture,
|
textures,
|
||||||
draw_ranges: create_info.mesh.draw_ranges.clone(),
|
draw_ranges: create_info.mesh.draw_ranges.clone(),
|
||||||
|
draw_texture_indices,
|
||||||
frame_sync: Vec::new(),
|
frame_sync: Vec::new(),
|
||||||
images_in_flight: Vec::new(),
|
images_in_flight: Vec::new(),
|
||||||
current_frame: 0,
|
current_frame: 0,
|
||||||
@@ -322,12 +335,14 @@ impl VulkanSmokeRenderer {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn texture_ref(&self) -> Result<&super::VulkanAllocatedImage, VulkanSmokeRendererError> {
|
fn textures_ref(&self) -> Result<&[super::VulkanAllocatedImage], VulkanSmokeRendererError> {
|
||||||
self.texture
|
if self.textures.is_empty() {
|
||||||
.as_ref()
|
Err(VulkanSmokeRendererError::InvariantViolation {
|
||||||
.ok_or(VulkanSmokeRendererError::InvariantViolation {
|
context: "static material textures",
|
||||||
context: "static material texture",
|
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
Ok(&self.textures)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn surface_ref(&self) -> Result<&VulkanSurfaceProbe, VulkanSmokeRendererError> {
|
fn surface_ref(&self) -> Result<&VulkanSurfaceProbe, VulkanSmokeRendererError> {
|
||||||
@@ -544,7 +559,7 @@ impl VulkanSmokeRenderer {
|
|||||||
self.command_pool,
|
self.command_pool,
|
||||||
self.vertex_buffer_ref()?,
|
self.vertex_buffer_ref()?,
|
||||||
self.index_buffer_ref()?,
|
self.index_buffer_ref()?,
|
||||||
self.texture_ref()?,
|
self.textures_ref()?,
|
||||||
reuse_command_pool,
|
reuse_command_pool,
|
||||||
)?,
|
)?,
|
||||||
|resources| destroy_swapchain_resources(device, self.command_pool, resources),
|
|resources| destroy_swapchain_resources(device, self.command_pool, resources),
|
||||||
@@ -621,14 +636,6 @@ impl VulkanSmokeRenderer {
|
|||||||
vk::PipelineBindPoint::GRAPHICS,
|
vk::PipelineBindPoint::GRAPHICS,
|
||||||
resources.pipeline,
|
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 vertex_buffers = [self.vertex_buffer_ref()?.buffer];
|
||||||
let offsets = [0_u64];
|
let offsets = [0_u64];
|
||||||
device
|
device
|
||||||
@@ -640,7 +647,20 @@ impl VulkanSmokeRenderer {
|
|||||||
0,
|
0,
|
||||||
vk::IndexType::UINT16,
|
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(
|
device.device().cmd_draw_indexed(
|
||||||
command_buffer,
|
command_buffer,
|
||||||
range.index_count,
|
range.index_count,
|
||||||
@@ -689,7 +709,7 @@ impl VulkanSmokeRenderer {
|
|||||||
fn destroy_device_owned_resources(&mut self) {
|
fn destroy_device_owned_resources(&mut self) {
|
||||||
self.destroy_swapchain_resources();
|
self.destroy_swapchain_resources();
|
||||||
if let Some(device) = self.device.as_ref() {
|
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);
|
destroy_allocated_image(device, &texture);
|
||||||
}
|
}
|
||||||
if let Some(buffer) = self.index_buffer.take() {
|
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,
|
/// This initial bridge keeps positions in clip-space. MSH transforms,
|
||||||
/// materials, and textures are deliberately higher-level Stage 3 work.
|
/// materials, and textures are deliberately higher-level Stage 3 work.
|
||||||
pub mesh: VulkanStaticMesh,
|
pub mesh: VulkanStaticMesh,
|
||||||
/// Optional RGBA8 texture uploaded before the first live frame.
|
/// Material textures uploaded before the first live frame.
|
||||||
pub texture: Option<VulkanStaticTexture>,
|
///
|
||||||
|
/// 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.
|
/// Optional shared bootstrap progress tracker for failure evidence.
|
||||||
pub bootstrap_progress: Option<Arc<VulkanSmokeBootstrapProgress>>,
|
pub bootstrap_progress: Option<Arc<VulkanSmokeBootstrapProgress>>,
|
||||||
}
|
}
|
||||||
@@ -64,6 +68,17 @@ pub struct VulkanStaticDrawRange {
|
|||||||
pub first_index: u32,
|
pub first_index: u32,
|
||||||
/// Number of indices in this draw.
|
/// Number of indices in this draw.
|
||||||
pub index_count: u32,
|
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.
|
/// 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 {
|
impl VulkanStaticMesh {
|
||||||
/// Returns the compatibility triangle used by the native Stage 0 smoke app.
|
/// Returns the compatibility triangle used by the native Stage 0 smoke app.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@@ -124,6 +172,7 @@ impl VulkanStaticMesh {
|
|||||||
draw_ranges: vec![VulkanStaticDrawRange {
|
draw_ranges: vec![VulkanStaticDrawRange {
|
||||||
first_index: 0,
|
first_index: 0,
|
||||||
index_count: 3,
|
index_count: 3,
|
||||||
|
material_index: 0,
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,6 +238,7 @@ mod static_mesh_tests {
|
|||||||
draw_ranges: vec![VulkanStaticDrawRange {
|
draw_ranges: vec![VulkanStaticDrawRange {
|
||||||
first_index: 0,
|
first_index: 0,
|
||||||
index_count: 2,
|
index_count: 2,
|
||||||
|
material_index: 0,
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
let out_of_range_index = VulkanStaticMesh {
|
let out_of_range_index = VulkanStaticMesh {
|
||||||
@@ -207,6 +257,42 @@ mod static_mesh_tests {
|
|||||||
Err("static mesh index exceeds vertex count")
|
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.
|
/// 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) swapchain_resources: Option<VulkanSwapchainResources>,
|
||||||
pub(super) vertex_buffer: Option<VulkanAllocatedBuffer>,
|
pub(super) vertex_buffer: Option<VulkanAllocatedBuffer>,
|
||||||
pub(super) index_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_ranges: Vec<VulkanStaticDrawRange>,
|
||||||
|
pub(super) draw_texture_indices: Vec<usize>,
|
||||||
pub(super) frame_sync: Vec<VulkanFrameSync>,
|
pub(super) frame_sync: Vec<VulkanFrameSync>,
|
||||||
pub(super) images_in_flight: Vec<vk::Fence>,
|
pub(super) images_in_flight: Vec<vk::Fence>,
|
||||||
pub(super) current_frame: usize,
|
pub(super) current_frame: usize,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ pub(super) struct VulkanSwapchainResources {
|
|||||||
pub(super) pipeline_layout: vk::PipelineLayout,
|
pub(super) pipeline_layout: vk::PipelineLayout,
|
||||||
pub(super) descriptor_set_layout: vk::DescriptorSetLayout,
|
pub(super) descriptor_set_layout: vk::DescriptorSetLayout,
|
||||||
pub(super) descriptor_pool: vk::DescriptorPool,
|
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) sampler: vk::Sampler,
|
||||||
pub(super) pipeline: vk::Pipeline,
|
pub(super) pipeline: vk::Pipeline,
|
||||||
pub(super) framebuffers: Vec<vk::Framebuffer>,
|
pub(super) framebuffers: Vec<vk::Framebuffer>,
|
||||||
@@ -39,7 +39,7 @@ pub(super) fn create_swapchain_resources(
|
|||||||
command_pool: vk::CommandPool,
|
command_pool: vk::CommandPool,
|
||||||
vertex_buffer: &VulkanAllocatedBuffer,
|
vertex_buffer: &VulkanAllocatedBuffer,
|
||||||
index_buffer: &VulkanAllocatedBuffer,
|
index_buffer: &VulkanAllocatedBuffer,
|
||||||
texture: &VulkanAllocatedImage,
|
textures: &[VulkanAllocatedImage],
|
||||||
reuse_command_pool: bool,
|
reuse_command_pool: bool,
|
||||||
) -> Result<VulkanSwapchainResources, VulkanSmokeRendererError> {
|
) -> Result<VulkanSwapchainResources, VulkanSmokeRendererError> {
|
||||||
// SAFETY: The swapchain is live and owned by this renderer for the duration of the query.
|
// 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,
|
pipeline,
|
||||||
descriptor_set_layout,
|
descriptor_set_layout,
|
||||||
descriptor_pool,
|
descriptor_pool,
|
||||||
descriptor_set,
|
descriptor_sets,
|
||||||
sampler,
|
sampler,
|
||||||
) = match create_swapchain_pipeline_bundle(
|
) = match create_swapchain_pipeline_bundle(
|
||||||
device,
|
device,
|
||||||
swapchain.report.plan.format.format,
|
swapchain.report.plan.format.format,
|
||||||
swapchain.report.plan.extent,
|
swapchain.report.plan.extent,
|
||||||
texture,
|
textures,
|
||||||
) {
|
) {
|
||||||
Ok(bundle) => bundle,
|
Ok(bundle) => bundle,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
@@ -126,7 +126,7 @@ pub(super) fn create_swapchain_resources(
|
|||||||
pipeline_layout,
|
pipeline_layout,
|
||||||
descriptor_set_layout,
|
descriptor_set_layout,
|
||||||
descriptor_pool,
|
descriptor_pool,
|
||||||
descriptor_set,
|
descriptor_sets,
|
||||||
sampler,
|
sampler,
|
||||||
pipeline,
|
pipeline,
|
||||||
framebuffers: partial.framebuffers,
|
framebuffers: partial.framebuffers,
|
||||||
@@ -151,7 +151,7 @@ fn create_swapchain_pipeline_bundle(
|
|||||||
device: &VulkanLogicalDeviceProbe,
|
device: &VulkanLogicalDeviceProbe,
|
||||||
format: i32,
|
format: i32,
|
||||||
extent: (u32, u32),
|
extent: (u32, u32),
|
||||||
texture: &VulkanAllocatedImage,
|
textures: &[VulkanAllocatedImage],
|
||||||
) -> Result<
|
) -> Result<
|
||||||
(
|
(
|
||||||
vk::RenderPass,
|
vk::RenderPass,
|
||||||
@@ -159,14 +159,14 @@ fn create_swapchain_pipeline_bundle(
|
|||||||
vk::Pipeline,
|
vk::Pipeline,
|
||||||
vk::DescriptorSetLayout,
|
vk::DescriptorSetLayout,
|
||||||
vk::DescriptorPool,
|
vk::DescriptorPool,
|
||||||
vk::DescriptorSet,
|
Vec<vk::DescriptorSet>,
|
||||||
vk::Sampler,
|
vk::Sampler,
|
||||||
),
|
),
|
||||||
VulkanSmokeRendererError,
|
VulkanSmokeRendererError,
|
||||||
> {
|
> {
|
||||||
let render_pass = create_render_pass(device, format)?;
|
let render_pass = create_render_pass(device, format)?;
|
||||||
let (descriptor_set_layout, descriptor_pool, descriptor_set, sampler) =
|
let (descriptor_set_layout, descriptor_pool, descriptor_sets, sampler) =
|
||||||
create_texture_descriptor_bundle(device, texture).inspect_err(|_| {
|
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.
|
// 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) };
|
unsafe { device.device().destroy_render_pass(render_pass, None) };
|
||||||
})?;
|
})?;
|
||||||
@@ -207,7 +207,7 @@ fn create_swapchain_pipeline_bundle(
|
|||||||
pipeline,
|
pipeline,
|
||||||
descriptor_set_layout,
|
descriptor_set_layout,
|
||||||
descriptor_pool,
|
descriptor_pool,
|
||||||
descriptor_set,
|
descriptor_sets,
|
||||||
sampler,
|
sampler,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -334,12 +334,12 @@ fn create_pipeline_layout(
|
|||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn create_texture_descriptor_bundle(
|
fn create_texture_descriptor_bundle(
|
||||||
device: &VulkanLogicalDeviceProbe,
|
device: &VulkanLogicalDeviceProbe,
|
||||||
texture: &VulkanAllocatedImage,
|
textures: &[VulkanAllocatedImage],
|
||||||
) -> Result<
|
) -> Result<
|
||||||
(
|
(
|
||||||
vk::DescriptorSetLayout,
|
vk::DescriptorSetLayout,
|
||||||
vk::DescriptorPool,
|
vk::DescriptorPool,
|
||||||
vk::DescriptorSet,
|
Vec<vk::DescriptorSet>,
|
||||||
vk::Sampler,
|
vk::Sampler,
|
||||||
),
|
),
|
||||||
VulkanSmokeRendererError,
|
VulkanSmokeRendererError,
|
||||||
@@ -361,13 +361,23 @@ fn create_texture_descriptor_bundle(
|
|||||||
context: "vkCreateDescriptorSetLayout",
|
context: "vkCreateDescriptorSetLayout",
|
||||||
result,
|
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()
|
let pool_size = vk::DescriptorPoolSize::default()
|
||||||
.ty(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
.ty(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
||||||
.descriptor_count(1);
|
.descriptor_count(texture_count);
|
||||||
let pool_sizes = [pool_size];
|
let pool_sizes = [pool_size];
|
||||||
let pool_info = vk::DescriptorPoolCreateInfo::default()
|
let pool_info = vk::DescriptorPoolCreateInfo::default()
|
||||||
.pool_sizes(&pool_sizes)
|
.pool_sizes(&pool_sizes)
|
||||||
.max_sets(1);
|
.max_sets(texture_count);
|
||||||
let pool =
|
let pool =
|
||||||
// SAFETY: The pool description is stack-owned and reserves exactly one descriptor.
|
// SAFETY: The pool description is stack-owned and reserves exactly one descriptor.
|
||||||
unsafe { device.device().create_descriptor_pool(&pool_info, None) }.map_err(|result| {
|
unsafe { device.device().create_descriptor_pool(&pool_info, None) }.map_err(|result| {
|
||||||
@@ -378,12 +388,12 @@ fn create_texture_descriptor_bundle(
|
|||||||
result,
|
result,
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
let layouts = [layout];
|
let layouts = vec![layout; textures.len()];
|
||||||
let allocate_info = vk::DescriptorSetAllocateInfo::default()
|
let allocate_info = vk::DescriptorSetAllocateInfo::default()
|
||||||
.descriptor_pool(pool)
|
.descriptor_pool(pool)
|
||||||
.set_layouts(&layouts);
|
.set_layouts(&layouts);
|
||||||
// SAFETY: The pool and layout are live on this logical device.
|
// 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| {
|
.map_err(|result| {
|
||||||
// SAFETY: Resources were created above on this device and are rolled back once.
|
// SAFETY: Resources were created above on this device and are rolled back once.
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -394,7 +404,7 @@ fn create_texture_descriptor_bundle(
|
|||||||
context: "vkAllocateDescriptorSets",
|
context: "vkAllocateDescriptorSets",
|
||||||
result,
|
result,
|
||||||
}
|
}
|
||||||
})?[0];
|
})?;
|
||||||
let sampler_info = vk::SamplerCreateInfo::default()
|
let sampler_info = vk::SamplerCreateInfo::default()
|
||||||
.mag_filter(vk::Filter::LINEAR)
|
.mag_filter(vk::Filter::LINEAR)
|
||||||
.min_filter(vk::Filter::LINEAR)
|
.min_filter(vk::Filter::LINEAR)
|
||||||
@@ -416,19 +426,30 @@ fn create_texture_descriptor_bundle(
|
|||||||
result,
|
result,
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
let image_info = vk::DescriptorImageInfo::default()
|
let image_infos = textures
|
||||||
.sampler(sampler)
|
.iter()
|
||||||
.image_view(texture.view)
|
.map(|texture| {
|
||||||
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
|
vk::DescriptorImageInfo::default()
|
||||||
let image_infos = [image_info];
|
.sampler(sampler)
|
||||||
let write = vk::WriteDescriptorSet::default()
|
.image_view(texture.view)
|
||||||
.dst_set(descriptor_set)
|
.image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL)
|
||||||
.dst_binding(0)
|
})
|
||||||
.descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER)
|
.collect::<Vec<_>>();
|
||||||
.image_info(&image_infos);
|
let writes = descriptor_sets
|
||||||
// SAFETY: The descriptor set, sampler and image view are live and the texture upload completed its shader-read transition.
|
.iter()
|
||||||
unsafe { device.device().update_descriptor_sets(&[write], &[]) };
|
.copied()
|
||||||
Ok((layout, pool, descriptor_set, sampler))
|
.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)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ use fparkan_platform_winit::{window_native_handles, WinitWindowPlan};
|
|||||||
use fparkan_render_vulkan::{
|
use fparkan_render_vulkan::{
|
||||||
project_msh_to_static_mesh, VulkanSmokeBootstrapProgress, VulkanSmokeFrameOutcome,
|
project_msh_to_static_mesh, VulkanSmokeBootstrapProgress, VulkanSmokeFrameOutcome,
|
||||||
VulkanSmokeRenderer, VulkanSmokeRendererCreateInfo, VulkanSmokeRendererReport,
|
VulkanSmokeRenderer, VulkanSmokeRendererCreateInfo, VulkanSmokeRendererReport,
|
||||||
VulkanSmokeShutdownReport, VulkanStaticMesh, VulkanStaticTexture, VulkanValidationReport,
|
VulkanSmokeShutdownReport, VulkanStaticMaterial, VulkanStaticMesh, VulkanStaticTexture,
|
||||||
|
VulkanValidationReport,
|
||||||
};
|
};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -56,7 +57,7 @@ fn main() {
|
|||||||
fn run(args: &[String]) -> Result<String, String> {
|
fn run(args: &[String]) -> Result<String, String> {
|
||||||
let options = SmokeOptions::parse(args)?;
|
let options = SmokeOptions::parse(args)?;
|
||||||
let mesh = options.load_mesh()?;
|
let mesh = options.load_mesh()?;
|
||||||
let texture = options.load_texture()?;
|
let materials = options.load_materials(&mesh)?;
|
||||||
remove_stale_output(&options)?;
|
remove_stale_output(&options)?;
|
||||||
let event_loop = EventLoop::new().map_err(|err| format!("winit event loop: {err}"))?;
|
let event_loop = EventLoop::new().map_err(|err| format!("winit event loop: {err}"))?;
|
||||||
event_loop.set_control_flow(ControlFlow::Poll);
|
event_loop.set_control_flow(ControlFlow::Poll);
|
||||||
@@ -67,7 +68,7 @@ fn run(args: &[String]) -> Result<String, String> {
|
|||||||
Arc::clone(&completed),
|
Arc::clone(&completed),
|
||||||
Arc::clone(&progress),
|
Arc::clone(&progress),
|
||||||
);
|
);
|
||||||
let mut app = SmokeApp::new(options, mesh, texture, completed, progress);
|
let mut app = SmokeApp::new(options, mesh, materials, completed, progress);
|
||||||
if let Err(err) = event_loop.run_app(&mut app) {
|
if let Err(err) = event_loop.run_app(&mut app) {
|
||||||
app.error = Some(format!("winit event loop: {err}"));
|
app.error = Some(format!("winit event loop: {err}"));
|
||||||
}
|
}
|
||||||
@@ -131,10 +132,18 @@ struct WearMaterialInput {
|
|||||||
material_index: u16,
|
material_index: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
struct WearInput {
|
||||||
|
root: PathBuf,
|
||||||
|
archive: String,
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
enum TextureInput {
|
enum TextureInput {
|
||||||
Direct(ResourceInput),
|
Direct(ResourceInput),
|
||||||
WearMaterial(WearMaterialInput),
|
WearMaterial(WearMaterialInput),
|
||||||
|
WearBatchMaterials(WearInput),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextureInput {
|
impl TextureInput {
|
||||||
@@ -142,13 +151,14 @@ impl TextureInput {
|
|||||||
match self {
|
match self {
|
||||||
Self::Direct(_) => "original-texm",
|
Self::Direct(_) => "original-texm",
|
||||||
Self::WearMaterial(_) => "original-wear-mat0-texm",
|
Self::WearMaterial(_) => "original-wear-mat0-texm",
|
||||||
|
Self::WearBatchMaterials(_) => "original-wear-mat0-texm-batch",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn archive(&self) -> &str {
|
fn archive(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Self::Direct(input) => &input.archive,
|
Self::Direct(input) => &input.archive,
|
||||||
Self::WearMaterial(_) => "Textures.lib",
|
Self::WearMaterial(_) | Self::WearBatchMaterials(_) => "Textures.lib",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +166,7 @@ impl TextureInput {
|
|||||||
match self {
|
match self {
|
||||||
Self::Direct(input) => &input.name,
|
Self::Direct(input) => &input.name,
|
||||||
// The selected name comes from MAT0 at load time and is not a CLI request.
|
// The selected name comes from MAT0 at load time and is not a CLI request.
|
||||||
Self::WearMaterial(_) => "",
|
Self::WearMaterial(_) | Self::WearBatchMaterials(_) => "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -363,9 +373,16 @@ impl SmokeOptions {
|
|||||||
material_index,
|
material_index,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
(Some(root), Some(archive), Some(name), None) => {
|
||||||
|
Some(TextureInput::WearBatchMaterials(WearInput {
|
||||||
|
root,
|
||||||
|
archive,
|
||||||
|
name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(
|
return Err(
|
||||||
"--wear-root, --wear-archive, --wear-name and --material-index must be supplied together"
|
"--wear-root, --wear-archive and --wear-name must be supplied together; --material-index is optional"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -400,42 +417,93 @@ impl SmokeOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_texture(&self) -> Result<Option<LoadedTexture>, String> {
|
fn load_materials(&self, mesh: &VulkanStaticMesh) -> Result<Vec<LoadedTexture>, String> {
|
||||||
self.texture_input.as_ref().map_or(Ok(None), |input| {
|
let Some(input) = self.texture_input.as_ref() else {
|
||||||
let (image, selected_name) = match input {
|
return Ok(Vec::new());
|
||||||
TextureInput::Direct(input) => (
|
};
|
||||||
fparkan_inspection::load_texture_mip0_rgba8_from_root(
|
match input {
|
||||||
|
TextureInput::Direct(input) => Ok(vec![Self::load_material(
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
let image = fparkan_inspection::load_texture_mip0_rgba8_from_root(
|
||||||
&input.root,
|
&input.root,
|
||||||
&input.archive,
|
&input.archive,
|
||||||
&input.name,
|
&input.name,
|
||||||
)?,
|
)?;
|
||||||
input.name.clone(),
|
(image.width, image.height, image.rgba8)
|
||||||
),
|
|
||||||
TextureInput::WearMaterial(input) => {
|
|
||||||
let selected =
|
|
||||||
fparkan_inspection::load_wear_material_texture_mip0_rgba8_from_root(
|
|
||||||
&input.root,
|
|
||||||
&input.archive,
|
|
||||||
&input.name,
|
|
||||||
input.material_index,
|
|
||||||
)?;
|
|
||||||
(selected.image, selected.texture_name)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(Some(LoadedTexture {
|
|
||||||
texture: VulkanStaticTexture {
|
|
||||||
width: image.width,
|
|
||||||
height: image.height,
|
|
||||||
rgba8: image.rgba8,
|
|
||||||
},
|
},
|
||||||
selected_name,
|
input.name.clone(),
|
||||||
}))
|
)]),
|
||||||
})
|
TextureInput::WearMaterial(input) => {
|
||||||
|
let selected = fparkan_inspection::load_wear_material_texture_mip0_rgba8_from_root(
|
||||||
|
&input.root,
|
||||||
|
&input.archive,
|
||||||
|
&input.name,
|
||||||
|
input.material_index,
|
||||||
|
)?;
|
||||||
|
Ok(vec![Self::load_material(
|
||||||
|
input.material_index,
|
||||||
|
(
|
||||||
|
selected.image.width,
|
||||||
|
selected.image.height,
|
||||||
|
selected.image.rgba8,
|
||||||
|
),
|
||||||
|
selected.texture_name,
|
||||||
|
)])
|
||||||
|
}
|
||||||
|
TextureInput::WearBatchMaterials(input) => {
|
||||||
|
let mut selectors = mesh
|
||||||
|
.draw_ranges
|
||||||
|
.iter()
|
||||||
|
.map(|range| range.material_index)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
selectors.sort_unstable();
|
||||||
|
selectors.dedup();
|
||||||
|
selectors
|
||||||
|
.into_iter()
|
||||||
|
.map(|material_index| {
|
||||||
|
let selected =
|
||||||
|
fparkan_inspection::load_wear_material_texture_mip0_rgba8_from_root(
|
||||||
|
&input.root,
|
||||||
|
&input.archive,
|
||||||
|
&input.name,
|
||||||
|
material_index,
|
||||||
|
)?;
|
||||||
|
Ok(Self::load_material(
|
||||||
|
material_index,
|
||||||
|
(
|
||||||
|
selected.image.width,
|
||||||
|
selected.image.height,
|
||||||
|
selected.image.rgba8,
|
||||||
|
),
|
||||||
|
selected.texture_name,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_material(
|
||||||
|
material_index: u16,
|
||||||
|
image: (u32, u32, Vec<u8>),
|
||||||
|
selected_name: String,
|
||||||
|
) -> LoadedTexture {
|
||||||
|
LoadedTexture {
|
||||||
|
material_index,
|
||||||
|
texture: VulkanStaticTexture {
|
||||||
|
width: image.0,
|
||||||
|
height: image.1,
|
||||||
|
rgba8: image.2,
|
||||||
|
},
|
||||||
|
selected_name,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct LoadedTexture {
|
struct LoadedTexture {
|
||||||
|
material_index: u16,
|
||||||
texture: VulkanStaticTexture,
|
texture: VulkanStaticTexture,
|
||||||
selected_name: String,
|
selected_name: String,
|
||||||
}
|
}
|
||||||
@@ -443,7 +511,7 @@ struct LoadedTexture {
|
|||||||
struct SmokeApp {
|
struct SmokeApp {
|
||||||
options: SmokeOptions,
|
options: SmokeOptions,
|
||||||
mesh: VulkanStaticMesh,
|
mesh: VulkanStaticMesh,
|
||||||
texture: Option<LoadedTexture>,
|
materials: Vec<LoadedTexture>,
|
||||||
completed: Arc<AtomicBool>,
|
completed: Arc<AtomicBool>,
|
||||||
progress: Arc<SharedSmokeProgress>,
|
progress: Arc<SharedSmokeProgress>,
|
||||||
window_id: Option<WindowId>,
|
window_id: Option<WindowId>,
|
||||||
@@ -488,14 +556,14 @@ impl SmokeApp {
|
|||||||
fn new(
|
fn new(
|
||||||
options: SmokeOptions,
|
options: SmokeOptions,
|
||||||
mesh: VulkanStaticMesh,
|
mesh: VulkanStaticMesh,
|
||||||
texture: Option<LoadedTexture>,
|
materials: Vec<LoadedTexture>,
|
||||||
completed: Arc<AtomicBool>,
|
completed: Arc<AtomicBool>,
|
||||||
progress: Arc<SharedSmokeProgress>,
|
progress: Arc<SharedSmokeProgress>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
options,
|
options,
|
||||||
mesh,
|
mesh,
|
||||||
texture,
|
materials,
|
||||||
completed,
|
completed,
|
||||||
progress,
|
progress,
|
||||||
window_id: None,
|
window_id: None,
|
||||||
@@ -603,23 +671,35 @@ impl SmokeApp {
|
|||||||
.texture_input
|
.texture_input
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or("", TextureInput::archive),
|
.map_or("", TextureInput::archive),
|
||||||
texture_name: self.texture.as_ref().map_or_else(
|
texture_name: match self.materials.as_slice() {
|
||||||
|| {
|
[] => self
|
||||||
self.options
|
.options
|
||||||
.texture_input
|
.texture_input
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or("", TextureInput::requested_name)
|
.map_or("", TextureInput::requested_name),
|
||||||
},
|
[material] => material.selected_name.as_str(),
|
||||||
|texture| texture.selected_name.as_str(),
|
_ => "multiple",
|
||||||
),
|
},
|
||||||
texture_width: self
|
texture_width: self
|
||||||
.texture
|
.materials
|
||||||
.as_ref()
|
.first()
|
||||||
.map_or(0, |texture| texture.texture.width),
|
.map_or(0, |material| material.texture.width),
|
||||||
texture_height: self
|
texture_height: self
|
||||||
.texture
|
.materials
|
||||||
.as_ref()
|
.first()
|
||||||
.map_or(0, |texture| texture.texture.height),
|
.map_or(0, |material| material.texture.height),
|
||||||
|
material_binding_count: self.materials.len(),
|
||||||
|
mesh_material_indices: self
|
||||||
|
.mesh
|
||||||
|
.draw_ranges
|
||||||
|
.iter()
|
||||||
|
.map(|range| range.material_index)
|
||||||
|
.collect(),
|
||||||
|
material_texture_names: self
|
||||||
|
.materials
|
||||||
|
.iter()
|
||||||
|
.map(|material| material.selected_name.clone())
|
||||||
|
.collect(),
|
||||||
shader_manifest_hash: renderer
|
shader_manifest_hash: renderer
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or("", |snapshot| snapshot.report.shader_manifest_hash.as_str()),
|
.map_or("", |snapshot| snapshot.report.shader_manifest_hash.as_str()),
|
||||||
@@ -839,6 +919,9 @@ fn render_timeout_failure_report(
|
|||||||
.map_or("", TextureInput::requested_name),
|
.map_or("", TextureInput::requested_name),
|
||||||
texture_width: 0,
|
texture_width: 0,
|
||||||
texture_height: 0,
|
texture_height: 0,
|
||||||
|
material_binding_count: 0,
|
||||||
|
mesh_material_indices: Vec::new(),
|
||||||
|
material_texture_names: Vec::new(),
|
||||||
shader_manifest_hash: "",
|
shader_manifest_hash: "",
|
||||||
vulkan_loader_status: if bootstrap.loader_available {
|
vulkan_loader_status: if bootstrap.loader_available {
|
||||||
"available"
|
"available"
|
||||||
@@ -934,7 +1017,14 @@ impl ApplicationHandler for SmokeApp {
|
|||||||
render_request: RenderRequest::conservative(),
|
render_request: RenderRequest::conservative(),
|
||||||
enable_validation: true,
|
enable_validation: true,
|
||||||
mesh: self.mesh.clone(),
|
mesh: self.mesh.clone(),
|
||||||
texture: self.texture.as_ref().map(|texture| texture.texture.clone()),
|
materials: self
|
||||||
|
.materials
|
||||||
|
.iter()
|
||||||
|
.map(|material| VulkanStaticMaterial {
|
||||||
|
material_index: material.material_index,
|
||||||
|
texture: material.texture.clone(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
bootstrap_progress: Some(Arc::clone(&self.progress.bootstrap)),
|
bootstrap_progress: Some(Arc::clone(&self.progress.bootstrap)),
|
||||||
}) {
|
}) {
|
||||||
Ok(renderer) => renderer,
|
Ok(renderer) => renderer,
|
||||||
@@ -1073,6 +1163,9 @@ struct SmokeReport<'a> {
|
|||||||
texture_name: &'a str,
|
texture_name: &'a str,
|
||||||
texture_width: u32,
|
texture_width: u32,
|
||||||
texture_height: u32,
|
texture_height: u32,
|
||||||
|
material_binding_count: usize,
|
||||||
|
mesh_material_indices: Vec<u16>,
|
||||||
|
material_texture_names: Vec<String>,
|
||||||
shader_manifest_hash: &'a str,
|
shader_manifest_hash: &'a str,
|
||||||
vulkan_loader_status: &'a str,
|
vulkan_loader_status: &'a str,
|
||||||
vulkan_instance_status: &'a str,
|
vulkan_instance_status: &'a str,
|
||||||
@@ -1463,6 +1556,28 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_original_wear_batch_material_input_without_override() {
|
||||||
|
let parsed = SmokeOptions::parse(&[
|
||||||
|
"--out".to_string(),
|
||||||
|
"target/report.json".to_string(),
|
||||||
|
"--wear-root".to_string(),
|
||||||
|
"C:/GOG".to_string(),
|
||||||
|
"--wear-archive".to_string(),
|
||||||
|
"system.rlb".to_string(),
|
||||||
|
"--wear-name".to_string(),
|
||||||
|
"MODEL.WEA".to_string(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
parsed,
|
||||||
|
Ok(SmokeOptions {
|
||||||
|
texture_input: Some(TextureInput::WearBatchMaterials(WearInput { archive, name, .. })),
|
||||||
|
..
|
||||||
|
}) if archive == "system.rlb" && name == "MODEL.WEA"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rejects_deprecated_self_assertion_flags() {
|
fn rejects_deprecated_self_assertion_flags() {
|
||||||
for flag in [
|
for flag in [
|
||||||
@@ -1533,6 +1648,9 @@ mod tests {
|
|||||||
texture_name: "DEFAULT.0",
|
texture_name: "DEFAULT.0",
|
||||||
texture_width: 16,
|
texture_width: 16,
|
||||||
texture_height: 16,
|
texture_height: 16,
|
||||||
|
material_binding_count: 1,
|
||||||
|
mesh_material_indices: vec![0],
|
||||||
|
material_texture_names: vec!["DEFAULT.0".to_string()],
|
||||||
shader_manifest_hash: "deadbeef",
|
shader_manifest_hash: "deadbeef",
|
||||||
vulkan_loader_status: "available",
|
vulkan_loader_status: "available",
|
||||||
vulkan_instance_status: "created",
|
vulkan_instance_status: "created",
|
||||||
@@ -1585,7 +1703,7 @@ mod tests {
|
|||||||
texture_input: None,
|
texture_input: None,
|
||||||
},
|
},
|
||||||
mesh: VulkanStaticMesh::smoke_triangle(),
|
mesh: VulkanStaticMesh::smoke_triangle(),
|
||||||
texture: None,
|
materials: Vec::new(),
|
||||||
completed: Arc::new(AtomicBool::new(false)),
|
completed: Arc::new(AtomicBool::new(false)),
|
||||||
progress: Arc::new(SharedSmokeProgress::default()),
|
progress: Arc::new(SharedSmokeProgress::default()),
|
||||||
window_id: None,
|
window_id: None,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
## Current repository status
|
## Current repository status
|
||||||
|
|
||||||
- Реальный Vulkan в репозитории имеет smoke triangle path и узкий static asset bridge. Помимо direct TEXM input, `fparkan-vulkan-smoke --model-root <GOG_ROOT> --model-archive fortif.rlb --model-name FR_L_MTP.msh --wear-root <GOG_ROOT> --wear-archive fortif.rlb --wear-name FR_L_MTP.WEA --material-index 0` проходит `WEAR → MAT0 → Textures.lib`, выбирает `MTP_01.0`, сохраняет 237 исходных MSH batch ranges и выпускает 237 indexed Vulkan draws, затем загружает selected TEXM mip-0 в device-local image. Image переходит в sampling layout, записывается в `set=0,binding=0` combined image sampler и sampled fragment shader-ом. `Res5` UV0 декодируется как signed fixed point `int16 / 1024.0`; XZ-planar UV остаётся только fallback для модели без optional stream. Все 237 draws пока используют один explicitly selected material, а не полный per-batch/phase/lightmap/terrain или gameplay renderer.
|
- Реальный Vulkan в репозитории имеет smoke triangle path и узкий static asset bridge. `VulkanStaticDrawRange` сохраняет исходный `Batch20.material_index`; когда smoke запускается с `--wear-root`, `--wear-archive`, `--wear-name` без override, он дедуплицирует selectors, проходит каждый через `WEAR → MAT0 → Textures.lib`, создаёт по одному image/descriptor set и бинит set непосредственно перед соответствующим indexed draw. Direct TEXM и `--material-index` — намеренно однотекстурные compatibility modes. Fresh GOG `fortif.rlb::FR_L_MTP.msh` подтверждает 237 batch draws, но его selectors все `0`, поэтому live report содержит один binding `MTP_01.0`; unit contract подтверждает точное сопоставление двух разных selectors с разными descriptor sets. Это не доказывает material phase animation, lightmaps, alpha/depth/cull state, terrain, camera/node transforms или pixel approval.
|
||||||
- `apps/fparkan-game` сейчас выдает `render-planning` JSON report поверх
|
- `apps/fparkan-game` сейчас выдает `render-planning` JSON report поверх
|
||||||
synthetic window descriptor и `VulkanPlanningBackend`.
|
synthetic window descriptor и `VulkanPlanningBackend`.
|
||||||
- `apps/fparkan-viewer` сейчас inspection-only CLI и не открывает live Vulkan
|
- `apps/fparkan-viewer` сейчас inspection-only CLI и не открывает live Vulkan
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ S3-VK-MESH-UPLOAD-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --
|
|||||||
S3-VK-TEXM-UPLOAD-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-mtcheck-texm-upload.json --frames 300 --resize-frame 120 --timeout-seconds 90 --model-root <GOG_ROOT> --model-archive system.rlb --model-name MTCHECK.MSH --texture-root <GOG_ROOT> --texture-archive Textures.lib --texture-name DEFAULT.0; original TEXM 16x16 decoded RGBA8, staged into device-local image and transitioned to SHADER_READ_ONLY_OPTIMAL
|
S3-VK-TEXM-UPLOAD-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-mtcheck-texm-upload.json --frames 300 --resize-frame 120 --timeout-seconds 90 --model-root <GOG_ROOT> --model-archive system.rlb --model-name MTCHECK.MSH --texture-root <GOG_ROOT> --texture-archive Textures.lib --texture-name DEFAULT.0; original TEXM 16x16 decoded RGBA8, staged into device-local image and transitioned to SHADER_READ_ONLY_OPTIMAL
|
||||||
S3-VK-DESCRIPTOR-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-texm-sampled.json --frames 300 --resize-frame 120 --timeout-seconds 90 --model-root <GOG_ROOT> --model-archive system.rlb --model-name MTCHECK.MSH --texture-root <GOG_ROOT> --texture-archive Textures.lib --texture-name DEFAULT.0; original TEXM is bound as set=0,binding=0 combined image sampler and sampled in fragment shader; validation_warning_count=0 and validation_error_count=0
|
S3-VK-DESCRIPTOR-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-texm-sampled.json --frames 300 --resize-frame 120 --timeout-seconds 90 --model-root <GOG_ROOT> --model-archive system.rlb --model-name MTCHECK.MSH --texture-root <GOG_ROOT> --texture-archive Textures.lib --texture-name DEFAULT.0; original TEXM is bound as set=0,binding=0 combined image sampler and sampled in fragment shader; validation_warning_count=0 and validation_error_count=0
|
||||||
S3-VK-PIPELINE-001 blocked awaits Stage 3 graphics pipeline key and cache implementation
|
S3-VK-PIPELINE-001 blocked awaits Stage 3 graphics pipeline key and cache implementation
|
||||||
S3-VK-DRAW-MODEL-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-fr-l-mtp-batches.json --frames 300 --resize-frame 120 --timeout-seconds 90 --model-root <GOG_ROOT> --model-archive fortif.rlb --model-name FR_L_MTP.msh --wear-root <GOG_ROOT> --wear-archive fortif.rlb --wear-name FR_L_MTP.WEA --material-index 0; live Win32 Vulkan issues 237 source-preserving indexed batch draws for original MSH with WEAR→MAT0-selected MTP_01.0 TEXM
|
S3-VK-DRAW-MODEL-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-fr-l-mtp-per-batch-materials.json --frames 300 --resize-frame 120 --timeout-seconds 90 --model-root <GOG_ROOT> --model-archive fortif.rlb --model-name FR_L_MTP.msh --wear-root <GOG_ROOT> --wear-archive fortif.rlb --wear-name FR_L_MTP.WEA; live Win32 Vulkan issues 237 source-preserving indexed batch draws and binds the deduplicated WEAR→MAT0 material descriptor selected by each Batch20.material_index (this corpus model has selector 0 only)
|
||||||
S3-VK-DRAW-TERRAIN-001 blocked awaits Stage 3 terrain Vulkan draw path
|
S3-VK-DRAW-TERRAIN-001 blocked awaits Stage 3 terrain Vulkan draw path
|
||||||
S3-VK-PIXEL-CAPTURE-001 blocked awaits Stage 3 fixed-camera pixel capture approval flow
|
S3-VK-PIXEL-CAPTURE-001 blocked awaits Stage 3 fixed-camera pixel capture approval flow
|
||||||
S3-VK-VALIDATION-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-mtcheck-msh.json --frames 300 --resize-frame 120 --timeout-seconds 90 --model-root <GOG_ROOT> --model-archive system.rlb --model-name MTCHECK.MSH; validation_warning_count=0 and validation_error_count=0
|
S3-VK-VALIDATION-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-mtcheck-msh.json --frames 300 --resize-frame 120 --timeout-seconds 90 --model-root <GOG_ROOT> --model-archive system.rlb --model-name MTCHECK.MSH; validation_warning_count=0 and validation_error_count=0
|
||||||
|
|||||||
|
Reference in New Issue
Block a user