Validate temporal scale and deterministic internal extent
This commit is contained in:
@@ -26,6 +26,13 @@ TemporalFallbackReason temporal_fallback_reason(TemporalMode requested,
|
|||||||
TemporalMode select_effective_temporal_mode(TemporalMode requested,
|
TemporalMode select_effective_temporal_mode(TemporalMode requested,
|
||||||
TemporalCapabilities available) noexcept;
|
TemporalCapabilities available) noexcept;
|
||||||
|
|
||||||
|
// Validate the requested mode and scale before allocating scene targets.
|
||||||
|
// Off/TAA are 1:1; Upscale accepts [0.5, 1). Ceil rounding is deterministic
|
||||||
|
// for odd output extents and never produces a zero-sized internal target.
|
||||||
|
std::array<std::uint32_t, 2> temporal_internal_extent(std::uint32_t output_width,
|
||||||
|
std::uint32_t output_height,
|
||||||
|
TemporalMode mode, float render_scale);
|
||||||
|
|
||||||
enum class TemporalResetReason {
|
enum class TemporalResetReason {
|
||||||
None,
|
None,
|
||||||
FirstFrame,
|
FirstFrame,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <faset/render/temporal.hpp>
|
#include <faset/render/temporal.hpp>
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
@@ -23,6 +24,25 @@ TemporalMode select_effective_temporal_mode(TemporalMode requested,
|
|||||||
? requested : TemporalMode::Off;
|
? requested : TemporalMode::Off;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::array<std::uint32_t, 2> temporal_internal_extent(std::uint32_t output_width,
|
||||||
|
std::uint32_t output_height,
|
||||||
|
TemporalMode mode, float render_scale) {
|
||||||
|
if ((mode != TemporalMode::Off && mode != TemporalMode::TAA &&
|
||||||
|
mode != TemporalMode::Upscale) ||
|
||||||
|
!output_width || !output_height || !std::isfinite(render_scale) ||
|
||||||
|
(mode == TemporalMode::Upscale
|
||||||
|
? render_scale < 0.5f || render_scale >= 1.f
|
||||||
|
: render_scale != 1.f))
|
||||||
|
throw std::invalid_argument("Invalid temporal mode, scale or output extent");
|
||||||
|
if (mode != TemporalMode::Upscale)
|
||||||
|
return {output_width, output_height};
|
||||||
|
const auto scaled = [&](std::uint32_t extent) {
|
||||||
|
return static_cast<std::uint32_t>(
|
||||||
|
std::max(1.0, std::ceil(static_cast<double>(extent) * render_scale)));
|
||||||
|
};
|
||||||
|
return {scaled(output_width), scaled(output_height)};
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
float halton(std::uint32_t index, std::uint32_t base) noexcept {
|
float halton(std::uint32_t index, std::uint32_t base) noexcept {
|
||||||
float sample = 0.f;
|
float sample = 0.f;
|
||||||
|
|||||||
@@ -171,6 +171,42 @@ void deterministic_jitter() {
|
|||||||
}
|
}
|
||||||
require(rejected_zero_extent, "Zero viewport extent cannot produce finite clip jitter");
|
require(rejected_zero_extent, "Zero viewport extent cannot produce finite clip jitter");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void render_scale_policy() {
|
||||||
|
const auto full = temporal_internal_extent(320, 240, TemporalMode::Off, 1.f);
|
||||||
|
require(full == std::array<std::uint32_t, 2>{320, 240},
|
||||||
|
"Off keeps scene and output at the same extent");
|
||||||
|
require(temporal_internal_extent(319, 241, TemporalMode::TAA, 1.f) ==
|
||||||
|
std::array<std::uint32_t, 2>{319, 241},
|
||||||
|
"TAA is a one-to-one reconstruction mode");
|
||||||
|
require(temporal_internal_extent(320, 240, TemporalMode::Upscale, .67f) ==
|
||||||
|
std::array<std::uint32_t, 2>{215, 161},
|
||||||
|
"Upscale uses deterministic ceil dimensions for odd pixel products");
|
||||||
|
require(temporal_internal_extent(1, 1, TemporalMode::Upscale, .5f) ==
|
||||||
|
std::array<std::uint32_t, 2>{1, 1},
|
||||||
|
"A supported output always has at least one internal pixel");
|
||||||
|
auto rejected = [](TemporalMode mode, float scale) {
|
||||||
|
try {
|
||||||
|
(void)temporal_internal_extent(320, 240, mode, scale);
|
||||||
|
return false;
|
||||||
|
} catch (const std::invalid_argument&) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
require(rejected(TemporalMode::TAA, .75f) && rejected(TemporalMode::Upscale, 1.f) &&
|
||||||
|
rejected(TemporalMode::Upscale, .49f) &&
|
||||||
|
rejected(TemporalMode::Upscale, std::numeric_limits<float>::quiet_NaN()) &&
|
||||||
|
rejected(TemporalMode::Off, .67f) &&
|
||||||
|
rejected(static_cast<TemporalMode>(42), 1.f),
|
||||||
|
"Invalid mode/scale combinations must fail before target allocation");
|
||||||
|
bool zero_rejected = false;
|
||||||
|
try {
|
||||||
|
(void)temporal_internal_extent(0, 240, TemporalMode::TAA, 1.f);
|
||||||
|
} catch (const std::invalid_argument&) {
|
||||||
|
zero_rejected = true;
|
||||||
|
}
|
||||||
|
require(zero_rejected, "Zero output width is not a valid temporal target");
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
@@ -179,4 +215,5 @@ int main() {
|
|||||||
rendered_history_and_camera_motion();
|
rendered_history_and_camera_motion();
|
||||||
incompatible_view_state();
|
incompatible_view_state();
|
||||||
deterministic_jitter();
|
deterministic_jitter();
|
||||||
|
render_scale_policy();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user