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 a0a4e29d48 Add measured optional tiled Forward+ lighting
Native and manual checks / native (ubuntu-24.04) (push) Failing after 36s
Native and manual checks / manual (push) Successful in 25s
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:37:04 +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 2dfff62f8c Document P3 lighting and provisional acceptance 2026-09-24 03:02: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
Emil b191ae0bed Report measured P3 shadow allocation and GPU time
Native and manual checks / native (ubuntu-24.04) (push) Failing after 32s
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 02:54:37 +03:00
Emil a428a33cb0 Record build profile and temporal raster cost in lighting benchmark 2026-09-24 02:53:01 +03:00
Emil ac50ac8855 Check real lighting benchmark output and driver identity 2026-09-24 02:53:01 +03:00
Emil 6c29e177c6 Measure P3 lighting sweep and Forward+ threshold 2026-09-24 02:53:01 +03:00
Emil 2773f0b75c Add repeatable P3 lighting renderer benchmark 2026-09-24 02:53:01 +03:00
Emil a5fb2167d6 Add bounded point and spot shadows with fallback diagnostics 2026-09-24 02:51:11 +03:00
Emil 252f7e2e91 Render stable cascaded sun shadows into a bounded atlas 2026-09-24 02:37:42 +03:00
1820 changed files with 13144 additions and 250 deletions
+10 -1
View File
@@ -199,7 +199,16 @@ GPU instance record содержит стабильные slot/generation; пл
### P3. Освещение, тени и temporal reconstruction
Расширить local lights, добавить clustered/Forward+ при измеренной необходимости, cascaded sun shadows и ограниченный local shadow atlas. Shadow views имеют собственную видимость и бюджеты.
**Освещение и тени реализованы до измеренного выбора пути; приёмка этапа ещё
открыта.** Есть 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 и повторные измерения входят в
оставшуюся работу. [Протокол проверки](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 видны затраты и причины обновления.
+147 -3
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;
@@ -48,6 +96,7 @@ struct ProfileSample {
bool physicsDebug{};
bool gpuVisibilityActive{};
faset::render::VisibilityMode effectiveVisibilityMode{faset::render::VisibilityMode::Direct};
faset::render::FrameStats lighting;
};
Json distribution(std::vector<double> values) {
if (values.empty())
@@ -96,7 +145,63 @@ Json profileFrames(const std::vector<ProfileSample>& samples) {
{"physics_debug", sample.physicsDebug},
{"gpu_visibility_active", sample.gpuVisibilityActive},
{"effective_visibility_mode",
visibility_mode_name(sample.effectiveVisibilityMode)}});
visibility_mode_name(sample.effectiveVisibilityMode)},
{"effective_lighting_path", sample.lighting.effective_lighting_path},
{"submitted_local_lights", sample.lighting.submitted_local_lights},
{"omitted_local_lights", sample.lighting.omitted_local_lights},
{"requested_sun_cascades", sample.lighting.requested_sun_cascades},
{"effective_sun_cascades", sample.lighting.effective_sun_cascades},
{"requested_local_shadow_faces",
sample.lighting.requested_local_shadow_faces},
{"local_shadow_faces", sample.lighting.local_shadow_faces},
{"local_shadow_tiles", sample.lighting.local_shadow_tiles},
{"dropped_shadow_faces", sample.lighting.dropped_shadow_faces},
{"dropped_point_shadow_faces",
sample.lighting.dropped_point_shadow_faces},
{"shadow_atlas_full_drops", sample.lighting.shadow_atlas_full_drops},
{"shadow_caster_budget_drops",
sample.lighting.shadow_caster_budget_drops},
{"shadow_unavailable_drops",
sample.lighting.shadow_unavailable_drops},
{"shadow_caster_draws", sample.lighting.shadow_caster_draws},
{"sun_shadow_atlas_bytes",
sample.lighting.sun_shadow_atlas_bytes},
{"local_shadow_atlas_bytes",
sample.lighting.local_shadow_atlas_bytes},
{"gpu_main_raster_ms",
gpuMeasured ? Json(sample.lighting.gpu_main_raster_ms) : Json(nullptr)},
{"gpu_sun_shadow_ms",
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},
{"light_tile_counts_valid", sample.lighting.light_tile_counts_valid},
{"light_tile_candidate_count", sample.lighting.light_tile_counts_valid
? Json(sample.lighting.light_tile_candidate_count) : Json(nullptr)},
{"light_tile_overflow_count", sample.lighting.light_tile_counts_valid
? Json(sample.lighting.light_tile_overflow_count) : Json(nullptr)}});
}
return {{"samples", std::move(frames)},
{"summary_ms",
@@ -205,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;
@@ -215,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 "
@@ -233,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;
@@ -269,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")
@@ -280,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))
@@ -384,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;
@@ -575,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);
@@ -587,7 +720,8 @@ int player_main(int argc, char** argv) {
milliseconds(renderStarted, frameFinished), measured.cpu_ms, measured.gpu_ms,
measured.readback_cpu_ms, runtimeStats, measured.draw_calls, measured.vertices,
measured.gpu_allocated_bytes, measured.texture_count, debugPhysics,
measured.gpu_visibility_active, measured.effective_visibility_mode});
measured.gpu_visibility_active, measured.effective_visibility_mode,
measured});
}
++frames;
}
@@ -619,6 +753,13 @@ 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},
{"percentile_method", "nearest_rank_all_completed_frames_no_warmup_exclusion"},
@@ -647,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()
+80 -1
View File
@@ -17,6 +17,32 @@ 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"
--compiler "${SLANGC_EXECUTABLE}" --source "${PROJECT_SOURCE_DIR}/shaders/light_tiles.slang"
--entry lightTileMain --output "${FASET_SHADER_DIRECTORY}"
BYPRODUCTS "${FASET_SHADER_DIRECTORY}/lightTileMain.slang-reflection.json"
DEPENDS "${PROJECT_SOURCE_DIR}/shaders/light_tiles.slang" "${PROJECT_SOURCE_DIR}/tools/compile_shader.py" VERBATIM)
list(APPEND FASET_SHADER_OUTPUTS "${FASET_SHADER_OUTPUT}" "${FASET_SHADER_DIRECTORY}/lightTileMain.reflection.json")
foreach(FASET_ENTRY gpuVertexMain gpuShadowMain gpuCullMain gpuHzbMain gpuPostCullMain)
if(FASET_ENTRY STREQUAL "gpuVertexMain" OR FASET_ENTRY STREQUAL "gpuShadowMain")
set(FASET_GPU_DEFINE FASET_GPU_GRAPHICS=1)
@@ -34,23 +60,76 @@ 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)
target_compile_definitions(faset_render PRIVATE FASET_SHADER_DIRECTORY="${FASET_SHADER_DIRECTORY}")
add_dependencies(faset_render faset_shaders)
if(BUILD_TESTING)
add_executable(faset_render_lighting_gpu_tests "${PROJECT_SOURCE_DIR}/tests/render_lighting_gpu_tests.cpp")
target_link_libraries(faset_render_lighting_gpu_tests PRIVATE faset_render)
add_test(NAME render_lighting_sun COMMAND faset_render_lighting_gpu_tests --sun)
set_tests_properties(render_lighting_sun PROPERTIES LABELS "gpu;p3")
add_test(NAME render_lighting_local COMMAND faset_render_lighting_gpu_tests --local)
set_tests_properties(render_lighting_local PROPERTIES LABELS "gpu;p3")
add_test(NAME render_lighting_tiled COMMAND faset_render_lighting_gpu_tests --tiled)
set_tests_properties(render_lighting_tiled PROPERTIES LABELS "gpu;p3")
add_executable(faset_render_lighting_policy_tests "${PROJECT_SOURCE_DIR}/tests/render_lighting_policy_tests.cpp")
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)
+45
View File
@@ -466,3 +466,48 @@ rendered 120 frames in Direct mode. The corresponding
[native/manual CI run](https://github.com/emil28092005/Faset_Engine/actions/runs/35922643004)
passed on Linux and Windows. Unsupported-HZB integration and physical Windows
GPU coverage remain untested.
## P3 lighting checkpoint — authored lights and bounded shadow views
At source revision `b191ae0`, the versioned `faset.light` schema and SceneView
extract directional, point, and spot lights. Any authored Light, even disabled,
suppresses the compatibility sun; scenes without a Light keep their previous
appearance. The renderer validates all local records, then selects at most 128
by priority, projected influence, and stable ID. A single typed lighting
descriptor ABI serves Direct and P2 GPU graphics: materials remain set 0,
lighting is set 1, GPU scene graphics data moves to set 2, and existing push
constant sizes remain unchanged. Both paths shade the same sun/local PBR lights
before tone mapping.
A pure CPU shadow planner builds up to four texel-snapped sun cascades from an
explicit camera frustum, ending at at most 80 world units; a low-level Snapshot
without the frustum keeps one shadow view. Shadow caster bounds come from the
source LOD-0 draw and are tested against the light view, independently of
camera/P2 culling. The Vulkan backend renders the sun to its own D32 atlas and
point/spot shadows to a separate 4×4 D32 atlas. A point light claims six faces
atomically, a spot one. Both atlases try 2048² and then 1024² if required by
capabilities or allocation. The combined frame budget is 4096 caster draws;
scheduled tiles are cleared and redrawn each frame. Overflow, disabled shadow,
or unavailable atlas leaves a submitted light illuminating without shadow.
There is no hidden sun raster when the sun is absent, its shadow is disabled, or
the scene only has sprites. Atlas ownership, dropout, submitted light counts,
actual raster work and GPU timings are exposed in `FrameStats`, Player profiles
and the optional Editor diagnostics overlay.
The implementation's Linux Debug checkpoint at `a5fb216` built all targets and
ran 60 CTests with no failures; the existing native window lifecycle test
skipped under the compositor. The optional ImGui overlay passed its dedicated
test in an enabled build. After benchmark integration at `b191ae0`, six focused
tests passed, including the real Vulkan benchmark smoke. These are bounded
checks, not a final P3 acceptance run. The [lighting validation record](validation/p3-lighting-2026-09-24/README.md)
lists cases, exact revision, and remaining Windows/Release evidence.
The fixed-scene Release reference-GPU sweep uses 1920×1080, 0/4/16/32/64/128
lights, Direct/GPU frustum/GPU occlusion, shadows on/off, three independent
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. 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

+27 -1
View File
@@ -12,11 +12,37 @@ 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.
The Vulkan backend emits `VK_EXT_debug_utils` labels for `ShadowMap`, `ForwardAndUI`, `Readback`, and, when presenting, `Presentation`. A graphics capture tool that supports this extension can identify those command-buffer regions. Labels remain available without the Khronos validation layer when the extension is exposed; unsupported systems continue rendering and report labels unavailable. A submitted-label count confirms calls were emitted, not that an external capture tool was tested.
The **Lighting and shadows** section reports the actual local lights submitted
and omitted, requested/effective sun cascades, requested/rasterized local faces,
allocated local tiles, and shadow caster draws against the 4096-draw limit.
Dropped-face counters distinguish a full atlas, caster budget, and unavailable
atlas; `point` counts faces dropped as a complete six-face group. A light whose
shadow faces are dropped still illuminates without a shadow. Atlas memory is the
live explicit Vulkan allocation size for the separate sun and local atlases.
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.
See [Lighting](lighting.md) for the 128-light and 16-tile limits.
The Vulkan backend emits `VK_EXT_debug_utils` labels for `SunShadowAtlas`,
`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
the extension is exposed; unsupported systems continue rendering and report
labels unavailable. A submitted-label count confirms calls were emitted, not
that an external capture tool was tested.
This module is disabled by default and is linked only to the graphical Editor and its dedicated test when enabled. Player and exported games do not link ImGui. No overlay control changes authoring documents, gameplay state or export settings.
+87 -37
View File
@@ -1,37 +1,82 @@
# Add lights to a 3D scene
# Light a 3D scene
Add a **Light** component to a scene entity. The entity's transform places a point
or spot light; its rotation aims a spot light along local negative Z. A directional
light uses the entity's orientation. Light colors and intensity contribute to the
mesh's linear PBR illumination before tone mapping. Sprites and UI retain their
unlit tint.
Select an entity in the **Scene** tree, choose **+ Add Component** in the
**Inspector**, and add **Light**. Its Transform places a point or spot light. A
spot light points along the entity's local negative Z axis; a directional light
uses the entity's orientation. Lights affect 3D meshes in linear PBR shading
before tone mapping. Sprites and Editor UI remain unlit.
The version-1 `faset.light` component has three `kind` values:
| Kind | Position and direction | Useful fields |
| Light kind | Coverage | Shadow cost |
| --- | --- | --- |
| `directional` | Direction from the entity transform | `color`, `intensity`, `casts_shadow` |
| `point` | Position from the entity transform; illuminates every direction | `color`, `intensity`, `range` |
| `spot` | Position and local negative-Z direction | `color`, `intensity`, `range`, `inner_angle`, `outer_angle` |
| `directional` | A sun-like direction, independent of position | Up to four cascade tiles |
| `point` | All directions within `range` | Six local-atlas tiles, assigned together |
| `spot` | A cone within `range` | One local-atlas tile |
Angles are radians. A spot's inner angle must not exceed its outer angle. Intensity
must be nonnegative and range positive. `enabled: false` keeps the component in the
scene without contributing light. The `shadow_priority` integer is reserved for the
bounded local-shadow scheduler; it does not change brightness.
The Light component's fields are:
In the current rendering checkpoint, one enabled directional light can cast the
existing single-map shadow. Point and spot lights illuminate meshes but do not yet
cast shadows. The [P3 lighting plan](https://github.com/emil28092005/Faset_Engine/blob/main/docs/superpowers/plans/2026-09-24-p3-lighting.md)
tracks cascades and the bounded local-shadow atlas. A scene with no Light component
keeps the legacy white sun so older projects retain their appearance. Adding any
Light component, even a disabled one, turns off that compatibility fallback. If
several directionals are enabled, Faset chooses the one with the smallest stable
entity ID and reports a diagnostic for the others.
| Field | Default | Meaning |
| --- | --- | --- |
| `kind` | `directional` | `directional`, `point`, or `spot` |
| `enabled` | `true` | A disabled light contributes no illumination |
| `color` | `[1, 1, 1, 1]` | RGB illumination color; alpha is part of the schema color value |
| `intensity` | `1` | Nonnegative brightness |
| `range` | `10` | Positive reach of point and spot lights |
| `inner_angle` | `0.35` | Full-strength spot cone half-angle, in radians |
| `outer_angle` | `0.7` | Outer spot cone half-angle, in radians; must be at least `inner_angle` |
| `casts_shadow` | `true` | Allow this light to use its shadow atlas |
| `shadow_priority` | `0` | Higher local-light selection and shadow priority; does not change brightness |
## Author a point light through MCP
The Inspector validates the spot angles together. Their allowed outer limit is
below π/2 radians. A point light does not depend on the entity's rotation.
After editing the light or Transform, save the scene as usual. [Editor
workspace](workspace.md) explains Inspector editing, Undo, and save conflicts.
Use `faset_schema` to inspect the current field IDs, then send a `faset_scene_edit`
batch with the document ID, current revision, and target entity ID. For example:
## Sun shadows and compatibility
With a 3D scene camera, a shadow-casting directional light uses four cascades
covering the camera near plane through at most **80 world units**, or the camera
far plane if it is closer. Faset blends samples near cascade splits and snaps
each shadow projection to texels to reduce shimmer during small camera moves.
Objects outside the camera view can still cast into a visible receiver: shadow
visibility uses each light's view and the source mesh's LOD 0, separately from
the main camera's Direct or GPU visibility result. A low-level renderer Snapshot
without an explicit camera frustum uses one compatibility sun view.
Only one enabled directional light is used. If there are several, Faset chooses
the one with the smallest stable entity ID and reports the ignored lights. A
scene with **no Light component** retains the older white sun. Adding any Light
component, including a disabled one, suppresses that compatibility sun. Thus a
local-only scene does not receive an unexpected directional light.
## Local shadow capacity and fallbacks
The renderer accepts at most **128** local lights per frame. It sorts candidates
by `shadow_priority` (highest first), then projected influence, then stable ID.
The `omitted_local_lights` counter reports lights beyond this limit; an omitted
light contributes no illumination. All authored light records are validated,
including candidates past the limit.
The separate local shadow atlas has **16 tiles**. A spot consumes one; a point
consumes six or none. Sun cascades and local shadows share a maximum of **4096
caster draws** per frame. A light whose shadow group does not fit the remaining
tiles or draw budget still illuminates, **without a shadow**. Disabling
`casts_shadow` also keeps illumination while skipping that light's shadow work.
The renderer reports requested faces, rendered faces, tiles, and drops by cause
in [Diagnostics](diagnostics.md) and the [Player profile](profiling.md).
Faset uses separate sampled D32 sun and local atlases, normally 2048×2048 pixels
each. If a device cannot use that size, the renderer tries 1024×1024; if a
sampled depth atlas cannot be created, the affected lights fall back to unshadowed
illumination and report unavailable shadow views. Scheduled atlas tiles are
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.
## Add a point light through MCP
MCP edits the **Editor document**, not entities in a running game. Use
`faset_schema` to inspect the current field IDs, then send a `faset_scene_edit`
batch with the document ID, its current revision, and a target entity ID:
```json
{
@@ -46,22 +91,24 @@ batch with the document ID, current revision, and target entity ID. For example:
"kind": "point",
"color": [1, 0.15, 0.1, 1],
"intensity": 8,
"range": 6
"range": 6,
"shadow_priority": 2
}
}]
}
```
Move the entity with its Transform component. `component.add` fills any omitted
light fields from the version-1 schema; use `component.set` for later edits. See
[MCP and command line](mcp.md) for revision and retry handling.
Move the entity with its Transform component. `component.add` fills omitted
fields from the schema; `component.set` changes an existing field. Save the
document with `faset_document_save`. [MCP and command line](mcp.md) covers
revisions, retries, and transactions.
## Supply lights directly from C++
When building a `faset::render::Snapshot` yourself, set
`authored_lights_present` to suppress the compatibility sun in a local-only scene.
Provide a stable ID for each light so future shadow scheduling remains independent
of submission order.
Code that constructs a renderer `faset::render::Snapshot` can supply lights
directly. Set `authored_lights_present` even when the only authored Light is
disabled, so the renderer does not synthesize the compatibility sun. Use stable,
unique IDs for deterministic capacity decisions:
```cpp
faset::render::Snapshot snapshot;
@@ -74,6 +121,7 @@ point.position = {-2, 1.5f, 0};
point.color = {1, 0.3f, 0.1f, 1};
point.intensity = 8;
point.range = 6;
point.shadow_priority = 2;
snapshot.local_lights.push_back(point);
faset::render::LocalLight spot;
@@ -88,5 +136,7 @@ spot.range = 9;
snapshot.local_lights.push_back(spot);
```
The renderer submits at most 128 local lights per frame in stable-ID order. Later
P3 work adds explicit overflow diagnostics and measured light-list optimization.
This is the **renderer Snapshot API**, not a gameplay `Update()` method. The
current gameplay scripting API does not expose live Light-component creation or
modification; author lights in the Inspector or through Editor MCP. See
[Gameplay scripting](../scripting/index.md) for the APIs available to game code.
+78 -6
View File
@@ -118,13 +118,85 @@ 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
submitted/omitted, requested/effective sun cascades, requested/rasterized local
shadow faces, tile use, shadow drop reasons, caster draws, and explicit atlas
allocation bytes. `gpu_main_raster_ms`, `gpu_sun_shadow_ms`, and
`gpu_local_shadow_ms` are GPU timestamps or `null` when timestamps are
unavailable. A light can illuminate while its shadow faces are dropped. A
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 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
with ten warm-up and thirty recorded frames. First inspect the planned matrix:
```sh
python3 tools/benchmark_p3_lighting.py --list-runs
```
From the repository, after a Linux Release renderer build, run one shadow setting
into a new output directory. Supply the actual device driver identity:
```sh
python3 tools/benchmark_p3_lighting.py --sweep \
--executable build/linux-release/faset_p3_lighting_benchmark \
--output .cache/p3-lighting-off \
--shadows off --driver 'REPLACE_WITH_ACTUAL_DRIVER' --validation off
```
The wrapper writes one raw CSV per run, `merged.csv`, and `summary.json`. Keep
all three with the exact source revision and device. It checks that every run
used its requested visibility mode and submitted every requested light. GPU
timestamps for the main raster isolate fragment-heavy lighting better than
renderer wall time, which includes GPU waits and synchronous readback. Shadow
time is split into sun and local GPU durations. The Forward+ decision compares
the median of three run medians against the matching zero-light configuration;
the threshold is **1.0 ms extra main raster time or 15% of the zero-light GPU
frame** at 32, 64, or 128 lights on the Linux physical reference GPU. The
[P3 lighting validation record](https://github.com/emil28092005/Faset_Engine/blob/main/docs/validation/p3-lighting-2026-09-24/README.md)
states the measured decision and scope. A software Vulkan run checks
functionality, not physical GPU performance.
## Current performance scope
The accepted MVP path uses direct draws and CPU culling; P2 adds optional GPU
visibility for opaque static meshes, with prepared LODs supplied by the project.
Both paths currently use one graphics queue and synchronous full-image
capture/readback. Use measurements to find the next bottleneck before introducing
parallel jobs or expanding GPU-driven rendering. Neither an offscreen capture
benchmark nor a tiny demo is a promise of a production frame budget. Observed
measurements and follow-up targets belong in the implementation acceptance report
with their source revision and method.
P3 adds local lights and bounded sun/local shadow atlases. The benchmark's
`lighting_path` and a Player profile's `effective_lighting_path` identify the
algorithm actually used. Both paths currently use one graphics queue and
synchronous full-image capture/readback. Use measurements to find the next
bottleneck before introducing parallel jobs or expanding GPU-driven rendering.
Neither an offscreen capture benchmark nor a tiny demo is a promise of a
production frame budget. Observed measurements and follow-up targets belong in
the implementation acceptance report with their source revision and method.
+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.
+2
View File
@@ -5,6 +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, 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,98 @@
# P3 lighting and shadows — acceptance record
This record tracks P3 lighting separately from temporal reconstruction. The
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
- Authored directional, point, and spot lights are extracted from the same
versioned Light schema used by the Inspector and MCP. No authored Light
component retains the legacy sun; any authored Light, including disabled or
local-only, suppresses that fallback.
- Direct, GPU frustum, and GPU occlusion graphics paths use the same typed
lighting data. Material descriptors remain set 0, lighting set 1, and GPU
graphics scene data set 2. Sun and local light contributions accumulate before
tone mapping. Sprites and UI remain unlit.
- Four texel-snapped sun cascades cover up to 80 world units for an explicit
camera frustum. A low-level Snapshot without one keeps a single sun view.
Shadow caster selection uses each light view and source LOD 0, independent of
camera visibility and prepared camera LOD. Missing/disabled sun and sprite-only
scenes skip sun shadow raster.
- The separate D32 local atlas admits 16 faces, one per spot or six atomically
per point. Both shadow systems share a 4096-caster-draw frame budget. Up to 128
local lights are submitted by priority, projected influence, then stable ID.
Overflow or unsupported-atlas lights remain unshadowed when submitted; omitted
lights beyond 128 do not illuminate. Both atlases try 2048², then 1024².
- The editor overlay and Player profile expose actual submitted/omitted lights,
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.
## Acceptance matrix
| Case | Automated evidence | Current status |
| --- | --- | --- |
| 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 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
assert zero reported Vulkan errors; a test result is a validation-layer pass only
when the layer was actually active. `render_window_lifecycle` can skip if the
Linux compositor declines programmatic restore. The Windows workflow uses pinned
SwiftShader, not a physical Windows GPU, and may lack the Khronos layer. Linux
reference-GPU results cannot establish physical Windows performance.
## Reproduction and retained evidence
The P3 CTest registrations are `render_lighting_policy` and
`render_lighting_benchmark_schema` (CPU), plus `render_lighting_sun`,
`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 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
`render_lighting_policy` and `render_lighting_benchmark_schema` 2/2 with zero
failures. The [strict MkDocs build](strict-mkdocs.txt) passed for these Manual
changes. This run deliberately excluded Vulkan tests while the 1920×1080
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.
```sh
cmake --build --preset linux-debug --parallel 2
ctest --test-dir build/linux-debug -L p3 --no-tests=error --output-on-failure
ctest --test-dir build/linux-debug --output-on-failure
cmake --build --preset linux-release --parallel 2
ctest --test-dir build/linux-release --output-on-failure
```
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, 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 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
physical Windows GPU performance claim follows from these fixtures.
@@ -0,0 +1,12 @@
Test project /home/emil/Desktop/.worktrees/Faset_Engine-p3-lighting/build/linux-debug
Start 9: render_lighting_policy
1/2 Test #9: render_lighting_policy ............. Passed 0.04 sec
Start 17: render_lighting_benchmark_schema
2/2 Test #17: render_lighting_benchmark_schema ... Passed 4.95 sec
100% tests passed, 0 tests failed out of 2
Label Time Summary:
p3 = 4.99 sec*proc (2 tests)
Total Test time (real) = 5.00 sec
@@ -0,0 +1,8 @@
Test project /home/emil/Desktop/.worktrees/Faset_Engine-p3-lighting/build/linux-debug
Test #7: render_lighting_sun
Test #8: render_lighting_local
Test #9: render_lighting_policy
Test #17: render_lighting_benchmark_schema
Test #18: render_lighting_benchmark_smoke
Total Tests: 5
@@ -0,0 +1,20 @@
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.84 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

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