158 lines
7.0 KiB
C++
158 lines
7.0 KiB
C++
#include <faset/render/temporal.hpp>
|
|
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <optional>
|
|
#include <stdexcept>
|
|
|
|
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<float>::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();
|
|
}
|