#include #include #include #include #include namespace { using namespace faset::render; void require(bool condition, const char* message) { if (!condition) throw std::runtime_error(message); } void capability_fallback() { constexpr TemporalCapabilities full{true, true, true}; require(select_effective_temporal_mode(TemporalMode::Off, {}) == TemporalMode::Off, "Off must not require temporal GPU capabilities"); require(temporal_fallback_reason(TemporalMode::Off, {}) == TemporalFallbackReason::None, "Explicit Off is not a capability fallback"); require(select_effective_temporal_mode(TemporalMode::TAA, full) == TemporalMode::TAA, "Available TAA must remain active"); require(select_effective_temporal_mode(TemporalMode::Upscale, full) == TemporalMode::Upscale, "Available upscaling must remain active"); require(select_effective_temporal_mode(TemporalMode::TAA, {false, true, true}) == TemporalMode::Off && temporal_fallback_reason(TemporalMode::TAA, {false, true, true}) == TemporalFallbackReason::ComputeUnavailable, "Missing compute must produce a named Off fallback"); require(temporal_fallback_reason(TemporalMode::TAA, {true, false, true}) == TemporalFallbackReason::FormatUnavailable, "Missing sampled/storage formats must identify the format fallback"); require(temporal_fallback_reason(TemporalMode::Upscale, {true, true, false}) == TemporalFallbackReason::ExtentUnsupported, "An unsupported target extent must identify the extent fallback"); } TemporalHistoryKey steady_view() { TemporalHistoryKey key; key.view_id = "main-camera"; key.output_width = key.internal_width = 320; key.output_height = key.internal_height = 240; key.scene_rect = {7, 11, 301, 219}; key.projection = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; key.view_projection = key.projection; key.mode = TemporalMode::TAA; key.render_scale = 1; key.shader_generation = 4; return key; } void rendered_history_and_camera_motion() { const auto previous = steady_view(); auto current = previous; require(evaluate_temporal_history(std::nullopt, current).reason == TemporalResetReason::FirstFrame, "A first temporal frame must not claim history"); current.camera_eye = {0.5f, 0, 0}; current.view_projection[12] = -0.5f; const auto ordinary_motion = evaluate_temporal_history(previous, current); require(ordinary_motion.valid && ordinary_motion.reason == TemporalResetReason::None, "Ordinary camera motion must retain compatible history"); current.camera_cut = true; require(evaluate_temporal_history(previous, current).reason == TemporalResetReason::CameraCut, "An explicit cut must override otherwise compatible history"); current.camera_cut = false; current.camera_eye = {6, 0, 0}; require(evaluate_temporal_history(previous, current).reason == TemporalResetReason::CameraDiscontinuity, "A large unmarked teleport must invalidate history"); current = previous; current.view_projection[10] = -1; require(evaluate_temporal_history(previous, current).reason == TemporalResetReason::CameraDiscontinuity, "A large camera turn must invalidate history"); current = previous; current.view_projection[0] = std::numeric_limits::quiet_NaN(); require(evaluate_temporal_history(previous, current).reason == TemporalResetReason::CameraDiscontinuity, "Nonfinite camera matrices cannot admit history"); } void incompatible_view_state() { const auto previous = steady_view(); auto current = previous; current.view_id = "other-camera"; require(evaluate_temporal_history(previous, current).reason == TemporalResetReason::ViewChanged, "A second view cannot inherit another view's color history"); current = previous; ++current.output_width; require(evaluate_temporal_history(previous, current).reason == TemporalResetReason::Resize, "Output resize invalidates history"); current = previous; current.scene_rect[0] += 1; require(evaluate_temporal_history(previous, current).reason == TemporalResetReason::ViewportChanged, "Moving the editor viewport invalidates history"); current = previous; current.projection[0] += 0.1f; require(evaluate_temporal_history(previous, current).reason == TemporalResetReason::ProjectionChanged, "FOV or aspect change invalidates history"); current = previous; current.mode = TemporalMode::Upscale; current.render_scale = 0.67f; current.internal_width = 215; current.internal_height = 161; require(evaluate_temporal_history(previous, current).reason == TemporalResetReason::ScaleChanged, "New internal render scale invalidates full-resolution history"); current = previous; current.mode = TemporalMode::Off; require(evaluate_temporal_history(previous, current).reason == TemporalResetReason::ModeChanged, "Turning temporal processing off invalidates history"); current = previous; ++current.shader_generation; require(evaluate_temporal_history(previous, current).reason == TemporalResetReason::ShaderReload, "A changed shading generation invalidates history"); } void deterministic_jitter() { const auto first = temporal_jitter(0, 320, 240); const auto second = temporal_jitter(1, 320, 240); require(std::abs(first[0]) < 1e-7f && std::abs(first[1] + 1.f / 720.f) < 1e-7f, "First Halton(2,3) sample must be a clip-space offset"); require(std::abs(second[0] + 1.f / 640.f) < 1e-7f && std::abs(second[1] - 1.f / 720.f) < 1e-7f, "Second Halton sample must visit a different subpixel position"); require(first == temporal_jitter(16, 320, 240), "The finite sequence must repeat on frame sixteen"); require(std::abs(temporal_jitter(0, 640, 480)[1] * 2.f - first[1]) < 1e-7f, "Clip jitter must scale inversely with viewport extent"); for (std::uint64_t frame = 0; frame < 16; ++frame) { const auto sample = temporal_jitter(frame, 319, 241); require(std::abs(sample[0]) <= 1.f / 319.f && std::abs(sample[1]) <= 1.f / 241.f, "Every jitter sample must stay within half an output pixel"); } bool rejected_zero_extent = false; try { (void)temporal_jitter(0, 0, 240); } catch (const std::invalid_argument&) { rejected_zero_extent = true; } require(rejected_zero_extent, "Zero viewport extent cannot produce finite clip jitter"); } } // namespace int main() { capability_fallback(); rendered_history_and_camera_motion(); incompatible_view_state(); deterministic_jitter(); }