Fix temporal tiled lighting and HZB extents
Native and manual checks / native (ubuntu-24.04) (push) Failing after 29s
Native and manual checks / manual (push) Successful in 26s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
Native and manual checks / native (ubuntu-24.04) (push) Failing after 29s
Native and manual checks / manual (push) Successful in 26s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
This commit is contained in:
+13
-10
@@ -135,6 +135,7 @@ struct Image {
|
||||
VkDeviceMemory memory{};
|
||||
VkImageView view{};
|
||||
std::vector<VkImageView> mip_views;
|
||||
std::uint32_t width{}, height{};
|
||||
std::uint32_t mip_levels{1};
|
||||
VkImageLayout layout{VK_IMAGE_LAYOUT_UNDEFINED};
|
||||
VkDeviceSize allocation_size{};
|
||||
@@ -539,6 +540,8 @@ struct Renderer::Impl {
|
||||
Image make_image(std::uint32_t w, std::uint32_t h, VkFormat format, VkImageUsageFlags usage,
|
||||
VkImageAspectFlags aspect, std::uint32_t mip_levels = 1) {
|
||||
Image image{};
|
||||
image.width = w;
|
||||
image.height = h;
|
||||
image.mip_levels = mip_levels;
|
||||
VkImageCreateInfo info{};
|
||||
info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
@@ -2403,6 +2406,8 @@ struct Renderer::Impl {
|
||||
raster_vp[column * 4 + 1] +=
|
||||
statistics.temporal_jitter[1] * raster_vp[column * 4 + 3];
|
||||
}
|
||||
const auto raster_width = temporal_active ? temporal.internal_width : width;
|
||||
const auto raster_height = temporal_active ? temporal.internal_height : height;
|
||||
const std::string view_id = snapshot.view_id.empty() ? "default" : snapshot.view_id;
|
||||
TemporalHistoryKey temporal_key;
|
||||
temporal_key.view_id = view_id;
|
||||
@@ -2976,10 +2981,10 @@ struct Renderer::Impl {
|
||||
constexpr std::uint32_t light_tile_side = 16;
|
||||
constexpr std::uint32_t light_tile_stride_words = 66;
|
||||
constexpr std::uint32_t light_tile_capacity = 64;
|
||||
const std::uint32_t light_tiles_x = width / light_tile_side +
|
||||
(width % light_tile_side != 0);
|
||||
const std::uint32_t light_tiles_y = height / light_tile_side +
|
||||
(height % light_tile_side != 0);
|
||||
const std::uint32_t light_tiles_x = raster_width / light_tile_side +
|
||||
(raster_width % light_tile_side != 0);
|
||||
const std::uint32_t light_tiles_y = raster_height / light_tile_side +
|
||||
(raster_height % light_tile_side != 0);
|
||||
const std::uint64_t light_tile_count =
|
||||
std::uint64_t(light_tiles_x) * light_tiles_y;
|
||||
const std::uint64_t light_tile_bytes =
|
||||
@@ -3306,7 +3311,7 @@ struct Renderer::Impl {
|
||||
light_tile_pipeline_layout, 0, 1,
|
||||
&light_tile_set, 0, nullptr);
|
||||
const LightTilePush tile_push{
|
||||
snapshot.view_projection, scene_viewport,
|
||||
raster_vp, scene_viewport,
|
||||
{light_tiles_x, light_tiles_y, lighting.counts[0], light_tile_capacity}};
|
||||
vkCmdPushConstants(command, light_tile_pipeline_layout,
|
||||
VK_SHADER_STAGE_COMPUTE_BIT, 0,
|
||||
@@ -3385,8 +3390,6 @@ struct Renderer::Impl {
|
||||
}
|
||||
});
|
||||
Image& raster_color = temporal_active ? temporal.scene_color : color;
|
||||
const auto raster_width = temporal_active ? temporal.internal_width : width;
|
||||
const auto raster_height = temporal_active ? temporal.internal_height : height;
|
||||
add_pass(occlusion || temporal_active ? "MainRaster" : "ForwardAndUI",
|
||||
gpu_active ? std::vector<std::string>{"shadow", "local_shadow",
|
||||
"light_tiles", "main_indirect", "main_visible"}
|
||||
@@ -4139,8 +4142,9 @@ std::optional<HzbDebugImage> Renderer::hzb_debug_image(std::uint32_t mip) {
|
||||
return std::nullopt;
|
||||
if (mip >= renderer.scene.hzb_mips)
|
||||
throw std::out_of_range("HZB debug mip outside pyramid");
|
||||
const std::uint32_t w = std::max(1u, std::bit_ceil(renderer.width) >> mip);
|
||||
const std::uint32_t h = std::max(1u, std::bit_ceil(renderer.height) >> mip);
|
||||
auto& pyramid = renderer.scene.hzb[renderer.scene.hzb_current];
|
||||
const std::uint32_t w = std::max(1u, pyramid.width >> mip);
|
||||
const std::uint32_t h = std::max(1u, pyramid.height >> mip);
|
||||
auto staging = renderer.make_buffer(VkDeviceSize(w) * h * sizeof(float),
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
||||
@@ -4148,7 +4152,6 @@ std::optional<HzbDebugImage> Renderer::hzb_debug_image(std::uint32_t mip) {
|
||||
VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
|
||||
try {
|
||||
renderer.begin();
|
||||
auto& pyramid = renderer.scene.hzb[renderer.scene.hzb_current];
|
||||
renderer.transition(renderer.command, pyramid, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
VK_IMAGE_ASPECT_COLOR_BIT);
|
||||
VkBufferImageCopy copy{};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "render_temporal_fixtures.hpp"
|
||||
#include <faset/render/temporal.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
@@ -164,6 +165,112 @@ void static_edge_reduces_jitter_variation() {
|
||||
spatial.stats().validation_errors == 0,
|
||||
"Static-edge temporal sequence must not raise Vulkan validation errors");
|
||||
}
|
||||
|
||||
void temporal_tiled_lighting_uses_scene_raster_extent() {
|
||||
constexpr std::uint32_t width = 319, height = 241;
|
||||
for (const auto temporal_mode : {TemporalMode::TAA, TemporalMode::Upscale}) {
|
||||
for (const auto visibility : {VisibilityMode::Direct,
|
||||
VisibilityMode::GpuFrustum,
|
||||
VisibilityMode::GpuOcclusion}) {
|
||||
auto config = headless_config(width, height, visibility);
|
||||
config.temporal_mode = temporal_mode;
|
||||
config.render_scale = temporal_mode == TemporalMode::Upscale ? .5f : 1.f;
|
||||
config.lighting_mode = LightingMode::Forward;
|
||||
Renderer forward(config);
|
||||
config.lighting_mode = LightingMode::Tiled;
|
||||
Renderer tiled(config);
|
||||
auto frame = lit_scene(width, height);
|
||||
frame.scene_rect = {11, 9, 297, 223};
|
||||
frame.draws.push_back(cube({0, 0, 0}, {.8f, .8f, .8f, 1}, "tile-receiver"));
|
||||
for (int i = -3; i <= 3; ++i) {
|
||||
LocalLight light;
|
||||
light.stable_id = "tile-light-" + std::to_string(i);
|
||||
light.position = {float(i) * .32f, .12f, 1.2f};
|
||||
light.range = .65f;
|
||||
light.intensity = 30.f;
|
||||
light.casts_shadow = false;
|
||||
frame.local_lights.push_back(light);
|
||||
}
|
||||
const auto expected_tiles = temporal_mode == TemporalMode::Upscale
|
||||
? 10u * 8u : 20u * 16u;
|
||||
for (unsigned phase = 0; phase < 8; ++phase) {
|
||||
forward.render(frame);
|
||||
tiled.render(frame);
|
||||
const auto& stats = tiled.stats();
|
||||
require(stats.effective_lighting_path == "tiled" &&
|
||||
stats.light_tile_count == expected_tiles,
|
||||
"Temporal tiled lighting must build the grid at scene raster resolution");
|
||||
require(stats.validation_errors == 0 &&
|
||||
forward.stats().validation_errors == 0,
|
||||
"Temporal tiled/forward lighting must pass Vulkan validation");
|
||||
const double error = mean_rgb_error(tiled.pixels(), forward.pixels(),
|
||||
width, height, {11, 9, 297, 223});
|
||||
require(error <= 1.0,
|
||||
"Jittered Direct/P2 tiled lighting must match forward shading near tile boundaries");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tile_membership_tracks_raster_jitter() {
|
||||
auto config = headless_config(160, 128, VisibilityMode::Direct);
|
||||
config.temporal_mode = TemporalMode::TAA;
|
||||
config.lighting_mode = LightingMode::Tiled;
|
||||
config.visibility_diagnostics = true;
|
||||
Renderer tiled(config);
|
||||
Snapshot frame;
|
||||
frame.view_id = "jittered-light-tile-membership";
|
||||
frame.eye = {0, 0, 6};
|
||||
frame.projection = orthographic(-2, 2, -1.6f, 1.6f, .1f, 20.f);
|
||||
frame.view_projection = multiply(frame.projection,
|
||||
look_at(frame.eye, {0, 0, 0}));
|
||||
frame.authored_lights_present = true;
|
||||
LocalLight edge_light;
|
||||
edge_light.stable_id = "boundary-light";
|
||||
edge_light.position = {-.2f, .37f, 0};
|
||||
edge_light.range = .2f;
|
||||
edge_light.intensity = 20;
|
||||
edge_light.casts_shadow = false;
|
||||
frame.local_lights.push_back(edge_light);
|
||||
std::vector<std::uint32_t> memberships;
|
||||
for (unsigned phase = 0; phase < 16; ++phase) {
|
||||
tiled.render(frame);
|
||||
const auto& stats = tiled.stats();
|
||||
require(stats.effective_lighting_path == "tiled" &&
|
||||
stats.light_tile_counts_valid &&
|
||||
stats.validation_errors == 0,
|
||||
"Jittered tile membership fixture requires diagnostic tile readback");
|
||||
memberships.push_back(stats.light_tile_candidate_count);
|
||||
}
|
||||
require(*std::min_element(memberships.begin(), memberships.end()) <
|
||||
*std::max_element(memberships.begin(), memberships.end()),
|
||||
"A light grazing a tile edge must follow the scene raster jitter");
|
||||
}
|
||||
|
||||
void occlusion_upscale_hzb_debug_uses_internal_extent() {
|
||||
constexpr std::uint32_t width = 319, height = 241;
|
||||
auto config = headless_config(width, height, VisibilityMode::GpuOcclusion);
|
||||
config.temporal_mode = TemporalMode::Upscale;
|
||||
config.render_scale = .5f;
|
||||
Renderer renderer(config);
|
||||
auto frame = lit_scene(width, height);
|
||||
frame.draws.push_back(cube({0, 0, 0}, {.8f, .6f, .2f, 1}, "hzb-cube"));
|
||||
renderer.render(frame);
|
||||
require(renderer.stats().effective_visibility_mode == VisibilityMode::GpuOcclusion &&
|
||||
renderer.stats().temporal_internal_width == 160 &&
|
||||
renderer.stats().temporal_internal_height == 121,
|
||||
"HZB debug regression requires active occlusion and an odd internal extent");
|
||||
const auto mip0 = renderer.hzb_debug_image(0);
|
||||
const auto mip1 = renderer.hzb_debug_image(1);
|
||||
require(mip0 && mip0->width == 256 && mip0->height == 128 &&
|
||||
mip0->rgba.size() == std::size_t(256) * 128 * 4 &&
|
||||
mip1 && mip1->width == 128 && mip1->height == 64 &&
|
||||
mip1->rgba.size() == std::size_t(128) * 64 * 4,
|
||||
"HZB debug readback must copy the allocated internal pyramid extent per mip");
|
||||
renderer.render(frame);
|
||||
require(renderer.stats().validation_errors == 0,
|
||||
"HZB debug readback followed by occlusion render must pass Vulkan validation");
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
@@ -172,4 +279,7 @@ int main() {
|
||||
moving_reveal_and_camera_resets(VisibilityMode::GpuOcclusion);
|
||||
lower_resolution_scene_and_output_ui();
|
||||
static_edge_reduces_jitter_variation();
|
||||
temporal_tiled_lighting_uses_scene_raster_extent();
|
||||
tile_membership_tracks_raster_jitter();
|
||||
occlusion_upscale_hzb_debug_uses_internal_extent();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user