Author SHA1 Message Date
Emil a9bac5b6a8 Record three-path temporal quality and spatial references
Native and manual checks / native (ubuntu-24.04) (push) Failing after 36s
Native and manual checks / manual (push) Successful in 27s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
2026-09-24 05:36:15 +03:00
Emil 5cb818f0cc Record active Vulkan validation in temporal 720p profile 2026-09-24 05:34:13 +03:00
Emil fe3a589174 Capture temporal quality across visibility paths and spatial references 2026-09-24 05:27:41 +03:00
Emil c8580ec3b2 Document temporal quality captures and 720p costs
Native and manual checks / native (ubuntu-24.04) (push) Failing after 33s
Native and manual checks / manual (push) Successful in 30s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 05:03:07 +03:00
Emil 1a1f69f7e8 Preserve temporal edge contrast with depth-aware history
Native and manual checks / native (ubuntu-24.04) (push) Failing after 35s
Native and manual checks / manual (push) Successful in 31s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 04:51:58 +03:00
Emil a2c2b085ef Fix temporal tiled lighting and HZB extents
Native and manual checks / native (ubuntu-24.04) (push) Failing after 29s
Native and manual checks / manual (push) Successful in 26s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 04:15:16 +03:00
Emil 0c96ce5cdf Integrate Forward+ lighting with temporal rendering
Native and manual checks / native (ubuntu-24.04) (push) Failing after 34s
Native and manual checks / manual (push) Successful in 28s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 04:05:58 +03:00
Emil 7297d01436 Expose and package temporal rendering with guarded edge history
Native and manual checks / native (ubuntu-24.04) (push) Failing after 37s
Native and manual checks / manual (push) Successful in 27s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 03:59:22 +03:00
Emil 0dcc8790a0 Render temporal scene with motion, resolve and sharp UI
Native and manual checks / native (ubuntu-24.04) (push) Failing after 37s
Native and manual checks / manual (push) Successful in 28s
Windows editor and software Vulkan / windows-graphics (push) Canceled after 0s
Native and manual checks / native (windows-2025) (push) Canceled after 0s
2026-09-24 03:16:44 +03:00
Emil c53e51520c Specify temporal GPU graph and image acceptance in red tests 2026-09-24 02:57:50 +03:00
Emil 8fbaf83326 Compile and validate temporal resolve shader bundle 2026-09-24 02:57:37 +03:00
Emil 7c88356fb4 Reject cross-surface temporal motion dilation 2026-09-24 02:57:25 +03:00
Emil 3a4a262750 Add bounded CPU reference for temporal resolve 2026-09-24 02:57:25 +03:00
Emil 1e247e60e9 Specify renderer temporal lifecycle in red tests 2026-09-24 02:57:06 +03:00
Emil 56a5bc62f6 Commit temporal history only after completed frames 2026-09-24 02:56:51 +03:00
Emil 4b8f9b8132 Validate temporal scale and deterministic internal extent 2026-09-24 02:56:51 +03:00
Emil 17177357fb Define independent temporal motion metadata and UV oracle 2026-09-24 02:56:51 +03:00
Emil 068f2e9af9 Handle camera roll and explicit temporal Off 2026-09-24 02:56:39 +03:00
Emil b67cdc1ce8 Add temporal history and jitter policy 2026-09-24 02:56:39 +03:00
1819 changed files with 11412 additions and 1641 deletions
+5 -8
View File
@@ -199,19 +199,16 @@ GPU instance record содержит стабильные slot/generation; пл
### P3. Освещение, тени и temporal reconstruction
**Освещение, тени и измеренный выбор пути реализованы; приёмка всего P3 ещё
**Освещение и тени реализованы до измеренного выбора пути; приёмка этапа ещё
открыта.** Есть authored directional/point/spot lights, общий shader ABI для
Direct и P2, четыре каскада солнца, отдельный 16-face atlas для point/spot,
видимость каскадеров из shadow views и общий бюджет 4096 caster draws.
Ранжирование 128 local lights, атомарный отказ от шести point faces и
unshadowed fallback доступны с диагностикой. На Linux reference GPU Release
1920×1080 измеренный рост стоимости main raster превысил порог для проверки
Forward+. Depth-free tiled путь 16×16 прошёл image parity, но полный build +
raster на плотной контрольной сцене оказался медленнее; `Auto` оставлен на
forward, явный tiled доступен для локализованных источников и выиграл в
отдельном сценарии. [Исследование](docs/studies/23-p3-forward-plus-2026-09-24.md)
и [протокол проверки](docs/validation/p3-lighting-2026-09-24/README.md)
отделяют этот Linux checkpoint от финальной Windows/temporal приёмки.
1920×1080 измеренный рост стоимости main raster уже превысил порог для
Forward+, поэтому depth-free tiled путь 16×16 и повторные измерения входят в
оставшуюся работу. [Протокол проверки](docs/validation/p3-lighting-2026-09-24/README.md)
отделяет текущий checkpoint от финальной Linux/Windows приёмки.
Затем: previous transforms, motion vectors, jitter, history rejection и TAA; temporal upscaling — после устойчивого TAA. Проверять тонкую геометрию, движение, disocclusion, camera cut и смену разрешения, сравнивать с режимом без temporal. У cache/pass видны затраты и причины обновления.
+106 -1
View File
@@ -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()
+64 -1
View File
@@ -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)
+3 -21
View File
@@ -508,24 +508,6 @@ repeats, ten warm-up and thirty measured frames per configuration. It reached
the agreed Forward+ gate: main-raster overhead at 32 lights was about 0.50 ms
relative to the matching zero-light case, roughly 30% of that GPU frame;
64 and 128 lights added about 1.02 and 2.03 ms. Its raw CSV/report are being
published separately with the exact benchmark revision and driver.
Revision `a0a4e29` adds an explicit depth-free 16×16 tiled Forward+ path. One
compute invocation tests every submitted point/spot range sphere against a
tile's four screen-space planes and writes at most 64 stable-order indices.
An overflowing tile scans the entire submitted list in the fragment shader;
there is no dropped light. The tile shader has exact reflection validation,
package/build integration, GPU timing, optional occupancy readback, and
reload rollback. Direct, GPU frustum and GPU occlusion image tests cover a
cropped viewport, resize, near-plane light/shadow, and overflow. A 128-light
localized 1920×1080 forward/tiled capture matched byte for byte.
The [paired Release study](studies/23-p3-forward-plus-2026-09-24.md) measured
tile build **plus** raster on the same RTX 2080 Ti source revision and shader
bundle. At 32/64/128 broad overlapping lights it was 0.064/0.122/0.222 ms
slower; all 8160 tiles overflowed at 128. A separate localized-range scene
was 0.103/0.214/0.440 ms faster at those counts, with no overflows. There is
no robust scene/device runtime predictor yet, so `Auto` remains forward and
`Tiled` is explicit. Linux Debug passed 62 CTests with one window skip;
pinned Linux SwiftShader passed all six P3 cases. Windows CI on this new
revision and P3 temporal reconstruction are separate acceptance work.
published separately with the exact benchmark revision and driver. A 16×16
tiled Forward+ path, image parity and before/after build+raster measurement are
therefore pending. Temporal reconstruction is developed and accepted separately.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

