feat(render): bridge legacy camera to Vulkan
This commit is contained in:
@@ -1 +1 @@
|
|||||||
{"schema":2,"target_env":"vulkan1.1","compiler":{"name":"glslangValidator","version":"11:16.3.0","binary_sha256":"9bcd69d830b350aaa6e2254915ff74e46070e217b67f38daad27c1fc1f22910f"},"validator":{"name":"spirv-val","version":"SPIRV-Tools v2026.2 unknown hash, 2026-04-29T17:02:58+00:00","binary_sha256":"f6d5b96ff19f073f3af0c0bcfa0c18702d288d3ec598efc242d01cd104d8354f"},"modules":[{"name":"triangle.vert","stage":"vertex","entry_point":"main","source_path":"adapters/fparkan-render-vulkan/shaders/triangle.vert","source_sha256":"82f9ee39a30b094203041205954576ca820d75771421bb27ddd300359e4e9043","spirv_path":"adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","word_count":290,"sha256":"1984662f78873b70135ec444ccd86d86fa66dfca3f615d19ddaf0cda1587d4cf","descriptor_sets":0,"push_constant_bytes":0,"compile_command":"glslangValidator -V --target-env vulkan1.1 -S vert -e main adapters/fparkan-render-vulkan/shaders/triangle.vert -o adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","validate_command":"spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","interface_hash":"23e1d3d9d32e7f7ec0b9ca87f8b86be8f8363c7eb5d745fc5a157cb8433eb138"},{"name":"triangle.frag","stage":"fragment","entry_point":"main","source_path":"adapters/fparkan-render-vulkan/shaders/triangle.frag","source_sha256":"f9162fccce56111e85fdf0f58251798393c37aa23f9f3d979ba404bf6773c27b","spirv_path":"adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","word_count":296,"sha256":"536a5c9a4389f9d34ca11a25f20d0acbc53d3eb0782c18375326647319336a85","descriptor_sets":1,"push_constant_bytes":4,"compile_command":"glslangValidator -V --target-env vulkan1.1 -S frag -e main adapters/fparkan-render-vulkan/shaders/triangle.frag -o adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","validate_command":"spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","interface_hash":"7692547c3195074fefc2321e1f4d4a09a3d83c65465cefd2237c8bae1df58d88"}],"manifest_hash":"5a16fb791e86bb790cd2d85151627e0d39193c5c073a9294130f149d4ff5ba58"}
|
{"schema":2,"target_env":"vulkan1.1","compiler":{"name":"glslangValidator","version":"11:16.3.0","binary_sha256":"9bcd69d830b350aaa6e2254915ff74e46070e217b67f38daad27c1fc1f22910f"},"validator":{"name":"spirv-val","version":"SPIRV-Tools v2026.2 unknown hash, 2026-04-29T17:02:58+00:00","binary_sha256":"f6d5b96ff19f073f3af0c0bcfa0c18702d288d3ec598efc242d01cd104d8354f"},"modules":[{"name":"triangle.vert","stage":"vertex","entry_point":"main","source_path":"adapters/fparkan-render-vulkan/shaders/triangle.vert","source_sha256":"fe3721202477220d2d9677b6572d7f3baecb374e94f04eea9d7286ed5e98b6c9","spirv_path":"adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","word_count":358,"sha256":"4e2051ac43b933a57e5557e596bcc95952b4efbfcb45315180f89581e094398d","descriptor_sets":0,"push_constant_bytes":64,"compile_command":"glslangValidator -V --target-env vulkan1.1 -S vert -e main adapters/fparkan-render-vulkan/shaders/triangle.vert -o adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","validate_command":"spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.vert.spv","interface_hash":"f76cc92b9aaa2261eda33bd320503e1be100a2dd7e1a9fcfe13351678dda2a06"},{"name":"triangle.frag","stage":"fragment","entry_point":"main","source_path":"adapters/fparkan-render-vulkan/shaders/triangle.frag","source_sha256":"fb3ed435af48e4fb4a817f160b467f7a561c7c547c87b6f0c1e89f5f58af7329","spirv_path":"adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","word_count":296,"sha256":"8ab7bf835e166892b04b10fa1100258daf355d2f693ad311d014dbee22de5c7b","descriptor_sets":1,"push_constant_bytes":68,"compile_command":"glslangValidator -V --target-env vulkan1.1 -S frag -e main adapters/fparkan-render-vulkan/shaders/triangle.frag -o adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","validate_command":"spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.frag.spv","interface_hash":"91552fce09a408a2baf8a608bc642b6632372d3d2316217051384159ba14cee2"}],"manifest_hash":"cf471972978ddb5c8919711ad64c93d0d64e18b1f590a89ce014f1e2a743cfb5"}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ layout(location = 0) out vec4 out_color;
|
|||||||
layout(set = 0, binding = 0) uniform sampler2D base_color;
|
layout(set = 0, binding = 0) uniform sampler2D base_color;
|
||||||
|
|
||||||
layout(push_constant) uniform AlphaTestConstants {
|
layout(push_constant) uniform AlphaTestConstants {
|
||||||
|
layout(offset = 64)
|
||||||
float alpha_cutoff;
|
float alpha_cutoff;
|
||||||
} alpha_test;
|
} alpha_test;
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -1,14 +1,18 @@
|
|||||||
#version 450
|
#version 450
|
||||||
|
|
||||||
layout(location = 0) in vec2 in_position;
|
layout(location = 0) in vec3 in_position;
|
||||||
layout(location = 1) in vec3 in_color;
|
layout(location = 1) in vec3 in_color;
|
||||||
layout(location = 2) in vec2 in_uv;
|
layout(location = 2) in vec2 in_uv;
|
||||||
|
|
||||||
layout(location = 0) out vec3 out_color;
|
layout(location = 0) out vec3 out_color;
|
||||||
layout(location = 1) out vec2 out_uv;
|
layout(location = 1) out vec2 out_uv;
|
||||||
|
|
||||||
|
layout(push_constant) uniform CameraConstants {
|
||||||
|
mat4 clip_from_world;
|
||||||
|
} camera;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
out_color = in_color;
|
out_color = in_color;
|
||||||
out_uv = in_uv;
|
out_uv = in_uv;
|
||||||
gl_Position = vec4(in_position, 0.0, 1.0);
|
gl_Position = camera.clip_from_world * vec4(in_position, 1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@@ -142,7 +142,11 @@ pub fn project_msh_to_static_mesh_in_xy_frame(
|
|||||||
position[2] * scale[2] + translation[2],
|
position[2] * scale[2] + translation[2],
|
||||||
];
|
];
|
||||||
Ok(VulkanStaticVertex {
|
Ok(VulkanStaticVertex {
|
||||||
position: frame.project(transformed),
|
position: [
|
||||||
|
frame.project(transformed)[0],
|
||||||
|
frame.project(transformed)[1],
|
||||||
|
0.0,
|
||||||
|
],
|
||||||
color: [0.82, 0.72, 0.31],
|
color: [0.82, 0.72, 0.31],
|
||||||
// Iron3D stores Res5 UV0 as signed fixed point with 1/1024 units.
|
// Iron3D stores Res5 UV0 as signed fixed point with 1/1024 units.
|
||||||
// Models that omit this optional stream retain the static viewer's
|
// Models that omit this optional stream retain the static viewer's
|
||||||
@@ -232,7 +236,11 @@ pub fn project_land_msh_to_static_mesh_in_xy_frame(
|
|||||||
return Err(VulkanAssetMeshError::NonFinitePosition);
|
return Err(VulkanAssetMeshError::NonFinitePosition);
|
||||||
}
|
}
|
||||||
Ok(VulkanStaticVertex {
|
Ok(VulkanStaticVertex {
|
||||||
position: frame.project(*position),
|
position: [
|
||||||
|
frame.project(*position)[0],
|
||||||
|
frame.project(*position)[1],
|
||||||
|
0.0,
|
||||||
|
],
|
||||||
color: [0.31, 0.58, 0.27],
|
color: [0.31, 0.58, 0.27],
|
||||||
uv: terrain
|
uv: terrain
|
||||||
.uv0
|
.uv0
|
||||||
@@ -380,9 +388,9 @@ mod tests {
|
|||||||
alpha_test_reference: 0,
|
alpha_test_reference: 0,
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
assert_eq!(mesh.vertices[1].position, [-0.8, -0.8]);
|
assert_eq!(mesh.vertices[1].position, [-0.8, -0.8, 0.0]);
|
||||||
assert_eq!(mesh.vertices[2].position, [0.8, -0.8]);
|
assert_eq!(mesh.vertices[2].position, [0.8, -0.8, 0.0]);
|
||||||
assert_eq!(mesh.vertices[3].position, [-0.8, 0.8]);
|
assert_eq!(mesh.vertices[3].position, [-0.8, 0.8, 0.0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -466,8 +474,8 @@ mod tests {
|
|||||||
assert_eq!(mesh.indices, vec![0, 1, 2, 1, 3, 2]);
|
assert_eq!(mesh.indices, vec![0, 1, 2, 1, 3, 2]);
|
||||||
assert_eq!(mesh.draw_ranges.len(), 1);
|
assert_eq!(mesh.draw_ranges.len(), 1);
|
||||||
assert_eq!(mesh.draw_ranges[0].index_count, 6);
|
assert_eq!(mesh.draw_ranges[0].index_count, 6);
|
||||||
assert_eq!(mesh.vertices[0].position, [-0.8, -0.8]);
|
assert_eq!(mesh.vertices[0].position, [-0.8, -0.8, 0.0]);
|
||||||
assert_eq!(mesh.vertices[3].position, [0.8, 0.8]);
|
assert_eq!(mesh.vertices[3].position, [0.8, 0.8, 0.0]);
|
||||||
assert_eq!(mesh.vertices[0].uv, [1.0, -0.5]);
|
assert_eq!(mesh.vertices[0].uv, [1.0, -0.5]);
|
||||||
assert_eq!(mesh.vertices[2].uv, [-1.0, 0.5]);
|
assert_eq!(mesh.vertices[2].uv, [-1.0, 0.5]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ pub use self::smoke_types::{
|
|||||||
VulkanReadbackArtifact, VulkanSmokeBootstrapProgress, VulkanSmokeBootstrapSnapshot,
|
VulkanReadbackArtifact, VulkanSmokeBootstrapProgress, VulkanSmokeBootstrapSnapshot,
|
||||||
VulkanSmokeFrameOutcome, VulkanSmokeRenderer, VulkanSmokeRendererCreateInfo,
|
VulkanSmokeFrameOutcome, VulkanSmokeRenderer, VulkanSmokeRendererCreateInfo,
|
||||||
VulkanSmokeRendererError, VulkanSmokeRendererReport, VulkanSmokeShutdownReport,
|
VulkanSmokeRendererError, VulkanSmokeRendererReport, VulkanSmokeShutdownReport,
|
||||||
VulkanStaticDrawRange, VulkanStaticMaterial, VulkanStaticMesh, VulkanStaticTexture,
|
VulkanStaticCamera, VulkanStaticDrawRange, VulkanStaticMaterial, VulkanStaticMesh,
|
||||||
VulkanStaticVertex, VulkanValidationReport,
|
VulkanStaticTexture, VulkanStaticVertex, VulkanValidationReport,
|
||||||
};
|
};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use self::surface::extension_name;
|
use self::surface::extension_name;
|
||||||
@@ -497,7 +497,7 @@ const fn spirv_words<const WORD_COUNT: usize>(bytes: &[u8]) -> [u32; WORD_COUNT]
|
|||||||
words
|
words
|
||||||
}
|
}
|
||||||
|
|
||||||
static TRIANGLE_VERTEX_SHADER_DATA: [u32; 290] =
|
static TRIANGLE_VERTEX_SHADER_DATA: [u32; 358] =
|
||||||
spirv_words(include_bytes!("../shaders/triangle.vert.spv"));
|
spirv_words(include_bytes!("../shaders/triangle.vert.spv"));
|
||||||
static TRIANGLE_FRAGMENT_SHADER_DATA: [u32; 296] =
|
static TRIANGLE_FRAGMENT_SHADER_DATA: [u32; 296] =
|
||||||
spirv_words(include_bytes!("../shaders/triangle.frag.spv"));
|
spirv_words(include_bytes!("../shaders/triangle.frag.spv"));
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ pub(super) fn create_static_mesh_vertex_buffer(
|
|||||||
device: &VulkanLogicalDeviceProbe,
|
device: &VulkanLogicalDeviceProbe,
|
||||||
mesh: &VulkanStaticMesh,
|
mesh: &VulkanStaticMesh,
|
||||||
) -> Result<VulkanAllocatedBuffer, VulkanSmokeRendererError> {
|
) -> Result<VulkanAllocatedBuffer, VulkanSmokeRendererError> {
|
||||||
let mut bytes = Vec::with_capacity(mesh.vertices.len() * 7 * std::mem::size_of::<f32>());
|
let mut bytes = Vec::with_capacity(mesh.vertices.len() * 8 * std::mem::size_of::<f32>());
|
||||||
for vertex in &mesh.vertices {
|
for vertex in &mesh.vertices {
|
||||||
for value in vertex
|
for value in vertex
|
||||||
.position
|
.position
|
||||||
|
|||||||
@@ -111,6 +111,11 @@ impl VulkanSmokeRenderer {
|
|||||||
.mesh
|
.mesh
|
||||||
.validate()
|
.validate()
|
||||||
.map_err(|context| VulkanSmokeRendererError::InvalidStaticMesh { context })?;
|
.map_err(|context| VulkanSmokeRendererError::InvalidStaticMesh { context })?;
|
||||||
|
if !create_info.camera.is_finite() {
|
||||||
|
return Err(VulkanSmokeRendererError::InvalidStaticCamera {
|
||||||
|
context: "non-finite clip_from_world matrix",
|
||||||
|
});
|
||||||
|
}
|
||||||
let draw_texture_indices =
|
let draw_texture_indices =
|
||||||
resolve_draw_texture_indices(&create_info.mesh.draw_ranges, &create_info.materials)
|
resolve_draw_texture_indices(&create_info.mesh.draw_ranges, &create_info.materials)
|
||||||
.map_err(|context| VulkanSmokeRendererError::InvalidStaticMesh { context })?;
|
.map_err(|context| VulkanSmokeRendererError::InvalidStaticMesh { context })?;
|
||||||
@@ -226,6 +231,7 @@ impl VulkanSmokeRenderer {
|
|||||||
textures,
|
textures,
|
||||||
draw_ranges: create_info.mesh.draw_ranges.clone(),
|
draw_ranges: create_info.mesh.draw_ranges.clone(),
|
||||||
draw_texture_indices,
|
draw_texture_indices,
|
||||||
|
camera: create_info.camera,
|
||||||
frame_sync: Vec::new(),
|
frame_sync: Vec::new(),
|
||||||
images_in_flight: Vec::new(),
|
images_in_flight: Vec::new(),
|
||||||
current_frame: 0,
|
current_frame: 0,
|
||||||
@@ -671,6 +677,14 @@ impl VulkanSmokeRenderer {
|
|||||||
0,
|
0,
|
||||||
vk::IndexType::UINT16,
|
vk::IndexType::UINT16,
|
||||||
);
|
);
|
||||||
|
let clip_from_world = matrix_bytes(self.camera.clip_from_world);
|
||||||
|
device.device().cmd_push_constants(
|
||||||
|
command_buffer,
|
||||||
|
resources.pipeline_layout,
|
||||||
|
vk::ShaderStageFlags::VERTEX,
|
||||||
|
0,
|
||||||
|
&clip_from_world,
|
||||||
|
);
|
||||||
for (range, &texture_index) in self.draw_ranges.iter().zip(&self.draw_texture_indices) {
|
for (range, &texture_index) in self.draw_ranges.iter().zip(&self.draw_texture_indices) {
|
||||||
let pipeline = resources.pipelines.get(&range.pipeline_key()).ok_or(
|
let pipeline = resources.pipelines.get(&range.pipeline_key()).ok_or(
|
||||||
VulkanSmokeRendererError::InvariantViolation {
|
VulkanSmokeRendererError::InvariantViolation {
|
||||||
@@ -700,7 +714,7 @@ impl VulkanSmokeRenderer {
|
|||||||
command_buffer,
|
command_buffer,
|
||||||
resources.pipeline_layout,
|
resources.pipeline_layout,
|
||||||
vk::ShaderStageFlags::FRAGMENT,
|
vk::ShaderStageFlags::FRAGMENT,
|
||||||
0,
|
64,
|
||||||
&alpha_cutoff,
|
&alpha_cutoff,
|
||||||
);
|
);
|
||||||
device.device().cmd_draw_indexed(
|
device.device().cmd_draw_indexed(
|
||||||
@@ -928,6 +942,14 @@ impl VulkanSmokeRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn matrix_bytes(matrix: [f32; 16]) -> [u8; 64] {
|
||||||
|
let mut bytes = [0; 64];
|
||||||
|
for (index, value) in matrix.into_iter().enumerate() {
|
||||||
|
bytes[index * 4..(index + 1) * 4].copy_from_slice(&value.to_ne_bytes());
|
||||||
|
}
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
fn fnv1a64(bytes: &[u8]) -> u64 {
|
fn fnv1a64(bytes: &[u8]) -> u64 {
|
||||||
bytes.iter().fold(0xcbf2_9ce4_8422_2325_u64, |hash, byte| {
|
bytes.iter().fold(0xcbf2_9ce4_8422_2325_u64, |hash, byte| {
|
||||||
(hash ^ u64::from(*byte)).wrapping_mul(0x0000_0100_0000_01b3)
|
(hash ^ u64::from(*byte)).wrapping_mul(0x0000_0100_0000_01b3)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use ash::vk;
|
use ash::vk;
|
||||||
use fparkan_platform::{NativeWindowHandles, RenderRequest};
|
use fparkan_platform::{NativeWindowHandles, RenderRequest};
|
||||||
use fparkan_render::{LegacyPipelineState, PipelineKey};
|
use fparkan_render::{LegacyD3d7Projection, LegacyPipelineState, PipelineKey, RawCameraTransform};
|
||||||
use std::sync::atomic::{AtomicU8, Ordering};
|
use std::sync::atomic::{AtomicU8, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -26,10 +26,11 @@ pub struct VulkanSmokeRendererCreateInfo {
|
|||||||
/// Whether validation layers must be enabled.
|
/// Whether validation layers must be enabled.
|
||||||
pub enable_validation: bool,
|
pub enable_validation: bool,
|
||||||
/// Static indexed geometry uploaded before the first live frame.
|
/// Static indexed geometry uploaded before the first live frame.
|
||||||
///
|
|
||||||
/// This initial bridge keeps positions in clip-space. MSH transforms,
|
|
||||||
/// materials, and textures are deliberately higher-level Stage 3 work.
|
|
||||||
pub mesh: VulkanStaticMesh,
|
pub mesh: VulkanStaticMesh,
|
||||||
|
/// Camera contract for static geometry.
|
||||||
|
///
|
||||||
|
/// The default identity matrix retains the initial XY diagnostic viewer.
|
||||||
|
pub camera: VulkanStaticCamera,
|
||||||
/// Material textures uploaded before the first live frame.
|
/// Material textures uploaded before the first live frame.
|
||||||
///
|
///
|
||||||
/// An empty list retains the compatibility white fallback. A singleton list
|
/// An empty list retains the compatibility white fallback. A singleton list
|
||||||
@@ -43,14 +44,68 @@ pub struct VulkanSmokeRendererCreateInfo {
|
|||||||
/// One vertex accepted by the initial static Vulkan geometry path.
|
/// One vertex accepted by the initial static Vulkan geometry path.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct VulkanStaticVertex {
|
pub struct VulkanStaticVertex {
|
||||||
/// Position in Vulkan clip-space XY coordinates.
|
/// World or clip position transformed by [`VulkanStaticCamera`].
|
||||||
pub position: [f32; 2],
|
pub position: [f32; 3],
|
||||||
/// Linear RGB vertex color.
|
/// Linear RGB vertex color.
|
||||||
pub color: [f32; 3],
|
pub color: [f32; 3],
|
||||||
/// Texture coordinate consumed by the static material bridge.
|
/// Texture coordinate consumed by the static material bridge.
|
||||||
pub uv: [f32; 2],
|
pub uv: [f32; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A static geometry camera represented in the shader's matrix memory order.
|
||||||
|
///
|
||||||
|
/// `clip_from_world` contains row-major D3D7 data. GLSL's default column-major
|
||||||
|
/// `mat4` interpretation deliberately transposes that storage, making
|
||||||
|
/// `matrix * vec4(position, 1)` equivalent to D3D7's row-vector multiplication.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct VulkanStaticCamera {
|
||||||
|
/// Row-major clip-from-world transform consumed by the vertex shader.
|
||||||
|
pub clip_from_world: [f32; 16],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VulkanStaticCamera {
|
||||||
|
/// Builds a static camera from the recovered Ngi32 D3D7 camera contract.
|
||||||
|
#[must_use]
|
||||||
|
pub fn from_legacy_d3d7(
|
||||||
|
transform: RawCameraTransform,
|
||||||
|
projection: LegacyD3d7Projection,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let view = transform.try_direct3d7_view_row_major()?;
|
||||||
|
let projection = projection.try_direct3d7_projection_row_major()?;
|
||||||
|
Some(Self {
|
||||||
|
clip_from_world: multiply_row_major(view, projection),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether every matrix element is finite.
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_finite(self) -> bool {
|
||||||
|
self.clip_from_world.iter().all(|value| value.is_finite())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for VulkanStaticCamera {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
clip_from_world: [
|
||||||
|
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn multiply_row_major(left: [f32; 16], right: [f32; 16]) -> [f32; 16] {
|
||||||
|
let mut result = [0.0; 16];
|
||||||
|
for row in 0..4 {
|
||||||
|
for column in 0..4 {
|
||||||
|
result[row * 4 + column] = (0..4)
|
||||||
|
.map(|inner| left[row * 4 + inner] * right[inner * 4 + column])
|
||||||
|
.sum();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
/// Static indexed geometry uploaded to live Vulkan buffers.
|
/// Static indexed geometry uploaded to live Vulkan buffers.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct VulkanStaticMesh {
|
pub struct VulkanStaticMesh {
|
||||||
@@ -176,17 +231,17 @@ impl VulkanStaticMesh {
|
|||||||
Self {
|
Self {
|
||||||
vertices: vec![
|
vertices: vec![
|
||||||
VulkanStaticVertex {
|
VulkanStaticVertex {
|
||||||
position: [0.0, -0.55],
|
position: [0.0, -0.55, 0.0],
|
||||||
color: [1.0, 0.2, 0.2],
|
color: [1.0, 0.2, 0.2],
|
||||||
uv: [0.5, 0.0],
|
uv: [0.5, 0.0],
|
||||||
},
|
},
|
||||||
VulkanStaticVertex {
|
VulkanStaticVertex {
|
||||||
position: [0.55, 0.55],
|
position: [0.55, 0.55, 0.0],
|
||||||
color: [0.2, 1.0, 0.2],
|
color: [0.2, 1.0, 0.2],
|
||||||
uv: [1.0, 1.0],
|
uv: [1.0, 1.0],
|
||||||
},
|
},
|
||||||
VulkanStaticVertex {
|
VulkanStaticVertex {
|
||||||
position: [-0.55, 0.55],
|
position: [-0.55, 0.55, 0.0],
|
||||||
color: [0.2, 0.4, 1.0],
|
color: [0.2, 0.4, 1.0],
|
||||||
uv: [0.0, 1.0],
|
uv: [0.0, 1.0],
|
||||||
},
|
},
|
||||||
@@ -250,6 +305,44 @@ mod static_mesh_tests {
|
|||||||
assert_eq!(mesh.validate(), Ok(()));
|
assert_eq!(mesh.validate(), Ok(()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn static_camera_composes_recovered_d3d7_view_before_projection() {
|
||||||
|
let transform = RawCameraTransform {
|
||||||
|
words: [
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
(-1.0_f32).to_bits(),
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
10.0_f32.to_bits(),
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
20.0_f32.to_bits(),
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
30.0_f32.to_bits(),
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
0.0_f32.to_bits(),
|
||||||
|
1.0_f32.to_bits(),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
let projection = LegacyD3d7Projection {
|
||||||
|
viewport: [0, 0, 1024, 768],
|
||||||
|
near_plane: 0.5,
|
||||||
|
far_plane: 700.0,
|
||||||
|
field_of_view_radians: 1.3,
|
||||||
|
};
|
||||||
|
|
||||||
|
let camera = VulkanStaticCamera::from_legacy_d3d7(transform, projection)
|
||||||
|
.expect("recovered camera inputs are valid");
|
||||||
|
|
||||||
|
assert!(camera.is_finite());
|
||||||
|
assert_eq!(camera.clip_from_world[0], 0.65_f32.cos());
|
||||||
|
assert_eq!(camera.clip_from_world[6], 700.0_f32 / 699.5);
|
||||||
|
assert_eq!(camera.clip_from_world[7], 0.65_f32.sin());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn static_mesh_rejects_bad_triangle_topology_and_indices() {
|
fn static_mesh_rejects_bad_triangle_topology_and_indices() {
|
||||||
let no_vertices = VulkanStaticMesh {
|
let no_vertices = VulkanStaticMesh {
|
||||||
@@ -547,6 +640,11 @@ pub enum VulkanSmokeRendererError {
|
|||||||
/// Validation failure detail.
|
/// Validation failure detail.
|
||||||
context: &'static str,
|
context: &'static str,
|
||||||
},
|
},
|
||||||
|
/// The static camera matrix contains a non-finite element.
|
||||||
|
InvalidStaticCamera {
|
||||||
|
/// Validation failure detail.
|
||||||
|
context: &'static str,
|
||||||
|
},
|
||||||
/// The submitted static texture cannot be represented by this path.
|
/// The submitted static texture cannot be represented by this path.
|
||||||
InvalidStaticTexture {
|
InvalidStaticTexture {
|
||||||
/// Validation failure detail.
|
/// Validation failure detail.
|
||||||
@@ -574,6 +672,9 @@ impl std::fmt::Display for VulkanSmokeRendererError {
|
|||||||
write!(f, "{context}: no compatible Vulkan memory type")
|
write!(f, "{context}: no compatible Vulkan memory type")
|
||||||
}
|
}
|
||||||
Self::InvalidStaticMesh { context } => write!(f, "invalid static mesh: {context}"),
|
Self::InvalidStaticMesh { context } => write!(f, "invalid static mesh: {context}"),
|
||||||
|
Self::InvalidStaticCamera { context } => {
|
||||||
|
write!(f, "invalid static camera: {context}")
|
||||||
|
}
|
||||||
Self::InvalidStaticTexture { context } => {
|
Self::InvalidStaticTexture { context } => {
|
||||||
write!(f, "invalid static texture: {context}")
|
write!(f, "invalid static texture: {context}")
|
||||||
}
|
}
|
||||||
@@ -600,6 +701,7 @@ pub struct VulkanSmokeRenderer {
|
|||||||
pub(super) textures: Vec<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) draw_texture_indices: Vec<usize>,
|
||||||
|
pub(super) camera: VulkanStaticCamera,
|
||||||
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,
|
||||||
|
|||||||
@@ -411,10 +411,16 @@ fn create_pipeline_layout(
|
|||||||
descriptor_set_layout: vk::DescriptorSetLayout,
|
descriptor_set_layout: vk::DescriptorSetLayout,
|
||||||
) -> Result<vk::PipelineLayout, VulkanSmokeRendererError> {
|
) -> Result<vk::PipelineLayout, VulkanSmokeRendererError> {
|
||||||
let set_layouts = [descriptor_set_layout];
|
let set_layouts = [descriptor_set_layout];
|
||||||
let push_constant_ranges = [vk::PushConstantRange::default()
|
let push_constant_ranges = [
|
||||||
.stage_flags(vk::ShaderStageFlags::FRAGMENT)
|
vk::PushConstantRange::default()
|
||||||
|
.stage_flags(vk::ShaderStageFlags::VERTEX)
|
||||||
.offset(0)
|
.offset(0)
|
||||||
.size(u32::try_from(std::mem::size_of::<f32>()).unwrap_or(u32::MAX))];
|
.size(64),
|
||||||
|
vk::PushConstantRange::default()
|
||||||
|
.stage_flags(vk::ShaderStageFlags::FRAGMENT)
|
||||||
|
.offset(64)
|
||||||
|
.size(u32::try_from(std::mem::size_of::<f32>()).unwrap_or(u32::MAX)),
|
||||||
|
];
|
||||||
let create_info = vk::PipelineLayoutCreateInfo::default()
|
let create_info = vk::PipelineLayoutCreateInfo::default()
|
||||||
.set_layouts(&set_layouts)
|
.set_layouts(&set_layouts)
|
||||||
.push_constant_ranges(&push_constant_ranges);
|
.push_constant_ranges(&push_constant_ranges);
|
||||||
@@ -620,24 +626,24 @@ fn create_graphics_pipeline(
|
|||||||
];
|
];
|
||||||
let vertex_binding = vk::VertexInputBindingDescription::default()
|
let vertex_binding = vk::VertexInputBindingDescription::default()
|
||||||
.binding(0)
|
.binding(0)
|
||||||
.stride(u32::try_from(7 * std::mem::size_of::<f32>()).unwrap_or(u32::MAX))
|
.stride(u32::try_from(8 * std::mem::size_of::<f32>()).unwrap_or(u32::MAX))
|
||||||
.input_rate(vk::VertexInputRate::VERTEX);
|
.input_rate(vk::VertexInputRate::VERTEX);
|
||||||
let vertex_attributes = [
|
let vertex_attributes = [
|
||||||
vk::VertexInputAttributeDescription::default()
|
vk::VertexInputAttributeDescription::default()
|
||||||
.binding(0)
|
.binding(0)
|
||||||
.location(0)
|
.location(0)
|
||||||
.format(vk::Format::R32G32_SFLOAT)
|
.format(vk::Format::R32G32B32_SFLOAT)
|
||||||
.offset(0),
|
.offset(0),
|
||||||
vk::VertexInputAttributeDescription::default()
|
vk::VertexInputAttributeDescription::default()
|
||||||
.binding(0)
|
.binding(0)
|
||||||
.location(1)
|
.location(1)
|
||||||
.format(vk::Format::R32G32B32_SFLOAT)
|
.format(vk::Format::R32G32B32_SFLOAT)
|
||||||
.offset(u32::try_from(2 * std::mem::size_of::<f32>()).unwrap_or(u32::MAX)),
|
.offset(u32::try_from(3 * std::mem::size_of::<f32>()).unwrap_or(u32::MAX)),
|
||||||
vk::VertexInputAttributeDescription::default()
|
vk::VertexInputAttributeDescription::default()
|
||||||
.binding(0)
|
.binding(0)
|
||||||
.location(2)
|
.location(2)
|
||||||
.format(vk::Format::R32G32_SFLOAT)
|
.format(vk::Format::R32G32_SFLOAT)
|
||||||
.offset(u32::try_from(5 * std::mem::size_of::<f32>()).unwrap_or(u32::MAX)),
|
.offset(u32::try_from(6 * std::mem::size_of::<f32>()).unwrap_or(u32::MAX)),
|
||||||
];
|
];
|
||||||
let vertex_bindings = [vertex_binding];
|
let vertex_bindings = [vertex_binding];
|
||||||
let vertex_input_state = vk::PipelineVertexInputStateCreateInfo::default()
|
let vertex_input_state = vk::PipelineVertexInputStateCreateInfo::default()
|
||||||
|
|||||||
@@ -669,13 +669,13 @@ fn triangle_shader_manifest_hashes_are_stable() {
|
|||||||
TRIANGLE_VERTEX_SOURCE_SHA256
|
TRIANGLE_VERTEX_SOURCE_SHA256
|
||||||
);
|
);
|
||||||
assert_eq!(report.modules[0].spirv_path, TRIANGLE_VERTEX_SPIRV_PATH);
|
assert_eq!(report.modules[0].spirv_path, TRIANGLE_VERTEX_SPIRV_PATH);
|
||||||
assert_eq!(report.modules[0].word_count, 290);
|
assert_eq!(report.modules[0].word_count, 358);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
report.modules[0].sha256,
|
report.modules[0].sha256,
|
||||||
"1984662f78873b70135ec444ccd86d86fa66dfca3f615d19ddaf0cda1587d4cf"
|
"4e2051ac43b933a57e5557e596bcc95952b4efbfcb45315180f89581e094398d"
|
||||||
);
|
);
|
||||||
assert_eq!(report.modules[0].descriptor_sets, 0);
|
assert_eq!(report.modules[0].descriptor_sets, 0);
|
||||||
assert_eq!(report.modules[0].push_constant_bytes, 0);
|
assert_eq!(report.modules[0].push_constant_bytes, 64);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
report.modules[0].compile_command,
|
report.modules[0].compile_command,
|
||||||
TRIANGLE_VERTEX_COMPILE_COMMAND
|
TRIANGLE_VERTEX_COMPILE_COMMAND
|
||||||
@@ -687,11 +687,11 @@ fn triangle_shader_manifest_hashes_are_stable() {
|
|||||||
assert!(!report.modules[0].interface_hash.is_empty());
|
assert!(!report.modules[0].interface_hash.is_empty());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
report.modules[1].sha256,
|
report.modules[1].sha256,
|
||||||
"536a5c9a4389f9d34ca11a25f20d0acbc53d3eb0782c18375326647319336a85"
|
"8ab7bf835e166892b04b10fa1100258daf355d2f693ad311d014dbee22de5c7b"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
report.manifest_hash,
|
report.manifest_hash,
|
||||||
"5a16fb791e86bb790cd2d85151627e0d39193c5c073a9294130f149d4ff5ba58"
|
"cf471972978ddb5c8919711ad64c93d0d64e18b1f590a89ce014f1e2a743cfb5"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ pub(crate) const SPIRV_VALIDATOR_BINARY_SHA256: &str =
|
|||||||
pub(crate) const TRIANGLE_VERTEX_SOURCE_PATH: &str =
|
pub(crate) const TRIANGLE_VERTEX_SOURCE_PATH: &str =
|
||||||
"adapters/fparkan-render-vulkan/shaders/triangle.vert";
|
"adapters/fparkan-render-vulkan/shaders/triangle.vert";
|
||||||
pub(crate) const TRIANGLE_VERTEX_SOURCE_SHA256: &str =
|
pub(crate) const TRIANGLE_VERTEX_SOURCE_SHA256: &str =
|
||||||
"82f9ee39a30b094203041205954576ca820d75771421bb27ddd300359e4e9043";
|
"fe3721202477220d2d9677b6572d7f3baecb374e94f04eea9d7286ed5e98b6c9";
|
||||||
pub(crate) const TRIANGLE_VERTEX_SPIRV_PATH: &str =
|
pub(crate) const TRIANGLE_VERTEX_SPIRV_PATH: &str =
|
||||||
"adapters/fparkan-render-vulkan/shaders/triangle.vert.spv";
|
"adapters/fparkan-render-vulkan/shaders/triangle.vert.spv";
|
||||||
pub(crate) const TRIANGLE_VERTEX_COMPILE_COMMAND: &str =
|
pub(crate) const TRIANGLE_VERTEX_COMPILE_COMMAND: &str =
|
||||||
@@ -29,7 +29,7 @@ pub(crate) const TRIANGLE_VERTEX_VALIDATE_COMMAND: &str =
|
|||||||
"spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.vert.spv";
|
"spirv-val --target-env vulkan1.1 adapters/fparkan-render-vulkan/shaders/triangle.vert.spv";
|
||||||
const TRIANGLE_FRAGMENT_SOURCE_PATH: &str = "adapters/fparkan-render-vulkan/shaders/triangle.frag";
|
const TRIANGLE_FRAGMENT_SOURCE_PATH: &str = "adapters/fparkan-render-vulkan/shaders/triangle.frag";
|
||||||
const TRIANGLE_FRAGMENT_SOURCE_SHA256: &str =
|
const TRIANGLE_FRAGMENT_SOURCE_SHA256: &str =
|
||||||
"f9162fccce56111e85fdf0f58251798393c37aa23f9f3d979ba404bf6773c27b";
|
"fb3ed435af48e4fb4a817f160b467f7a561c7c547c87b6f0c1e89f5f58af7329";
|
||||||
const TRIANGLE_FRAGMENT_SPIRV_PATH: &str =
|
const TRIANGLE_FRAGMENT_SPIRV_PATH: &str =
|
||||||
"adapters/fparkan-render-vulkan/shaders/triangle.frag.spv";
|
"adapters/fparkan-render-vulkan/shaders/triangle.frag.spv";
|
||||||
const TRIANGLE_FRAGMENT_COMPILE_COMMAND: &str =
|
const TRIANGLE_FRAGMENT_COMPILE_COMMAND: &str =
|
||||||
@@ -213,7 +213,7 @@ pub fn triangle_shader_manifest() -> Vec<VulkanShaderModuleManifest> {
|
|||||||
stage: VulkanShaderStage::Vertex,
|
stage: VulkanShaderStage::Vertex,
|
||||||
entry_point: "main",
|
entry_point: "main",
|
||||||
descriptor_sets: 0,
|
descriptor_sets: 0,
|
||||||
push_constant_bytes: 0,
|
push_constant_bytes: 64,
|
||||||
source_path: TRIANGLE_VERTEX_SOURCE_PATH,
|
source_path: TRIANGLE_VERTEX_SOURCE_PATH,
|
||||||
source_sha256: TRIANGLE_VERTEX_SOURCE_SHA256,
|
source_sha256: TRIANGLE_VERTEX_SOURCE_SHA256,
|
||||||
spirv_path: TRIANGLE_VERTEX_SPIRV_PATH,
|
spirv_path: TRIANGLE_VERTEX_SPIRV_PATH,
|
||||||
@@ -226,7 +226,7 @@ pub fn triangle_shader_manifest() -> Vec<VulkanShaderModuleManifest> {
|
|||||||
stage: VulkanShaderStage::Fragment,
|
stage: VulkanShaderStage::Fragment,
|
||||||
entry_point: "main",
|
entry_point: "main",
|
||||||
descriptor_sets: 1,
|
descriptor_sets: 1,
|
||||||
push_constant_bytes: 4,
|
push_constant_bytes: 68,
|
||||||
source_path: TRIANGLE_FRAGMENT_SOURCE_PATH,
|
source_path: TRIANGLE_FRAGMENT_SOURCE_PATH,
|
||||||
source_sha256: TRIANGLE_FRAGMENT_SOURCE_SHA256,
|
source_sha256: TRIANGLE_FRAGMENT_SOURCE_SHA256,
|
||||||
spirv_path: TRIANGLE_FRAGMENT_SPIRV_PATH,
|
spirv_path: TRIANGLE_FRAGMENT_SPIRV_PATH,
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ use fparkan_render::{
|
|||||||
use fparkan_render_vulkan::{
|
use fparkan_render_vulkan::{
|
||||||
project_land_msh_to_static_mesh_in_xy_frame, project_msh_to_static_mesh_in_xy_frame,
|
project_land_msh_to_static_mesh_in_xy_frame, project_msh_to_static_mesh_in_xy_frame,
|
||||||
VulkanPlanningBackend, VulkanSmokeFrameOutcome, VulkanSmokeRenderer,
|
VulkanPlanningBackend, VulkanSmokeFrameOutcome, VulkanSmokeRenderer,
|
||||||
VulkanSmokeRendererCreateInfo, VulkanStaticMaterial, VulkanStaticMesh, VulkanStaticTexture,
|
VulkanSmokeRendererCreateInfo, VulkanStaticCamera, VulkanStaticMaterial, VulkanStaticMesh,
|
||||||
VulkanStaticXyFrame,
|
VulkanStaticTexture, VulkanStaticXyFrame,
|
||||||
};
|
};
|
||||||
use fparkan_runtime::{
|
use fparkan_runtime::{
|
||||||
create, frame, load_mission, load_mission_static_preview, load_mission_static_preview_roots,
|
create, frame, load_mission, load_mission_static_preview, load_mission_static_preview_roots,
|
||||||
@@ -658,6 +658,7 @@ impl ApplicationHandler for StaticVulkanApp {
|
|||||||
render_request: WinitWindow::default_render_request(),
|
render_request: WinitWindow::default_render_request(),
|
||||||
enable_validation: true,
|
enable_validation: true,
|
||||||
mesh: self.mesh.clone(),
|
mesh: self.mesh.clone(),
|
||||||
|
camera: VulkanStaticCamera::default(),
|
||||||
materials: self.materials.clone(),
|
materials: self.materials.clone(),
|
||||||
bootstrap_progress: None,
|
bootstrap_progress: None,
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ use fparkan_render_vulkan::{
|
|||||||
project_land_msh_to_static_mesh, project_msh_to_static_mesh, VulkanReadbackArtifact,
|
project_land_msh_to_static_mesh, project_msh_to_static_mesh, VulkanReadbackArtifact,
|
||||||
VulkanSmokeBootstrapProgress, VulkanSmokeFrameOutcome, VulkanSmokeRenderer,
|
VulkanSmokeBootstrapProgress, VulkanSmokeFrameOutcome, VulkanSmokeRenderer,
|
||||||
VulkanSmokeRendererCreateInfo, VulkanSmokeRendererReport, VulkanSmokeShutdownReport,
|
VulkanSmokeRendererCreateInfo, VulkanSmokeRendererReport, VulkanSmokeShutdownReport,
|
||||||
VulkanStaticMaterial, VulkanStaticMesh, VulkanStaticTexture, VulkanValidationReport,
|
VulkanStaticCamera, VulkanStaticMaterial, VulkanStaticMesh, VulkanStaticTexture,
|
||||||
|
VulkanValidationReport,
|
||||||
};
|
};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -1175,6 +1176,7 @@ 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(),
|
||||||
|
camera: VulkanStaticCamera::default(),
|
||||||
materials: self
|
materials: self
|
||||||
.materials
|
.materials
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
+14
-4
@@ -1412,10 +1412,20 @@ An elevated read-only AutoDemo probe sampled a 1024×768 renderer with
|
|||||||
near=`0.5`, far=`700`, FOV=`1.3` radians and showed its view-source pointer
|
near=`0.5`, far=`700`, FOV=`1.3` radians and showed its view-source pointer
|
||||||
byte-identical to the active Terrain outer camera's selector-0 block. This
|
byte-identical to the active Terrain outer camera's selector-0 block. This
|
||||||
proves ownership and the D3D7 boundary, while keeping the earlier
|
proves ownership and the D3D7 boundary, while keeping the earlier
|
||||||
`CBufferingCamera` FOV=`1.04` as a distinct upstream interface value. The
|
`CBufferingCamera` FOV=`1.04` as a distinct upstream interface value.
|
||||||
backend-neutral render crate now exposes the two recovered D3D7 matrices, but
|
|
||||||
does not yet use them as Vulkan matrices: vector and clip-space conversion is
|
The Vulkan static path now accepts this recovered camera as
|
||||||
still an explicit next task.
|
`VulkanStaticCamera`. It composes the row-major D3D7 result as
|
||||||
|
`view * projection`, uploads its 64 bytes through a vertex-stage push constant,
|
||||||
|
and changes static vertices from XY to XYZ. GLSL reads the same bytes as its
|
||||||
|
default column-major `mat4`; therefore `matrix * vec4(position, 1)` is the
|
||||||
|
transpose-equivalent of the original D3D7 row-vector multiplication. Both APIs
|
||||||
|
use the 0..1 depth range, and this renderer uses a positive viewport height, so
|
||||||
|
this narrow bridge needs no extra depth or Y correction. The fragment alpha
|
||||||
|
cutoff moved to byte offset 64 in its own push-constant range. The default
|
||||||
|
identity camera deliberately preserves the old XY diagnostic preview; feeding
|
||||||
|
live terrain camera/projection values into mission rendering remains later
|
||||||
|
runtime wiring, not a claim of scene parity.
|
||||||
|
|
||||||
A fresh no-input launch of the canonical `iron_3d.exe` did create a responsive
|
A fresh no-input launch of the canonical `iron_3d.exe` did create a responsive
|
||||||
window titled `Parkan. Железная Стратегия`. A read-only probe then requested
|
window titled `Parkan. Железная Стратегия`. A read-only probe then requested
|
||||||
|
|||||||
Reference in New Issue
Block a user