Commit temporal history only after completed frames
This commit is contained in:
@@ -73,6 +73,18 @@ 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;
|
||||||
|
|
||||||
|
// The renderer calls prepare before recording and complete only after a
|
||||||
|
// successful GPU submission. An exception or failed submission leaves the
|
||||||
|
// previously completed key intact. This state is independent of P2 HZB.
|
||||||
|
class TemporalHistoryState {
|
||||||
|
public:
|
||||||
|
TemporalHistoryDecision prepare(const TemporalHistoryKey& current) const noexcept;
|
||||||
|
void complete(const TemporalHistoryKey& rendered);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<TemporalHistoryKey> completed_;
|
||||||
|
};
|
||||||
|
|
||||||
// Sixteen-phase Halton(2,3) offset in clip-space units for scene rasterization.
|
// Sixteen-phase Halton(2,3) offset in clip-space units for scene rasterization.
|
||||||
// UI, picking and history keys use unjittered space. Culling must account for
|
// UI, picking and history keys use unjittered space. Culling must account for
|
||||||
// this jitter with a conservative edge; HZB depth must match jittered geometry.
|
// this jitter with a conservative edge; HZB depth must match jittered geometry.
|
||||||
|
|||||||
@@ -151,6 +151,18 @@ evaluate_temporal_history(const std::optional<TemporalHistoryKey>& previous,
|
|||||||
return {true, TemporalResetReason::None};
|
return {true, TemporalResetReason::None};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TemporalHistoryDecision
|
||||||
|
TemporalHistoryState::prepare(const TemporalHistoryKey& current) const noexcept {
|
||||||
|
return evaluate_temporal_history(completed_, current);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TemporalHistoryState::complete(const TemporalHistoryKey& rendered) {
|
||||||
|
if (rendered.mode == TemporalMode::Off)
|
||||||
|
completed_.reset();
|
||||||
|
else
|
||||||
|
completed_ = rendered;
|
||||||
|
}
|
||||||
|
|
||||||
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,
|
||||||
std::uint32_t viewport_height) {
|
std::uint32_t viewport_height) {
|
||||||
|
|||||||
@@ -207,6 +207,36 @@ void render_scale_policy() {
|
|||||||
}
|
}
|
||||||
require(zero_rejected, "Zero output width is not a valid temporal target");
|
require(zero_rejected, "Zero output width is not a valid temporal target");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void completed_frames_only_become_history() {
|
||||||
|
TemporalHistoryState history;
|
||||||
|
auto frame = steady_view();
|
||||||
|
require(history.prepare(frame).reason == TemporalResetReason::FirstFrame,
|
||||||
|
"A prepared first frame has no committed history");
|
||||||
|
history.complete(frame);
|
||||||
|
require(history.prepare(frame).valid,
|
||||||
|
"A successfully completed frame becomes reusable history");
|
||||||
|
|
||||||
|
auto failed_frame = frame;
|
||||||
|
failed_frame.view_id = "failed-submit-view";
|
||||||
|
require(history.prepare(failed_frame).reason == TemporalResetReason::ViewChanged,
|
||||||
|
"A candidate view switch is detected before submission");
|
||||||
|
try {
|
||||||
|
throw std::runtime_error("synthetic queue submit failure");
|
||||||
|
} catch (const std::runtime_error&) {
|
||||||
|
// The caller never invokes complete() on a failed submission.
|
||||||
|
}
|
||||||
|
require(history.prepare(frame).valid,
|
||||||
|
"A failed submission must not replace the last completed history key");
|
||||||
|
require(history.prepare(failed_frame).reason == TemporalResetReason::ViewChanged,
|
||||||
|
"An uncommitted frame must not become the next frame's predecessor");
|
||||||
|
|
||||||
|
frame.mode = TemporalMode::Off;
|
||||||
|
history.complete(frame);
|
||||||
|
frame.mode = TemporalMode::TAA;
|
||||||
|
require(history.prepare(frame).reason == TemporalResetReason::FirstFrame,
|
||||||
|
"Completing an Off frame discards temporal history");
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
@@ -216,4 +246,5 @@ int main() {
|
|||||||
incompatible_view_state();
|
incompatible_view_state();
|
||||||
deterministic_jitter();
|
deterministic_jitter();
|
||||||
render_scale_policy();
|
render_scale_policy();
|
||||||
|
completed_frames_only_become_history();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user