Handle camera roll and explicit temporal Off
This commit is contained in:
@@ -66,8 +66,9 @@ TemporalHistoryDecision
|
|||||||
evaluate_temporal_history(const std::optional<TemporalHistoryKey>& previous,
|
evaluate_temporal_history(const std::optional<TemporalHistoryKey>& previous,
|
||||||
const TemporalHistoryKey& current) noexcept;
|
const TemporalHistoryKey& current) noexcept;
|
||||||
|
|
||||||
// Sixteen-phase Halton(2,3) offset in clip-space units, for the scene raster
|
// Sixteen-phase Halton(2,3) offset in clip-space units for scene rasterization.
|
||||||
// projection only. UI, picking, culling, and history keys use unjittered space.
|
// UI, picking and history keys use unjittered space. Culling must account for
|
||||||
|
// this jitter with a conservative edge; HZB depth must match jittered geometry.
|
||||||
// A zero viewport extent throws std::invalid_argument.
|
// A zero viewport extent throws std::invalid_argument.
|
||||||
std::array<float, 2> temporal_jitter(std::uint64_t frame_index,
|
std::array<float, 2> temporal_jitter(std::uint64_t frame_index,
|
||||||
std::uint32_t viewport_width,
|
std::uint32_t viewport_width,
|
||||||
|
|||||||
+31
-19
@@ -45,18 +45,11 @@ bool finite_camera(const TemporalHistoryKey& key) noexcept {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<float, 3> view_direction(const TemporalHistoryKey& key) noexcept {
|
std::array<float, 3> normalized_view_row(const TemporalHistoryKey& key, int row) noexcept {
|
||||||
// A perspective VP encodes view forward in its fourth row. An orthographic
|
std::array<float, 3> direction{key.view_projection[row], key.view_projection[row + 4],
|
||||||
// projection has a constant fourth row; its third row carries direction.
|
key.view_projection[row + 8]};
|
||||||
std::array<float, 3> direction{key.view_projection[3], key.view_projection[7],
|
const float length_squared = direction[0] * direction[0] +
|
||||||
key.view_projection[11]};
|
direction[1] * direction[1] + direction[2] * direction[2];
|
||||||
float length_squared = direction[0] * direction[0] + direction[1] * direction[1] +
|
|
||||||
direction[2] * direction[2];
|
|
||||||
if (length_squared < 1e-12f) {
|
|
||||||
direction = {key.view_projection[2], key.view_projection[6], key.view_projection[10]};
|
|
||||||
length_squared = direction[0] * direction[0] + direction[1] * direction[1] +
|
|
||||||
direction[2] * direction[2];
|
|
||||||
}
|
|
||||||
if (!std::isfinite(length_squared) || length_squared < 1e-12f)
|
if (!std::isfinite(length_squared) || length_squared < 1e-12f)
|
||||||
return {};
|
return {};
|
||||||
const float reciprocal = 1.f / std::sqrt(length_squared);
|
const float reciprocal = 1.f / std::sqrt(length_squared);
|
||||||
@@ -65,6 +58,14 @@ std::array<float, 3> view_direction(const TemporalHistoryKey& key) noexcept {
|
|||||||
return direction;
|
return direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool large_axis_turn(const std::array<float, 3>& a,
|
||||||
|
const std::array<float, 3>& b) noexcept {
|
||||||
|
if (a == std::array<float, 3>{} || b == std::array<float, 3>{})
|
||||||
|
return true;
|
||||||
|
const float dot = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
||||||
|
return !std::isfinite(dot) || dot < 0.70710678f; // turn greater than 45 degrees
|
||||||
|
}
|
||||||
|
|
||||||
bool camera_discontinuity(const TemporalHistoryKey& previous,
|
bool camera_discontinuity(const TemporalHistoryKey& previous,
|
||||||
const TemporalHistoryKey& current) noexcept {
|
const TemporalHistoryKey& current) noexcept {
|
||||||
if (!finite_camera(previous) || !finite_camera(current))
|
if (!finite_camera(previous) || !finite_camera(current))
|
||||||
@@ -78,11 +79,21 @@ bool camera_discontinuity(const TemporalHistoryKey& previous,
|
|||||||
// camera motion and smaller view changes are handled by motion vectors.
|
// camera motion and smaller view changes are handled by motion vectors.
|
||||||
if (!std::isfinite(translation_squared) || translation_squared > 25.f)
|
if (!std::isfinite(translation_squared) || translation_squared > 25.f)
|
||||||
return true;
|
return true;
|
||||||
const auto a = view_direction(previous), b = view_direction(current);
|
// Compare horizontal and vertical camera axes too: comparing only forward
|
||||||
if (a == std::array<float, 3>{} || b == std::array<float, 3>{})
|
// cannot detect a sudden roll about the unchanged viewing direction.
|
||||||
return true;
|
for (int row = 0; row < 2; ++row)
|
||||||
const float dot = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
if (large_axis_turn(normalized_view_row(previous, row),
|
||||||
return !std::isfinite(dot) || dot < 0.70710678f; // turn greater than 45 degrees
|
normalized_view_row(current, row)))
|
||||||
|
return true;
|
||||||
|
// Perspective VP encodes forward in row four. Orthographic projection has
|
||||||
|
// a constant fourth row, so its third row carries the viewing direction.
|
||||||
|
auto a = normalized_view_row(previous, 3);
|
||||||
|
auto b = normalized_view_row(current, 3);
|
||||||
|
if (a == std::array<float, 3>{} && b == std::array<float, 3>{}) {
|
||||||
|
a = normalized_view_row(previous, 2);
|
||||||
|
b = normalized_view_row(current, 2);
|
||||||
|
}
|
||||||
|
return large_axis_turn(a, b);
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -90,6 +101,9 @@ TemporalHistoryDecision
|
|||||||
evaluate_temporal_history(const std::optional<TemporalHistoryKey>& previous,
|
evaluate_temporal_history(const std::optional<TemporalHistoryKey>& previous,
|
||||||
const TemporalHistoryKey& current) noexcept {
|
const TemporalHistoryKey& current) noexcept {
|
||||||
auto reset = [](TemporalResetReason reason) { return TemporalHistoryDecision{false, reason}; };
|
auto reset = [](TemporalResetReason reason) { return TemporalHistoryDecision{false, reason}; };
|
||||||
|
if (current.mode == TemporalMode::Off)
|
||||||
|
return reset(previous && previous->mode != TemporalMode::Off
|
||||||
|
? TemporalResetReason::ModeChanged : TemporalResetReason::None);
|
||||||
if (current.camera_cut)
|
if (current.camera_cut)
|
||||||
return reset(TemporalResetReason::CameraCut);
|
return reset(TemporalResetReason::CameraCut);
|
||||||
if (!previous)
|
if (!previous)
|
||||||
@@ -106,8 +120,6 @@ evaluate_temporal_history(const std::optional<TemporalHistoryKey>& previous,
|
|||||||
return reset(TemporalResetReason::ScaleChanged);
|
return reset(TemporalResetReason::ScaleChanged);
|
||||||
if (before.mode != current.mode)
|
if (before.mode != current.mode)
|
||||||
return reset(TemporalResetReason::ModeChanged);
|
return reset(TemporalResetReason::ModeChanged);
|
||||||
if (current.mode == TemporalMode::Off)
|
|
||||||
return reset(TemporalResetReason::Unsupported);
|
|
||||||
if (before.scene_rect != current.scene_rect)
|
if (before.scene_rect != current.scene_rect)
|
||||||
return reset(TemporalResetReason::ViewportChanged);
|
return reset(TemporalResetReason::ViewportChanged);
|
||||||
if (before.projection != current.projection)
|
if (before.projection != current.projection)
|
||||||
|
|||||||
@@ -75,6 +75,19 @@ void rendered_history_and_camera_motion() {
|
|||||||
TemporalResetReason::CameraDiscontinuity,
|
TemporalResetReason::CameraDiscontinuity,
|
||||||
"A large camera turn must invalidate history");
|
"A large camera turn must invalidate history");
|
||||||
current = previous;
|
current = previous;
|
||||||
|
current.view_projection[0] = -1;
|
||||||
|
current.view_projection[5] = -1;
|
||||||
|
require(evaluate_temporal_history(previous, current).reason ==
|
||||||
|
TemporalResetReason::CameraDiscontinuity,
|
||||||
|
"A 180-degree roll must invalidate history even when forward is unchanged");
|
||||||
|
current = previous;
|
||||||
|
current.view_projection[0] = 0.98480775f;
|
||||||
|
current.view_projection[1] = 0.17364818f;
|
||||||
|
current.view_projection[4] = -0.17364818f;
|
||||||
|
current.view_projection[5] = 0.98480775f;
|
||||||
|
require(evaluate_temporal_history(previous, current).valid,
|
||||||
|
"An ordinary small camera roll must retain compatible history");
|
||||||
|
current = previous;
|
||||||
current.view_projection[0] = std::numeric_limits<float>::quiet_NaN();
|
current.view_projection[0] = std::numeric_limits<float>::quiet_NaN();
|
||||||
require(evaluate_temporal_history(previous, current).reason ==
|
require(evaluate_temporal_history(previous, current).reason ==
|
||||||
TemporalResetReason::CameraDiscontinuity,
|
TemporalResetReason::CameraDiscontinuity,
|
||||||
@@ -121,6 +134,17 @@ void incompatible_view_state() {
|
|||||||
"A changed shading generation invalidates history");
|
"A changed shading generation invalidates history");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void intentionally_disabled_temporal_mode() {
|
||||||
|
auto off = steady_view();
|
||||||
|
off.mode = TemporalMode::Off;
|
||||||
|
const auto first = evaluate_temporal_history(std::nullopt, off);
|
||||||
|
require(!first.valid && first.reason == TemporalResetReason::None,
|
||||||
|
"Explicit Off has no temporal history to reset or unsupported fallback to report");
|
||||||
|
const auto later = evaluate_temporal_history(off, off);
|
||||||
|
require(!later.valid && later.reason == TemporalResetReason::None,
|
||||||
|
"Continuing in Off must remain a deliberate non-temporal mode");
|
||||||
|
}
|
||||||
|
|
||||||
void deterministic_jitter() {
|
void deterministic_jitter() {
|
||||||
const auto first = temporal_jitter(0, 320, 240);
|
const auto first = temporal_jitter(0, 320, 240);
|
||||||
const auto second = temporal_jitter(1, 320, 240);
|
const auto second = temporal_jitter(1, 320, 240);
|
||||||
@@ -151,6 +175,7 @@ void deterministic_jitter() {
|
|||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
capability_fallback();
|
capability_fallback();
|
||||||
|
intentionally_disabled_temporal_mode();
|
||||||
rendered_history_and_camera_motion();
|
rendered_history_and_camera_motion();
|
||||||
incompatible_view_state();
|
incompatible_view_state();
|
||||||
deterministic_jitter();
|
deterministic_jitter();
|
||||||
|
|||||||
Reference in New Issue
Block a user