+7 -7
View File
@@ -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.
@@ -27,16 +33,10 @@ When GPU timestamps are available, the panel shows sun and local shadow pass
durations. A zero duration after a disabled sun or sprite-only frame confirms
that no sun shadow raster ran. The lighting path names the algorithm actually
used, so compare it with a benchmark's requested mode before interpreting costs.
For an explicitly tiled frame, the overlay also reports the 16×16 grid size,
its compute GPU duration, the number of stored light candidates, and how many
tiles overflowed their 64-index list and scanned all lights. Candidate and
overflow counts require the diagnostics readback; **unavailable** is distinct
from a measured zero. The diagnostic copy itself adds work, so close the panel
before measuring performance.
See [Lighting](lighting.md) for the 128-light and 16-tile limits.
The Vulkan backend emits `VK_EXT_debug_utils` labels for `SunShadowAtlas`,
`LocalShadowAtlas`, `LightTileBuild` when tiled, `ForwardAndUI`, `Readback`, and, when presenting,
`LocalShadowAtlas`, `ForwardAndUI`, `Readback`, and, when presenting,
`Presentation`. A fallback frame can have no shadow-raster label. A graphics
capture tool that supports this extension can identify the command-buffer
regions. Labels remain available without the Khronos validation layer when
-19
View File
@@ -72,25 +72,6 @@ cleared and redrawn each frame; there is no persistent shadow cache yet.
Sprite-only scenes, a missing sun, and a sun with `casts_shadow: false` skip sun
shadow raster work.
## Local-light rendering path
The normal `Auto` setting uses the measured forward light scan. It is the
current default for Editor and Player. A C++ renderer integration can explicitly
set `RendererConfig::lighting_mode = LightingMode::Tiled` to build depth-free
16×16 screen-tile lists on a capable Vulkan device. Each tile stores at most
64 light indices in stable order. If more lights touch a tile, its fragment
shader scans the complete submitted list, so an overflow never removes
illumination. The path falls back to forward when no local lights are present
or the compute/buffer requirements are unavailable. Sprites and UI stay unlit.
This explicit path can help when light ranges occupy small parts of the screen;
it costs extra work when nearly every light covers nearly every tile. The
fixed dense benchmark was slower after including tile construction, so there
is no automatic scene-dependent switch yet. The Player profile reports
`effective_lighting_path`, tile GPU time and grid size; optional Editor
diagnostics also report stored candidates and overflowing tiles. See
[Profiling](profiling.md) and the [measured Forward+ study](https://github.com/emil28092005/Faset_Engine/blob/main/docs/studies/23-p3-forward-plus-2026-09-24.md).
## Add a point light through MCP
MCP edits the **Editor document**, not entities in a running game. Use
+26 -21
View File
@@ -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
@@ -130,15 +156,6 @@ submitted-light count of zero is a different workload from 128 lights whose
shadows are disabled. See [Lighting](lighting.md) for the capacity policy and
[Diagnostics](diagnostics.md) for the Editor counters.
The same sample includes `effective_lighting_path` (`forward` or `tiled`),
`gpu_light_tiles_ms`, and `light_tile_count`. Stored candidate and overflow
counts are present only when visibility diagnostics readback was enabled;
`light_tile_counts_valid: false` means their `null` values are unavailable,
not zero. The normal `Auto` setting currently resolves to `forward` after the
fixed dense 1080p benchmark showed that tile construction cost outweighed its
raster savings. A C++ renderer integration can explicitly request `Tiled` for
a localized-light scene, then check the actual path before comparing timings.
The fixed-scene benchmark compares 0, 4, 16, 32, 64, and 128 local lights under
Direct, GPU frustum, and GPU occlusion visibility, with shadows on and off. Its
wrapper runs three independent 1920×1080 repetitions per configuration, each
@@ -171,18 +188,6 @@ frame** at 32, 64, or 128 lights on the Linux physical reference GPU. The
states the measured decision and scope. A software Vulkan run checks
functionality, not physical GPU performance.
For a direct comparison of the two algorithms on the same scene, invoke the
Release executable twice with `--lighting forward` and `--lighting tiled`,
using the same `--lights`, `--shadows`, `--visibility`, and output size. The
default `--light-layout dense` preserves the fixed benchmark scene;
`--light-layout localized` reduces point-light ranges to 1.75 units as a
separately labelled workload. Compare `gpu_build_plus_raster_ms`, which includes
`gpu_light_tiles_ms`, rather than raster time alone. One optional diagnostic
frame with `--tile-diagnostics on` reports candidate and overflow counts but
adds a GPU readback, so do not mix it into the timed runs. The
[Forward+ measurement](https://github.com/emil28092005/Faset_Engine/blob/main/docs/studies/23-p3-forward-plus-2026-09-24.md) retains
raw frames, shader hashes, and the decision.
## Current performance scope
The accepted MVP path uses direct draws and CPU culling; P2 adds optional GPU
+65
View File
@@ -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.
@@ -1,105 +0,0 @@
# P3 Forward+ experiment: correctness and cost on localized lights
The fixed 1920×1080 P3 benchmark crossed the agreed threshold for trying
Forward+. A depth-free 16×16 tiled implementation now exists, but the measured
**build + raster** cost is higher than a full light scan on that benchmark's
dense lights. `RendererConfig::lighting_mode = Auto` therefore keeps the forward
path. `Tiled` is an explicit option for scenes whose projected light volumes
are localized. There is no unmeasured automatic occupancy heuristic.
This is a follow-up to the fixed-scene baseline sweep, which is being merged
as a separate study. It compares both paths in the same source revision
`a0a4e29d480ed3344f19bd3565d48668ca913fed`. The baseline's dense
placement remains the default. An explicit `--light-layout localized` changes
only point-light range from 8 to 1.75 world units; camera, nine casters,
receiver, positions, colors, light count, and output size are unchanged. The
localized fixture is a separate workload, not a replacement for the fixed
baseline gate.
## Renderer behavior and safety
The compute pass builds up to 64 stable-order light indices per screen tile.
It tests each world-space range sphere against four clip-space tile planes.
It does not use depth or reject near-plane intersections. A tile with more than
64 candidates sets an overflow bit; the fragment shader then scans **all**
submitted lights for that tile. Zero lights, missing capability, excessive
buffer size, failed optional allocation, and `Auto` use the forward path. The
shader contract checks the new compute entry's descriptors and 96-byte push
constants; Direct and P2 GPU graphics still use materials at set 0, lighting
at set 1, and GPU scene data at set 2. The tile list is set 1 binding 4 in the
shared fragment shader. Sprite/UI shading returns before tile reads.
The Linux Vulkan image test compares forward and tiled output in Direct, GPU
frustum, and GPU occlusion modes, including a cropped scene viewport, near-plane
crossing point light and shadow, resize, an offscreen light, and 80 coincident
lights that exceed tile capacity. Every overflowing tile falls back to the full
list. Shader reload preserves a working tiled pipeline after invalid bytecode
and recreates it after a valid reload. A separate 1920×1080 capture with 128
localized lights was byte-identical across both paths; its SHA-256 is in the
[provenance record](data/p3-forward-plus-provenance-2026-09-24.json).
## Measurement
The device was NVIDIA GeForce RTX 2080 Ti with NVIDIA driver 595.84.0.0,
Linux Clang Release, Direct visibility, shadows off, 1920×1080. Each mode had
three independent process runs with ten warm-up and thirty measured frames.
Forward/tiled run order alternated. The table uses the median of the three
per-run medians in milliseconds. The tile build column is an actual GPU
timestamp; `build + raster` also includes post raster if present. The dense
and localized CSVs contain every one of the 1080 measured frames, with a
`source_csv` identifier. The executable and all loaded `.spv`/reflection
SHA-256 values are in the provenance record.
| Light layout | Lights | Forward raster | Tile build | Tiled raster | Tiled build + raster | Tiled change |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| Dense fixed scene | 32 | 0.5500 | 0.0617 | 0.5527 | 0.6144 | +0.0644 ms (11.7% slower) |
| Dense fixed scene | 64 | 1.0701 | 0.1177 | 1.0740 | 1.1921 | +0.1220 ms (11.4% slower) |
| Dense fixed scene | 128 | 2.1172 | 0.2219 | 2.1164 | 2.3388 | +0.2216 ms (10.5% slower) |
| Localized range 1.75 | 32 | 0.2336 | 0.0555 | 0.0758 | 0.1312 | −0.1025 ms (43.9% faster) |
| Localized range 1.75 | 64 | 0.4254 | 0.1060 | 0.1057 | 0.2109 | −0.2144 ms (50.4% faster) |
| Localized range 1.75 | 128 | 0.8094 | 0.2048 | 0.1643 | 0.3691 | −0.4404 ms (54.4% faster) |
At 32 dense lights, the first forward process had a 0.7405 ms run median;
the other two were 0.5488 and 0.5500 ms. A single paired run would have
incorrectly suggested a tiled win. The median of three process medians and a
separate earlier repeat both support the slower dense result. This is why
`Auto` remains forward despite the localized-scene gain. The total GPU frame
also includes visibility, shadow fallback, copies, and synchronous readback;
the table isolates the passes that the optimization changes. For example, at
32 localized lights the full GPU frame was 1.6494 ms forward and 1.6472 ms
tiled, essentially unchanged despite lower build + raster cost. At 128 it
was 2.2788 versus 1.7948 ms.
One diagnostic frame per layout/count copied the tile buffer after the timed
draw. That copy was **not enabled** in the 1080 performance frames. The grid
has 8160 tiles and a 64-index capacity per tile.
| Layout | Lights | Stored candidates across tiles | Overflowed tiles |
| --- | ---: | ---: | ---: |
| Dense | 32 | 259,896 | 0 |
| Dense | 64 | 519,792 | 0 |
| Dense | 128 | 522,240 | 8,160 |
| Localized | 32 | 38,237 | 0 |
| Localized | 64 | 76,103 | 0 |
| Localized | 128 | 152,202 | 0 |
The dense 128 candidate count is capped at 64 × 8160 stored slots; all tiles
overflow and correctly evaluate all 128 lights in the fragment shader. This
explains why paying for tile construction cannot help that frame. The localized
128 scene averages about 19 stored candidates per tile and avoids fallback.
Raw data: [all paired frames](data/p3-forward-plus-ab-2026-09-24.csv),
[diagnostic frames](data/p3-forward-plus-diagnostics-2026-09-24.csv), and
[binary/shader provenance](data/p3-forward-plus-provenance-2026-09-24.json).
## Verification and scope
At the implementation revision, Linux Debug built all targets and passed
62/63 CTests, with the compositor-dependent window lifecycle case skipped and
no failures. The pinned Linux SwiftShader ICD passed all six P3 cases, including
the tiled parity/overflow test. The [lighting validation record](../validation/p3-lighting-2026-09-24/README.md)
retains those logs. These are functional checks on Linux and software Vulkan,
not physical Windows GPU performance. The A/B numbers apply to one GPU, driver,
camera, receiver and two synthetic light layouts. They do not establish an
engine-wide speedup. A measured runtime occupancy predictor and representative
game scenes are prerequisites before changing `Auto` from forward.
+1 -2
View File
@@ -1,6 +1,6 @@
# Исследования для Faset Engine
Обновлено 24.09.2026. Исходники и официальная документация исследовались прежде всего 17.09.2026; затем результаты согласованы с принятой архитектурой. P2/P3 implementation/acceptance добавлены позже и отделены от исходного статического исследования.
Обновлено 23.09.2026. Исходники и официальная документация исследовались прежде всего 17.09.2026; затем результаты согласованы с принятой архитектурой. P2 implementation/acceptance добавлены позже и отделены от исходного статического исследования.
**Актуальные решения — в [ARCHITECTURE.md](../ARCHITECTURE.md), порядок реализации — в [PLAN.md](../../PLAN.md).** Реализация MVP и проверки идут отдельно: [журнал реализации](../IMPLEMENTATION.md), [результаты проверок](../validation/README.md), [пользовательский Manual](../manual/index.md). Исследования дают обоснования и проверочные сценарии; их статический анализ не является измерением Faset.
@@ -41,7 +41,6 @@ Linux/Windows, десктопные 2D/3D, C++ сначала и Lua следу
- [19 — P2 GPU visibility: протокол приёмки](19-p2-gpu-visibility-acceptance.md): GPU-сценарии, допуски сравнения и методика измерений; отделяет проверку реализации от предложений исследования 15.
- [20 — P2 GPU visibility: первый benchmark](20-p2-gpu-visibility-benchmark-2026-09-23.md): три запуска, raw CSV, p50/p95 и границы интерпретации на Linux reference GPU.
- [21 — P2 GPU visibility: оптимизация MainCull](21-p2-gpu-visibility-optimization-2026-09-23.md): отдельные измерения переноса выходных буферов в память GPU и замены CAS-цикла на atomic add.
- [23 — P3 Forward+: корректность и стоимость](23-p3-forward-plus-2026-09-24.md): сравнение полной цены построения плиток и рисования на плотной и локализованной сценах, raw CSV и точный shader/binary provenance.
## Происхождение и воспроизводимость
File diff suppressed because it is too large Load Diff
@@ -1,7 +0,0 @@
light_count,light_layout,shadows,visibility,effective_visibility,lighting_path,requested_lighting,build_configuration,run_index,frame,device,driver,commit,width,height,validation_enabled,validation_errors,submitted_local_lights,omitted_local_lights,requested_local_shadow_faces,rendered_local_shadow_faces,dropped_shadow_faces,shadow_atlas_full_drops,shadow_tiles,draw_calls,gpu_bytes,gpu_main_raster_ms,gpu_post_raster_ms,gpu_post_visible,visibility_counters_valid,gpu_sun_shadow_ms,gpu_local_shadow_ms,gpu_shadow_ms,gpu_light_tiles_ms,gpu_build_plus_raster_ms,light_tile_count,light_tile_counts_valid,light_tile_candidate_count,light_tile_overflow_count,gpu_ms,cpu_ms,readback_cpu_ms
32,dense,off,direct,direct,tiled,tiled,Release,0,0,NVIDIA GeForce RTX 2080 Ti,NVIDIA 595.84.0.0,a0a4e29d480ed3344f19bd3565d48668ca913fed,1920,1080,0,0,32,0,0,0,0,0,0,10,63881248,0.551200,0.000000,0,0,0.000000,0.000000,0.000000,0.061472,0.612672,8160,1,259896,0,3.324384,4.920211,1.106698
64,dense,off,direct,direct,tiled,tiled,Release,0,0,NVIDIA GeForce RTX 2080 Ti,NVIDIA 595.84.0.0,a0a4e29d480ed3344f19bd3565d48668ca913fed,1920,1080,0,0,64,0,0,0,0,0,0,10,63883808,1.077376,0.000000,0,0,0.000000,0.000000,0.000000,0.117440,1.194816,8160,1,519792,0,3.713568,5.236988,1.014093
128,dense,off,direct,direct,tiled,tiled,Release,0,0,NVIDIA GeForce RTX 2080 Ti,NVIDIA 595.84.0.0,a0a4e29d480ed3344f19bd3565d48668ca913fed,1920,1080,0,0,128,0,0,0,0,0,0,10,63888928,2.109440,0.000000,0,0,0.000000,0.000000,0.000000,0.221184,2.330624,8160,1,522240,8160,4.435136,6.211166,0.970080
32,localized,off,direct,direct,tiled,tiled,Release,0,0,NVIDIA GeForce RTX 2080 Ti,NVIDIA 595.84.0.0,a0a4e29d480ed3344f19bd3565d48668ca913fed,1920,1080,0,0,32,0,0,0,0,0,0,10,63881248,0.075328,0.000000,0,0,0.000000,0.000000,0.000000,0.055296,0.130624,8160,1,38237,0,2.355808,3.586245,0.930515
64,localized,off,direct,direct,tiled,tiled,Release,0,0,NVIDIA GeForce RTX 2080 Ti,NVIDIA 595.84.0.0,a0a4e29d480ed3344f19bd3565d48668ca913fed,1920,1080,0,0,64,0,0,0,0,0,0,10,63883808,0.105248,0.000000,0,0,0.000000,0.000000,0.000000,0.105312,0.210560,8160,1,76103,0,2.445600,3.884127,1.069417
128,localized,off,direct,direct,tiled,tiled,Release,0,0,NVIDIA GeForce RTX 2080 Ti,NVIDIA 595.84.0.0,a0a4e29d480ed3344f19bd3565d48668ca913fed,1920,1080,0,0,128,0,0,0,0,0,0,10,63888928,0.215040,0.000000,0,0,0.000000,0.000000,0.000000,0.229376,0.444416,8160,1,152202,0,2.585504,4.059668,0.951374
1 light_count light_layout shadows visibility effective_visibility lighting_path requested_lighting build_configuration run_index frame device driver commit width height validation_enabled validation_errors submitted_local_lights omitted_local_lights requested_local_shadow_faces rendered_local_shadow_faces dropped_shadow_faces shadow_atlas_full_drops shadow_tiles draw_calls gpu_bytes gpu_main_raster_ms gpu_post_raster_ms gpu_post_visible visibility_counters_valid gpu_sun_shadow_ms gpu_local_shadow_ms gpu_shadow_ms gpu_light_tiles_ms gpu_build_plus_raster_ms light_tile_count light_tile_counts_valid light_tile_candidate_count light_tile_overflow_count gpu_ms cpu_ms readback_cpu_ms
2 32 dense off direct direct tiled tiled Release 0 0 NVIDIA GeForce RTX 2080 Ti NVIDIA 595.84.0.0 a0a4e29d480ed3344f19bd3565d48668ca913fed 1920 1080 0 0 32 0 0 0 0 0 0 10 63881248 0.551200 0.000000 0 0 0.000000 0.000000 0.000000 0.061472 0.612672 8160 1 259896 0 3.324384 4.920211 1.106698
3 64 dense off direct direct tiled tiled Release 0 0 NVIDIA GeForce RTX 2080 Ti NVIDIA 595.84.0.0 a0a4e29d480ed3344f19bd3565d48668ca913fed 1920 1080 0 0 64 0 0 0 0 0 0 10 63883808 1.077376 0.000000 0 0 0.000000 0.000000 0.000000 0.117440 1.194816 8160 1 519792 0 3.713568 5.236988 1.014093
4 128 dense off direct direct tiled tiled Release 0 0 NVIDIA GeForce RTX 2080 Ti NVIDIA 595.84.0.0 a0a4e29d480ed3344f19bd3565d48668ca913fed 1920 1080 0 0 128 0 0 0 0 0 0 10 63888928 2.109440 0.000000 0 0 0.000000 0.000000 0.000000 0.221184 2.330624 8160 1 522240 8160 4.435136 6.211166 0.970080
5 32 localized off direct direct tiled tiled Release 0 0 NVIDIA GeForce RTX 2080 Ti NVIDIA 595.84.0.0 a0a4e29d480ed3344f19bd3565d48668ca913fed 1920 1080 0 0 32 0 0 0 0 0 0 10 63881248 0.075328 0.000000 0 0 0.000000 0.000000 0.000000 0.055296 0.130624 8160 1 38237 0 2.355808 3.586245 0.930515
6 64 localized off direct direct tiled tiled Release 0 0 NVIDIA GeForce RTX 2080 Ti NVIDIA 595.84.0.0 a0a4e29d480ed3344f19bd3565d48668ca913fed 1920 1080 0 0 64 0 0 0 0 0 0 10 63883808 0.105248 0.000000 0 0 0.000000 0.000000 0.000000 0.105312 0.210560 8160 1 76103 0 2.445600 3.884127 1.069417
7 128 localized off direct direct tiled tiled Release 0 0 NVIDIA GeForce RTX 2080 Ti NVIDIA 595.84.0.0 a0a4e29d480ed3344f19bd3565d48668ca913fed 1920 1080 0 0 128 0 0 0 0 0 0 10 63888928 0.215040 0.000000 0 0 0.000000 0.000000 0.000000 0.229376 0.444416 8160 1 152202 0 2.585504 4.059668 0.951374
@@ -1,33 +0,0 @@
{
"commit": "a0a4e29d480ed3344f19bd3565d48668ca913fed",
"device": "NVIDIA GeForce RTX 2080 Ti",
"driver": "NVIDIA 595.84.0.0",
"build": "Linux Clang Release",
"executable_sha256": "db151a07eeaee04871920f8617f5bcf4c8ac7cb11abf4e36e5595cc526440a36",
"shader_bundle_sha256": "60d2abf2f43557f72f39de7a50f4bc92816eb7859c613b2797c0410e72dbaeed",
"shader_bundle_hash_method": "SHA256 of UTF-8 canonical JSON mapping sorted shader filenames to per-file SHA256",
"shader_files": {
"vertexMain.spv": "1ad2631c35d654f48166321ae43d4165043e1b919b14f1dc61caf97b1ada0898",
"vertexMain.reflection.json": "b3bdff863d53a0b385e06a36701a83a6ecafe9a93e97281a732cce8ff862c0db",
"fragmentMain.spv": "eb65edf62a4ead573f740903dbbd1a61565fe97d234fbe8a09934ce6415bec92",
"fragmentMain.reflection.json": "6b62a42ea448d9ae253d78b6b326dfe0d761532ce695b49263689ec649ede36c",
"shadowMain.spv": "7643b4d688492b5ee923b9606f0b0e70343ca05fa67673838206119a6f86d8d8",
"shadowMain.reflection.json": "d39e2f83c797589296eac9dce139b9ad836a895d6685bc40f79dd2609d687fee",
"lightTileMain.spv": "92850edcc5ee5b586a90ce84ee69a98075a6fff3c13db4a57528d9431afe8b68",
"lightTileMain.reflection.json": "bf6a9256aaa6d6edacfa1e1c265d7c8ce2c1e4ea4036e0bea078c22fd4b536ca",
"gpuVertexMain.spv": "fc9770f304976875f8dd1f6acbe380c77cb06c13276f5596554b3f0b396b31bd",
"gpuVertexMain.reflection.json": "09166d80fba7ab4167e97f85125a7c58ab92ea447c1a6a2ab5084e657401dae9",
"gpuShadowMain.spv": "2da7e7f96bc7de4e9c8ac365fe0a63f608c7bb26ab3e7bfebfcf88b32e86c71d",
"gpuShadowMain.reflection.json": "c9dd844aefb3840f29caad5dfc53986cacc2220f886eda5ad345ebdbb9ff5b1c",
"gpuCullMain.spv": "7691360ec55f41d40d43652f506b051cef8b3a3ddcdcc7db61a7d4d97f9e87d8",
"gpuCullMain.reflection.json": "996fe5c7e31c6b8f316d8f290b3be0ae7063eb46c500a661e8715e9b822a2598",
"gpuHzbMain.spv": "08682362ee2120ed19d260e613285071747c3e436bb09bb6238e46f9cedc2308",
"gpuHzbMain.reflection.json": "589818f670881c64f3aba726ee3bc82fe4b9d835a347f99134e0e157dc4a112e",
"gpuPostCullMain.spv": "62cb72e356346e1511c6d640a3cecb4a6e5283795338299b357edae5f7f52be4",
"gpuPostCullMain.reflection.json": "eed140bbe09c7d14935b688c770261ac0e81677c0974aa811c88ed67ea779b95"
},
"matrix": "Direct, shadows off, 1920x1080, dense or localized, 32/64/128 lights, 3 repeats, 10 warmup + 30 measured frames; alternating A/B order",
"raw_rows": 1080,
"localized_128_capture_sha256": "2aa5d408867f7193b7fd83984f2333b188623bda4d083512ede6b8318f11b45c",
"localized_128_capture_exact_equal": true
}
+2 -1
View File
@@ -5,7 +5,8 @@ These files preserve bounded checks and their inputs. Each record states its sou
- [MVP acceptance dossier](mvp-acceptance.md): criterion-by-criterion closure, tested revisions and remaining compatibility coverage.
- [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, Forward+ A/B and image parity, acceptance matrix, bounded Linux evidence, and remaining Windows/platform checks; temporal reconstruction is tracked separately.
- [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.
@@ -1,11 +1,11 @@
# P3 lighting and shadows — acceptance record
This record tracks P3 lighting separately from temporal reconstruction. The
implementation and measured Forward+ checkpoint is source revision
`a0a4e29d480ed3344f19bd3565d48668ca913fed` on `feat/p3-lighting`.
The earlier shadow/benchmark integration checkpoint was `b191ae0`. The
lighting slice has Linux functional and reference-GPU evidence; Windows CI for
the new tiled revision is pending. Temporal reconstruction has its own acceptance.
implementation checkpoint is source revision
`b191ae0bed77a1544a49504b9a3f07e9a3c691f2` on `feat/p3-lighting`. The
Manual/record edit itself is documentation-only. A later Forward+ change and
its measurements require a new revision and validation entry before the lighting
slice can be called complete. Temporal reconstruction has its own acceptance.
## Implemented at the checkpoint
@@ -31,26 +31,18 @@ the new tiled revision is pending. Temporal reconstruction has its own acceptanc
requested/effective views, drop reasons, atlas bytes, caster draws, GPU shadow
durations, and the effective lighting path. Shadow tiles are redrawn every
frame; no persistent depth cache is claimed.
- An explicit 16×16 depth-free tiled Forward+ path uses at most 64 light indices
per tile and evaluates the complete submitted list on overflow. The compute
entry has checked reflection and is included in game builds. `Auto` uses
forward: three-run Release measurements found tile build + raster slower on
the dense fixed scene. The [paired study](../../studies/23-p3-forward-plus-2026-09-24.md)
retains a separate localized-light win and exact binary/shader provenance.
## Acceptance matrix
| Case | Automated evidence | Current status |
| --- | --- | --- |
| Empty, disabled, local-only, multiple sun; schema bounds | `scene_view`, `render_lighting_policy`, `render_offscreen` | Linux Debug green at `a0a4e29`; integrated revision pending |
| Four cascades, split bounds, subtexel stabilization, offscreen/source-LOD0 caster | `render_lighting_policy`, `render_lighting_sun` | Linux GPU and pinned SwiftShader P3 green at `a0a4e29` |
| Spot cone, six point faces and seam, dropped whole point shadow | `render_lighting_local`, `render_lighting_policy` | Linux GPU and pinned SwiftShader P3 green at `a0a4e29` |
| Empty, disabled, local-only, multiple sun; schema bounds | `scene_view`, `render_lighting_policy`, `render_offscreen` | Covered by Debug tests at the implementation checkpoint; re-run on final revision |
| Four cascades, split bounds, subtexel stabilization, offscreen/source-LOD0 caster | `render_lighting_policy`, `render_lighting_sun` | Covered by CPU policy and Linux Vulkan image tests; final-revision runs pending |
| Spot cone, six point faces and seam, dropped whole point shadow | `render_lighting_local`, `render_lighting_policy` | Covered by Linux Vulkan image and CPU tests; final-revision runs pending |
| 128-light/16-face/4096-draw limits, unsupported-atlas fallback | `render_lighting_policy`, `render_offscreen`, `render_lighting_local` | CPU and supported-atlas GPU paths covered; actual unsupported Vulkan device not tested |
| Direct/GPU frustum/GPU occlusion image parity, P2 reload and 2D/UI independence | `render_lighting_sun`, `render_lighting_local`, `render_shader_reload`, `render_offscreen` | Linux Debug green at `a0a4e29`; integrated revision pending |
| Forward+/forward parity, near plane, resize, overflow, and shader reload | `render_lighting_tiled`, `render_shader_reload`, `render_shader_reflection`, `build_schema_publication` | Linux Debug and pinned SwiftShader P3 green at `a0a4e29`; 128-light localized Release captures match exactly |
| Driver, profile, real 64×64 benchmark smoke | `render_lighting_benchmark_schema`, `render_lighting_benchmark_smoke`, `player_shutdown_diagnostics` | Full Linux Debug green at `a0a4e29` |
| 1920×1080 0/4/16/32/64/128 Release sweep, three repeats, both shadow states | `tools/benchmark_p3_lighting.py --sweep` | Forward baseline measured; its separate raw study is being integrated |
| 1920×1080 paired paths, 32/64/128 dense and localized lights | `faset_p3_lighting_benchmark --lighting forward|tiled` | Raw 1080 frames and six diagnostic samples retained in study 23; dense slower, localized faster by build+raster |
| Direct/GPU frustum/GPU occlusion image parity, P2 reload and 2D/UI independence | `render_lighting_sun`, `render_lighting_local`, `render_shader_reload`, `render_offscreen` | Linux supported-driver paths covered; final-revision runs pending |
| Driver, profile, real 64×64 benchmark smoke | `render_lighting_benchmark_schema`, `render_lighting_benchmark_smoke`, `player_shutdown_diagnostics` | Focused integration tests passed on `b191ae0`; full raw log pending |
| 1920×1080 0/4/16/32/64/128 Release sweep, three repeats, both shadow states | `tools/benchmark_p3_lighting.py --sweep` | Baseline measured on Linux physical GPU; raw CSV and post-Forward+ comparison pending publication |
| Windows native build, pinned SwiftShader GPU tests, relocated Release 2D/3D Players | `windows-graphics.yml`, `ci.yml` | New P3 revision has not yet completed Windows CI |
The supported-atlas GPU tests create a renderer with validation requested and
@@ -64,12 +56,11 @@ reference-GPU results cannot establish physical Windows performance.
The P3 CTest registrations are `render_lighting_policy` and
`render_lighting_benchmark_schema` (CPU), plus `render_lighting_sun`,
`render_lighting_local`, `render_lighting_tiled`, and
`render_lighting_benchmark_smoke` (labelled `gpu;p3`). Use
`ctest --test-dir build/linux-debug -N -L p3` to confirm those six cases exist
before running them; an empty test selection is not a pass.
`render_lighting_local`, and `render_lighting_benchmark_smoke` (labelled
`gpu;p3`). Use `ctest --test-dir build/linux-debug -N -L p3` to confirm those
five cases exist before running them; an empty test selection is not a pass.
The Windows full graphics job runs all registered tests, while the native
Windows CPU job uses `-LE gpu` and therefore excludes the four Vulkan cases.
Windows CPU job uses `-LE gpu` and therefore excludes the three Vulkan cases.
On the Linux host at `b191ae0`, the [CTest inventory](linux-debug-p3-inventory.txt)
listed all five cases. The [CPU-only P3 run](linux-debug-cpu-ctest.txt) passed
@@ -80,22 +71,6 @@ physical-GPU baseline was being measured, so it is not a final GPU acceptance
result. The local host was Linux x86_64, kernel 7.0.0-31-generic; the source
checkout had documentation changes only during these checks.
At `a0a4e29`, the [full Linux Debug run](linux-debug-tiled-ctest.txt) had
63 registered cases: 62 passed, no failures, and the compositor-dependent
window lifecycle case skipped. The [pinned Linux SwiftShader P3 run](linux-swiftshader-tiled-p3-ctest.txt)
passed all six P3 cases without a skip. The Vulkan image cases requested
validation and asserted zero reported errors. The RTX 2080 Ti A/B used NVIDIA
driver 595.84.0.0; study 23 records the executable and shader bundle hashes,
all raw per-frame timings, tile overflow counts, and exact image equality for
the localized 128-light capture. Its first dense 32-light forward run was an
outlier, so the decision uses the median of three process medians rather than
the apparent win in one paired run.
At the earlier `b191ae0` checkpoint, [GitHub native/manual CI](https://github.com/emil28092005/Faset_Engine/actions/runs/35935899512)
and [Windows graphics/SwiftShader CI](https://github.com/emil28092005/Faset_Engine/actions/runs/35935899505)
passed. These jobs did **not** include the new tile shader; Windows CI for
`a0a4e29` is still required.
```sh
cmake --build --preset linux-debug --parallel 2
ctest --test-dir build/linux-debug -L p3 --no-tests=error --output-on-failure
@@ -108,16 +83,15 @@ The benchmark wrapper retains one raw CSV per run, a merged CSV, and a summary.
It rejects visibility fallback, missing GPU timestamps, missing lights, duplicate
frames, and validation errors. An offscreen capture's `cpu_ms` includes GPU wait
and readback; it is not thread CPU time. The exact Release benchmark revision,
driver and paired path data are retained in study 23. Release full CTest,
final integrated Windows Actions and relocated Player checks still need to be
added before this is a complete P3 lighting acceptance record.
driver, CSV paths, before/after Forward+ gate, Linux SwiftShader results, final
Debug/Release CTest logs, and Windows Actions links will be added after those
checks run. Do not use this provisional record as a P3 completion claim.
## Limits carried forward
The default path scans all submitted lights in each mesh fragment; the
measured Forward+ threshold prompted a bounded tiled implementation. `Auto`
still uses forward because this dense fixed workload was slower after tile
construction. Transparent/game UI and sprites keep their existing
The current checkpoint scans all submitted lights in each mesh fragment; the
measured Forward+ threshold was reached on the Linux reference GPU, so a bounded
tiled path is in progress. Transparent/game UI and sprites keep their existing
ordering and unlit behavior. The atlas caps are fixed budgets, not adaptive
quality settings, and shadow depth is redrawn each frame. The renderer still
performs synchronous framebuffer readback. No broad scene/driver matrix or
@@ -1,140 +0,0 @@
Test project /home/emil/Desktop/.worktrees/Faset_Engine-p3-lighting/build/linux-debug
Start 1: authoring
1/63 Test #1: authoring .......................... Passed 0.06 sec
Start 2: runtime_contracts
2/63 Test #2: runtime_contracts .................. Passed 0.12 sec
Start 3: assets_pipeline
3/63 Test #3: assets_pipeline .................... Passed 0.14 sec
Start 4: assets_blender_bundle
4/63 Test #4: assets_blender_bundle .............. Passed 0.08 sec
Start 5: lua_contracts
5/63 Test #5: lua_contracts ...................... Passed 0.11 sec
Start 6: lua_safety_contracts
6/63 Test #6: lua_safety_contracts ............... Passed 0.21 sec
Start 7: render_lighting_sun
7/63 Test #7: render_lighting_sun ................ Passed 0.92 sec
Start 8: render_lighting_local
8/63 Test #8: render_lighting_local .............. Passed 0.87 sec
Start 9: render_lighting_tiled
9/63 Test #9: render_lighting_tiled .............. Passed 2.75 sec
Start 10: render_lighting_policy
10/63 Test #10: render_lighting_policy ............. Passed 0.04 sec
Start 11: render_graph
11/63 Test #11: render_graph ....................... Passed 0.00 sec
Start 12: render_offscreen
12/63 Test #12: render_offscreen ................... Passed 0.43 sec
Start 13: render_sprite_alpha
13/63 Test #13: render_sprite_alpha ................ Passed 0.36 sec
Start 14: render_shader_reload
14/63 Test #14: render_shader_reload ............... Passed 4.49 sec
Start 15: render_gpu_shader_contract
15/63 Test #15: render_gpu_shader_contract ......... Passed 0.06 sec
Start 16: render_shader_reflection
16/63 Test #16: render_shader_reflection ........... Passed 3.63 sec
Start 17: render_window_lifecycle
17/63 Test #17: render_window_lifecycle ............***Skipped 4.02 sec
Start 18: render_lighting_benchmark_schema
18/63 Test #18: render_lighting_benchmark_schema ... Passed 4.02 sec
Start 19: render_lighting_benchmark_smoke
19/63 Test #19: render_lighting_benchmark_smoke .... Passed 0.44 sec
Start 20: visibility_policy
20/63 Test #20: visibility_policy .................. Passed 0.01 sec
Start 21: gpu_visibility
21/63 Test #21: gpu_visibility ..................... Passed 0.90 sec
Start 22: render_gpu_empty
22/63 Test #22: render_gpu_empty ................... Passed 0.59 sec
Start 23: render_gpu_capacity
23/63 Test #23: render_gpu_capacity ................ Passed 0.65 sec
Start 24: render_gpu_dense
24/63 Test #24: render_gpu_dense ................... Passed 0.64 sec
Start 25: render_gpu_door
25/63 Test #25: render_gpu_door .................... Passed 0.60 sec
Start 26: render_gpu_shadow
26/63 Test #26: render_gpu_shadow .................. Passed 0.60 sec
Start 27: render_gpu_cut
27/63 Test #27: render_gpu_cut ..................... Passed 0.60 sec
Start 28: render_gpu_resize
28/63 Test #28: render_gpu_resize .................. Passed 0.63 sec
Start 29: render_gpu_lod
29/63 Test #29: render_gpu_lod ..................... Passed 0.66 sec
Start 30: render_gpu_teleport
30/63 Test #30: render_gpu_teleport ................ Passed 0.61 sec
Start 31: render_gpu_near
31/63 Test #31: render_gpu_near .................... Passed 0.61 sec
Start 32: render_gpu_lifecycle
32/63 Test #32: render_gpu_lifecycle ............... Passed 1.32 sec
Start 33: render_gpu_views
33/63 Test #33: render_gpu_views ................... Passed 0.59 sec
Start 34: render_gpu_projection
34/63 Test #34: render_gpu_projection .............. Passed 0.57 sec
Start 35: render_gpu_open_sequence
35/63 Test #35: render_gpu_open_sequence ........... Passed 1.80 sec
Start 36: render_gpu_transparent
36/63 Test #36: render_gpu_transparent ............. Passed 0.62 sec
Start 37: player_scene_contracts
37/63 Test #37: player_scene_contracts ............. Passed 0.03 sec
Start 38: player_shutdown_diagnostics
38/63 Test #38: player_shutdown_diagnostics ........ Passed 1.53 sec
Start 39: lua_cli_contracts
39/63 Test #39: lua_cli_contracts .................. Passed 0.13 sec
Start 40: lua_player_reload
40/63 Test #40: lua_player_reload .................. Passed 2.45 sec
Start 41: editor_mcp
41/63 Test #41: editor_mcp ......................... Passed 0.01 sec
Start 42: process_and_cook
42/63 Test #42: process_and_cook ................... Passed 0.32 sec
Start 43: build_schema_publication
43/63 Test #43: build_schema_publication ........... Passed 9.27 sec
Start 44: editor_plugins
44/63 Test #44: editor_plugins ..................... Passed 0.03 sec
Start 45: editor_session_settings
45/63 Test #45: editor_session_settings ............ Passed 0.02 sec
Start 46: ui_widgets
46/63 Test #46: ui_widgets ......................... Passed 0.12 sec
Start 47: ui_render
47/63 Test #47: ui_render .......................... Passed 0.48 sec
Start 48: editor_ui_import_conflicts
48/63 Test #48: editor_ui_import_conflicts ......... Passed 0.82 sec
Start 49: editor_ui_project_settings
49/63 Test #49: editor_ui_project_settings ......... Passed 0.97 sec
Start 50: editor_ui_reload
50/63 Test #50: editor_ui_reload ................... Passed 4.04 sec
Start 51: editor_ui_launcher
51/63 Test #51: editor_ui_launcher ................. Passed 0.84 sec
Start 52: editor_ui_templates
52/63 Test #52: editor_ui_templates ................ Passed 2.48 sec
Start 53: editor_ui_gizmos
53/63 Test #53: editor_ui_gizmos ................... Passed 0.92 sec
Start 54: editor_ui_authoring
54/63 Test #54: editor_ui_authoring ................ Passed 1.71 sec
Start 55: editor_mcp_stdio
55/63 Test #55: editor_mcp_stdio ................... Passed 0.27 sec
Start 56: editor_gui_mcp
56/63 Test #56: editor_gui_mcp ..................... Passed 4.65 sec
Start 57: core
57/63 Test #57: core ............................... Passed 0.16 sec
Start 58: tutorial_moving
58/63 Test #58: tutorial_moving .................... Passed 0.02 sec
Start 59: tutorial_following
59/63 Test #59: tutorial_following ................. Passed 0.01 sec
Start 60: tutorial_spawning
60/63 Test #60: tutorial_spawning .................. Passed 0.02 sec
Start 61: tutorial_physics
61/63 Test #61: tutorial_physics ................... Passed 0.06 sec
Start 62: playable_2d
62/63 Test #62: playable_2d ........................ Passed 0.40 sec
Start 63: playable_3d
63/63 Test #63: playable_3d ........................ Passed 0.93 sec
100% tests passed, 0 tests failed out of 63
Label Time Summary:
gpu = 47.20 sec*proc (35 tests)
p2 = 12.02 sec*proc (16 tests)
p3 = 9.05 sec*proc (6 tests)
window = 8.67 sec*proc (2 tests)
Total Test time (real) = 67.51 sec
The following tests did not run:
17 - render_window_lifecycle (Skipped)
@@ -1,21 +0,0 @@
Test project /home/emil/Desktop/.worktrees/Faset_Engine-p3-lighting/build/linux-debug
Start 7: render_lighting_sun
1/6 Test #7: render_lighting_sun ................ Passed 5.14 sec
Start 8: render_lighting_local
2/6 Test #8: render_lighting_local .............. Passed 4.76 sec
Start 9: render_lighting_tiled
3/6 Test #9: render_lighting_tiled .............. Passed 14.30 sec
Start 10: render_lighting_policy
4/6 Test #10: render_lighting_policy ............. Passed 0.03 sec
Start 18: render_lighting_benchmark_schema
5/6 Test #18: render_lighting_benchmark_schema ... Passed 4.04 sec
Start 19: render_lighting_benchmark_smoke
6/6 Test #19: render_lighting_benchmark_smoke .... Passed 1.23 sec
100% tests passed, 0 tests failed out of 6
Label Time Summary:
gpu = 25.42 sec*proc (4 tests)
p3 = 29.50 sec*proc (6 tests)
Total Test time (real) = 29.50 sec
@@ -1,20 +0,0 @@
warning: An executable named `mkdocs` is not provided by package `mkdocs-material` but is available via the dependency `mkdocs`. Consider using `uvx --from mkdocs mkdocs` instead.
 │ ⚠ Warning from the Material for MkDocs team
 │
 │ MkDocs 2.0, the underlying framework of Material for MkDocs,
 │ will introduce backward-incompatible changes, including:
 │
 │ × All plugins will stop working – the plugin system has been removed
 │ × All theme overrides will break – the theming system has been rewritten
 │ × No migration path exists – existing projects cannot be upgraded
 │ × Closed contribution model – community members can't report bugs
 │ × Currently unlicensed – unsuitable for production use
 │
 │ Our full analysis:
 │
 │ https://squidfunk.github.io/mkdocs-material/blog/2026/02/18/mkdocs-2.0/

INFO - Cleaning site directory
INFO - Building documentation to directory: /home/emil/Desktop/.worktrees/Faset_Engine-p3-lighting/build/manual
INFO - Documentation built in 0.76 seconds
@@ -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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Some files were not shown because too many files have changed in this diff Show More