feat(vulkan): add depth attachment pipeline support
This commit is contained in:
@@ -53,10 +53,10 @@ pub use self::instance::{
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use self::instance::{cstring_vec, ensure_instance_extensions_available};
|
use self::instance::{cstring_vec, ensure_instance_extensions_available};
|
||||||
use self::resources::{
|
use self::resources::{
|
||||||
color_subresource_range, create_command_pool, create_frame_sync,
|
color_subresource_range, create_command_pool, create_depth_attachment, create_frame_sync,
|
||||||
create_static_mesh_index_buffer, create_static_mesh_vertex_buffer, create_static_texture_image,
|
create_static_mesh_index_buffer, create_static_mesh_vertex_buffer, create_static_texture_image,
|
||||||
destroy_allocated_buffer, destroy_allocated_image, VulkanAllocatedBuffer, VulkanAllocatedImage,
|
destroy_allocated_buffer, destroy_allocated_image, destroy_depth_attachment,
|
||||||
VulkanFrameSync,
|
VulkanAllocatedBuffer, VulkanAllocatedImage, VulkanDepthAttachment, VulkanFrameSync,
|
||||||
};
|
};
|
||||||
pub use self::runtime::{
|
pub use self::runtime::{
|
||||||
create_vulkan_logical_device_probe, create_vulkan_logical_device_probe_for_request,
|
create_vulkan_logical_device_probe, create_vulkan_logical_device_probe_for_request,
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#![allow(unsafe_code)]
|
#![allow(unsafe_code)]
|
||||||
|
|
||||||
use ash::vk;
|
use ash::vk;
|
||||||
|
use fparkan_platform::DepthStencilSupport;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
VulkanInstanceProbe, VulkanLogicalDeviceProbe, VulkanSmokeRendererError, VulkanStaticMesh,
|
VulkanInstanceProbe, VulkanLogicalDeviceProbe, VulkanSmokeRendererError, VulkanStaticMesh,
|
||||||
VulkanStaticTexture,
|
VulkanStaticTexture,
|
||||||
};
|
};
|
||||||
|
use crate::select_depth_stencil_attachment_format;
|
||||||
|
|
||||||
pub(super) struct VulkanAllocatedBuffer {
|
pub(super) struct VulkanAllocatedBuffer {
|
||||||
pub(super) buffer: vk::Buffer,
|
pub(super) buffer: vk::Buffer,
|
||||||
@@ -18,6 +20,171 @@ pub(super) struct VulkanAllocatedImage {
|
|||||||
pub(super) view: vk::ImageView,
|
pub(super) view: vk::ImageView,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Depth/stencil image and the exact format selected for its render pass.
|
||||||
|
pub(super) struct VulkanDepthAttachment {
|
||||||
|
pub(super) image: VulkanAllocatedImage,
|
||||||
|
pub(super) format: vk::Format,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
pub(super) fn create_depth_attachment(
|
||||||
|
instance: &VulkanInstanceProbe,
|
||||||
|
device: &VulkanLogicalDeviceProbe,
|
||||||
|
extent: (u32, u32),
|
||||||
|
request: DepthStencilSupport,
|
||||||
|
) -> Result<VulkanDepthAttachment, VulkanSmokeRendererError> {
|
||||||
|
let supported_formats = depth_attachment_formats(instance, device);
|
||||||
|
let format = select_depth_stencil_attachment_format(&supported_formats, request)
|
||||||
|
.map(vk::Format::from_raw)
|
||||||
|
.ok_or(VulkanSmokeRendererError::InvalidStaticMesh {
|
||||||
|
context: "logical device has no selected depth attachment format",
|
||||||
|
})?;
|
||||||
|
let image_info = vk::ImageCreateInfo::default()
|
||||||
|
.image_type(vk::ImageType::TYPE_2D)
|
||||||
|
.format(format)
|
||||||
|
.extent(vk::Extent3D {
|
||||||
|
width: extent.0,
|
||||||
|
height: extent.1,
|
||||||
|
depth: 1,
|
||||||
|
})
|
||||||
|
.mip_levels(1)
|
||||||
|
.array_layers(1)
|
||||||
|
.samples(vk::SampleCountFlags::TYPE_1)
|
||||||
|
.tiling(vk::ImageTiling::OPTIMAL)
|
||||||
|
.usage(vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT)
|
||||||
|
.sharing_mode(vk::SharingMode::EXCLUSIVE)
|
||||||
|
.initial_layout(vk::ImageLayout::UNDEFINED);
|
||||||
|
// SAFETY: The creation info is stack-owned and uses the live non-zero swapchain extent.
|
||||||
|
let image = unsafe { device.device().create_image(&image_info, None) }.map_err(|result| {
|
||||||
|
VulkanSmokeRendererError::VulkanOperation {
|
||||||
|
context: "vkCreateImage(depth attachment)",
|
||||||
|
result,
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
// SAFETY: The newly created image belongs to this device.
|
||||||
|
let requirements = unsafe { device.device().get_image_memory_requirements(image) };
|
||||||
|
let Some(memory_type_index) = find_memory_type(
|
||||||
|
instance,
|
||||||
|
device.physical_device(),
|
||||||
|
requirements.memory_type_bits,
|
||||||
|
vk::MemoryPropertyFlags::DEVICE_LOCAL,
|
||||||
|
) else {
|
||||||
|
// SAFETY: The unbound image is rolled back on its owning device.
|
||||||
|
unsafe { device.device().destroy_image(image, None) };
|
||||||
|
return Err(VulkanSmokeRendererError::MissingMemoryType {
|
||||||
|
context: "depth attachment",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
let allocation = vk::MemoryAllocateInfo::default()
|
||||||
|
.allocation_size(requirements.size)
|
||||||
|
.memory_type_index(memory_type_index);
|
||||||
|
// SAFETY: Allocation parameters are derived from this image's requirements.
|
||||||
|
let allocation_result = unsafe { device.device().allocate_memory(&allocation, None) };
|
||||||
|
let memory = match allocation_result {
|
||||||
|
Ok(memory) => memory,
|
||||||
|
Err(result) => {
|
||||||
|
// SAFETY: The unbound image is rolled back on allocation failure.
|
||||||
|
unsafe { device.device().destroy_image(image, None) };
|
||||||
|
return Err(VulkanSmokeRendererError::VulkanOperation {
|
||||||
|
context: "vkAllocateMemory(depth attachment)",
|
||||||
|
result,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// SAFETY: The allocation satisfies this image's queried requirements.
|
||||||
|
if let Err(result) = unsafe { device.device().bind_image_memory(image, memory, 0) } {
|
||||||
|
// SAFETY: Both newly created resources are rolled back together.
|
||||||
|
unsafe {
|
||||||
|
device.device().destroy_image(image, None);
|
||||||
|
device.device().free_memory(memory, None);
|
||||||
|
}
|
||||||
|
return Err(VulkanSmokeRendererError::VulkanOperation {
|
||||||
|
context: "vkBindImageMemory(depth attachment)",
|
||||||
|
result,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let range = vk::ImageSubresourceRange::default()
|
||||||
|
.aspect_mask(depth_aspect(format))
|
||||||
|
.base_mip_level(0)
|
||||||
|
.level_count(1)
|
||||||
|
.base_array_layer(0)
|
||||||
|
.layer_count(1);
|
||||||
|
let view_info = vk::ImageViewCreateInfo::default()
|
||||||
|
.image(image)
|
||||||
|
.view_type(vk::ImageViewType::TYPE_2D)
|
||||||
|
.format(format)
|
||||||
|
.subresource_range(range);
|
||||||
|
// SAFETY: The image is bound and the depth/stencil aspect matches its selected format.
|
||||||
|
let view_result = unsafe { device.device().create_image_view(&view_info, None) };
|
||||||
|
let view = match view_result {
|
||||||
|
Ok(view) => view,
|
||||||
|
Err(result) => {
|
||||||
|
// SAFETY: Both newly created resources are rolled back together.
|
||||||
|
unsafe {
|
||||||
|
device.device().destroy_image(image, None);
|
||||||
|
device.device().free_memory(memory, None);
|
||||||
|
}
|
||||||
|
return Err(VulkanSmokeRendererError::VulkanOperation {
|
||||||
|
context: "vkCreateImageView(depth attachment)",
|
||||||
|
result,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(VulkanDepthAttachment {
|
||||||
|
image: VulkanAllocatedImage {
|
||||||
|
image,
|
||||||
|
memory,
|
||||||
|
view,
|
||||||
|
},
|
||||||
|
format,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn destroy_depth_attachment(
|
||||||
|
device: &VulkanLogicalDeviceProbe,
|
||||||
|
attachment: &VulkanDepthAttachment,
|
||||||
|
) {
|
||||||
|
destroy_allocated_image(device, &attachment.image);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn depth_attachment_formats(
|
||||||
|
instance: &VulkanInstanceProbe,
|
||||||
|
device: &VulkanLogicalDeviceProbe,
|
||||||
|
) -> Vec<i32> {
|
||||||
|
[
|
||||||
|
vk::Format::D16_UNORM,
|
||||||
|
vk::Format::X8_D24_UNORM_PACK32,
|
||||||
|
vk::Format::D32_SFLOAT,
|
||||||
|
vk::Format::D16_UNORM_S8_UINT,
|
||||||
|
vk::Format::D24_UNORM_S8_UINT,
|
||||||
|
vk::Format::D32_SFLOAT_S8_UINT,
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.filter(|format| {
|
||||||
|
// SAFETY: The physical device belongs to this instance and the query returns a value copy.
|
||||||
|
unsafe {
|
||||||
|
instance
|
||||||
|
.instance
|
||||||
|
.get_physical_device_format_properties(device.physical_device(), *format)
|
||||||
|
}
|
||||||
|
.optimal_tiling_features
|
||||||
|
.contains(vk::FormatFeatureFlags::DEPTH_STENCIL_ATTACHMENT)
|
||||||
|
})
|
||||||
|
.map(vk::Format::as_raw)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn depth_aspect(format: vk::Format) -> vk::ImageAspectFlags {
|
||||||
|
match format {
|
||||||
|
vk::Format::D16_UNORM_S8_UINT
|
||||||
|
| vk::Format::D24_UNORM_S8_UINT
|
||||||
|
| vk::Format::D32_SFLOAT_S8_UINT => {
|
||||||
|
vk::ImageAspectFlags::DEPTH | vk::ImageAspectFlags::STENCIL
|
||||||
|
}
|
||||||
|
_ => vk::ImageAspectFlags::DEPTH,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) struct VulkanFrameSync {
|
pub(super) struct VulkanFrameSync {
|
||||||
pub(super) image_available: vk::Semaphore,
|
pub(super) image_available: vk::Semaphore,
|
||||||
pub(super) render_finished: vk::Semaphore,
|
pub(super) render_finished: vk::Semaphore,
|
||||||
|
|||||||
@@ -228,6 +228,7 @@ impl VulkanSmokeRenderer {
|
|||||||
frame_sync: Vec::new(),
|
frame_sync: Vec::new(),
|
||||||
images_in_flight: Vec::new(),
|
images_in_flight: Vec::new(),
|
||||||
current_frame: 0,
|
current_frame: 0,
|
||||||
|
depth_request: create_info.render_request.depth,
|
||||||
pending_extent: None,
|
pending_extent: None,
|
||||||
swapchain_recreate_count: 0,
|
swapchain_recreate_count: 0,
|
||||||
report: VulkanSmokeRendererReport {
|
report: VulkanSmokeRendererReport {
|
||||||
@@ -554,6 +555,7 @@ impl VulkanSmokeRenderer {
|
|||||||
let swapchain = self.swapchain_ref()?;
|
let swapchain = self.swapchain_ref()?;
|
||||||
let resources = RollbackOnDrop::new(
|
let resources = RollbackOnDrop::new(
|
||||||
create_swapchain_resources(
|
create_swapchain_resources(
|
||||||
|
self.instance_ref()?,
|
||||||
device,
|
device,
|
||||||
swapchain,
|
swapchain,
|
||||||
self.command_pool,
|
self.command_pool,
|
||||||
@@ -561,6 +563,7 @@ impl VulkanSmokeRenderer {
|
|||||||
self.index_buffer_ref()?,
|
self.index_buffer_ref()?,
|
||||||
self.textures_ref()?,
|
self.textures_ref()?,
|
||||||
&self.draw_ranges,
|
&self.draw_ranges,
|
||||||
|
self.depth_request,
|
||||||
reuse_command_pool,
|
reuse_command_pool,
|
||||||
)?,
|
)?,
|
||||||
|resources| destroy_swapchain_resources(device, self.command_pool, resources),
|
|resources| destroy_swapchain_resources(device, self.command_pool, resources),
|
||||||
@@ -608,11 +611,19 @@ impl VulkanSmokeRenderer {
|
|||||||
result: error,
|
result: error,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let clear_values = [vk::ClearValue {
|
let clear_values = [
|
||||||
|
vk::ClearValue {
|
||||||
color: vk::ClearColorValue {
|
color: vk::ClearColorValue {
|
||||||
float32: [0.05, 0.08, 0.11, 1.0],
|
float32: [0.05, 0.08, 0.11, 1.0],
|
||||||
},
|
},
|
||||||
}];
|
},
|
||||||
|
vk::ClearValue {
|
||||||
|
depth_stencil: vk::ClearDepthStencilValue {
|
||||||
|
depth: 1.0,
|
||||||
|
stencil: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
let render_area = vk::Rect2D {
|
let render_area = vk::Rect2D {
|
||||||
offset: vk::Offset2D { x: 0, y: 0 },
|
offset: vk::Offset2D { x: 0, y: 0 },
|
||||||
extent: vk::Extent2D {
|
extent: vk::Extent2D {
|
||||||
|
|||||||
@@ -543,6 +543,7 @@ pub struct VulkanSmokeRenderer {
|
|||||||
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,
|
||||||
|
pub(super) depth_request: fparkan_platform::DepthStencilSupport,
|
||||||
pub(super) pending_extent: Option<(u32, u32)>,
|
pub(super) pending_extent: Option<(u32, u32)>,
|
||||||
pub(super) swapchain_recreate_count: u32,
|
pub(super) swapchain_recreate_count: u32,
|
||||||
pub(super) report: VulkanSmokeRendererReport,
|
pub(super) report: VulkanSmokeRendererReport,
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
#![allow(unsafe_code)]
|
#![allow(unsafe_code)]
|
||||||
|
|
||||||
use ash::vk;
|
use ash::vk;
|
||||||
|
use fparkan_platform::DepthStencilSupport;
|
||||||
use fparkan_render::{
|
use fparkan_render::{
|
||||||
LegacyBlendMode, LegacyCullMode, LegacyDepthMode, LegacyPipelineState, PipelineKey,
|
LegacyBlendMode, LegacyCullMode, LegacyDepthMode, LegacyPipelineState, PipelineKey,
|
||||||
};
|
};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
color_subresource_range, VulkanAllocatedBuffer, VulkanAllocatedImage, VulkanLogicalDeviceProbe,
|
color_subresource_range, create_depth_attachment, destroy_depth_attachment,
|
||||||
VulkanSmokeRendererError, VulkanSwapchainProbe, TRIANGLE_FRAGMENT_SHADER_WORDS,
|
VulkanAllocatedBuffer, VulkanAllocatedImage, VulkanDepthAttachment, VulkanInstanceProbe,
|
||||||
TRIANGLE_VERTEX_SHADER_WORDS,
|
VulkanLogicalDeviceProbe, VulkanSmokeRendererError, VulkanSwapchainProbe,
|
||||||
|
TRIANGLE_FRAGMENT_SHADER_WORDS, TRIANGLE_VERTEX_SHADER_WORDS,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) struct VulkanSwapchainResources {
|
pub(super) struct VulkanSwapchainResources {
|
||||||
pub(super) image_views: Vec<vk::ImageView>,
|
pub(super) image_views: Vec<vk::ImageView>,
|
||||||
|
pub(super) depth_attachment: VulkanDepthAttachment,
|
||||||
pub(super) render_pass: vk::RenderPass,
|
pub(super) render_pass: vk::RenderPass,
|
||||||
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,
|
||||||
@@ -28,6 +31,7 @@ pub(super) struct VulkanSwapchainResources {
|
|||||||
|
|
||||||
struct PartialSwapchainResources {
|
struct PartialSwapchainResources {
|
||||||
image_views: Vec<vk::ImageView>,
|
image_views: Vec<vk::ImageView>,
|
||||||
|
depth_attachment: Option<VulkanDepthAttachment>,
|
||||||
render_pass: Option<vk::RenderPass>,
|
render_pass: Option<vk::RenderPass>,
|
||||||
pipeline_layout: Option<vk::PipelineLayout>,
|
pipeline_layout: Option<vk::PipelineLayout>,
|
||||||
descriptor_set_layout: Option<vk::DescriptorSetLayout>,
|
descriptor_set_layout: Option<vk::DescriptorSetLayout>,
|
||||||
@@ -38,8 +42,9 @@ struct PartialSwapchainResources {
|
|||||||
command_buffers: Vec<vk::CommandBuffer>,
|
command_buffers: Vec<vk::CommandBuffer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
|
||||||
pub(super) fn create_swapchain_resources(
|
pub(super) fn create_swapchain_resources(
|
||||||
|
instance: &VulkanInstanceProbe,
|
||||||
device: &VulkanLogicalDeviceProbe,
|
device: &VulkanLogicalDeviceProbe,
|
||||||
swapchain: &VulkanSwapchainProbe,
|
swapchain: &VulkanSwapchainProbe,
|
||||||
command_pool: vk::CommandPool,
|
command_pool: vk::CommandPool,
|
||||||
@@ -47,6 +52,7 @@ pub(super) fn create_swapchain_resources(
|
|||||||
index_buffer: &VulkanAllocatedBuffer,
|
index_buffer: &VulkanAllocatedBuffer,
|
||||||
textures: &[VulkanAllocatedImage],
|
textures: &[VulkanAllocatedImage],
|
||||||
draw_ranges: &[super::VulkanStaticDrawRange],
|
draw_ranges: &[super::VulkanStaticDrawRange],
|
||||||
|
depth_request: DepthStencilSupport,
|
||||||
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.
|
||||||
@@ -65,6 +71,7 @@ pub(super) fn create_swapchain_resources(
|
|||||||
&images,
|
&images,
|
||||||
swapchain.report.plan.format.format,
|
swapchain.report.plan.format.format,
|
||||||
)?,
|
)?,
|
||||||
|
depth_attachment: None,
|
||||||
render_pass: None,
|
render_pass: None,
|
||||||
pipeline_layout: None,
|
pipeline_layout: None,
|
||||||
descriptor_set_layout: None,
|
descriptor_set_layout: None,
|
||||||
@@ -75,6 +82,7 @@ pub(super) fn create_swapchain_resources(
|
|||||||
command_buffers: Vec::new(),
|
command_buffers: Vec::new(),
|
||||||
};
|
};
|
||||||
let (
|
let (
|
||||||
|
depth_attachment,
|
||||||
render_pass,
|
render_pass,
|
||||||
pipeline_layout,
|
pipeline_layout,
|
||||||
pipelines,
|
pipelines,
|
||||||
@@ -83,11 +91,13 @@ pub(super) fn create_swapchain_resources(
|
|||||||
descriptor_sets,
|
descriptor_sets,
|
||||||
sampler,
|
sampler,
|
||||||
) = match create_swapchain_pipeline_bundle(
|
) = match create_swapchain_pipeline_bundle(
|
||||||
|
instance,
|
||||||
device,
|
device,
|
||||||
swapchain.report.plan.format.format,
|
swapchain.report.plan.format.format,
|
||||||
swapchain.report.plan.extent,
|
swapchain.report.plan.extent,
|
||||||
textures,
|
textures,
|
||||||
draw_ranges,
|
draw_ranges,
|
||||||
|
depth_request,
|
||||||
) {
|
) {
|
||||||
Ok(bundle) => bundle,
|
Ok(bundle) => bundle,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
@@ -95,6 +105,8 @@ pub(super) fn create_swapchain_resources(
|
|||||||
return Err(error);
|
return Err(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let depth_view = depth_attachment.image.view;
|
||||||
|
partial.depth_attachment = Some(depth_attachment);
|
||||||
partial.render_pass = Some(render_pass);
|
partial.render_pass = Some(render_pass);
|
||||||
partial.pipeline_layout = Some(pipeline_layout);
|
partial.pipeline_layout = Some(pipeline_layout);
|
||||||
partial.descriptor_set_layout = Some(descriptor_set_layout);
|
partial.descriptor_set_layout = Some(descriptor_set_layout);
|
||||||
@@ -105,6 +117,7 @@ pub(super) fn create_swapchain_resources(
|
|||||||
device,
|
device,
|
||||||
render_pass,
|
render_pass,
|
||||||
&partial.image_views,
|
&partial.image_views,
|
||||||
|
depth_view,
|
||||||
swapchain.report.plan.extent,
|
swapchain.report.plan.extent,
|
||||||
) {
|
) {
|
||||||
Ok(framebuffers) => framebuffers,
|
Ok(framebuffers) => framebuffers,
|
||||||
@@ -128,8 +141,16 @@ pub(super) fn create_swapchain_resources(
|
|||||||
};
|
};
|
||||||
partial.command_buffers = command_buffers;
|
partial.command_buffers = command_buffers;
|
||||||
let _ = (vertex_buffer, index_buffer);
|
let _ = (vertex_buffer, index_buffer);
|
||||||
|
let depth_attachment =
|
||||||
|
partial
|
||||||
|
.depth_attachment
|
||||||
|
.take()
|
||||||
|
.ok_or(VulkanSmokeRendererError::InvariantViolation {
|
||||||
|
context: "depth attachment ownership after swapchain setup",
|
||||||
|
})?;
|
||||||
Ok(VulkanSwapchainResources {
|
Ok(VulkanSwapchainResources {
|
||||||
image_views: partial.image_views,
|
image_views: partial.image_views,
|
||||||
|
depth_attachment,
|
||||||
render_pass,
|
render_pass,
|
||||||
pipeline_layout,
|
pipeline_layout,
|
||||||
descriptor_set_layout,
|
descriptor_set_layout,
|
||||||
@@ -156,13 +177,16 @@ fn create_swapchain_image_views(
|
|||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
fn create_swapchain_pipeline_bundle(
|
fn create_swapchain_pipeline_bundle(
|
||||||
|
instance: &VulkanInstanceProbe,
|
||||||
device: &VulkanLogicalDeviceProbe,
|
device: &VulkanLogicalDeviceProbe,
|
||||||
format: i32,
|
format: i32,
|
||||||
extent: (u32, u32),
|
extent: (u32, u32),
|
||||||
textures: &[VulkanAllocatedImage],
|
textures: &[VulkanAllocatedImage],
|
||||||
draw_ranges: &[super::VulkanStaticDrawRange],
|
draw_ranges: &[super::VulkanStaticDrawRange],
|
||||||
|
depth_request: DepthStencilSupport,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
(
|
(
|
||||||
|
VulkanDepthAttachment,
|
||||||
vk::RenderPass,
|
vk::RenderPass,
|
||||||
vk::PipelineLayout,
|
vk::PipelineLayout,
|
||||||
BTreeMap<PipelineKey, vk::Pipeline>,
|
BTreeMap<PipelineKey, vk::Pipeline>,
|
||||||
@@ -173,11 +197,19 @@ fn create_swapchain_pipeline_bundle(
|
|||||||
),
|
),
|
||||||
VulkanSmokeRendererError,
|
VulkanSmokeRendererError,
|
||||||
> {
|
> {
|
||||||
let render_pass = create_render_pass(device, format)?;
|
let depth_attachment = create_depth_attachment(instance, device, extent, depth_request)?;
|
||||||
|
let render_pass = match create_render_pass(device, format, depth_attachment.format) {
|
||||||
|
Ok(render_pass) => render_pass,
|
||||||
|
Err(error) => {
|
||||||
|
destroy_depth_attachment(device, &depth_attachment);
|
||||||
|
return Err(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
let (descriptor_set_layout, descriptor_pool, descriptor_sets, sampler) =
|
let (descriptor_set_layout, descriptor_pool, descriptor_sets, sampler) =
|
||||||
create_texture_descriptor_bundle(device, textures).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) };
|
||||||
|
destroy_depth_attachment(device, &depth_attachment);
|
||||||
})?;
|
})?;
|
||||||
let pipeline_layout =
|
let pipeline_layout =
|
||||||
create_pipeline_layout(device, descriptor_set_layout).inspect_err(|_| {
|
create_pipeline_layout(device, descriptor_set_layout).inspect_err(|_| {
|
||||||
@@ -192,6 +224,7 @@ fn create_swapchain_pipeline_bundle(
|
|||||||
.destroy_descriptor_set_layout(descriptor_set_layout, None);
|
.destroy_descriptor_set_layout(descriptor_set_layout, None);
|
||||||
device.device().destroy_render_pass(render_pass, None);
|
device.device().destroy_render_pass(render_pass, None);
|
||||||
}
|
}
|
||||||
|
destroy_depth_attachment(device, &depth_attachment);
|
||||||
})?;
|
})?;
|
||||||
let pipelines =
|
let pipelines =
|
||||||
create_graphics_pipeline_cache(device, render_pass, pipeline_layout, extent, draw_ranges)
|
create_graphics_pipeline_cache(device, render_pass, pipeline_layout, extent, draw_ranges)
|
||||||
@@ -210,8 +243,10 @@ fn create_swapchain_pipeline_bundle(
|
|||||||
.destroy_descriptor_set_layout(descriptor_set_layout, None);
|
.destroy_descriptor_set_layout(descriptor_set_layout, None);
|
||||||
device.device().destroy_render_pass(render_pass, None);
|
device.device().destroy_render_pass(render_pass, None);
|
||||||
}
|
}
|
||||||
|
destroy_depth_attachment(device, &depth_attachment);
|
||||||
})?;
|
})?;
|
||||||
Ok((
|
Ok((
|
||||||
|
depth_attachment,
|
||||||
render_pass,
|
render_pass,
|
||||||
pipeline_layout,
|
pipeline_layout,
|
||||||
pipelines,
|
pipelines,
|
||||||
@@ -226,11 +261,12 @@ fn create_swapchain_framebuffers(
|
|||||||
device: &VulkanLogicalDeviceProbe,
|
device: &VulkanLogicalDeviceProbe,
|
||||||
render_pass: vk::RenderPass,
|
render_pass: vk::RenderPass,
|
||||||
image_views: &[vk::ImageView],
|
image_views: &[vk::ImageView],
|
||||||
|
depth_view: vk::ImageView,
|
||||||
extent: (u32, u32),
|
extent: (u32, u32),
|
||||||
) -> Result<Vec<vk::Framebuffer>, VulkanSmokeRendererError> {
|
) -> Result<Vec<vk::Framebuffer>, VulkanSmokeRendererError> {
|
||||||
let mut framebuffers = Vec::with_capacity(image_views.len());
|
let mut framebuffers = Vec::with_capacity(image_views.len());
|
||||||
for image_view in image_views.iter().copied() {
|
for image_view in image_views.iter().copied() {
|
||||||
match create_framebuffer(device, render_pass, image_view, extent) {
|
match create_framebuffer(device, render_pass, image_view, depth_view, extent) {
|
||||||
Ok(framebuffer) => framebuffers.push(framebuffer),
|
Ok(framebuffer) => framebuffers.push(framebuffer),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
// SAFETY: These framebuffers were created above on this live logical device and are destroyed on setup failure.
|
// SAFETY: These framebuffers were created above on this live logical device and are destroyed on setup failure.
|
||||||
@@ -288,6 +324,7 @@ fn create_image_view(
|
|||||||
fn create_render_pass(
|
fn create_render_pass(
|
||||||
device: &VulkanLogicalDeviceProbe,
|
device: &VulkanLogicalDeviceProbe,
|
||||||
format: i32,
|
format: i32,
|
||||||
|
depth_format: vk::Format,
|
||||||
) -> Result<vk::RenderPass, VulkanSmokeRendererError> {
|
) -> Result<vk::RenderPass, VulkanSmokeRendererError> {
|
||||||
let color_attachment = vk::AttachmentDescription::default()
|
let color_attachment = vk::AttachmentDescription::default()
|
||||||
.format(vk::Format::from_raw(format))
|
.format(vk::Format::from_raw(format))
|
||||||
@@ -299,10 +336,23 @@ fn create_render_pass(
|
|||||||
let color_attachment_ref = vk::AttachmentReference::default()
|
let color_attachment_ref = vk::AttachmentReference::default()
|
||||||
.attachment(0)
|
.attachment(0)
|
||||||
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
|
.layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
|
||||||
|
let depth_attachment = vk::AttachmentDescription::default()
|
||||||
|
.format(depth_format)
|
||||||
|
.samples(vk::SampleCountFlags::TYPE_1)
|
||||||
|
.load_op(vk::AttachmentLoadOp::CLEAR)
|
||||||
|
.store_op(vk::AttachmentStoreOp::DONT_CARE)
|
||||||
|
.stencil_load_op(vk::AttachmentLoadOp::DONT_CARE)
|
||||||
|
.stencil_store_op(vk::AttachmentStoreOp::DONT_CARE)
|
||||||
|
.initial_layout(vk::ImageLayout::UNDEFINED)
|
||||||
|
.final_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
|
||||||
|
let depth_attachment_ref = vk::AttachmentReference::default()
|
||||||
|
.attachment(1)
|
||||||
|
.layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
|
||||||
let color_attachments = [color_attachment_ref];
|
let color_attachments = [color_attachment_ref];
|
||||||
let subpass = vk::SubpassDescription::default()
|
let subpass = vk::SubpassDescription::default()
|
||||||
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
|
.pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS)
|
||||||
.color_attachments(&color_attachments);
|
.color_attachments(&color_attachments)
|
||||||
|
.depth_stencil_attachment(&depth_attachment_ref);
|
||||||
let dependency = vk::SubpassDependency::default()
|
let dependency = vk::SubpassDependency::default()
|
||||||
.src_subpass(vk::SUBPASS_EXTERNAL)
|
.src_subpass(vk::SUBPASS_EXTERNAL)
|
||||||
.dst_subpass(0)
|
.dst_subpass(0)
|
||||||
@@ -310,7 +360,7 @@ fn create_render_pass(
|
|||||||
.dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
|
.dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT)
|
||||||
.src_access_mask(vk::AccessFlags::empty())
|
.src_access_mask(vk::AccessFlags::empty())
|
||||||
.dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE);
|
.dst_access_mask(vk::AccessFlags::COLOR_ATTACHMENT_WRITE);
|
||||||
let attachments = [color_attachment];
|
let attachments = [color_attachment, depth_attachment];
|
||||||
let subpasses = [subpass];
|
let subpasses = [subpass];
|
||||||
let dependencies = [dependency];
|
let dependencies = [dependency];
|
||||||
let create_info = vk::RenderPassCreateInfo::default()
|
let create_info = vk::RenderPassCreateInfo::default()
|
||||||
@@ -512,11 +562,6 @@ fn create_graphics_pipeline(
|
|||||||
extent: (u32, u32),
|
extent: (u32, u32),
|
||||||
state: LegacyPipelineState,
|
state: LegacyPipelineState,
|
||||||
) -> Result<vk::Pipeline, VulkanSmokeRendererError> {
|
) -> Result<vk::Pipeline, VulkanSmokeRendererError> {
|
||||||
if state.depth != LegacyDepthMode::Disabled {
|
|
||||||
return Err(VulkanSmokeRendererError::InvalidStaticMesh {
|
|
||||||
context: "static renderer has no depth attachment for requested pipeline state",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if state.alpha_test {
|
if state.alpha_test {
|
||||||
return Err(VulkanSmokeRendererError::InvalidStaticMesh {
|
return Err(VulkanSmokeRendererError::InvalidStaticMesh {
|
||||||
context:
|
context:
|
||||||
@@ -604,6 +649,12 @@ fn create_graphics_pipeline(
|
|||||||
let multisample_state = vk::PipelineMultisampleStateCreateInfo::default()
|
let multisample_state = vk::PipelineMultisampleStateCreateInfo::default()
|
||||||
.sample_shading_enable(false)
|
.sample_shading_enable(false)
|
||||||
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
|
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
|
||||||
|
let depth_stencil_state = vk::PipelineDepthStencilStateCreateInfo::default()
|
||||||
|
.depth_test_enable(state.depth != LegacyDepthMode::Disabled)
|
||||||
|
.depth_write_enable(state.depth == LegacyDepthMode::TestWrite)
|
||||||
|
.depth_compare_op(vk::CompareOp::LESS_OR_EQUAL)
|
||||||
|
.depth_bounds_test_enable(false)
|
||||||
|
.stencil_test_enable(false);
|
||||||
let color_blend_attachment = vk::PipelineColorBlendAttachmentState::default()
|
let color_blend_attachment = vk::PipelineColorBlendAttachmentState::default()
|
||||||
.color_write_mask(
|
.color_write_mask(
|
||||||
vk::ColorComponentFlags::R
|
vk::ColorComponentFlags::R
|
||||||
@@ -629,6 +680,7 @@ fn create_graphics_pipeline(
|
|||||||
.viewport_state(&viewport_state)
|
.viewport_state(&viewport_state)
|
||||||
.rasterization_state(&rasterization_state)
|
.rasterization_state(&rasterization_state)
|
||||||
.multisample_state(&multisample_state)
|
.multisample_state(&multisample_state)
|
||||||
|
.depth_stencil_state(&depth_stencil_state)
|
||||||
.color_blend_state(&color_blend_state)
|
.color_blend_state(&color_blend_state)
|
||||||
.layout(pipeline_layout)
|
.layout(pipeline_layout)
|
||||||
.render_pass(render_pass)
|
.render_pass(render_pass)
|
||||||
@@ -671,9 +723,10 @@ fn create_framebuffer(
|
|||||||
device: &VulkanLogicalDeviceProbe,
|
device: &VulkanLogicalDeviceProbe,
|
||||||
render_pass: vk::RenderPass,
|
render_pass: vk::RenderPass,
|
||||||
image_view: vk::ImageView,
|
image_view: vk::ImageView,
|
||||||
|
depth_view: vk::ImageView,
|
||||||
extent: (u32, u32),
|
extent: (u32, u32),
|
||||||
) -> Result<vk::Framebuffer, VulkanSmokeRendererError> {
|
) -> Result<vk::Framebuffer, VulkanSmokeRendererError> {
|
||||||
let attachments = [image_view];
|
let attachments = [image_view, depth_view];
|
||||||
let create_info = vk::FramebufferCreateInfo::default()
|
let create_info = vk::FramebufferCreateInfo::default()
|
||||||
.render_pass(render_pass)
|
.render_pass(render_pass)
|
||||||
.attachments(&attachments)
|
.attachments(&attachments)
|
||||||
@@ -738,6 +791,7 @@ pub(super) fn destroy_swapchain_resources(
|
|||||||
device
|
device
|
||||||
.device()
|
.device()
|
||||||
.destroy_render_pass(resources.render_pass, None);
|
.destroy_render_pass(resources.render_pass, None);
|
||||||
|
destroy_depth_attachment(device, &resources.depth_attachment);
|
||||||
for image_view in resources.image_views {
|
for image_view in resources.image_views {
|
||||||
device.device().destroy_image_view(image_view, None);
|
device.device().destroy_image_view(image_view, None);
|
||||||
}
|
}
|
||||||
@@ -783,6 +837,9 @@ fn destroy_partial_swapchain_resources(
|
|||||||
if let Some(render_pass) = partial.render_pass {
|
if let Some(render_pass) = partial.render_pass {
|
||||||
device.device().destroy_render_pass(render_pass, None);
|
device.device().destroy_render_pass(render_pass, None);
|
||||||
}
|
}
|
||||||
|
if let Some(depth_attachment) = partial.depth_attachment {
|
||||||
|
destroy_depth_attachment(device, &depth_attachment);
|
||||||
|
}
|
||||||
for image_view in partial.image_views {
|
for image_view in partial.image_views {
|
||||||
device.device().destroy_image_view(image_view, None);
|
device.device().destroy_image_view(image_view, None);
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-7
@@ -845,20 +845,23 @@ swapchain resources, создаёт один `vk::Pipeline` на уникаль
|
|||||||
созданные variants. Baseline оригинального MSH подтверждён с состоянием
|
созданные variants. Baseline оригинального MSH подтверждён с состоянием
|
||||||
`opaque + depth disabled + cull disabled + alpha-test disabled`.
|
`opaque + depth disabled + cull disabled + alpha-test disabled`.
|
||||||
|
|
||||||
`SourceAlpha` и front/back culling имеют Vulkan mapping в этом кэше. Запрос
|
`SourceAlpha`, front/back culling и depth modes имеют Vulkan mapping в этом
|
||||||
depth или alpha-test сейчас завершается явной ошибкой: renderer ещё не имеет
|
кэше. При создании swapchain resources renderer выбирает capability-approved
|
||||||
depth attachment либо alpha-test shader variant, поэтому он не должен молча
|
depth/stencil format, выделяет device-local attachment, очищает его в render
|
||||||
подменять state. Также не установлен источник значений для Batch20/MAT0: их
|
pass и прикрепляет к каждому framebuffer. `TestWrite` включает depth test и
|
||||||
|
write; `TestReadOnly` включает test без write. Alpha-test пока завершается
|
||||||
|
явной ошибкой: shader variant ещё не существует, поэтому renderer не должен
|
||||||
|
молча подменять state. Также не установлен источник значений для Batch20/MAT0: их
|
||||||
поля нельзя объявлять blend/depth/cull mapping без dynamic capture или
|
поля нельзя объявлять blend/depth/cull mapping без dynamic capture или
|
||||||
дополнительного дизассемблирования. Это частично реализованная compatibility
|
дополнительного дизассемблирования. Это частично реализованная compatibility
|
||||||
boundary, а не заявление о готовой parity fixed-function state.
|
boundary, а не заявление о готовой parity fixed-function state.
|
||||||
|
|
||||||
Выбор будущего depth/stencil attachment уже отделён от renderer lifetime:
|
Выбор depth/stencil attachment отделён от renderer lifetime:
|
||||||
`select_depth_stencil_attachment_format` применяет тот же фиксированный порядок
|
`select_depth_stencil_attachment_format` применяет тот же фиксированный порядок
|
||||||
форматов, что и capability gate, к фактически поддерживаемому списку GPU.
|
форматов, что и capability gate, к фактически поддерживаемому списку GPU.
|
||||||
Это исключает ситуацию, когда admission принимает один совместимый формат, а
|
Это исключает ситуацию, когда admission принимает один совместимый формат, а
|
||||||
allocation позднее выбирает другой; сам attachment и render-pass integration
|
allocation позднее выбирает другой; resource уничтожается после framebuffer и
|
||||||
остаются следующей отдельной задачей.
|
render pass при recreate/teardown.
|
||||||
|
|
||||||
После последнего world pass renderer закрывает сцену и выводит back buffer.
|
После последнего world pass renderer закрывает сцену и выводит back buffer.
|
||||||
World3D снимает `in_render`, восстанавливает временный viewport state и вызывает
|
World3D снимает `in_render`, восстанавливает временный viewport state и вызывает
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ S2-PLANNING-RENDER-009 covered cargo xtask policy
|
|||||||
S3-VK-MESH-UPLOAD-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; original MSH reports 128 vertices and 252 indices
|
S3-VK-MESH-UPLOAD-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; original MSH reports 128 vertices and 252 indices
|
||||||
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 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-pipeline-key-cache-smoke.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; PipelineKey selects a deduplicated live Vulkan pipeline per static draw range (baseline key only); unproven Batch20/MAT0 state mapping and unsupported depth/alpha-test variants remain explicit gaps
|
S3-VK-PIPELINE-001 covered-gpu cargo run -p fparkan-vulkan-smoke --release --locked -- --out target/fparkan/stage3-depth-attachment-smoke.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; PipelineKey selects a deduplicated live Vulkan pipeline per static draw range; capability-selected device-local depth attachment supports TestWrite/TestReadOnly, baseline key is live-proven; unproven Batch20/MAT0 state mapping and unsupported alpha-test remain explicit gaps
|
||||||
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-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
|
||||||
|
|||||||
|
Reference in New Issue
Block a user