Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9bac5b6a8 | ||
|
|
5cb818f0cc | ||
|
|
fe3a589174 | ||
|
|
c8580ec3b2 | ||
|
|
1a1f69f7e8 | ||
|
|
a2c2b085ef | ||
|
|
0c96ce5cdf | ||
|
|
7297d01436 | ||
|
|
0dcc8790a0 | ||
|
|
c53e51520c | ||
|
|
8fbaf83326 | ||
|
|
7c88356fb4 | ||
|
|
3a4a262750 | ||
|
|
1e247e60e9 | ||
|
|
56a5bc62f6 | ||
|
|
4b8f9b8132 | ||
|
|
17177357fb | ||
|
|
068f2e9af9 | ||
|
|
b67cdc1ce8 |
@@ -39,6 +39,54 @@ const char* visibility_mode_name(faset::render::VisibilityMode mode) {
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
const char* temporal_mode_name(faset::render::TemporalMode mode) {
|
||||
switch (mode) {
|
||||
case faset::render::TemporalMode::Off: return "off";
|
||||
case faset::render::TemporalMode::TAA: return "taa";
|
||||
case faset::render::TemporalMode::Upscale: return "upscale";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
const char* temporal_fallback_name(faset::render::TemporalFallbackReason reason) {
|
||||
using Reason = faset::render::TemporalFallbackReason;
|
||||
switch (reason) {
|
||||
case Reason::None: return "none";
|
||||
case Reason::ComputeUnavailable: return "compute-unavailable";
|
||||
case Reason::FormatUnavailable: return "format-unavailable";
|
||||
case Reason::ExtentUnsupported: return "extent-unsupported";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
const char* temporal_reset_name(faset::render::TemporalResetReason reason) {
|
||||
using Reason = faset::render::TemporalResetReason;
|
||||
switch (reason) {
|
||||
case Reason::None: return "none";
|
||||
case Reason::FirstFrame: return "first-frame";
|
||||
case Reason::CameraCut: return "camera-cut";
|
||||
case Reason::CameraDiscontinuity: return "camera-discontinuity";
|
||||
case Reason::ViewChanged: return "view-changed";
|
||||
case Reason::ViewportChanged: return "viewport-changed";
|
||||
case Reason::ProjectionChanged: return "projection-changed";
|
||||
case Reason::Resize: return "resize";
|
||||
case Reason::ModeChanged: return "mode-changed";
|
||||
case Reason::ScaleChanged: return "scale-changed";
|
||||
case Reason::ShaderReload: return "shader-reload";
|
||||
case Reason::Unsupported: return "unsupported";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
float render_scale_value(const std::string& value) {
|
||||
std::size_t consumed{};
|
||||
float scale{};
|
||||
try {
|
||||
scale = std::stof(value, &consumed);
|
||||
} catch (const std::exception&) {
|
||||
throw std::invalid_argument("--render-scale must be a finite number");
|
||||
}
|
||||
if (consumed != value.size() || !std::isfinite(scale))
|
||||
throw std::invalid_argument("--render-scale must be a finite number");
|
||||
return scale;
|
||||
}
|
||||
struct ProfileSample {
|
||||
double wall{}, simulation{}, snapshot{}, render{}, rendererCpu{}, gpu{}, readbackCpu{};
|
||||
faset::runtime::FrameStats runtime;
|
||||
@@ -126,6 +174,26 @@ Json profileFrames(const std::vector<ProfileSample>& samples) {
|
||||
gpuMeasured ? Json(sample.lighting.gpu_sun_shadow_ms) : Json(nullptr)},
|
||||
{"gpu_local_shadow_ms",
|
||||
gpuMeasured ? Json(sample.lighting.gpu_local_shadow_ms) : Json(nullptr)},
|
||||
{"requested_temporal_mode",
|
||||
temporal_mode_name(sample.lighting.requested_temporal_mode)},
|
||||
{"effective_temporal_mode",
|
||||
temporal_mode_name(sample.lighting.effective_temporal_mode)},
|
||||
{"temporal_fallback_reason",
|
||||
temporal_fallback_name(sample.lighting.temporal_fallback_reason)},
|
||||
{"temporal_reset_reason",
|
||||
temporal_reset_name(sample.lighting.temporal_reset_reason)},
|
||||
{"temporal_history_valid", sample.lighting.temporal_history_valid},
|
||||
{"temporal_valid_motion_instances",
|
||||
sample.lighting.temporal_valid_motion_instances},
|
||||
{"temporal_internal_width", sample.lighting.temporal_internal_width},
|
||||
{"temporal_internal_height", sample.lighting.temporal_internal_height},
|
||||
{"temporal_jitter", sample.lighting.temporal_jitter},
|
||||
{"gpu_temporal_resolve_ms",
|
||||
gpuMeasured ? Json(sample.lighting.gpu_temporal_resolve_ms) : Json(nullptr)},
|
||||
{"gpu_temporal_composite_ms",
|
||||
gpuMeasured ? Json(sample.lighting.gpu_temporal_composite_ms) : Json(nullptr)},
|
||||
{"gpu_ui_ms",
|
||||
gpuMeasured ? Json(sample.lighting.gpu_ui_ms) : Json(nullptr)},
|
||||
{"gpu_light_tiles_ms",
|
||||
gpuMeasured ? Json(sample.lighting.gpu_light_tiles_ms) : Json(nullptr)},
|
||||
{"light_tile_count", sample.lighting.light_tile_count},
|
||||
@@ -242,6 +310,8 @@ int player_main(int argc, char** argv) {
|
||||
projectRoot;
|
||||
bool headless = false, validateOnly = false, debugPhysics = false, watchLua = false;
|
||||
auto visibilityMode = faset::render::VisibilityMode::Direct;
|
||||
auto temporalMode = faset::render::TemporalMode::Off;
|
||||
float renderScale = 1.f;
|
||||
std::string visibilityName = "direct";
|
||||
std::uint64_t maximumFrames = 0;
|
||||
std::set<std::string> options;
|
||||
@@ -252,7 +322,8 @@ int player_main(int argc, char** argv) {
|
||||
<< "faset_player [--scene PATH] [--assets CACHE] [--frames N] "
|
||||
"[--headless] [--capture PATH.ppm] [--validate] [--control PATH] "
|
||||
"[--profile PATH.json] [--debug-physics] [--project ROOT] "
|
||||
"[--watch-lua] [--visibility direct|gpu-frustum|gpu-occlusion]\n"
|
||||
"[--watch-lua] [--visibility direct|gpu-frustum|gpu-occlusion] "
|
||||
"[--temporal off|taa|upscale] [--render-scale 0.5..1]\n"
|
||||
"No --scene: open scene.fscene beside the executable. CACHE contains "
|
||||
"assets/<id>/.\n"
|
||||
"Headless uses offscreen Vulkan; --frames uses the configured fixed "
|
||||
@@ -270,6 +341,9 @@ int player_main(int argc, char** argv) {
|
||||
"--visibility selects the renderer for this Player run; Direct is "
|
||||
"the default. GPU modes require their packaged shader bundle and "
|
||||
"device capabilities.\n"
|
||||
"--temporal selects scene TAA or temporal upscaling; Off is the default. "
|
||||
"--render-scale applies only to upscale and must be at least 0.5 "
|
||||
"and less than 1. UI remains at output resolution.\n"
|
||||
"Keys: A/D horizontal, W/S vertical, Space jump, E interact, P pause, "
|
||||
"N single-step, F3 physics boxes, Escape quit.\n";
|
||||
return 0;
|
||||
@@ -306,6 +380,18 @@ int player_main(int argc, char** argv) {
|
||||
"or gpu-occlusion");
|
||||
} else if (arg == "--frames")
|
||||
maximumFrames = count(value());
|
||||
else if (arg == "--temporal") {
|
||||
const auto selected = value();
|
||||
if (selected == "off")
|
||||
temporalMode = faset::render::TemporalMode::Off;
|
||||
else if (selected == "taa")
|
||||
temporalMode = faset::render::TemporalMode::TAA;
|
||||
else if (selected == "upscale")
|
||||
temporalMode = faset::render::TemporalMode::Upscale;
|
||||
else
|
||||
throw std::invalid_argument("--temporal must be off, taa or upscale");
|
||||
} else if (arg == "--render-scale")
|
||||
renderScale = render_scale_value(value());
|
||||
else if (arg == "--headless")
|
||||
headless = true;
|
||||
else if (arg == "--validate")
|
||||
@@ -317,6 +403,7 @@ int player_main(int argc, char** argv) {
|
||||
else
|
||||
throw std::invalid_argument("Unknown option: " + arg);
|
||||
}
|
||||
(void)faset::render::temporal_internal_extent(1280, 720, temporalMode, renderScale);
|
||||
if (options.contains("--profile") &&
|
||||
(profilePath.empty() || !options.contains("--frames") || maximumFrames > 100000 ||
|
||||
validateOnly))
|
||||
@@ -421,6 +508,8 @@ int player_main(int argc, char** argv) {
|
||||
renderConfig.headless = headless;
|
||||
renderConfig.validation = true;
|
||||
renderConfig.visibility_mode = visibilityMode;
|
||||
renderConfig.temporal_mode = temporalMode;
|
||||
renderConfig.render_scale = renderScale;
|
||||
faset::render::Renderer renderer(renderConfig);
|
||||
const auto rendererReady = Clock::now();
|
||||
std::vector<ProfileSample> profile;
|
||||
@@ -612,6 +701,13 @@ int player_main(int argc, char** argv) {
|
||||
<< "using "
|
||||
<< visibility_mode_name(renderer.stats().effective_visibility_mode)
|
||||
<< " rendering.\n";
|
||||
if (frames == 0 && temporalMode != renderer.stats().effective_temporal_mode)
|
||||
std::cerr << "Requested " << temporal_mode_name(temporalMode)
|
||||
<< " temporal rendering is unavailable ("
|
||||
<< temporal_fallback_name(renderer.stats().temporal_fallback_reason)
|
||||
<< "); using "
|
||||
<< temporal_mode_name(renderer.stats().effective_temporal_mode)
|
||||
<< ".\n";
|
||||
const auto frameFinished = Clock::now();
|
||||
if (frames == 0)
|
||||
firstFrameMs = milliseconds(started, frameFinished);
|
||||
@@ -657,6 +753,12 @@ int player_main(int argc, char** argv) {
|
||||
{"visibility_mode", visibilityName},
|
||||
{"effective_visibility_mode",
|
||||
visibility_mode_name(stats.effective_visibility_mode)},
|
||||
{"temporal_mode", temporal_mode_name(temporalMode)},
|
||||
{"render_scale", renderScale},
|
||||
{"effective_temporal_mode",
|
||||
temporal_mode_name(stats.effective_temporal_mode)},
|
||||
{"temporal_fallback_reason",
|
||||
temporal_fallback_name(stats.temporal_fallback_reason)},
|
||||
{"effective_lighting_path", stats.effective_lighting_path},
|
||||
{"simulation_mode", "synthetic_fixed_timestep"},
|
||||
{"fixed_delta_seconds", config.fixedDelta},
|
||||
@@ -686,6 +788,9 @@ int player_main(int argc, char** argv) {
|
||||
{"visibility_mode", visibilityName},
|
||||
{"effective_visibility_mode",
|
||||
visibility_mode_name(stats.effective_visibility_mode)},
|
||||
{"temporal_mode", temporal_mode_name(temporalMode)},
|
||||
{"effective_temporal_mode",
|
||||
temporal_mode_name(stats.effective_temporal_mode)},
|
||||
{"gpu_visibility_active", stats.gpu_visibility_active},
|
||||
{"validation_errors", stats.validation_errors}}
|
||||
.dump()
|
||||
|
||||
@@ -17,6 +17,24 @@ foreach(FASET_ENTRY vertexMain fragmentMain shadowMain)
|
||||
DEPENDS "${PROJECT_SOURCE_DIR}/shaders/baseline.slang" "${PROJECT_SOURCE_DIR}/tools/compile_shader.py" VERBATIM)
|
||||
list(APPEND FASET_SHADER_OUTPUTS "${FASET_SHADER_OUTPUT}" "${FASET_SHADER_DIRECTORY}/${FASET_ENTRY}.reflection.json")
|
||||
endforeach()
|
||||
foreach(FASET_ENTRY temporalVertexMain temporalFragmentMain)
|
||||
set(FASET_SHADER_OUTPUT "${FASET_SHADER_DIRECTORY}/${FASET_ENTRY}.spv")
|
||||
add_custom_command(OUTPUT "${FASET_SHADER_OUTPUT}" "${FASET_SHADER_DIRECTORY}/${FASET_ENTRY}.reflection.json"
|
||||
COMMAND "${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/tools/compile_shader.py"
|
||||
--compiler "${SLANGC_EXECUTABLE}" --source "${PROJECT_SOURCE_DIR}/shaders/baseline.slang"
|
||||
--entry "${FASET_ENTRY}" --output "${FASET_SHADER_DIRECTORY}"
|
||||
BYPRODUCTS "${FASET_SHADER_DIRECTORY}/${FASET_ENTRY}.slang-reflection.json"
|
||||
DEPENDS "${PROJECT_SOURCE_DIR}/shaders/baseline.slang" "${PROJECT_SOURCE_DIR}/tools/compile_shader.py" VERBATIM)
|
||||
list(APPEND FASET_SHADER_OUTPUTS "${FASET_SHADER_OUTPUT}" "${FASET_SHADER_DIRECTORY}/${FASET_ENTRY}.reflection.json")
|
||||
endforeach()
|
||||
set(FASET_SHADER_OUTPUT "${FASET_SHADER_DIRECTORY}/gpuTemporalVertexMain.spv")
|
||||
add_custom_command(OUTPUT "${FASET_SHADER_OUTPUT}" "${FASET_SHADER_DIRECTORY}/gpuTemporalVertexMain.reflection.json"
|
||||
COMMAND "${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/tools/compile_shader.py"
|
||||
--compiler "${SLANGC_EXECUTABLE}" --source "${PROJECT_SOURCE_DIR}/shaders/gpu_scene.slang"
|
||||
--entry gpuTemporalVertexMain --define FASET_GPU_GRAPHICS=1 --output "${FASET_SHADER_DIRECTORY}"
|
||||
BYPRODUCTS "${FASET_SHADER_DIRECTORY}/gpuTemporalVertexMain.slang-reflection.json"
|
||||
DEPENDS "${PROJECT_SOURCE_DIR}/shaders/gpu_scene.slang" "${PROJECT_SOURCE_DIR}/tools/compile_shader.py" VERBATIM)
|
||||
list(APPEND FASET_SHADER_OUTPUTS "${FASET_SHADER_OUTPUT}" "${FASET_SHADER_DIRECTORY}/gpuTemporalVertexMain.reflection.json")
|
||||
set(FASET_SHADER_OUTPUT "${FASET_SHADER_DIRECTORY}/lightTileMain.spv")
|
||||
add_custom_command(OUTPUT "${FASET_SHADER_OUTPUT}" "${FASET_SHADER_DIRECTORY}/lightTileMain.reflection.json"
|
||||
COMMAND "${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/tools/compile_shader.py"
|
||||
@@ -42,13 +60,28 @@ foreach(FASET_ENTRY gpuVertexMain gpuShadowMain gpuCullMain gpuHzbMain gpuPostCu
|
||||
DEPENDS "${PROJECT_SOURCE_DIR}/shaders/gpu_scene.slang" "${PROJECT_SOURCE_DIR}/tools/compile_shader.py" VERBATIM)
|
||||
list(APPEND FASET_SHADER_OUTPUTS "${FASET_SHADER_OUTPUT}" "${FASET_SHADER_DIRECTORY}/${FASET_ENTRY}.reflection.json")
|
||||
endforeach()
|
||||
foreach(FASET_ENTRY temporalResolveMain temporalCompositeVertexMain temporalCompositeFragmentMain)
|
||||
if(FASET_ENTRY STREQUAL "temporalResolveMain")
|
||||
set(FASET_TEMPORAL_DEFINE FASET_TEMPORAL_RESOLVE=1)
|
||||
else()
|
||||
set(FASET_TEMPORAL_DEFINE FASET_TEMPORAL_COMPOSITE=1)
|
||||
endif()
|
||||
set(FASET_SHADER_OUTPUT "${FASET_SHADER_DIRECTORY}/${FASET_ENTRY}.spv")
|
||||
add_custom_command(OUTPUT "${FASET_SHADER_OUTPUT}" "${FASET_SHADER_DIRECTORY}/${FASET_ENTRY}.reflection.json"
|
||||
COMMAND "${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/tools/compile_shader.py"
|
||||
--compiler "${SLANGC_EXECUTABLE}" --source "${PROJECT_SOURCE_DIR}/shaders/temporal.slang"
|
||||
--entry "${FASET_ENTRY}" --define "${FASET_TEMPORAL_DEFINE}" --output "${FASET_SHADER_DIRECTORY}"
|
||||
BYPRODUCTS "${FASET_SHADER_DIRECTORY}/${FASET_ENTRY}.slang-reflection.json"
|
||||
DEPENDS "${PROJECT_SOURCE_DIR}/shaders/temporal.slang" "${PROJECT_SOURCE_DIR}/tools/compile_shader.py" VERBATIM)
|
||||
list(APPEND FASET_SHADER_OUTPUTS "${FASET_SHADER_OUTPUT}" "${FASET_SHADER_DIRECTORY}/${FASET_ENTRY}.reflection.json")
|
||||
endforeach()
|
||||
add_custom_command(OUTPUT "${FASET_SHADER_DIRECTORY}/compatibility.spv"
|
||||
COMMAND "${SLANGC_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/shaders/compatibility.hlsl"
|
||||
-entry compatibilityMain -stage compute -target spirv -profile spirv_1_6
|
||||
-o "${FASET_SHADER_DIRECTORY}/compatibility.spv"
|
||||
DEPENDS "${PROJECT_SOURCE_DIR}/shaders/compatibility.hlsl" VERBATIM)
|
||||
add_custom_target(faset_shaders DEPENDS ${FASET_SHADER_OUTPUTS} "${FASET_SHADER_DIRECTORY}/compatibility.spv")
|
||||
add_library(faset_render "${PROJECT_SOURCE_DIR}/src/render/renderer.cpp" "${PROJECT_SOURCE_DIR}/src/render/math.cpp" "${PROJECT_SOURCE_DIR}/src/render/render_graph.cpp" "${PROJECT_SOURCE_DIR}/src/render/shader_contract.cpp" "${PROJECT_SOURCE_DIR}/src/render/lighting.cpp")
|
||||
add_library(faset_render "${PROJECT_SOURCE_DIR}/src/render/renderer.cpp" "${PROJECT_SOURCE_DIR}/src/render/math.cpp" "${PROJECT_SOURCE_DIR}/src/render/render_graph.cpp" "${PROJECT_SOURCE_DIR}/src/render/shader_contract.cpp" "${PROJECT_SOURCE_DIR}/src/render/lighting.cpp" "${PROJECT_SOURCE_DIR}/src/render/temporal.cpp" "${PROJECT_SOURCE_DIR}/src/render/temporal_reference.cpp")
|
||||
target_include_directories(faset_render PUBLIC "${PROJECT_SOURCE_DIR}/include")
|
||||
target_compile_features(faset_render PUBLIC cxx_std_20)
|
||||
target_link_libraries(faset_render PRIVATE Vulkan::Vulkan SDL3::SDL3 faset_core)
|
||||
@@ -67,6 +100,36 @@ if(BUILD_TESTING)
|
||||
target_link_libraries(faset_render_lighting_policy_tests PRIVATE faset_render)
|
||||
add_test(NAME render_lighting_policy COMMAND faset_render_lighting_policy_tests)
|
||||
set_tests_properties(render_lighting_policy PROPERTIES LABELS "p3")
|
||||
add_executable(faset_render_temporal_graph_tests "${PROJECT_SOURCE_DIR}/tests/render_temporal_graph_tests.cpp")
|
||||
target_link_libraries(faset_render_temporal_graph_tests PRIVATE faset_render)
|
||||
add_test(NAME render_temporal_graph COMMAND faset_render_temporal_graph_tests)
|
||||
set_tests_properties(render_temporal_graph PROPERTIES LABELS "gpu")
|
||||
add_executable(faset_render_temporal_acceptance_tests "${PROJECT_SOURCE_DIR}/tests/render_temporal_acceptance_tests.cpp")
|
||||
target_link_libraries(faset_render_temporal_acceptance_tests PRIVATE faset_render)
|
||||
add_test(NAME render_temporal_acceptance COMMAND faset_render_temporal_acceptance_tests)
|
||||
set_tests_properties(render_temporal_acceptance PROPERTIES LABELS "gpu")
|
||||
add_test(NAME render_temporal_quality_matrix COMMAND
|
||||
"${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/tests/test_temporal_quality_matrix.py"
|
||||
"$<TARGET_FILE:faset_render_temporal_acceptance_tests>")
|
||||
set_tests_properties(render_temporal_quality_matrix PROPERTIES LABELS "p3")
|
||||
add_executable(faset_render_temporal_shader_contract_tests "${PROJECT_SOURCE_DIR}/tests/render_temporal_shader_contract_tests.cpp")
|
||||
target_include_directories(faset_render_temporal_shader_contract_tests PRIVATE "${PROJECT_SOURCE_DIR}/src/render")
|
||||
target_link_libraries(faset_render_temporal_shader_contract_tests PRIVATE faset_render faset_core)
|
||||
target_compile_definitions(faset_render_temporal_shader_contract_tests PRIVATE FASET_TEST_SHADER_DIRECTORY="${FASET_SHADER_DIRECTORY}")
|
||||
add_test(NAME render_temporal_shader_contract COMMAND faset_render_temporal_shader_contract_tests)
|
||||
add_executable(faset_render_temporal_reference_tests "${PROJECT_SOURCE_DIR}/tests/render_temporal_reference_tests.cpp")
|
||||
target_link_libraries(faset_render_temporal_reference_tests PRIVATE faset_render)
|
||||
add_test(NAME render_temporal_reference COMMAND faset_render_temporal_reference_tests)
|
||||
add_executable(faset_render_temporal_lifecycle_tests "${PROJECT_SOURCE_DIR}/tests/render_temporal_lifecycle_tests.cpp")
|
||||
target_link_libraries(faset_render_temporal_lifecycle_tests PRIVATE faset_render)
|
||||
add_test(NAME render_temporal_lifecycle COMMAND faset_render_temporal_lifecycle_tests)
|
||||
set_tests_properties(render_temporal_lifecycle PROPERTIES LABELS "gpu")
|
||||
add_executable(faset_render_temporal_motion_tests "${PROJECT_SOURCE_DIR}/tests/render_temporal_motion_tests.cpp")
|
||||
target_link_libraries(faset_render_temporal_motion_tests PRIVATE faset_render)
|
||||
add_test(NAME render_temporal_motion COMMAND faset_render_temporal_motion_tests)
|
||||
add_executable(faset_render_temporal_policy_tests "${PROJECT_SOURCE_DIR}/tests/render_temporal_policy_tests.cpp")
|
||||
target_link_libraries(faset_render_temporal_policy_tests PRIVATE faset_render)
|
||||
add_test(NAME render_temporal_policy COMMAND faset_render_temporal_policy_tests)
|
||||
add_executable(faset_render_tests "${PROJECT_SOURCE_DIR}/tests/render_tests.cpp")
|
||||
target_link_libraries(faset_render_tests PRIVATE faset_render SDL3::SDL3)
|
||||
add_test(NAME render_graph COMMAND faset_render_tests --unit)
|
||||
|
||||
|
After Width: | Height: | Size: 1.7 MiB |
@@ -12,6 +12,12 @@ On Windows, use `windows-debug` for both presets and `build/windows-debug/faset_
|
||||
|
||||
Use the **Visibility** selector to compare **Direct**, **GPU frustum**, and **GPU occlusion** on the same open scene. This is a live renderer setting for the Editor viewport; it does not change the scene or exported game. The selected mode is independent of **Freeze counters**. The counters describe the previous completed frame, so render one more frame after changing modes before reading them. **Effective path** names the algorithm that actually ran. A **Fallback from** line appears when device or target capabilities prevent the selected mode; for example, GPU occlusion may use GPU frustum if HZB is unavailable.
|
||||
|
||||
Use the **Temporal** selector for **Off**, **TAA**, or **Upscale**. Upscale shows a
|
||||
50–99% render-scale slider; output UI remains sharp. The requested/effective
|
||||
mode, fallback reason, internal extent, history reset reason and temporal GPU
|
||||
pass times are shown separately from visibility and HZB history. This selector
|
||||
only changes the live Editor viewport. See [Temporal rendering](temporal.md).
|
||||
|
||||
The panel reports the previous completed frame: renderer wall time, GPU timestamp time where available, synchronous readback time, draw calls, packed vertices, culled meshes, textures, explicit Vulkan allocation sizes, actual validation availability/errors, and GPU pass-label count. It also shows whether GPU visibility ran, submitted indirect bins, visible instances, frustum rejects, deferred and post-pass visible instances, HZB history validity, counts per prepared LOD level, and GPU pass timings where available. GPU counts are explicitly marked unavailable until the first frame rendered with diagnostics open; only a displayed zero is a measured zero. **Previous HZB history: invalid** is expected after a camera cut or resize until compatible depth history is available. A current HZB preview can still exist after that first frame because it was built from the current depth. Renderer wall time includes waiting for GPU work; it is not thread CPU usage. Memory excludes driver-internal allocations. The overlay itself adds drawing work, so hide it for a baseline performance measurement.
|
||||
|
||||
In **GPU occlusion** mode, enable **Show HZB** to inspect the current grayscale depth pyramid. The **Mip** slider selects a pyramid level; the preview starts at mip 3 to keep its readback small. A larger mip number shows coarser depth. The preview reads the HZB only while the panel and toggle are open, and only once per completed frame or mip change. Switching it off or closing the panel releases the preview; its GPU texture retires when the next frame begins. Opening diagnostics also enables readback of GPU visibility counters, which is disabled again when the panel closes. Disable the HZB preview for performance comparisons: its diagnostic copy and texture upload add GPU and CPU work. **Freeze counters** does not freeze the HZB image.
|
||||
|
||||
@@ -118,6 +118,32 @@ and reads back the full image, so `cpu_ms` is wall time including waits, not CPU
|
||||
utilization. An open scene can run slower with HZB; visibility correctness and
|
||||
full-frame speed are separate findings.
|
||||
|
||||
## Compare temporal modes
|
||||
|
||||
Use one scene, output resolution, camera sequence, visibility path, binary and GPU
|
||||
for Off, TAA and Upscale. Run enough frames to include both the first-frame reset
|
||||
and steady-state accumulation. Keep the raw captures as well as timing samples:
|
||||
|
||||
```sh
|
||||
./faset_player --headless --frames 240 --profile off.json --temporal off
|
||||
./faset_player --headless --frames 240 --profile taa.json --temporal taa
|
||||
./faset_player --headless --frames 240 --profile upscale.json \
|
||||
--temporal upscale --render-scale 0.67
|
||||
```
|
||||
|
||||
The profile records requested and effective temporal modes, fallback and history
|
||||
reset reason, internal/output extent, jitter, and valid previous-transform count
|
||||
per completed frame. `gpu_temporal_resolve_ms`, `gpu_temporal_composite_ms`, and
|
||||
`gpu_ui_ms` are separate submitted GPU pass times when timestamp queries work;
|
||||
otherwise they are `null`. `gpu_allocated_bytes` includes live temporal targets
|
||||
and histories, subject to the allocation limits described above. Compare full
|
||||
frame GPU and renderer wall time too: scene raster savings can be offset by
|
||||
resolve, memory and synchronous readback. A valid frame-level history flag says
|
||||
the previous frame may be sampled, not that every pixel accepted it. For image
|
||||
quality, inspect a still thin edge, a slow pan and a newly uncovered surface, and
|
||||
compare the same frame against Off. See [Temporal rendering](temporal.md) for
|
||||
mode controls and native C++ configuration.
|
||||
|
||||
## Measure P3 lighting and shadows
|
||||
|
||||
A Player `--profile` sample includes `effective_lighting_path`, local lights
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# Temporal rendering
|
||||
|
||||
Faset renders the scene with **Off** by default. In the optional Editor diagnostics
|
||||
panel (**F12**), choose **TAA** to accumulate a full-resolution scene over successive
|
||||
frames, or **Upscale** to render the scene at a lower resolution and reconstruct it
|
||||
at the output resolution. The Upscale slider accepts 50–99%; 67% is a useful
|
||||
starting point for visual comparison. UI text and controls always render at output
|
||||
resolution after the scene resolve. Shadow maps keep their own unjittered views.
|
||||
|
||||
The diagnostic selector affects only the current Editor viewport. It does not edit
|
||||
the scene, gameplay code, or an exported Player. The panel's **Requested** and
|
||||
**Effective** fields identify a device fallback. It also shows internal and output
|
||||
extent, whether the previous completed frame's color history was eligible, the
|
||||
reason it reset, and separate GPU times for resolve, composite and UI where
|
||||
timestamp queries are available. A reset on the first frame, camera cut, changed
|
||||
view, resize, scale switch or compatible shader reload is expected. A valid history
|
||||
does not imply every pixel reused it: newly visible surfaces can still reject
|
||||
their individual history samples.
|
||||
|
||||
For a Player or exported game, select the mode at launch:
|
||||
|
||||
```sh
|
||||
./faset_player --headless --frames 120 --temporal taa --profile taa.json
|
||||
./faset_player --headless --frames 120 --temporal upscale \
|
||||
--render-scale 0.67 --profile upscale.json
|
||||
```
|
||||
|
||||
`--temporal` accepts `off`, `taa`, or `upscale`. Off and TAA use scale `1`; Upscale
|
||||
requires a scale from `0.5` inclusive to `1` exclusive. An invalid mode or scale
|
||||
stops startup with an error. If Vulkan compute or the required image formats are
|
||||
unavailable, the renderer falls back to Off and records its effective mode and
|
||||
reason in the profile. Direct, GPU frustum and GPU occlusion visibility can be
|
||||
combined with either temporal mode. See [Profiling](profiling.md) for how to compare
|
||||
their timings fairly.
|
||||
|
||||
Native renderer users can make the same choice without modifying gameplay scripts:
|
||||
|
||||
```cpp
|
||||
faset::render::RendererConfig config;
|
||||
config.temporal_mode = faset::render::TemporalMode::Upscale;
|
||||
config.render_scale = 0.67f;
|
||||
faset::render::Renderer renderer(config);
|
||||
|
||||
// A live viewport switch recreates scene targets and resets color history.
|
||||
renderer.set_temporal_mode(faset::render::TemporalMode::TAA);
|
||||
```
|
||||
|
||||
Provide a stable `DrawItem::instance_key` for moving opaque objects so the renderer
|
||||
can find their previous model transform. Camera cuts must be marked in the
|
||||
`Snapshot`; cuts, teleports and incompatible projection changes reject old history.
|
||||
World transparency and sprites use the scene depth/order and reject stale color on
|
||||
their reactive pixels. TAA and Upscale are optional image-quality paths; compare
|
||||
them against Off on the actual game scene, especially thin geometry, slow pans,
|
||||
newly revealed surfaces and moving transparent content.
|
||||
|
||||
These are first-generation, opt-in reconstruction modes. They can reduce shimmer
|
||||
on a stationary edge while lowering the peak brightness of a subpixel line, and
|
||||
a newly revealed edge can differ by one pixel from its settled appearance. The
|
||||
amount depends on scene content, resolution and motion. Upscale also trades
|
||||
internal render resolution for resolve cost and extra images; it is not always
|
||||
faster. Compare Off, TAA and Upscale at the target output resolution with both
|
||||
still and moving cameras, and inspect thin objects and opening doors before
|
||||
choosing a mode for a game. The [P3 temporal validation record](https://github.com/emil28092005/Faset_Engine/blob/main/docs/validation/p3-temporal-2026-09-24/README.md)
|
||||
contains source captures, paired image measurements and a bounded 720p cost
|
||||
profile.
|
||||
@@ -6,6 +6,7 @@ These files preserve bounded checks and their inputs. Each record states its sou
|
||||
- [P2 GPU visibility Linux evidence](p2-gpu-visibility-2026-09-23/README.md): Debug/Release GPU acceptance, lavapipe functional checks, relocated Player exports, and explicit platform/performance limits.
|
||||
- [P2 pinned SwiftShader compatibility](p2-swiftshader-2026-09-23/README.md): the Windows CI regression, shader capability fix, independent review closure, final native CI and relocated Player evidence.
|
||||
- [P3 lighting and shadows](p3-lighting-2026-09-24/README.md): implementation, acceptance matrix, bounded evidence, and remaining Forward+/platform checks; temporal reconstruction is tracked separately.
|
||||
- [P3 temporal reconstruction](p3-temporal-2026-09-24/README.md): Off/TAA/Upscale source captures, three-path image-quality matrix, edge and reveal measurements, and bounded 720p costs.
|
||||
- [Windows software Vulkan](windows-software-vulkan-2026-09-18/README.md): fresh native build, 35 tests, launcher/window/MCP workflows and both relocated Release games on SwiftShader.
|
||||
- [Checkpoint 5 Linux acceptance](checkpoint5-linux-2026-09-18/README.md): clean offline source build, first Editor launch, exact-candidate standalone games and live Blender checks.
|
||||
- [Final Linux source checks](final-linux-2026-09-18/README.md): `4cb8255` integrated test results and both Release games after the asset-relocation correction, including package manifests and standalone captures.
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
# P3 temporal reconstruction: image quality and cost, 2026-09-24
|
||||
|
||||
This record covers Faset's first-generation, opt-in TAA and temporal upscaler.
|
||||
The 370-frame Direct captures and 720p cost profile below were executed from
|
||||
`1a1f69f7e88b3ea69986931dfcf6baf9b43a431c` on
|
||||
`feat/p3-temporal-integration`. The later [three-path quality matrix](matrix/README.md)
|
||||
was executed from `fe3a589174f8fe8e35ee231fe74fe938f4dd3cbd` before the
|
||||
pixel-diagnostics shader merge. The combined P1+P3 performance sweep at
|
||||
`4a3453e` is a different run, not the source of these images. The physical
|
||||
Vulkan device was an NVIDIA GeForce RTX 2080 Ti with proprietary driver 595.84
|
||||
on Linux. Both original Direct fixtures requested Vulkan validation and every
|
||||
recorded frame reported zero
|
||||
validation errors. These observations do not establish physical Windows GPU
|
||||
behavior or high-end reconstruction quality.
|
||||
|
||||
## Raw artifacts and method
|
||||
|
||||
- [Lossless PNG example](captures/wire-static-taa-15.png) and
|
||||
[frame-level CSV linking every capture](frames.csv) cover
|
||||
the Direct visibility path at 160×120, except the 319×241 resize frame.
|
||||
The sequences are static subpixel wire (16 phases), slow camera pan (16),
|
||||
moving cube (16), unobstructed background (16), opening door (4), camera cut
|
||||
(2), resize (2), and translucent world geometry plus sharp UI (2). Each has
|
||||
Off, jittered current-only TAA, accumulated TAA, current-only Upscale, and
|
||||
accumulated Upscale captures. The current-only controls mark each frame as a
|
||||
camera cut: they retain the same 16-phase jitter and internal extent while
|
||||
rejecting history. Upscale uses render scale 0.67. Off has no jitter.
|
||||
- [Computed metrics](metrics.json) contain every sample, fixed ROI coordinates,
|
||||
steady-state timing distributions and paired image errors. The
|
||||
[contact sheet](contact-sheet.png) shows representative source frames without
|
||||
artistic retouching. The [door edge detail](door-edge-nearest-5x.png) crops
|
||||
phase 3 and enlarges pixels 5× with nearest-neighbor sampling; its second row
|
||||
is the same-jitter unobstructed reference.
|
||||
`tools/analyze_temporal_quality.py` converts the raw PPM output of
|
||||
`faset_render_temporal_acceptance_tests --capture-quality DIR` to PNG
|
||||
losslessly and computes the metrics. It needs the pinned NumPy and Pillow
|
||||
versions in `tools/requirements-temporal-quality.txt`.
|
||||
- [720p raw profile](profile-720p-debug.csv) contains 30 measured frames per
|
||||
Off/TAA/Upscale mode after 10 warm-up frames, with rotating mode order.
|
||||
`faset_render_temporal_acceptance_tests --profile-720p CSV` reproduces this
|
||||
fixed Direct scene: one opaque cube and one thin wire, output 1280×720,
|
||||
Upscale internal 858×483. It was a Linux Debug run with validation requested,
|
||||
not a Release or gameplay frame-rate result. GPU timestamps include submitted
|
||||
work and the synchronous image-to-buffer capture; renderer CPU time includes
|
||||
the wait and host readback.
|
||||
|
||||
Reproduction from a configured build:
|
||||
|
||||
```sh
|
||||
cmake --build build/linux-debug --target faset_render_temporal_acceptance_tests --parallel 2
|
||||
ctest --test-dir build/linux-debug --no-tests=error -R '^render_temporal_acceptance$' --output-on-failure
|
||||
build/linux-debug/faset_render_temporal_acceptance_tests --capture-quality /tmp/faset-p3-quality
|
||||
python3 tools/analyze_temporal_quality.py --input /tmp/faset-p3-quality \
|
||||
--output docs/validation/p3-temporal-2026-09-24 \
|
||||
--revision 1a1f69f7e88b3ea69986931dfcf6baf9b43a431c \
|
||||
--driver 'NVIDIA proprietary 595.84'
|
||||
build/linux-debug/faset_render_temporal_acceptance_tests --profile-720p /tmp/faset-p3-720p.csv
|
||||
```
|
||||
|
||||
## Image-quality observations
|
||||
|
||||
The static variation measure is the mean absolute RGB difference between
|
||||
consecutive frames over phases 5–15 in the fixed ROI; lower means less frame
|
||||
shimmer, but it says nothing by itself about retained contrast. Wire energy is
|
||||
the mean summed RGB value over phases 4–15 in that ROI, and peak is the mean
|
||||
brightest channel per frame. All color figures use 8-bit output values.
|
||||
|
||||
| 160×120 fixture | Current-only | Accumulated | Change |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| Static wire RGB frame delta, TAA | 0.388 | 0.360 | −7.2% |
|
||||
| Static wire RGB frame delta, Upscale | 0.439 | 0.412 | −6.1% |
|
||||
| Static wire ROI RGB energy, TAA | 8,234 | 8,275 | +0.5% |
|
||||
| Static wire ROI RGB energy, Upscale | 7,429 | 7,485 | +0.8% |
|
||||
| Static wire mean peak, TAA | 179 | 167 | −6.7% |
|
||||
| Static wire mean peak, Upscale | 179 | 161 | −9.9% |
|
||||
| Slow pan RGB frame delta, TAA | 0.399 | 0.398 | −0.2% |
|
||||
| Moving cube RGB frame delta, TAA | 1.930 | 1.917 | includes real motion |
|
||||
|
||||
The separate static cube-edge acceptance ROI fell from 0.832 jittered
|
||||
current-only to about 0.739 under TAA. Its exact percentage does not transfer
|
||||
to the thin wire. The original resolver achieved a much larger apparent wire
|
||||
variance reduction by dimming the wire about 15%; the final depth-aware
|
||||
resolver retains total wire energy within 1% in this fixture. It still reduces
|
||||
peak contrast and offers little gain during a slow pan. Upscale is not
|
||||
equivalent to a full-resolution reference for thin detail.
|
||||
Off has zero static frame variation because it has no jitter; that does not
|
||||
make its edges alias-free.
|
||||
|
||||
The door's newly exposed center matched current-only within one RGB value on
|
||||
the first open frame. No red pixels from the old door appeared in the tested
|
||||
old-door ROI. Against a phase-aligned, unobstructed temporal background, the
|
||||
30×35 edge ROI had 121 TAA and 176 Upscale pixels with any channel difference
|
||||
above 8 on the first open frame, then 17 and 28 respectively on the next frame.
|
||||
The enlarged comparison shows a one-pixel dark green/top-bottom edge
|
||||
difference, with no displaced red silhouette. It is a bounded residual, not a
|
||||
claim of zero halo. The explicit cut output matched
|
||||
current-only exactly in its ROI; resize and UI pixels matched their controls.
|
||||
The existing Direct, GPU-frustum and GPU-occlusion tests separately cover
|
||||
moving reveal, cut, reset, resize, shader reload and Vulkan validation.
|
||||
|
||||
## 1280×720 cost on this sparse scene
|
||||
|
||||
All values below are milliseconds except allocation. p95 is linearly
|
||||
interpolated at rank `(n − 1) × 0.95` among the 30 measured frames.
|
||||
|
||||
| Mode | Full GPU p50 / p95 | Resolve p50 / p95 | Composite p50 / p95 | Renderer CPU p50 / p95 | Live Vulkan allocation |
|
||||
| --- | ---: | ---: | ---: | ---: | ---: |
|
||||
| Off | 0.560 / 0.580 | — | — | 2.256 / 2.527 | 43.02 MiB |
|
||||
| TAA | 0.671 / 0.715 | 0.081 / 0.101 | 0.026 / 0.028 | 2.545 / 2.927 | 76.77 MiB (+33.75) |
|
||||
| Upscale 0.67 | 0.672 / 0.703 | 0.079 / 0.099 | 0.025 / 0.027 | 2.630 / 3.448 | 68.52 MiB (+25.50) |
|
||||
|
||||
Main raster p50 was only 0.015–0.018 ms because this fixture is sparse; at
|
||||
this resolution and content, lower scene resolution did not pay for the output
|
||||
resolve/composite. The depth-aware 2×2 history sampling is included in these
|
||||
costs. A dense game scene, Release build, another driver or display path may
|
||||
have different results. The renderer's synchronous full-frame capture makes
|
||||
these CPU and GPU totals unsuitable as unqualified gameplay FPS predictions.
|
||||
|
||||
TAA and Upscale therefore remain opt-in. Compare Off and a jittered
|
||||
current-only capture at the game's target resolution, watch thin-object peak
|
||||
contrast and newly revealed edges, and profile the whole frame. These fixtures
|
||||
are a bounded regression check, not parity with Unreal TSR, FSR or DLSS.
|
||||
|
After Width: | Height: | Size: 186 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 185 B |
|
After Width: | Height: | Size: 262 B |
|
After Width: | Height: | Size: 186 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 185 B |
|
After Width: | Height: | Size: 254 B |
|
After Width: | Height: | Size: 185 B |
|
After Width: | Height: | Size: 254 B |
|
After Width: | Height: | Size: 209 B |
|
After Width: | Height: | Size: 205 B |
|
After Width: | Height: | Size: 214 B |
|
After Width: | Height: | Size: 210 B |
|
After Width: | Height: | Size: 210 B |
|
After Width: | Height: | Size: 207 B |
|
After Width: | Height: | Size: 209 B |
|
After Width: | Height: | Size: 213 B |
|
After Width: | Height: | Size: 214 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 209 B |
|
After Width: | Height: | Size: 211 B |
|
After Width: | Height: | Size: 209 B |
|
After Width: | Height: | Size: 205 B |
|
After Width: | Height: | Size: 215 B |
|
After Width: | Height: | Size: 205 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 209 B |
|
After Width: | Height: | Size: 237 B |
|
After Width: | Height: | Size: 250 B |
|
After Width: | Height: | Size: 263 B |
|
After Width: | Height: | Size: 251 B |
|
After Width: | Height: | Size: 253 B |
|
After Width: | Height: | Size: 261 B |
|
After Width: | Height: | Size: 253 B |
|
After Width: | Height: | Size: 255 B |
|
After Width: | Height: | Size: 254 B |
|
After Width: | Height: | Size: 248 B |
|
After Width: | Height: | Size: 242 B |
|
After Width: | Height: | Size: 260 B |
|
After Width: | Height: | Size: 242 B |
|
After Width: | Height: | Size: 258 B |
|
After Width: | Height: | Size: 262 B |
|
After Width: | Height: | Size: 205 B |
|
After Width: | Height: | Size: 244 B |
|
After Width: | Height: | Size: 274 B |
|
After Width: | Height: | Size: 264 B |
|
After Width: | Height: | Size: 257 B |
|
After Width: | Height: | Size: 255 B |
|
After Width: | Height: | Size: 271 B |
|
After Width: | Height: | Size: 261 B |
|
After Width: | Height: | Size: 246 B |
|
After Width: | Height: | Size: 267 B |
|
After Width: | Height: | Size: 286 B |
|
After Width: | Height: | Size: 257 B |
|
After Width: | Height: | Size: 263 B |
|
After Width: | Height: | Size: 261 B |
|
After Width: | Height: | Size: 252 B |
|
After Width: | Height: | Size: 276 B |
|
After Width: | Height: | Size: 205 B |
|
After Width: | Height: | Size: 199 B |
|
After Width: | Height: | Size: 198 B |
|
After Width: | Height: | Size: 203 B |
|
After Width: | Height: | Size: 200 B |
|
After Width: | Height: | Size: 204 B |
|
After Width: | Height: | Size: 201 B |
|
After Width: | Height: | Size: 203 B |
|
After Width: | Height: | Size: 202 B |
|
After Width: | Height: | Size: 204 B |
|
After Width: | Height: | Size: 200 B |
|
After Width: | Height: | Size: 204 B |
|
After Width: | Height: | Size: 201 B |
|
After Width: | Height: | Size: 202 B |
|
After Width: | Height: | Size: 198 B |
|
After Width: | Height: | Size: 199 B |
|
After Width: | Height: | Size: 263 B |
|
After Width: | Height: | Size: 267 B |