fix(render): match native reflection target sizes
Docs Deploy / Build and Deploy MkDocs (push) Successful in 35s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 35s
This commit is contained in:
5 files changed
+68
-16
No files matched your search
@@ -221,6 +221,9 @@ impl ReflectionResources {
|
|||||||
depth_request: fparkan_platform::DepthStencilSupport,
|
depth_request: fparkan_platform::DepthStencilSupport,
|
||||||
render_pass: vk::RenderPass,
|
render_pass: vk::RenderPass,
|
||||||
) -> Result<(), VulkanSmokeRendererError> {
|
) -> Result<(), VulkanSmokeRendererError> {
|
||||||
|
let side = fparkan_render::water::legacy_embm_reflection_size([extent.0, extent.1]);
|
||||||
|
let extent = (side, side);
|
||||||
|
eprintln!("render_reflection target_extent={side}x{side}");
|
||||||
let info = vk::ImageCreateInfo::default()
|
let info = vk::ImageCreateInfo::default()
|
||||||
.image_type(vk::ImageType::TYPE_2D)
|
.image_type(vk::ImageType::TYPE_2D)
|
||||||
.format(vk::Format::from_raw(format))
|
.format(vk::Format::from_raw(format))
|
||||||
@@ -236,7 +239,7 @@ impl ReflectionResources {
|
|||||||
.usage(vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::SAMPLED)
|
.usage(vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::SAMPLED)
|
||||||
.sharing_mode(vk::SharingMode::EXCLUSIVE)
|
.sharing_mode(vk::SharingMode::EXCLUSIVE)
|
||||||
.initial_layout(vk::ImageLayout::UNDEFINED);
|
.initial_layout(vk::ImageLayout::UNDEFINED);
|
||||||
// SAFETY: This target uses the validated non-zero swapchain extent and format.
|
// SAFETY: The validated swapchain format is retained; the square side is in 2..=1024.
|
||||||
let image = unsafe { self.device.create_image(&info, None) }.map_err(|result| {
|
let image = unsafe { self.device.create_image(&info, None) }.map_err(|result| {
|
||||||
VulkanSmokeRendererError::VulkanOperation {
|
VulkanSmokeRendererError::VulkanOperation {
|
||||||
context: "vkCreateImage(reflection)",
|
context: "vkCreateImage(reflection)",
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ impl VulkanSmokeRenderer {
|
|||||||
plane_heights.sort_unstable();
|
plane_heights.sort_unstable();
|
||||||
plane_heights.dedup();
|
plane_heights.dedup();
|
||||||
if !plane_heights.is_empty() {
|
if !plane_heights.is_empty() {
|
||||||
eprintln!("render_coverage planar_reflection_render_context_approx={} (native shifted window and bump UV; main-view depth range, sky/fog/FX and swapchain-sized target remain approximations; non-perspective cameras use mirror fallback)", plane_heights.len());
|
eprintln!("render_coverage planar_reflection_render_context_approx={} (native shifted window, target size and bump UV; main-view depth range and sky/fog/FX remain approximations; non-perspective cameras use mirror fallback)", plane_heights.len());
|
||||||
eprintln!("render_coverage planar_reflection_other_planes_unsupported={} (nearest active plane is reflected)", plane_heights.len().saturating_sub(1));
|
eprintln!("render_coverage planar_reflection_other_planes_unsupported={} (nearest active plane is reflected)", plane_heights.len().saturating_sub(1));
|
||||||
}
|
}
|
||||||
if unsupported_nonplanar != 0 {
|
if unsupported_nonplanar != 0 {
|
||||||
@@ -1874,17 +1874,27 @@ impl VulkanSmokeRenderer {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
let render_area = vk::Rect2D {
|
|
||||||
offset: vk::Offset2D { x: 0, y: 0 },
|
|
||||||
extent: vk::Extent2D {
|
|
||||||
width: swapchain.report.plan.extent.0,
|
|
||||||
height: swapchain.report.plan.extent.1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
for reflected in [true, false] {
|
for reflected in [true, false] {
|
||||||
if reflected && reflection_plane.is_none() {
|
if reflected && reflection_plane.is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
let extent = if reflected {
|
||||||
|
let target = resources.reflection.color.as_ref().ok_or(
|
||||||
|
VulkanSmokeRendererError::InvariantViolation {
|
||||||
|
context: "active reflection without a color target",
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
(target.width, target.height)
|
||||||
|
} else {
|
||||||
|
swapchain.report.plan.extent
|
||||||
|
};
|
||||||
|
let render_area = vk::Rect2D {
|
||||||
|
offset: vk::Offset2D { x: 0, y: 0 },
|
||||||
|
extent: vk::Extent2D {
|
||||||
|
width: extent.0,
|
||||||
|
height: extent.1,
|
||||||
|
},
|
||||||
|
};
|
||||||
let framebuffer = if reflected {
|
let framebuffer = if reflected {
|
||||||
resources.reflection.framebuffer
|
resources.reflection.framebuffer
|
||||||
} else {
|
} else {
|
||||||
@@ -2073,10 +2083,7 @@ impl VulkanSmokeRenderer {
|
|||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}) as f32;
|
}) as f32;
|
||||||
let viewport = super::swapchain_resources::draw_viewport(
|
let viewport = super::swapchain_resources::draw_viewport(extent, material_mode);
|
||||||
swapchain.report.plan.extent,
|
|
||||||
material_mode,
|
|
||||||
);
|
|
||||||
let next_viewport_depth = [viewport.min_depth, viewport.max_depth];
|
let next_viewport_depth = [viewport.min_depth, viewport.max_depth];
|
||||||
if viewport_depth != Some(next_viewport_depth) {
|
if viewport_depth != Some(next_viewport_depth) {
|
||||||
device
|
device
|
||||||
|
|||||||
@@ -7,6 +7,17 @@ pub const EMBM_BUMP_TILE: f32 = 100.0;
|
|||||||
/// Native diagonal bump matrix, without luminance modulation.
|
/// Native diagonal bump matrix, without luminance modulation.
|
||||||
pub const EMBM_COEFFICIENTS: [f32; 2] = [0.01, 0.01];
|
pub const EMBM_COEFFICIENTS: [f32; 2] = [0.01, 0.01];
|
||||||
|
|
||||||
|
/// Square reflection size chosen by Terrain.dll RVA 0x27FC7 from renderer dimensions.
|
||||||
|
#[must_use]
|
||||||
|
pub fn legacy_embm_reflection_size(extent: [u32; 2]) -> u32 {
|
||||||
|
let limit = extent[0].min(extent[1]).min(1024);
|
||||||
|
let mut side = 2;
|
||||||
|
while side * 2 <= limit {
|
||||||
|
side *= 2;
|
||||||
|
}
|
||||||
|
side
|
||||||
|
}
|
||||||
|
|
||||||
/// Generates the signed bytes written by Terrain.dll RVA 0x2BF50 with CW 0x007F.
|
/// Generates the signed bytes written by Terrain.dll RVA 0x2BF50 with CW 0x007F.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn legacy_embm_bump_map() -> Vec<[i8; 2]> {
|
pub fn legacy_embm_bump_map() -> Vec<[i8; 2]> {
|
||||||
@@ -116,6 +127,24 @@ pub fn legacy_embm_reflection_clip(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reflection_size_matches_native_renderer_dimension_vectors() {
|
||||||
|
for (extent, expected) in [
|
||||||
|
([1280, 1024], 1024),
|
||||||
|
([1280, 720], 512),
|
||||||
|
([800, 600], 512),
|
||||||
|
([640, 480], 256),
|
||||||
|
([1920, 1080], 1024),
|
||||||
|
([2048, 2048], 1024),
|
||||||
|
([4, 7], 4),
|
||||||
|
([3, 300], 2),
|
||||||
|
([1, 1], 2),
|
||||||
|
([700, 700], 512),
|
||||||
|
] {
|
||||||
|
assert_eq!(legacy_embm_reflection_size(extent), expected, "{extent:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn shifted_camera_maps_native_water_corners_and_retains_perspective_depth() {
|
fn shifted_camera_maps_native_water_corners_and_retains_perspective_depth() {
|
||||||
let projection = crate::LegacyD3d7Projection {
|
let projection = crate::LegacyD3d7Projection {
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ MSH-запрос со свободной preview-камерой; это не п
|
|||||||
среде, перехватить DirectDraw/Direct3D calls и surface flips, сохранить state
|
среде, перехватить DirectDraw/Direct3D calls и surface flips, сохранить state
|
||||||
log на минимальных сценах с одним типом материала.
|
log на минимальных сценах с одним типом материала.
|
||||||
|
|
||||||
Горизонтальные грани Land.msh с флагом `0x20000` используют исходную карту Du/Dv и окно shifted-камеры, проверенное исполнением Terrain.dll. Диапазон глубины, небо/туман/FX основного вида и размер target остаются приближениями: `planar_reflection_render_context_approx`; неперспективная камера API использует обычное зеркало.
|
Горизонтальные грани Land.msh с флагом `0x20000` используют исходную карту Du/Dv, окно shifted-камеры и квадратный размер текстуры, проверенные исполнением DLL. Диапазон глубины, небо/туман/FX основного вида и соответствие размеров при обрезанном capture остаются приближениями: `planar_reflection_render_context_approx`; неперспективная камера API использует обычное зеркало.
|
||||||
Raw queue clock задаёт фазу волн отдельно от времени суток и sky elapsed; старые захваты без него считаются в `captured_water_phase_unverified`.
|
Raw queue clock задаёт фазу волн отдельно от времени суток и sky elapsed; старые захваты без него считаются в `captured_water_phase_unverified`.
|
||||||
Один target отражает ближайшую активную высоту воды; остальные высоты и неплоские грани считаются в `planar_reflection_other_planes_unsupported` и `planar_reflection_nonplanar_unsupported` и сохраняют обычный terrain shader.
|
Один target отражает ближайшую активную высоту воды; остальные высоты и неплоские грани считаются в `planar_reflection_other_planes_unsupported` и `planar_reflection_nonplanar_unsupported` и сохраняют обычный terrain shader.
|
||||||
`terrain_face8_pipeline_unverified` теперь считает только исходные грани с `0x8` без `0x20000`; сама семантика оставшихся специальных проходов ещё не установлена.
|
`terrain_face8_pipeline_unverified` теперь считает только исходные грани с `0x8` без `0x20000`; сама семантика оставшихся специальных проходов ещё не установлена.
|
||||||
|
|||||||
+15
-2
@@ -1169,6 +1169,18 @@ Rust-тест отдельно проверяет углы водоёма и г
|
|||||||
водоёма с добавленным смещением волн. Для неперспективной камеры API остаётся
|
водоёма с добавленным смещением волн. Для неперспективной камеры API остаётся
|
||||||
обычное зеркало с экранными координатами.
|
обычное зеркало с экранными координатами.
|
||||||
|
|
||||||
|
Размер изображения отражения выбирается отдельно от размера основного кадра.
|
||||||
|
Terrain.dll в конструкторе CShade (RVA `0x278E0`, цикл с `0x27FC7`) берёт
|
||||||
|
ширину и высоту renderer через Ngi32.dll RVA `0x7FB0`. Сторона квадратной
|
||||||
|
текстуры — наибольшая степень двойки, не превосходящая меньшую из этих
|
||||||
|
величин и 1024; нижний предел равен 2. Например, экран 1280×1024 даёт
|
||||||
|
отражение 1024×1024, а 1280×720 — 512×512. Getter размеров и исходный цикл
|
||||||
|
выбора проверены исполнением DLL на десяти входах; создание нативной текстуры
|
||||||
|
в этом оракуле пропущено. Vulkan создаёт цвет и глубину с этой стороной и
|
||||||
|
задаёт каждому проходу собственные viewport и область очистки. Так уменьшение
|
||||||
|
отражения не обрезает основной кадр и воспроизводит исходный масштаб его
|
||||||
|
пикселей, от которого зависит чёткость берегов и ряби.
|
||||||
|
|
||||||
Цветовой target переходит из attachment layout в shader-read layout после
|
Цветовой target переходит из attachment layout в shader-read layout после
|
||||||
записи. Следующий зеркальный проход ждёт предыдущего чтения этой текстуры;
|
записи. Следующий зеркальный проход ждёт предыдущего чтения этой текстуры;
|
||||||
основной и отражённый виды используют разные слоты uniform buffer. Во время
|
основной и отражённый виды используют разные слоты uniform buffer. Во время
|
||||||
@@ -1196,9 +1208,10 @@ Rust-тест отдельно проверяет углы водоёма и г
|
|||||||
В Vulkan карта хранится в двух каналах несжатой текстуры и возвращается к
|
В Vulkan карта хранится в двух каналах несжатой текстуры и возвращается к
|
||||||
знаковым значениям перед вычислением смещения.
|
знаковым значениям перед вычислением смещения.
|
||||||
|
|
||||||
Матрица положения камеры, её окно и координаты волн восстановлены, но весь
|
Матрица положения камеры, её окно, размер текстуры и координаты волн восстановлены, но весь
|
||||||
контекст отражённого вида ещё не повторён. Диапазон глубины, небо, туман и FX
|
контекст отражённого вида ещё не повторён. Диапазон глубины, небо, туман и FX
|
||||||
заимствуются из основного вида, а размер target совпадает со swapchain.
|
заимствуются из основного вида. Для захвата с обрезанным viewport ещё не
|
||||||
|
подтверждено соответствие размера окна FParkan полному renderer оригинала.
|
||||||
Эти ограничения учитываются в `planar_reflection_render_context_approx`;
|
Эти ограничения учитываются в `planar_reflection_render_context_approx`;
|
||||||
совпадение генератора волн и окна не доказывает совпадения итогового отражения.
|
совпадение генератора волн и окна не доказывает совпадения итогового отражения.
|
||||||
Один target обслуживает ближайшую
|
Один target обслуживает ближайшую
|
||||||
|
|||||||
Reference in new issue
Block a user