Merge commit '0c96ce5cdfde8503e1ffc3fd65fdc9d7f3793d35' into feat/p1-p3-integration
# Conflicts: # PLAN.md # docs/IMPLEMENTATION.md # docs/manual/editor/diagnostics.md # docs/manual/editor/lighting.md # docs/manual/editor/profiling.md # docs/validation/README.md # docs/validation/p3-lighting-2026-09-24/README.md
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <faset/render/temporal.hpp>
|
||||
|
||||
namespace faset::render {
|
||||
using Vec2 = std::array<float, 2>;
|
||||
@@ -136,6 +137,7 @@ struct Snapshot {
|
||||
std::optional<CameraFrustum> camera_frustum{};
|
||||
};
|
||||
enum class VisibilityMode { Direct, GpuFrustum, GpuOcclusion };
|
||||
enum class LightingMode { Auto, Forward, Tiled };
|
||||
// CPU-only validation used before publishing a game or creating Vulkan pipelines.
|
||||
void validate_shader_bundle(const std::filesystem::path& directory);
|
||||
void validate_gpu_shader_bundle(const std::filesystem::path& directory);
|
||||
@@ -146,6 +148,11 @@ struct RendererConfig {
|
||||
bool headless{false};
|
||||
bool validation{true};
|
||||
VisibilityMode visibility_mode{VisibilityMode::Direct};
|
||||
TemporalMode temporal_mode{TemporalMode::Off};
|
||||
float render_scale{1.f};
|
||||
// Auto uses measured Forward on the reference workload. Tiled can be forced
|
||||
// for comparison and falls back to Forward if its device path is unavailable.
|
||||
LightingMode lighting_mode{LightingMode::Auto};
|
||||
// GPU counter readback is diagnostic-only; normal visibility uses no CPU feedback.
|
||||
bool visibility_diagnostics{false};
|
||||
// Optional isolated shader bundle, useful for editor preview and shader reload tests.
|
||||
@@ -204,6 +211,21 @@ struct FrameStats {
|
||||
double gpu_main_cull_ms{}, gpu_main_raster_ms{}, gpu_hzb_ms{};
|
||||
double gpu_post_cull_ms{}, gpu_post_raster_ms{};
|
||||
double gpu_sun_shadow_ms{}, gpu_local_shadow_ms{};
|
||||
TemporalMode requested_temporal_mode{TemporalMode::Off};
|
||||
TemporalMode effective_temporal_mode{TemporalMode::Off};
|
||||
TemporalFallbackReason temporal_fallback_reason{TemporalFallbackReason::None};
|
||||
TemporalResetReason temporal_reset_reason{TemporalResetReason::FirstFrame};
|
||||
bool temporal_history_valid{};
|
||||
std::uint32_t temporal_valid_motion_instances{};
|
||||
std::uint32_t temporal_internal_width{}, temporal_internal_height{};
|
||||
std::array<float, 2> temporal_jitter{};
|
||||
double gpu_temporal_resolve_ms{}, gpu_temporal_composite_ms{}, gpu_ui_ms{};
|
||||
std::vector<std::string> graph_passes;
|
||||
double gpu_light_tiles_ms{};
|
||||
std::uint32_t light_tile_count{};
|
||||
// Optional tile-list readback, valid only when visibility diagnostics are on.
|
||||
bool light_tile_counts_valid{};
|
||||
std::uint32_t light_tile_candidate_count{}, light_tile_overflow_count{};
|
||||
std::string effective_lighting_path{"forward"};
|
||||
std::string device;
|
||||
};
|
||||
@@ -224,6 +246,9 @@ class Renderer {
|
||||
void resize(std::uint32_t width, std::uint32_t height);
|
||||
void set_visibility_mode(VisibilityMode);
|
||||
VisibilityMode visibility_mode() const;
|
||||
void set_temporal_mode(TemporalMode mode, float render_scale = 1.f);
|
||||
TemporalMode temporal_mode() const;
|
||||
float render_scale() const;
|
||||
void set_visibility_diagnostics(bool enabled);
|
||||
// Reads the most recently completed HZB mip for editor diagnostics only.
|
||||
// Normal visibility decisions remain entirely on the GPU.
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace faset::render {
|
||||
|
||||
enum class TemporalMode { Off, TAA, Upscale };
|
||||
|
||||
struct TemporalCapabilities {
|
||||
bool compute{};
|
||||
bool formats{};
|
||||
bool extent{};
|
||||
};
|
||||
|
||||
enum class TemporalFallbackReason {
|
||||
None,
|
||||
ComputeUnavailable,
|
||||
FormatUnavailable,
|
||||
ExtentUnsupported
|
||||
};
|
||||
|
||||
TemporalFallbackReason temporal_fallback_reason(TemporalMode requested,
|
||||
TemporalCapabilities available) noexcept;
|
||||
TemporalMode select_effective_temporal_mode(TemporalMode requested,
|
||||
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 {
|
||||
None,
|
||||
FirstFrame,
|
||||
CameraCut,
|
||||
CameraDiscontinuity,
|
||||
ViewChanged,
|
||||
ViewportChanged,
|
||||
ProjectionChanged,
|
||||
Resize,
|
||||
ModeChanged,
|
||||
ScaleChanged,
|
||||
ShaderReload,
|
||||
Unsupported
|
||||
};
|
||||
|
||||
// Snapshot matrices/rect stay unjittered. This is independent of HZB history:
|
||||
// Direct rendering and GPU frustum mode can still accumulate temporal color.
|
||||
// A missing previous key means no completed frame is available to reuse.
|
||||
struct TemporalHistoryKey {
|
||||
std::string view_id;
|
||||
std::uint32_t output_width{}, output_height{};
|
||||
std::uint32_t internal_width{}, internal_height{};
|
||||
std::array<float, 4> scene_rect{};
|
||||
std::array<float, 16> projection{}, view_projection{};
|
||||
std::array<float, 3> camera_eye{};
|
||||
TemporalMode mode{TemporalMode::Off};
|
||||
float render_scale{1};
|
||||
std::uint64_t shader_generation{};
|
||||
bool camera_cut{};
|
||||
};
|
||||
|
||||
struct TemporalHistoryDecision {
|
||||
bool valid{};
|
||||
TemporalResetReason reason{TemporalResetReason::FirstFrame};
|
||||
};
|
||||
|
||||
TemporalHistoryDecision
|
||||
evaluate_temporal_history(const std::optional<TemporalHistoryKey>& previous,
|
||||
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.
|
||||
// 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.
|
||||
std::array<float, 2> temporal_jitter(std::uint64_t frame_index,
|
||||
std::uint32_t viewport_width,
|
||||
std::uint32_t viewport_height);
|
||||
|
||||
// Scene-local normalized UV motion, current minus previous. Both clips use
|
||||
// their own jittered scene VP and the same local vertex. Invalid/behind-eye
|
||||
// clips have no usable history; the shader writes velocity validity zero.
|
||||
std::optional<std::array<float, 2>>
|
||||
project_motion(const std::array<float, 4>& current_clip,
|
||||
const std::array<float, 4>& previous_clip) noexcept;
|
||||
|
||||
} // namespace faset::render
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace faset::render {
|
||||
|
||||
// CPU-only 1:1 oracle for the temporal resolve shader. Color is already
|
||||
// display-referred; motion is current-minus-previous normalized scene UV.
|
||||
// The renderer does not call this per-pixel path in production.
|
||||
struct TemporalReferencePixel {
|
||||
std::array<float, 4> color{0, 0, 0, 1};
|
||||
float depth{1};
|
||||
std::array<float, 2> motion{};
|
||||
float previous_depth{1};
|
||||
bool motion_valid{};
|
||||
float reactive{};
|
||||
};
|
||||
|
||||
struct TemporalReferenceFrame {
|
||||
std::uint32_t width{}, height{};
|
||||
std::vector<TemporalReferencePixel> pixels;
|
||||
};
|
||||
|
||||
struct TemporalReferenceHistory {
|
||||
std::uint32_t width{}, height{};
|
||||
std::vector<std::array<float, 4>> color;
|
||||
std::vector<float> depth;
|
||||
};
|
||||
|
||||
struct TemporalReferenceResult {
|
||||
TemporalReferenceHistory history;
|
||||
std::vector<std::uint8_t> accepted;
|
||||
std::uint32_t accepted_count{};
|
||||
};
|
||||
|
||||
// A null previous history is the first-frame/cut fallback. Extents must agree
|
||||
// when history is supplied; reset it instead of reprojecting stale dimensions.
|
||||
// Input is bounded to four million pixels so synthetic tests cannot consume
|
||||
// unbounded memory. Invalid motion rejects history for that pixel.
|
||||
TemporalReferenceResult temporal_reference_resolve(
|
||||
const TemporalReferenceFrame& current,
|
||||
const TemporalReferenceHistory* previous = nullptr);
|
||||
|
||||
} // namespace faset::render
|
||||
@@ -37,11 +37,19 @@ struct InstanceUpdate {
|
||||
bool previous_valid{};
|
||||
};
|
||||
|
||||
// GPU instance metadata: history valid, stable slot, then generation low/high.
|
||||
// GPU instance metadata: x bits 0/1 separately mark previous HZB bounds and
|
||||
// previous temporal transform; the other lanes carry stable slot/generation.
|
||||
// A zero generation identifies an anonymous, untracked draw.
|
||||
inline constexpr std::uint32_t gpu_hzb_history_bit = 1U;
|
||||
inline constexpr std::uint32_t gpu_temporal_history_bit = 2U;
|
||||
constexpr std::array<std::uint32_t, 4>
|
||||
gpu_instance_metadata(const InstanceUpdate& update, bool history_compatible) noexcept {
|
||||
return {update.previous_valid && history_compatible ? 1U : 0U, update.slot,
|
||||
gpu_instance_metadata(const InstanceUpdate& update, bool hzb_compatible,
|
||||
bool temporal_compatible = false) noexcept {
|
||||
return {update.previous_valid
|
||||
? (hzb_compatible ? gpu_hzb_history_bit : 0U) |
|
||||
(temporal_compatible ? gpu_temporal_history_bit : 0U)
|
||||
: 0U,
|
||||
update.slot,
|
||||
static_cast<std::uint32_t>(update.generation),
|
||||
static_cast<std::uint32_t>(update.generation >> 32)};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user