Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9bac5b6a8 | ||
|
|
5cb818f0cc | ||
|
|
fe3a589174 | ||
|
|
c8580ec3b2 | ||
|
|
1a1f69f7e8 | ||
|
|
a2c2b085ef | ||
|
|
0c96ce5cdf | ||
|
|
7297d01436 | ||
|
|
a0a4e29d48 | ||
|
|
0dcc8790a0 | ||
|
|
2dfff62f8c | ||
|
|
c53e51520c | ||
|
|
8fbaf83326 | ||
|
|
7c88356fb4 | ||
|
|
3a4a262750 | ||
|
|
1e247e60e9 | ||
|
|
56a5bc62f6 | ||
|
|
4b8f9b8132 | ||
|
|
17177357fb | ||
|
|
068f2e9af9 | ||
|
|
b67cdc1ce8 | ||
|
|
b191ae0bed | ||
|
|
a428a33cb0 | ||
|
|
ac50ac8855 | ||
|
|
6c29e177c6 | ||
|
|
2773f0b75c | ||
|
|
a5fb2167d6 | ||
|
|
252f7e2e91 | ||
|
|
36a44e14ca | ||
|
|
cfcfdab949 | ||
|
|
674e3e812b |
@@ -96,4 +96,3 @@ jobs:
|
||||
python-version: '3.12'
|
||||
- run: python -m pip install -r docs/requirements.txt
|
||||
- run: python -m mkdocs build --strict
|
||||
- run: python tests/verify_playable_exports_test.py
|
||||
|
||||
@@ -72,8 +72,8 @@ jobs:
|
||||
run: ctest --preset windows-debug --timeout 180
|
||||
- name: Real Release exports, 2D and 3D execution, incremental Debug rebuild
|
||||
run: build/windows-debug/faset_build_service_tests.exe --integration .cache/windows-export-e2e
|
||||
- name: Export and relocate C++ and Lua playable games
|
||||
run: python tools/verify_playable_exports.py --editor build/windows-debug/faset_editor.exe --output .cache/windows-playable-exports --include-lua
|
||||
- name: Export and relocate both checked-in playable games
|
||||
run: python tools/verify_playable_exports.py --editor build/windows-debug/faset_editor.exe --output .cache/windows-playable-exports
|
||||
- name: Preserve graphics and export evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
|
||||
@@ -74,16 +74,13 @@ if(TARGET faset_assets AND TARGET faset_authoring AND EXISTS "${PROJECT_SOURCE_D
|
||||
endif()
|
||||
if(TARGET faset_editor_commands AND TARGET faset_build_service)
|
||||
include(cmake/Plugins.cmake)
|
||||
add_library(faset_editor_session STATIC src/editor/session.cpp src/editor/autosave.cpp)
|
||||
add_library(faset_editor_session STATIC src/editor/session.cpp)
|
||||
target_link_libraries(faset_editor_session PUBLIC faset_editor_commands faset_build_service faset_assets faset_editor_plugins)
|
||||
if(BUILD_TESTING)
|
||||
add_executable(faset_editor_session_tests tests/editor_session_tests.cpp)
|
||||
target_link_libraries(faset_editor_session_tests PRIVATE faset_editor_session)
|
||||
target_compile_definitions(faset_editor_session_tests PRIVATE FASET_TEST_ENGINE="${PROJECT_SOURCE_DIR}")
|
||||
add_test(NAME editor_session_settings COMMAND faset_editor_session_tests)
|
||||
add_executable(faset_editor_autosave_tests tests/editor_autosave_tests.cpp)
|
||||
target_link_libraries(faset_editor_autosave_tests PRIVATE faset_editor_session)
|
||||
add_test(NAME editor_autosave COMMAND faset_editor_autosave_tests)
|
||||
endif()
|
||||
endif()
|
||||
foreach(module UI EditorUI Editor Applications)
|
||||
|
||||
@@ -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 видны затраты и причины обновления.
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <faset/editor/mcp.hpp>
|
||||
#include <faset/editor/session.hpp>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#ifdef FASET_HAS_EDITOR_UI
|
||||
#include <faset/editor/editor_ui.hpp>
|
||||
@@ -41,7 +40,7 @@ void help() {
|
||||
std::cout
|
||||
<< "Faset Editor\n"
|
||||
" faset_editor Open the project launcher\n"
|
||||
" faset_editor --project PATH [--new NAME --dimension 2|3 [--language cpp|lua]] [--scene RELATIVE_PATH]\n"
|
||||
" faset_editor --project PATH [--new NAME --dimension 2|3] [--scene RELATIVE_PATH]\n"
|
||||
" faset_editor --project PATH --mcp [--gui]\n"
|
||||
" faset_editor --project PATH --command JSON [--wait]\n"
|
||||
"Options: --engine SDK_SOURCE, --headless, --frames N, --capture PATH.ppm\n"
|
||||
@@ -53,10 +52,9 @@ int editor_main(int argc, char** argv) {
|
||||
using namespace faset::editor;
|
||||
try {
|
||||
std::filesystem::path project, engine = path_from_utf8(FASET_ENGINE_SOURCE), scene, capture;
|
||||
std::string new_name, command, language = "cpp";
|
||||
std::string new_name, command;
|
||||
int dimension = 3;
|
||||
bool mcp = false, gui = true, explicit_gui = false, wait = false;
|
||||
bool explicit_language = false;
|
||||
std::uint64_t frames = 0;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::string arg = argv[i];
|
||||
@@ -76,10 +74,6 @@ int editor_main(int argc, char** argv) {
|
||||
new_name = value();
|
||||
else if (arg == "--dimension")
|
||||
dimension = std::stoi(value());
|
||||
else if (arg == "--language") {
|
||||
language = value();
|
||||
explicit_language = true;
|
||||
}
|
||||
else if (arg == "--scene")
|
||||
scene = path_from_utf8(value());
|
||||
else if (arg == "--mcp")
|
||||
@@ -106,65 +100,30 @@ int editor_main(int argc, char** argv) {
|
||||
throw Error("cli.option", "Unknown option: " + arg);
|
||||
}
|
||||
require(!(mcp && !command.empty()), "cli.mode", "Choose MCP or a single command");
|
||||
require(!explicit_language || !new_name.empty(), "cli.language",
|
||||
"--language requires --new NAME");
|
||||
require(language == "cpp" || language == "lua", "cli.language",
|
||||
"Language must be cpp or lua");
|
||||
if (mcp && !explicit_gui)
|
||||
gui = false;
|
||||
require(!project.empty() || (gui && !mcp && new_name.empty()), "cli.project",
|
||||
"Use --project PATH for command, MCP, or --new modes");
|
||||
auto previous_project = project;
|
||||
#ifdef FASET_HAS_EDITOR_UI
|
||||
std::optional<ProjectSelection> retry_create;
|
||||
std::string creation_error;
|
||||
#endif
|
||||
for (;;) {
|
||||
if (project.empty()) {
|
||||
#ifdef FASET_HAS_EDITOR_UI
|
||||
const auto selection =
|
||||
run_project_launcher(engine, previous_project, frames, capture,
|
||||
retry_create, creation_error);
|
||||
retry_create.reset();
|
||||
creation_error.clear();
|
||||
run_project_launcher(engine, previous_project, frames, capture);
|
||||
if (!selection)
|
||||
return 0;
|
||||
project = selection->path;
|
||||
new_name = selection->create ? selection->name : std::string();
|
||||
dimension = selection->dimension;
|
||||
language = selection->language;
|
||||
explicit_language = selection->create;
|
||||
#else
|
||||
throw Error("editor.gui_unavailable",
|
||||
"This build has no graphical project launcher; use --project PATH");
|
||||
#endif
|
||||
}
|
||||
std::unique_ptr<Session> session_holder;
|
||||
try {
|
||||
session_holder = std::make_unique<Session>(SessionConfig{
|
||||
std::filesystem::absolute(project), std::filesystem::absolute(engine),
|
||||
executable_directory(argv[0])});
|
||||
if (!new_name.empty()) {
|
||||
if (explicit_language)
|
||||
session_holder->scaffold(new_name, dimension, language);
|
||||
else
|
||||
session_holder->scaffold(new_name, dimension);
|
||||
}
|
||||
} catch (const std::exception& error) {
|
||||
#ifdef FASET_HAS_EDITOR_UI
|
||||
if (gui && !new_name.empty()) {
|
||||
retry_create = ProjectSelection{std::filesystem::absolute(project),
|
||||
new_name, dimension, true, language};
|
||||
creation_error = error.what();
|
||||
project.clear();
|
||||
new_name.clear();
|
||||
scene.clear();
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
throw;
|
||||
}
|
||||
Session& session = *session_holder;
|
||||
Session session({std::filesystem::absolute(project), std::filesystem::absolute(engine),
|
||||
executable_directory(argv[0])});
|
||||
if (!new_name.empty())
|
||||
session.scaffold(new_name, dimension);
|
||||
const auto settings = session.project();
|
||||
#ifdef FASET_HAS_EDITOR_UI
|
||||
if (gui && std::filesystem::exists(project / "project.faset.json"))
|
||||
@@ -208,8 +167,6 @@ int editor_main(int argc, char** argv) {
|
||||
project.clear();
|
||||
scene.clear();
|
||||
new_name.clear();
|
||||
explicit_language = false;
|
||||
language = "cpp";
|
||||
continue;
|
||||
#else
|
||||
throw Error(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -75,22 +75,6 @@ ProjectSelection open_project(const fs::path& path) {
|
||||
throw std::runtime_error("Project name or scene type is invalid.");
|
||||
return {path, std::move(name), dimension, false};
|
||||
}
|
||||
bool fresh_destination(const fs::path& path) {
|
||||
if (!fs::exists(path))
|
||||
return true;
|
||||
if (!fs::is_directory(path) || fs::is_symlink(path))
|
||||
return false;
|
||||
for (const auto& entry : fs::directory_iterator(path)) {
|
||||
if (entry.path().filename() != ".faset" || !entry.is_directory() ||
|
||||
entry.is_symlink())
|
||||
return false;
|
||||
for (const auto& internal : fs::directory_iterator(entry.path()))
|
||||
if (internal.path().filename() != "cache" || !internal.is_directory() ||
|
||||
internal.is_symlink() || !fs::is_empty(internal.path()))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Json recent_records(const fs::path& file) {
|
||||
try {
|
||||
auto j = read_json(file);
|
||||
@@ -140,11 +124,9 @@ struct ProjectLauncher::Impl {
|
||||
std::optional<ProjectSelection> selected;
|
||||
bool create = false, cancelled = false, browsing = false, browser_valid = false;
|
||||
int dimension = 3;
|
||||
std::string language = "cpp";
|
||||
std::string error;
|
||||
Impl(render::Renderer& r, const fs::path& engine, const fs::path& initial,
|
||||
const fs::path& recents, const std::optional<ProjectSelection>& retry,
|
||||
std::string creation_error)
|
||||
const fs::path& recents)
|
||||
: renderer(r), ui(engine / "assets/fonts/NotoSans.ttf"),
|
||||
recents_file(recents.empty() ? recent_path() : recents) {
|
||||
auto theme = ui::Theme::load(engine / "assets/ui/dark.json");
|
||||
@@ -165,22 +147,13 @@ struct ProjectLauncher::Impl {
|
||||
ui.set_ime(
|
||||
[this](bool enabled) { renderer.set_text_input(enabled); },
|
||||
[this](ui::Rect r) { renderer.set_text_input_area(r.x, r.y, r.width, r.height); });
|
||||
const auto start = retry ? retry->path
|
||||
: initial.empty() ? user_home() / "FasetProjects/MyGame" : initial;
|
||||
const auto start = initial.empty() ? user_home() / "FasetProjects/MyGame" : initial;
|
||||
ui.update_text("launcher-path", path_to_utf8(start));
|
||||
create = retry ? retry->create : initial.empty();
|
||||
if (retry) {
|
||||
ui.update_text("launcher-name", retry->name);
|
||||
dimension = retry->dimension;
|
||||
language = retry->language;
|
||||
error = std::move(creation_error);
|
||||
}
|
||||
create = initial.empty();
|
||||
ui.find("launcher-open")->on_click = [this](Widget&) { set_mode(false); };
|
||||
ui.find("launcher-create")->on_click = [this](Widget&) { set_mode(true); };
|
||||
ui.find("launcher-2d")->on_click = [this](Widget&) { dimension = 2; };
|
||||
ui.find("launcher-3d")->on_click = [this](Widget&) { dimension = 3; };
|
||||
ui.find("launcher-cpp")->on_click = [this](Widget&) { language = "cpp"; };
|
||||
ui.find("launcher-lua")->on_click = [this](Widget&) { language = "lua"; };
|
||||
ui.find("launcher-submit")->on_click = [this](Widget&) { submit(); };
|
||||
ui.find("launcher-cancel")->on_click = [this](Widget&) { cancelled = true; };
|
||||
ui.find("launcher-name")->on_preview = [this](Widget&) { error.clear(); };
|
||||
@@ -236,14 +209,14 @@ struct ProjectLauncher::Impl {
|
||||
throw std::runtime_error("Enter a project name.");
|
||||
if (name.size() > 256 || name.find_first_of("\r\n\t") != std::string::npos)
|
||||
throw std::runtime_error("Use a short project name on one line.");
|
||||
if (!fresh_destination(path))
|
||||
if (fs::exists(path) && (!fs::is_directory(path) || !fs::is_empty(path)))
|
||||
throw std::runtime_error("Create requires a new or empty directory.");
|
||||
auto parent = path.parent_path();
|
||||
while (!parent.empty() && !fs::exists(parent))
|
||||
parent = parent.parent_path();
|
||||
if (parent.empty() || !fs::is_directory(parent))
|
||||
throw std::runtime_error("Project parent directory is unavailable.");
|
||||
selected = ProjectSelection{path, name, dimension, true, language};
|
||||
selected = ProjectSelection{path, name, dimension, true};
|
||||
} else
|
||||
selected = open_project(path);
|
||||
error.clear();
|
||||
@@ -378,24 +351,14 @@ struct ProjectLauncher::Impl {
|
||||
ui.find("launcher-create")->selected = create;
|
||||
ui.find("launcher-2d")->selected = dimension == 2;
|
||||
ui.find("launcher-3d")->selected = dimension == 3;
|
||||
ui.find("launcher-cpp")->selected = language == "cpp";
|
||||
ui.find("launcher-lua")->selected = language == "lua";
|
||||
ui.find("launcher-2d")->appearance =
|
||||
dimension == 2 ? Appearance::Quiet : Appearance::Default;
|
||||
ui.find("launcher-3d")->appearance =
|
||||
dimension == 3 ? Appearance::Quiet : Appearance::Default;
|
||||
ui.find("launcher-cpp")->appearance =
|
||||
language == "cpp" ? Appearance::Quiet : Appearance::Default;
|
||||
ui.find("launcher-lua")->appearance =
|
||||
language == "lua" ? Appearance::Quiet : Appearance::Default;
|
||||
ui.find("launcher-language-hint")->text =
|
||||
language == "cpp" ? "Compiled gameplay. Add Lua later if you need quick reloads."
|
||||
: "Lua gameplay reloads during development without a C++ rebuild.";
|
||||
for (const auto* id : {"launcher-name-label", "launcher-name", "launcher-dimension-label",
|
||||
"launcher-dimensions", "launcher-name-group",
|
||||
"launcher-name-space", "launcher-path-space",
|
||||
"launcher-dimension-group", "launcher-language-group",
|
||||
"launcher-language-space"})
|
||||
"launcher-dimension-group"})
|
||||
ui.find(id)->visible = create;
|
||||
ui.find("launcher-heading")->text = create ? "Create project" : "Open project";
|
||||
ui.find("launcher-submit")->text = create ? "Create project" : "Open project";
|
||||
@@ -461,11 +424,8 @@ struct ProjectLauncher::Impl {
|
||||
}
|
||||
};
|
||||
ProjectLauncher::ProjectLauncher(render::Renderer& r, const fs::path& engine,
|
||||
const fs::path& initial, const fs::path& recents,
|
||||
const std::optional<ProjectSelection>& retry,
|
||||
std::string creation_error)
|
||||
: impl_(std::make_unique<Impl>(r, engine, initial, recents, retry,
|
||||
std::move(creation_error))) {}
|
||||
const fs::path& initial, const fs::path& recents)
|
||||
: impl_(std::make_unique<Impl>(r, engine, initial, recents)) {}
|
||||
ProjectLauncher::~ProjectLauncher() = default;
|
||||
void ProjectLauncher::frame(const std::vector<render::Event>& events) {
|
||||
impl_->frame(events);
|
||||
@@ -485,11 +445,9 @@ bool ProjectLauncher::cancelled() const {
|
||||
std::optional<ProjectSelection> run_project_launcher(const fs::path& engine,
|
||||
const fs::path& initial,
|
||||
std::uint64_t max_frames,
|
||||
const fs::path& capture,
|
||||
const std::optional<ProjectSelection>& retry,
|
||||
std::string creation_error) {
|
||||
const fs::path& capture) {
|
||||
render::Renderer renderer({1100, 720, "Faset Engine — Projects", false, true});
|
||||
ProjectLauncher launcher(renderer, engine, initial, {}, retry, std::move(creation_error));
|
||||
ProjectLauncher launcher(renderer, engine, initial);
|
||||
std::uint64_t frames = 0;
|
||||
while (!renderer.should_close() && !launcher.cancelled() && !launcher.selection() &&
|
||||
(max_frames == 0 || frames < max_frames)) {
|
||||
|
||||
@@ -41,15 +41,6 @@
|
||||
{"id": "launcher-3d", "kind": "button", "text": "3D", "layout": {"width": 116}}
|
||||
]}
|
||||
]},
|
||||
{"id": "launcher-language-space", "kind": "row", "layout": {"height": 18}},
|
||||
{"id": "launcher-language-group", "kind": "column", "layout": {"gap": 6}, "children": [
|
||||
{"id": "launcher-language-label", "kind": "label", "text": "Gameplay language", "layout": {"height": 24}},
|
||||
{"id": "launcher-languages", "kind": "row", "layout": {"height": 42, "gap": 4}, "children": [
|
||||
{"id": "launcher-cpp", "kind": "button", "text": "C++", "layout": {"width": 116}},
|
||||
{"id": "launcher-lua", "kind": "button", "text": "Lua", "layout": {"width": 116}}
|
||||
]},
|
||||
{"id": "launcher-language-hint", "kind": "label", "text": "Compiled gameplay. Add Lua later if you need quick reloads.", "font_size": 12, "layout": {"height": 30}}
|
||||
]},
|
||||
{"id": "launcher-hint", "kind": "label", "text": "Open a directory containing project.faset.json.", "font_size": 13, "layout": {"height": 36}},
|
||||
{"id": "launcher-actions-space", "kind": "row", "layout": {"height": 22}},
|
||||
{"id": "launcher-actions-rule", "kind": "row", "layout": {"height": 1}},
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
add_library(faset_build_service STATIC
|
||||
${PROJECT_SOURCE_DIR}/src/editor/build_service.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/editor/build_cache.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/editor/build_diagnostics.cpp)
|
||||
add_library(faset_build_service STATIC ${PROJECT_SOURCE_DIR}/src/editor/build_service.cpp)
|
||||
target_include_directories(faset_build_service PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_features(faset_build_service PUBLIC cxx_std_20)
|
||||
target_link_libraries(faset_build_service PUBLIC faset_core faset_scripting_project PRIVATE faset_assets faset_authoring Threads::Threads)
|
||||
if(BUILD_TESTING)
|
||||
add_executable(faset_build_diagnostics_tests ${PROJECT_SOURCE_DIR}/tests/build_diagnostics_tests.cpp)
|
||||
target_link_libraries(faset_build_diagnostics_tests PRIVATE faset_build_service)
|
||||
add_test(NAME build_diagnostics COMMAND faset_build_diagnostics_tests)
|
||||
add_executable(faset_build_cache_tests ${PROJECT_SOURCE_DIR}/tests/build_cache_tests.cpp)
|
||||
target_link_libraries(faset_build_cache_tests PRIVATE faset_build_service)
|
||||
target_compile_definitions(faset_build_cache_tests PRIVATE FASET_ENGINE_SOURCE="${PROJECT_SOURCE_DIR}")
|
||||
add_test(NAME build_cache COMMAND faset_build_cache_tests)
|
||||
add_executable(faset_build_service_tests ${PROJECT_SOURCE_DIR}/tests/build_service_tests.cpp)
|
||||
target_link_libraries(faset_build_service_tests PRIVATE faset_build_service faset_assets)
|
||||
target_compile_definitions(faset_build_service_tests PRIVATE FASET_ENGINE_SOURCE="${PROJECT_SOURCE_DIR}")
|
||||
@@ -23,12 +13,5 @@ if(BUILD_TESTING)
|
||||
target_link_libraries(faset_build_schema_tests PRIVATE faset_build_service faset_authoring faset_editor_commands)
|
||||
add_dependencies(faset_build_schema_tests faset_build_schema_tool)
|
||||
add_test(NAME build_schema_publication COMMAND faset_build_schema_tests $<TARGET_FILE:faset_build_schema_tool> ${PROJECT_SOURCE_DIR})
|
||||
# This fixture performs many full cache invalidations and process launches.
|
||||
# Windows runner startup and antivirus overhead exceed 60 seconds even when
|
||||
# individual native fixture commands finish normally.
|
||||
if(WIN32)
|
||||
set_tests_properties(build_schema_publication PROPERTIES TIMEOUT 240)
|
||||
else()
|
||||
set_tests_properties(build_schema_publication PROPERTIES TIMEOUT 60)
|
||||
endif()
|
||||
set_tests_properties(build_schema_publication PROPERTIES TIMEOUT 60)
|
||||
endif()
|
||||
|
||||
@@ -47,8 +47,5 @@ if(TARGET faset_ui AND TARGET faset_editor_session AND TARGET faset_scene_view)
|
||||
set_tests_properties(editor_ui_gizmos PROPERTIES LABELS "gpu")
|
||||
add_test(NAME editor_ui_authoring COMMAND faset_editor_ui_tests)
|
||||
set_tests_properties(editor_ui_authoring PROPERTIES LABELS "gpu")
|
||||
add_executable(faset_editor_ui_latency ${PROJECT_SOURCE_DIR}/tests/editor_ui_latency.cpp)
|
||||
target_link_libraries(faset_editor_ui_latency PRIVATE faset_editor_ui)
|
||||
target_compile_definitions(faset_editor_ui_latency PRIVATE FASET_TEST_ENGINE="${PROJECT_SOURCE_DIR}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -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,19 +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")
|
||||
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)
|
||||
@@ -78,5 +161,17 @@ if(BUILD_TESTING)
|
||||
target_link_libraries(faset_render_window_tests PRIVATE faset_render SDL3::SDL3)
|
||||
add_test(NAME render_window_lifecycle COMMAND faset_render_window_tests "${CMAKE_BINARY_DIR}/window-test")
|
||||
set_tests_properties(render_window_lifecycle PROPERTIES LABELS "gpu;window" TIMEOUT 40 SKIP_RETURN_CODE 77)
|
||||
add_executable(faset_p3_lighting_benchmark
|
||||
"${PROJECT_SOURCE_DIR}/examples/renderer/p3_lighting_benchmark.cpp")
|
||||
target_link_libraries(faset_p3_lighting_benchmark PRIVATE faset_render faset_core)
|
||||
target_compile_definitions(faset_p3_lighting_benchmark PRIVATE
|
||||
FASET_BENCHMARK_CONFIGURATION="$<CONFIG>")
|
||||
add_test(NAME render_lighting_benchmark_schema COMMAND
|
||||
"${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/tests/test_p3_lighting_benchmark.py")
|
||||
set_tests_properties(render_lighting_benchmark_schema PROPERTIES LABELS "p3" TIMEOUT 90)
|
||||
add_test(NAME render_lighting_benchmark_smoke COMMAND
|
||||
"${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/tests/test_p3_lighting_benchmark.py"
|
||||
--real-executable "$<TARGET_FILE:faset_p3_lighting_benchmark>")
|
||||
set_tests_properties(render_lighting_benchmark_smoke PROPERTIES LABELS "gpu;p3" TIMEOUT 90)
|
||||
endif()
|
||||
install(FILES ${FASET_SHADER_OUTPUTS} DESTINATION shaders)
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
After Width: | Height: | Size: 1.7 MiB |
@@ -1,28 +1,5 @@
|
||||
# Developer diagnostics
|
||||
|
||||
## Build errors and source navigation
|
||||
|
||||
Build and export jobs report compiler, clang-cl/MSVC, and Lua errors as structured
|
||||
Console rows. Each row shows severity, message, and a one-based source location.
|
||||
Choose **Open source** to open a file under the current project's `Scripts/`
|
||||
directory at that line and column. Diagnostics from engine or external files remain
|
||||
visible but have no source action. The **Jobs** tab shows whether a verified build
|
||||
generation was reused, the elapsed phases, and an expandable raw process log;
|
||||
**Copy full log** retains the complete available output for troubleshooting.
|
||||
|
||||
The same action is available over Editor MCP:
|
||||
|
||||
```json
|
||||
{"command":"faset_source_open","arguments":{"path":"Scripts/Gameplay.cpp","line":17,"column":4}}
|
||||
```
|
||||
|
||||
`faset_script_open` remains Lua-only. Both commands use the project's optional
|
||||
`editor.script_editor` argv array, or Zed by default. The tokens `{file}`,
|
||||
`{line}`, `{column}`, and `{project}` may appear within one argument, for
|
||||
example `["zed", "{file}:{line}:{column}"]`. Faset passes each argument
|
||||
literally to the editor process without shell expansion. An invalid editor
|
||||
command produces a source-editor error without changing the build result.
|
||||
|
||||
The optional Dear ImGui overlay shows renderer counters inside the Editor. The Editor's normal interface remains the retained Faset UI. Enable the diagnostic build explicitly:
|
||||
|
||||
```sh
|
||||
@@ -35,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.
|
||||
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
# Light a 3D scene
|
||||
|
||||
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.
|
||||
|
||||
| Light kind | Coverage | Shadow cost |
|
||||
| --- | --- | --- |
|
||||
| `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 |
|
||||
|
||||
The Light component's fields are:
|
||||
|
||||
| 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 |
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
{
|
||||
"document": "REPLACE_WITH_DOCUMENT_ID",
|
||||
"revision": 4,
|
||||
"idempotency_key": "add-red-point-light",
|
||||
"operations": [{
|
||||
"op": "component.add",
|
||||
"entity": "REPLACE_WITH_ENTITY_ID",
|
||||
"type": "faset.light",
|
||||
"fields": {
|
||||
"kind": "point",
|
||||
"color": [1, 0.15, 0.1, 1],
|
||||
"intensity": 8,
|
||||
"range": 6,
|
||||
"shadow_priority": 2
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
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++
|
||||
|
||||
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;
|
||||
snapshot.authored_lights_present = true;
|
||||
|
||||
faset::render::LocalLight point;
|
||||
point.kind = faset::render::LocalLight::Kind::Point;
|
||||
point.stable_id = "level/torch";
|
||||
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;
|
||||
spot.kind = faset::render::LocalLight::Kind::Spot;
|
||||
spot.stable_id = "level/lamp";
|
||||
spot.position = {2, 3, 0};
|
||||
spot.direction = {0, -1, 0};
|
||||
spot.inner_angle = 0.25f;
|
||||
spot.outer_angle = 0.55f;
|
||||
spot.intensity = 5;
|
||||
spot.range = 9;
|
||||
snapshot.local_lights.push_back(spot);
|
||||
```
|
||||
|
||||
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.
|
||||
@@ -67,20 +67,6 @@ resume and single-step process controls; it cannot read or modify game entities.
|
||||
scenes that have never been saved. Restoring an already open document requires its
|
||||
current revision. Recovery refuses to overwrite an externally changed scene file.
|
||||
|
||||
Long-lived headless and graphical Editor sessions also poll the same autosave
|
||||
controller. With `editor.autosave` enabled (the default), a named dirty scene is
|
||||
saved after two seconds idle. `faset_autosave_status {}` reports `enabled` and each
|
||||
open document's `state`, `revision`, `path`, and `error`. States include `saved`,
|
||||
`pending`, `saving`, `conflict`, `failed`, `save_as_required`, and `disabled`.
|
||||
An unnamed scene remains in recovery until you give it an explicit path. An
|
||||
external disk edit produces `conflict` without overwriting that edit. For scripted
|
||||
workflows, use `faset_document_save` with `expected_revision` when you need a
|
||||
definite save boundary instead of waiting for idle autosave. If a conflict occurs,
|
||||
save to a new project-relative path and compare the two files. Change the
|
||||
preference through `faset_project_settings_get` and `faset_project_settings_set`
|
||||
with the returned project revision and `{"editor":{"autosave":false}}`; the
|
||||
new value applies to the current session and persists for the next Editor launch.
|
||||
|
||||
## Single-command CLI
|
||||
|
||||
The CLI is useful for scripts that do not need an MCP session:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -50,19 +50,6 @@ outside the Editor, ordinary Save reports a conflict instead of overwriting it.
|
||||
Save your in-memory work to a new path and compare the two versions before replacing
|
||||
anything.
|
||||
|
||||
Autosave is enabled by default. After a named scene has been idle for two seconds,
|
||||
the Editor saves its current revision to that scene file. The status bar shows
|
||||
**Pending autosave**, **Saving**, or **Saved**. An unnamed scene shows **Save As
|
||||
required** until you choose its path. The status bar's **Save As...** action opens
|
||||
an editable, new project-relative path; it never overwrites the conflicting file.
|
||||
|
||||
If the disk file changes outside Faset, autosave stops for that revision and shows
|
||||
**Save conflict**. Use **Save As...** to keep both versions, then compare or reload
|
||||
the original. **Save failed** preserves the unsaved scene in the recovery journal;
|
||||
correct the write error or use Save As. Autosave does not clear Undo history or
|
||||
replace the [recovery journal](#recover-unsaved-work). Recovery still protects
|
||||
unnamed scenes and edits before the idle deadline.
|
||||
|
||||
## Navigate and transform
|
||||
|
||||
- **Right drag:** orbit the 3D view; pan in a 2D scene.
|
||||
@@ -132,16 +119,13 @@ for the C++ iteration loop.
|
||||
## Project settings
|
||||
|
||||
Open **File → Project settings** to change the project name, initial **2D / 3D** type,
|
||||
start-scene path, or **Autosave named scenes** preference. Save a scene first, then
|
||||
choose it from the saved-scene list or enter its project-relative path. Choose
|
||||
**Save project** to update `project.faset.json` explicitly. A missing start scene is
|
||||
rejected and the dialog stays open. The Autosave preference applies immediately;
|
||||
when disabled, the status bar says **Autosave off** and you can still use Ctrl+S or
|
||||
Save As. The project file stores it as `editor.autosave`; omitting it means enabled.
|
||||
and start-scene path. Save a scene first, then choose it from the saved-scene list or
|
||||
enter its project-relative path. Choose **Save project** to update
|
||||
`project.faset.json` explicitly. A missing start scene is rejected and the dialog
|
||||
stays open.
|
||||
|
||||
The project name, default dimension, and start scene are used when the project opens
|
||||
again; they do not convert the active scene, switch its dimension, restart the Player,
|
||||
or add a scene Undo entry.
|
||||
These settings are used when the project opens again; they do not convert the active
|
||||
scene, switch its dimension, restart the Player, or add a scene Undo entry.
|
||||
**Cancel** or **Escape** discards this form's draft. If another writer changes the
|
||||
project settings while the dialog is open, Save reports a revision conflict and
|
||||
keeps your draft. **Reload saved** deliberately discards it and reloads the latest
|
||||
|
||||
@@ -31,27 +31,17 @@ cmake --build --preset linux-debug --parallel
|
||||
ctest --preset linux-debug
|
||||
```
|
||||
|
||||
Create a runnable C++ or Lua starter and open the native Editor:
|
||||
Create a project and open the native Editor:
|
||||
|
||||
```sh
|
||||
build/linux-debug/faset_editor --project "$PWD/MyGame" --new MyGame --dimension 3 --language cpp
|
||||
build/linux-debug/faset_editor --project "$PWD/LuaGame" --new LuaGame --dimension 2 --language lua
|
||||
build/linux-debug/faset_editor --project "$PWD/MyGame" --new MyGame --dimension 3
|
||||
```
|
||||
|
||||
You can also run `build/linux-debug/faset_editor` without arguments to open the
|
||||
project launcher. Its Create view offers C++ 2D, C++ 3D, Lua 2D, and Lua 3D.
|
||||
Each explicit starter includes `Scenes/main.scene.json` with a player, ground,
|
||||
and a behavior bound to `Scripts/Gameplay.cpp` or `Scripts/main.lua`. 3D starters
|
||||
also include a camera and light. Existing files in a destination directory are
|
||||
never replaced by starter creation. The older `--new NAME --dimension 2|3`
|
||||
command without `--language` retains its original scene-free C++ scaffold.
|
||||
project launcher and create or select a project using the native interface.
|
||||
|
||||
Use **Build** after changing `MyGame/Scripts/Gameplay.cpp`, then **Play**.
|
||||
The Player runs separately. Stop it before changing and rebuilding C++ gameplay.
|
||||
Lua starters declare their entry script in `project.faset.json`, install LuaLS
|
||||
annotations/configuration, and need no `Gameplay.cpp`. Use **Refresh Lua** after
|
||||
editing a Lua behavior declaration; development **Reload Lua** can apply a valid
|
||||
script edit to a running Player without recompiling C++.
|
||||
See [MCP and CLI](../editor/mcp.md) for headless authoring and automation.
|
||||
|
||||
For an optimized build use `linux-release`. The `linux-sanitize` preset enables
|
||||
|
||||
@@ -5,23 +5,6 @@ behaviors share the same runtime lifecycle, typed entity operations, scene compo
|
||||
and Inspector metadata. The Editor does not run gameplay code in its own process.
|
||||
There is no built-in script editor: edit `.lua` files in Zed or another external editor.
|
||||
|
||||
## Start with a runnable Lua project
|
||||
|
||||
Choose **Create project → Lua → 2D or 3D** in the native launcher, or run:
|
||||
|
||||
```sh
|
||||
build/linux-debug/faset_editor --project "$PWD/LuaGame" --new LuaGame --dimension 2 --language lua
|
||||
```
|
||||
|
||||
The starter creates `Scripts/main.lua`, a scene with a player and ground, and a
|
||||
manifest entry for that script. The player moves horizontally and jumps with the
|
||||
default input actions. The 3D choice also provides a camera and directional light.
|
||||
Open `Scripts/main.lua` in your external editor and change `speed` or
|
||||
`fixed_update`. **Refresh Lua** updates declared Inspector fields; **Reload Lua**
|
||||
updates a development Player after a valid edit without rebuilding native gameplay.
|
||||
The starter installs LuaLS declarations and `.luarc.json`; reopening it in Zed or
|
||||
another LuaLS-enabled editor provides completions for the Faset API.
|
||||
|
||||
## Enable Lua in a project
|
||||
|
||||
Add explicit entry scripts to `project.faset.json`:
|
||||
|
||||
@@ -42,9 +42,9 @@ Tasks 1–3 define the shared interfaces. Integrate Task 2's descriptor ABI befo
|
||||
|
||||
**Files:** Modify `include/faset/render/renderer.hpp`, `src/authoring/schema.cpp`, `src/player/SceneView.cpp`, `tests/authoring_tests.cpp`, `tests/runtime_player_tests.cpp`, and relevant Manual authoring examples.
|
||||
|
||||
**Interfaces:** Introduce `SunLight { stable_id, direction, color, intensity, casts_shadow }`, `LocalLight { Kind::Point|Spot, stable_id, position, direction, color, intensity, range, inner_angle, outer_angle, casts_shadow, shadow_priority }`, and `CameraFrustum { view, projection, near_plane, far_plane, perspective }`. Add `std::optional<SunLight> Snapshot::sun`, `std::vector<LocalLight> Snapshot::local_lights`, and `std::optional<CameraFrustum> Snapshot::camera_frustum` after existing aggregate fields; retain `Snapshot::light_direction`. `SceneView::build` fills camera data for 3D scenes and picks the first enabled directional by stable ID.
|
||||
**Interfaces:** Introduce `SunLight { stable_id, direction, color, intensity, casts_shadow }`, `LocalLight { Kind::Point|Spot, stable_id, position, direction, color, intensity, range, inner_angle, outer_angle, casts_shadow, shadow_priority }`, and `CameraFrustum { view, projection, near_plane, far_plane, perspective }`. Add `std::optional<SunLight> Snapshot::sun`, `std::vector<LocalLight> Snapshot::local_lights`, `bool Snapshot::authored_lights_present` (default false), and `std::optional<CameraFrustum> Snapshot::camera_frustum` after existing aggregate fields; retain `Snapshot::light_direction`. `SceneView::build` fills camera data for 3D scenes, sets authored-light presence even for disabled/future-version light components, and picks the first enabled directional by stable ID. A legacy sun is synthesized only when both sun and authored-light presence are absent.
|
||||
|
||||
- [ ] **Step 1: Write failing schema/extraction tests.** Construct a version-1 scene with directional, point, and spot entities in one order and reversed order. Assert all local IDs/properties agree; authored sun color/intensity are preserved; no-light scene retains the default legacy sun; extra directionals emit a diagnostic; invalid `range <= 0`, `inner_angle > outer_angle`, nonfinite color/transform, and unknown kind identify the entity/field. An essential assertion is:
|
||||
- [ ] **Step 1: Write failing schema/extraction tests.** Construct a version-1 scene with directional, point, and spot entities in one order and reversed order. Assert all local IDs/properties agree; authored sun color/intensity are preserved; a no-light scene has `authored_lights_present == false` and retains the default legacy sun, while a local-only or explicitly disabled-sun scene has the flag true and no sun. Extra directionals emit a diagnostic; invalid `range <= 0`, `inner_angle > outer_angle`, nonfinite color/transform, and unknown kind identify the entity/field. An essential assertion is:
|
||||
```cpp
|
||||
auto a = view.build(scene_with_three_lights(), 16.f / 9.f);
|
||||
auto b = view.build(reordered_scene_with_three_lights(), 16.f / 9.f);
|
||||
@@ -54,14 +54,14 @@ Tasks 1–3 define the shared interfaces. Integrate Task 2's descriptor ABI befo
|
||||
"Light ordering follows stable IDs, not entity array order");
|
||||
```
|
||||
- [ ] **Step 2: Run the focused tests red.** Run `cmake --preset linux-debug` and `cmake --build --preset linux-debug --target faset_authoring_tests faset_player_tests --parallel 4`; missing typed fields should fail compilation. If compilation succeeds, `ctest --test-dir build/linux-debug --output-on-failure --no-tests=error -R '^(authoring|player_scene_contracts)$'` must fail on a new behavioral assertion. Record the expected failure rather than assuming the build itself must be red.
|
||||
- [ ] **Step 3: Add schema defaults and extraction.** Keep builtin version 1; use `fields.value` for additive fields, normalize directions after the world transform, validate finite/color/range/cone values, sort by stable ID, and preserve the legacy fallback. Set `camera_frustum` from the same unjittered view/projection used to form `view_projection`. Update direct C++ API examples to construct one point and one spot light.
|
||||
- [ ] **Step 3: Add schema defaults and extraction.** Keep builtin version 1; use `fields.value` for additive fields, normalize directions after the world transform, validate finite/color/range/cone values, sort by stable ID, and preserve the legacy fallback only when no authored light component exists. Set `camera_frustum` from the same unjittered view/projection used to form `view_projection`. Update direct C++ API examples to construct one point and one spot light.
|
||||
```cpp
|
||||
struct CameraFrustum {
|
||||
Mat4 view{identity}, projection{identity};
|
||||
float near_plane{0.1f}, far_plane{1000.f};
|
||||
bool perspective{true};
|
||||
};
|
||||
// Snapshot::light_direction remains available to callers without Snapshot::sun.
|
||||
// Snapshot::light_direction remains a fallback only if authored_lights_present is false.
|
||||
```
|
||||
- [ ] **Step 4: Run focused tests green.** `ctest --test-dir build/linux-debug --output-on-failure -R '^(authoring|player_scene_contracts)$'` passes, including old version-1 scenes and exported-scene decoding.
|
||||
- [ ] **Step 5: Commit** `Expose authored sun, point, and spot lights in render snapshots`.
|
||||
|
||||
@@ -12,7 +12,7 @@ P3 supplies one sun plus point and spot lights, camera-fitted cascaded sun shado
|
||||
|
||||
Keep `faset.light` at builtin schema version 1 and add optional fields with defaults: `kind` (`directional`, `point`, `spot`, default `directional`), `enabled` (true), `color` (white), `intensity` (1, nonnegative), `range` (10, positive, for local lights), `inner_angle` (0.35 radians), `outer_angle` (0.70 radians, strictly below π/2), `casts_shadow` (true), and `shadow_priority` (integer 0). Existing fields and component IDs remain valid. Cross-field validation requires `0 <= inner_angle <= outer_angle`; local range, color channels, intensity, transforms, and cone directions must be finite. Invalid authored values return a diagnostic with entity and field rather than nonfinite GPU data. Missing new fields use the schema defaults.
|
||||
|
||||
`Snapshot` retains `light_direction` for existing direct-render clients. Add an optional `SunLight`, a vector of `LocalLight`, and an optional explicit `CameraFrustum` containing unjittered view/projection matrices, near/far distances, and projection kind. `SceneView` populates these from the authoring scene. Each light carries the stable entity/component identity, transformed position or normalized direction, color, intensity, range, cone angles, and shadow options. The first enabled directional light by stable ID is the sun; extra directionals produce a visible diagnostic until a later multi-sun design exists. If there is no authored directional light, the renderer synthesizes the legacy sun from `Snapshot::light_direction`. Local lights are ordered by stable ID to prevent reordering from changing atlas allocation or results. Point attenuation tends smoothly to zero at `range`; a spot multiplies it by a smooth inner-to-outer cone factor. The shader handles zero distance and invalid normals without NaN output.
|
||||
`Snapshot` retains `light_direction` for existing direct-render clients. Add an optional `SunLight`, a vector of `LocalLight`, an `authored_lights_present` flag (default false), and an optional explicit `CameraFrustum` containing unjittered view/projection matrices, near/far distances, and projection kind. `SceneView` populates these from the authoring scene and sets the flag if any authored light component exists, including a disabled or future-version component. Each light carries the stable entity/component identity, transformed position or normalized direction, color, intensity, range, cone angles, and shadow options. The first enabled directional light by stable ID is the sun; extra directionals produce a visible diagnostic until a later multi-sun design exists. The renderer synthesizes the legacy sun from `Snapshot::light_direction` only when `sun` is absent **and** `authored_lights_present` is false. Thus an old scene without light components keeps its previous appearance, while a local-only scene or explicitly disabled sun does not receive an unintended directional light. Low-level callers may set the flag to request a dark scene without authoring metadata. Local lights are ordered by stable ID to prevent reordering from changing atlas allocation or results. Point attenuation tends smoothly to zero at `range`; a spot multiplies it by a smooth inner-to-outer cone factor. The shader handles zero distance and invalid normals without NaN output.
|
||||
|
||||
Explicit camera frustum data is required for four cascades. If a low-level caller supplies only the legacy `view_projection`, the renderer uses one bounded legacy-compatible sun shadow view and reports `effective_sun_cascades = 1`; it never silently claims CSM. 2D sprite-only snapshots do not incur shadow work. The future temporal stage may jitter the main raster projection, but the shadow planner consumes the unjittered `CameraFrustum` exclusively.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -40,7 +40,7 @@ check. For sanitizers, configure with `-DFASET_SANITIZERS=ON`, build the
|
||||
`faset_lua_tests`, `faset_lua_safety_tests`, and `faset_schema_exporter` targets, then
|
||||
run `ctest --test-dir <build> --output-on-failure -R '^lua_'`.
|
||||
|
||||
## Not verified in the September 18 run
|
||||
## Not verified here
|
||||
|
||||
- Windows compilation or execution of the new module.
|
||||
- Graphical/window interaction and real-time Lua reload in a running rendered game.
|
||||
@@ -53,20 +53,3 @@ run `ctest --test-dir <build> --output-on-failure -R '^lua_'`.
|
||||
The renderer-linked CPU checks used the existing Vulkan loader, repo-pinned Vulkan
|
||||
headers and cached Slang, with SDL X11/Wayland disabled. No system graphics packages
|
||||
were installed. This proves linkage and CPU validation, not graphics compatibility.
|
||||
|
||||
## September 24 Release follow-up
|
||||
|
||||
The [Release execution record](lua-release-2026-09-24/README.md) closes the
|
||||
Linux offscreen export gap above. At source commit `4331857`, a Release Editor
|
||||
exported the checked-in Lua-only sample into a disposable Unicode-path project.
|
||||
The package was relocated, the source project path was hidden, and its Player
|
||||
validated and rendered 120 frames on an NVIDIA GeForce RTX 2080 Ti with driver
|
||||
595.84 and Khronos validation enabled; zero validation errors were reported.
|
||||
The package manifest has `lua_enabled: true`, declared Lua source and its license,
|
||||
and no project C++ gameplay source. The five focused Release Lua/schema tests
|
||||
passed. This verifies an offscreen run on Linux; a separate physical Windows GPU
|
||||
and manual window interaction have not been tested by this record. Windows
|
||||
SwiftShader execution is configured in CI and needs a passing run before it is
|
||||
claimed as covered.
|
||||
|
||||

|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
# Lua-only Release game: relocated execution
|
||||
|
||||
This is a local Linux result for source revision
|
||||
`433185717e1f324acb6eaf186818589d11697453` on 2026-09-24. The
|
||||
[machine-readable report](report.json) records the exact source input hashes,
|
||||
Release package, Editor binary hash, command arguments and elapsed times. The
|
||||
Editor itself was built with the `linux-release` preset. The exported Player
|
||||
and package also identify their configuration as Release.
|
||||
|
||||
| Check | Observed result |
|
||||
|---|---|
|
||||
| Disposable source | `examples/lua` copied to a Unicode-path temporary project outside the engine tree |
|
||||
| Package | `lua_enabled: true`; 32 hash-checked files, including Lua gameplay source and `Notices/lua/LICENSE.txt`; no project `Gameplay.cpp` or `.hpp` |
|
||||
| Relocation | Generation copied outside the project/engine; original project path hidden before Player launch |
|
||||
| CPU validation | Packaged Player `--validate` succeeded from an unrelated empty working directory |
|
||||
| GPU execution | 120/120 headless 1280×720 frames, 0 validation errors |
|
||||
| Device | NVIDIA GeForce RTX 2080 Ti; proprietary driver 595.84, Vulkan 1.4.329; selected-device details in the report and [Vulkan probe](vulkaninfo-summary.txt) |
|
||||
| Image | Four expected scene colors, including cyan player and gold collectible; raw [PPM](lua.ppm) SHA-256 `e2f4c3b84de75dadd2e31d388d0ba51abc1861f1ec76eb7ba51a4a2dcac720a7` |
|
||||
| Focused Release CTest | 5/5: `lua_contracts`, `lua_safety_contracts`, `lua_cli_contracts`, `lua_player_reload`, `build_schema_publication` |
|
||||
|
||||
The [manifest](lua-manifest.json) binds the package file checksums. The
|
||||
[Player profile](lua-profile.json) contains all 120 per-frame samples and
|
||||
resource/timing definitions; the [validate](lua-validate.json),
|
||||
[render](lua-run.json) and [export](lua-export.json) logs retain raw process
|
||||
results. The [PNG preview](lua.png) is a lossless conversion of the checked
|
||||
PPM.
|
||||
|
||||
The verifier also confirmed a clean Git source tree, rejected undeclared
|
||||
package files and unexpected C++ script files, classified the selected device
|
||||
as physical, and found the sample Lua `on_start` log in the relocated run.
|
||||
|
||||
To repeat this check from a built checkout, choose a **new, empty** output
|
||||
directory:
|
||||
|
||||
```sh
|
||||
cmake --preset linux-release
|
||||
cmake --build --preset linux-release --target faset_editor --parallel 4
|
||||
python3 tools/verify_playable_exports.py --editor build/linux-release/faset_editor \
|
||||
--output .cache/lua-release-check --only-lua
|
||||
cmake --build --preset linux-release --target faset_player faset_schema_exporter \
|
||||
faset_lua_tests faset_lua_safety_tests faset_build_schema_tests --parallel 4
|
||||
ctest --test-dir build/linux-release --no-tests=error --output-on-failure \
|
||||
-R '^(lua_contracts|lua_safety_contracts|lua_cli_contracts|lua_player_reload|build_schema_publication)$'
|
||||
```
|
||||
|
||||
The render used the Player's synthetic fixed timestep and offscreen Vulkan
|
||||
presentation. It does not establish manual window interaction or physical
|
||||
Windows GPU compatibility. The Windows graphics workflow now includes this Lua
|
||||
package beside both C++ examples under a pinned SwiftShader Vulkan driver; its
|
||||
result must be recorded separately after CI completes.
|
||||
|
||||

|
||||
@@ -1,220 +0,0 @@
|
||||
{
|
||||
"asset_generations": {},
|
||||
"build_fingerprint": "b745490f85f05987d849f6bb6f4d06953883f4b8e10c5551fadf164c7eec5857",
|
||||
"configuration": "Release",
|
||||
"executable": "faset_player",
|
||||
"files": [
|
||||
{
|
||||
"path": "README.txt",
|
||||
"sha256": "82bf72bc542643f6c4090e0a5de69b0014ca3fa17c6f9dfb12cc41e44c8b8db6",
|
||||
"size": 262
|
||||
},
|
||||
{
|
||||
"path": "Notices/Faset-NOTICE.txt",
|
||||
"sha256": "30c2e7e80f5153b31ab57675e07246aef9165965d64e36022b426a26449ece3f",
|
||||
"size": 204
|
||||
},
|
||||
{
|
||||
"path": "Notices/dependencies.json",
|
||||
"sha256": "262e8f63cf21c516829b1fedb0900ac90e76d62aa8f78d9ad02d8f24e0359e74",
|
||||
"size": 2468
|
||||
},
|
||||
{
|
||||
"path": "Notices/lua/LICENSE.txt",
|
||||
"sha256": "e42d195c77d3ad7255ee9c874772d3c53998e7f60f9acf453d4630fa161e0bfe",
|
||||
"size": 1126
|
||||
},
|
||||
{
|
||||
"path": "Notices/stb/LICENSE",
|
||||
"sha256": "bebfe904b14301657e4e5d655c811d51fd31b97c455b9cc2d8600d6bac6cff63",
|
||||
"size": 2510
|
||||
},
|
||||
{
|
||||
"path": "Notices/json/LICENSE.MIT",
|
||||
"sha256": "46a65cffd1ea955132d95a8dd921640714a8d6b537d2e4e482d31145ae95b603",
|
||||
"size": 1076
|
||||
},
|
||||
{
|
||||
"path": "Notices/box3d/LICENSE",
|
||||
"sha256": "da5e31a26bf3cfd5ba5c96d6823e480128c81e76c107ef9d3ee5d94789184b90",
|
||||
"size": 1067
|
||||
},
|
||||
{
|
||||
"path": "Notices/box2d/LICENSE",
|
||||
"sha256": "68a3e676d7e94093b102d5cba0d4e04af812040d6f230c3db67a6664574e43d2",
|
||||
"size": 1067
|
||||
},
|
||||
{
|
||||
"path": "Notices/entt/LICENSE",
|
||||
"sha256": "0785027ce472d7c61f05fba664a1d3ba6639c1593ced351ad9e4bed868765d99",
|
||||
"size": 1097
|
||||
},
|
||||
{
|
||||
"path": "Notices/sdl3/LICENSE.txt",
|
||||
"sha256": "97f35b302b361680ec1e891e95d2d52097bb95abff361434916d99dc1305f127",
|
||||
"size": 884
|
||||
},
|
||||
{
|
||||
"path": "shaders/gpuPostCullMain.reflection.json",
|
||||
"sha256": "eed140bbe09c7d14935b688c770261ac0e81677c0974aa811c88ed67ea779b95",
|
||||
"size": 3591
|
||||
},
|
||||
{
|
||||
"path": "shaders/gpuHzbMain.reflection.json",
|
||||
"sha256": "589818f670881c64f3aba726ee3bc82fe4b9d835a347f99134e0e157dc4a112e",
|
||||
"size": 2050
|
||||
},
|
||||
{
|
||||
"path": "shaders/gpuCullMain.reflection.json",
|
||||
"sha256": "996fe5c7e31c6b8f316d8f290b3be0ae7063eb46c500a661e8715e9b822a2598",
|
||||
"size": 3587
|
||||
},
|
||||
{
|
||||
"path": "shaders/gpuShadowMain.reflection.json",
|
||||
"sha256": "9a03a2b692d2b0c758f63fb27760352cfef20a068372ebc137f1ca1797dcd3c2",
|
||||
"size": 2755
|
||||
},
|
||||
{
|
||||
"path": "shaders/gpuVertexMain.reflection.json",
|
||||
"sha256": "9f43472c267dbb6a2ddc69e252f7f00c5ca46f376d3c7c27165c8d12a175179d",
|
||||
"size": 3098
|
||||
},
|
||||
{
|
||||
"path": "shaders/gpuPostCullMain.spv",
|
||||
"sha256": "62cb72e356346e1511c6d640a3cecb4a6e5283795338299b357edae5f7f52be4",
|
||||
"size": 12460
|
||||
},
|
||||
{
|
||||
"path": "shaders/gpuHzbMain.spv",
|
||||
"sha256": "08682362ee2120ed19d260e613285071747c3e436bb09bb6238e46f9cedc2308",
|
||||
"size": 4140
|
||||
},
|
||||
{
|
||||
"path": "shaders/gpuCullMain.spv",
|
||||
"sha256": "7691360ec55f41d40d43652f506b051cef8b3a3ddcdcc7db61a7d4d97f9e87d8",
|
||||
"size": 16180
|
||||
},
|
||||
{
|
||||
"path": "shaders/gpuShadowMain.spv",
|
||||
"sha256": "ffcdb91e70c41ce9452094865546a009b9203140cf8eeb3ad1b4e5868d82b815",
|
||||
"size": 2404
|
||||
},
|
||||
{
|
||||
"path": "shaders/gpuVertexMain.spv",
|
||||
"sha256": "4647ef4fe1a68ab623ee9c19dac56631907f825cba0becd30869c5221c2a0385",
|
||||
"size": 4928
|
||||
},
|
||||
{
|
||||
"path": "shaders/shadowMain.reflection.json",
|
||||
"sha256": "e3fd0889cd2f569d410f5d7558a4091d9f57f247a1dcf1788462f67525340819",
|
||||
"size": 2645
|
||||
},
|
||||
{
|
||||
"path": "shaders/fragmentMain.reflection.json",
|
||||
"sha256": "72ebf8d32452830650416dc4717d89a4324e97ece7028dfb97c66803c8dce71f",
|
||||
"size": 2649
|
||||
},
|
||||
{
|
||||
"path": "shaders/vertexMain.reflection.json",
|
||||
"sha256": "c584f3b957ebf194a292451362a98590f85ff3cfbfd8f917b1c1265039381777",
|
||||
"size": 2639
|
||||
},
|
||||
{
|
||||
"path": "shaders/shadowMain.spv",
|
||||
"sha256": "7643b4d688492b5ee923b9606f0b0e70343ca05fa67673838206119a6f86d8d8",
|
||||
"size": 784
|
||||
},
|
||||
{
|
||||
"path": "shaders/fragmentMain.spv",
|
||||
"sha256": "f5c4f289917ecab5d00d543c053aac1d10e6cca311fd172daf06d315160a3505",
|
||||
"size": 7436
|
||||
},
|
||||
{
|
||||
"path": "shaders/vertexMain.spv",
|
||||
"sha256": "1ad2631c35d654f48166321ae43d4165043e1b919b14f1dc61caf97b1ada0898",
|
||||
"size": 1224
|
||||
},
|
||||
{
|
||||
"path": "faset_player",
|
||||
"sha256": "99d7b8286a9151ec4eff9a9899e862d987048b031e1e40ca1938646afbde37bd",
|
||||
"size": 6329192
|
||||
},
|
||||
{
|
||||
"path": "project.faset.json",
|
||||
"sha256": "e07b12047f8141c3c4cc8f0e7d0905a5c635375e4494755e34191db0859ab86f",
|
||||
"size": 175
|
||||
},
|
||||
{
|
||||
"path": "Scripts/util/motion.lua",
|
||||
"sha256": "bad3dcdc1081fd1dec589c12dad8dc175b739d77ea52b957ead9ecb4b7cb88a3",
|
||||
"size": 150
|
||||
},
|
||||
{
|
||||
"path": "Scripts/player.lua",
|
||||
"sha256": "1c51810574a3413c46a7cbd0ea2e84a9b802da1206a2b72bb78f6cc951367a11",
|
||||
"size": 1376
|
||||
},
|
||||
{
|
||||
"path": "Scripts/beacon.lua",
|
||||
"sha256": "44d932ebedf66cadb9620f2d926177be2f8d43d71d56aa83611de686af026231",
|
||||
"size": 820
|
||||
},
|
||||
{
|
||||
"path": "scene.fscene",
|
||||
"sha256": "641ba14a1f9c3966b9ec797814fdfa54f954dc5e0d53ab00c645598edef61baf",
|
||||
"size": 1681
|
||||
}
|
||||
],
|
||||
"format": "faset.export",
|
||||
"generation": "aa31bf16-984e-4854-b46f-15655bd6cc67",
|
||||
"lua_enabled": true,
|
||||
"lua_fingerprint": "a9261789bb1e0ca38bd40b08b0454165cd51454cba3ad4725dd357812ced6e35",
|
||||
"platform": "linux",
|
||||
"prerequisites": [
|
||||
"Linux x86_64",
|
||||
"Vulkan 1.3 driver",
|
||||
"Compatible glibc and libstdc++ runtime"
|
||||
],
|
||||
"renderer_profile": {
|
||||
"api": "Vulkan 1.3",
|
||||
"materials": [
|
||||
"base-color factor and texture",
|
||||
"metallic and roughness factors"
|
||||
],
|
||||
"required_features": [
|
||||
"dynamicRendering",
|
||||
"synchronization2"
|
||||
],
|
||||
"shadow_map": {
|
||||
"resolution": 1024,
|
||||
"world_extent": 40
|
||||
},
|
||||
"texture_sampling": "linear clamp, one mip level",
|
||||
"unsupported_material_features": [
|
||||
"normal maps",
|
||||
"metallic-roughness maps",
|
||||
"emissive and occlusion maps",
|
||||
"alpha mode selection",
|
||||
"unlit mode",
|
||||
"per-material face culling"
|
||||
]
|
||||
},
|
||||
"scene_hash": "143724a468d711cbdf9a9700310d7b0acef003ce1b21422ca5204e0e565fa02c",
|
||||
"simulation": {
|
||||
"fixed_delta": 0.016666666666666666,
|
||||
"gravity": [
|
||||
0,
|
||||
-9.81,
|
||||
0
|
||||
],
|
||||
"max_catch_up_ticks": 4,
|
||||
"physics_substeps": 4
|
||||
},
|
||||
"units": {
|
||||
"angle": "radian",
|
||||
"coordinates": "right-handed Y-up",
|
||||
"distance": "metre"
|
||||
},
|
||||
"validation_log": "{\"dimension\":2,\"validated\":true}\n",
|
||||
"version": 1
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"arguments": [
|
||||
"/tmp/faset-playable-exports-8wzkrc_u/Faset Café 世界/lua/faset_player",
|
||||
"--headless",
|
||||
"--frames",
|
||||
"120",
|
||||
"--capture",
|
||||
"/tmp/faset-playable-exports-8wzkrc_u/Faset Café 世界/lua/verification.ppm",
|
||||
"--profile",
|
||||
"/tmp/faset-playable-exports-8wzkrc_u/Faset Café 世界/lua/profile.json"
|
||||
],
|
||||
"cwd": "/tmp/faset-playable-exports-8wzkrc_u/empty-working-directory",
|
||||
"seconds": 0.6288121230027173,
|
||||
"exit_code": 0,
|
||||
"timed_out": false,
|
||||
"stdout": "{\"device\":\"NVIDIA GeForce RTX 2080 Ti\",\"dimension\":2,\"effective_visibility_mode\":\"direct\",\"frames\":120,\"gpu_visibility_active\":false,\"ticks\":120,\"validation_errors\":0,\"visibility_mode\":\"direct\"}\n",
|
||||
"stderr": "Lua player ready: A/D move, Space jump, E reset\n"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"arguments": [
|
||||
"/tmp/faset-playable-exports-8wzkrc_u/Faset Café 世界/lua/faset_player",
|
||||
"--validate"
|
||||
],
|
||||
"cwd": "/tmp/faset-playable-exports-8wzkrc_u/empty-working-directory",
|
||||
"seconds": 0.007359832990914583,
|
||||
"exit_code": 0,
|
||||
"timed_out": false,
|
||||
"stdout": "{\"dimension\":2,\"validated\":true}\n",
|
||||
"stderr": ""
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.5 KiB |
@@ -1,134 +0,0 @@
|
||||
{
|
||||
"format": "faset.playable-export-verification",
|
||||
"version": 1,
|
||||
"started_utc": "2026-09-23T23:30:12.719805+00:00",
|
||||
"platform": "linux",
|
||||
"engine": "/home/emil/Desktop/.worktrees/Faset_Engine-p1-lua-export",
|
||||
"editor": "/home/emil/Desktop/.worktrees/Faset_Engine-p1-lua-export/build/linux-release/faset_editor",
|
||||
"standalone_root": "/tmp/faset-playable-exports-8wzkrc_u",
|
||||
"engine_revision": "433185717e1f324acb6eaf186818589d11697453",
|
||||
"engine_dirty": false,
|
||||
"engine_git_status": [],
|
||||
"engine_diff_sha256": null,
|
||||
"editor_sha256": "03ac75942f5f1d29e9d6fedb21ecffc6c74f4299401dbe84f01ab1b4e9c176a0",
|
||||
"project_workspace": "/tmp/faset-playable-projects-cuhq4iwr",
|
||||
"vulkaninfo_available": true,
|
||||
"driver_override": null,
|
||||
"frames_per_game": 120,
|
||||
"status": "passed",
|
||||
"projects": [
|
||||
{
|
||||
"name": "lua",
|
||||
"dimension": 2,
|
||||
"language": "lua",
|
||||
"source_inputs": [
|
||||
{
|
||||
"path": "README.md",
|
||||
"sha256": "8392da3c18916400ea628daed094c77c390c553cbdcf822da227bdcd3d86380c"
|
||||
},
|
||||
{
|
||||
"path": "Scenes/main.scene.json",
|
||||
"sha256": "f1ab54150a58b289c47bcc34566ad7ee3025ad931db81e7f5dce70d9fbcac8ab"
|
||||
},
|
||||
{
|
||||
"path": "Scripts/beacon.lua",
|
||||
"sha256": "44d932ebedf66cadb9620f2d926177be2f8d43d71d56aa83611de686af026231"
|
||||
},
|
||||
{
|
||||
"path": "Scripts/player.lua",
|
||||
"sha256": "1c51810574a3413c46a7cbd0ea2e84a9b802da1206a2b72bb78f6cc951367a11"
|
||||
},
|
||||
{
|
||||
"path": "Scripts/util/motion.lua",
|
||||
"sha256": "bad3dcdc1081fd1dec589c12dad8dc175b739d77ea52b957ead9ecb4b7cb88a3"
|
||||
},
|
||||
{
|
||||
"path": "project.faset.json",
|
||||
"sha256": "7e4c0781c1837208b4499a9991ffb702143edf1f933ae9c7e3d2543fd2577747"
|
||||
}
|
||||
],
|
||||
"generation": "aa31bf16-984e-4854-b46f-15655bd6cc67",
|
||||
"configuration": "Release",
|
||||
"standalone_directory": "/tmp/faset-playable-exports-8wzkrc_u/Faset Café 世界/lua",
|
||||
"executable": "faset_player",
|
||||
"package_file_count": 32,
|
||||
"asset_generations": {},
|
||||
"device": "NVIDIA GeForce RTX 2080 Ti",
|
||||
"device_class": "physical",
|
||||
"driver": {
|
||||
"apiVersion": "1.4.329",
|
||||
"driverVersion": "595.84.0.0",
|
||||
"deviceName": "NVIDIA GeForce RTX 2080 Ti",
|
||||
"driverID": "DRIVER_ID_NVIDIA_PROPRIETARY",
|
||||
"driverName": "NVIDIA",
|
||||
"driverInfo": "595.84"
|
||||
},
|
||||
"validation_enabled": true,
|
||||
"validation_errors": 0,
|
||||
"completed_frames": 120,
|
||||
"summary_ms": {
|
||||
"gpu": {
|
||||
"max": 0.584992,
|
||||
"min": 0.551712,
|
||||
"p50": 0.5608,
|
||||
"p95": 0.574592,
|
||||
"samples": 120
|
||||
},
|
||||
"render_call": {
|
||||
"max": 4.928888,
|
||||
"min": 1.544123,
|
||||
"p50": 1.919881,
|
||||
"p95": 2.423702,
|
||||
"samples": 120
|
||||
},
|
||||
"renderer_cpu": {
|
||||
"max": 4.923196,
|
||||
"min": 1.540666,
|
||||
"p50": 1.915753,
|
||||
"p95": 2.420426,
|
||||
"samples": 120
|
||||
},
|
||||
"renderer_readback_cpu": {
|
||||
"max": 2.837363,
|
||||
"min": 0.395756,
|
||||
"p50": 0.504853,
|
||||
"p95": 0.641169,
|
||||
"samples": 120
|
||||
},
|
||||
"simulation": {
|
||||
"max": 0.210056,
|
||||
"min": 0.084289,
|
||||
"p50": 0.122261,
|
||||
"p95": 0.171062,
|
||||
"samples": 120
|
||||
},
|
||||
"snapshot": {
|
||||
"max": 0.254931,
|
||||
"min": 0.046518,
|
||||
"p50": 0.065754,
|
||||
"p95": 0.087906,
|
||||
"samples": 120
|
||||
},
|
||||
"wall": {
|
||||
"max": 5.322149,
|
||||
"min": 1.702973,
|
||||
"p50": 2.142903,
|
||||
"p95": 2.634219,
|
||||
"samples": 120
|
||||
}
|
||||
},
|
||||
"capture": {
|
||||
"width": 1280,
|
||||
"height": 720,
|
||||
"sampled_colors": 4,
|
||||
"sha256": "e2f4c3b84de75dadd2e31d388d0ba51abc1861f1ec76eb7ba51a4a2dcac720a7"
|
||||
},
|
||||
"source_project_paths_unavailable": true,
|
||||
"status": "passed"
|
||||
}
|
||||
],
|
||||
"requested_projects": [
|
||||
"lua"
|
||||
],
|
||||
"finished_utc": "2026-09-23T23:32:18.151669+00:00"
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
==========
|
||||
VULKANINFO
|
||||
==========
|
||||
|
||||
Vulkan Instance Version: 1.4.341
|
||||
|
||||
|
||||
Instance Extensions: count = 27
|
||||
-------------------------------
|
||||
VK_EXT_acquire_drm_display : extension revision 1
|
||||
VK_EXT_acquire_xlib_display : extension revision 1
|
||||
VK_EXT_debug_report : extension revision 10
|
||||
VK_EXT_debug_utils : extension revision 2
|
||||
VK_EXT_direct_mode_display : extension revision 1
|
||||
VK_EXT_display_surface_counter : extension revision 1
|
||||
VK_EXT_headless_surface : extension revision 1
|
||||
VK_EXT_layer_settings : extension revision 2
|
||||
VK_EXT_surface_maintenance1 : extension revision 1
|
||||
VK_EXT_swapchain_colorspace : extension revision 5
|
||||
VK_KHR_device_group_creation : extension revision 1
|
||||
VK_KHR_display : extension revision 23
|
||||
VK_KHR_external_fence_capabilities : extension revision 1
|
||||
VK_KHR_external_memory_capabilities : extension revision 1
|
||||
VK_KHR_external_semaphore_capabilities : extension revision 1
|
||||
VK_KHR_get_display_properties2 : extension revision 1
|
||||
VK_KHR_get_physical_device_properties2 : extension revision 2
|
||||
VK_KHR_get_surface_capabilities2 : extension revision 1
|
||||
VK_KHR_portability_enumeration : extension revision 1
|
||||
VK_KHR_surface : extension revision 25
|
||||
VK_KHR_surface_maintenance1 : extension revision 1
|
||||
VK_KHR_surface_protected_capabilities : extension revision 1
|
||||
VK_KHR_wayland_surface : extension revision 6
|
||||
VK_KHR_xcb_surface : extension revision 6
|
||||
VK_KHR_xlib_surface : extension revision 6
|
||||
VK_LUNARG_direct_driver_loading : extension revision 1
|
||||
VK_NV_display_stereo : extension revision 1
|
||||
|
||||
Instance Layers: count = 12
|
||||
---------------------------
|
||||
VK_LAYER_INTEL_nullhw INTEL NULL HW 1.1.73 version 1
|
||||
VK_LAYER_KHRONOS_validation Khronos Validation Layer 1.4.341 version 1
|
||||
VK_LAYER_MESA_anti_lag Open-source implementation of the VK_AMD_anti_lag extension. 1.4.303 version 1
|
||||
VK_LAYER_MESA_device_select Linux device selection layer 1.4.303 version 1
|
||||
VK_LAYER_MESA_overlay Mesa Overlay layer 1.4.303 version 1
|
||||
VK_LAYER_MESA_screenshot Mesa Screenshot layer 1.4.303 version 1
|
||||
VK_LAYER_NV_optimus NVIDIA Optimus layer 1.4.329 version 1
|
||||
VK_LAYER_NV_present NVIDIA Presentation Layer 1.4.329 version 1
|
||||
VK_LAYER_VALVE_steam_fossilize_32 Steam Pipeline Caching Layer 1.3.207 version 1
|
||||
VK_LAYER_VALVE_steam_fossilize_64 Steam Pipeline Caching Layer 1.3.207 version 1
|
||||
VK_LAYER_VALVE_steam_overlay_32 Steam Overlay Layer 1.3.207 version 1
|
||||
VK_LAYER_VALVE_steam_overlay_64 Steam Overlay Layer 1.3.207 version 1
|
||||
|
||||
Devices:
|
||||
========
|
||||
GPU0:
|
||||
apiVersion = 1.4.329
|
||||
driverVersion = 595.84.0.0
|
||||
vendorID = 0x10de
|
||||
deviceID = 0x1e04
|
||||
deviceType = PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
|
||||
deviceName = NVIDIA GeForce RTX 2080 Ti
|
||||
driverID = DRIVER_ID_NVIDIA_PROPRIETARY
|
||||
driverName = NVIDIA
|
||||
driverInfo = 595.84
|
||||
conformanceVersion = 1.4.3.3
|
||||
deviceUUID = 2c71c7ba-97ac-c78e-2c06-af362631f3fe
|
||||
driverUUID = 027cbdd1-c478-513e-af69-ebc9eaa7de87
|
||||
GPU1:
|
||||
apiVersion = 1.4.335
|
||||
driverVersion = 26.0.8
|
||||
vendorID = 0x10005
|
||||
deviceID = 0x0000
|
||||
deviceType = PHYSICAL_DEVICE_TYPE_CPU
|
||||
deviceName = llvmpipe (LLVM 21.1.8, 256 bits)
|
||||
driverID = DRIVER_ID_MESA_LLVMPIPE
|
||||
driverName = llvmpipe
|
||||
driverInfo = Mesa 26.0.8-1ubuntu0.3 (LLVM 21.1.8)
|
||||
conformanceVersion = 1.3.1.1
|
||||
deviceUUID = 6d657361-3236-2e30-2e38-2d3175627500
|
||||
driverUUID = 6c6c766d-7069-7065-5555-494400000000
|
||||
WARNING: [Loader Message] Code 0 : ICD for selected physical device does not export vkGetPhysicalDeviceDisplayPlanePropertiesKHR!
|
||||
WARNING: [Loader Message] Code 0 : ICD for selected physical device does not export vkGetPhysicalDeviceDisplayPropertiesKHR!
|
||||
@@ -1,42 +0,0 @@
|
||||
# P1 starter projects: Linux checkpoint (2026-09-24)
|
||||
|
||||
This is a focused validation of the four explicit new-project choices in commit
|
||||
`cbb8b4508723e1a3a0bbd53c884e116a47b121dc`. It is not the final P1 acceptance
|
||||
record. The run used Linux 7.0.0-31-generic x86_64, Clang 21.1.8, CMake 4.2.3,
|
||||
and an NVIDIA GeForce RTX 2080 Ti with driver 595.84.
|
||||
|
||||
The Editor CLI created C++ 2D, C++ 3D, Lua 2D, and Lua 3D projects in disposable
|
||||
directories with `--new`, `--dimension`, and `--language`. The test checked the
|
||||
project manifest, authored start scene, behavior binding, Lua script declaration,
|
||||
LuaLS files, and the absence of a C++ gameplay stub in Lua-only projects. A real
|
||||
Debug `faset_build` succeeded for C++ 2D and Lua 3D. Their built Players then
|
||||
validated and rendered **both** corresponding scene dimensions using the same
|
||||
language module, so all four templates exercised runtime schema and rendering.
|
||||
Each headless one-frame render reported `validation_errors: 0` on the physical GPU.
|
||||
|
||||
| Project | Built Player used | `--validate` | One-frame headless render |
|
||||
| --- | --- | --- | --- |
|
||||
| C++ 2D | C++ 2D | passed | passed |
|
||||
| C++ 3D | C++ 2D | passed | passed |
|
||||
| Lua 2D | Lua 3D | passed | passed |
|
||||
| Lua 3D | Lua 3D | passed | passed |
|
||||
|
||||
The C++ and Lua source files are identical within each language choice; the 2D/3D
|
||||
difference is the authored scene. Reusing one built Player per language therefore
|
||||
checks scene validity without repeating an identical native compile. The focused
|
||||
CTest cases `process_and_cook` and `editor_ui_launcher` passed 2/2. They include a
|
||||
two-service concurrent creation test: exactly one starter wins, and the other
|
||||
cannot overwrite it. A fresh Lua starter was initialized as a Git repository;
|
||||
`git check-ignore` confirmed `.faset/cache` is ignored while the LuaLS API
|
||||
declaration `.faset/lua/faset.lua` can be committed. A strict MkDocs build passed.
|
||||
|
||||
These captures are representative output from the C++ 2D and 3D scenes. Lua
|
||||
scenes rendered the same layout with their own behavior module.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
The run did not test Windows, a relocated Release export, multi-frame gameplay
|
||||
input, or the final P1 build-cache integration. Those belong to the P1 acceptance
|
||||
matrix and must be recorded separately.
|
||||
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
@@ -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.
|
||||
|
||||
[31m │ ⚠ Warning from the Material for MkDocs team[0m
|
||||
[31m │[0m
|
||||
[31m │[0m MkDocs 2.0, the underlying framework of Material for MkDocs,
|
||||
[31m │[0m will introduce backward-incompatible changes, including:
|
||||
[31m │[0m
|
||||
[31m │ × [0mAll plugins will stop working – the plugin system has been removed
|
||||
[31m │ × [0mAll theme overrides will break – the theming system has been rewritten
|
||||
[31m │ × [0mNo migration path exists – existing projects cannot be upgraded
|
||||
[31m │ × [0mClosed contribution model – community members can't report bugs
|
||||
[31m │ × [0mCurrently unlicensed – unsuitable for production use
|
||||
[31m │[0m
|
||||
[31m │[0m Our full analysis:
|
||||
[31m │[0m
|
||||
[31m │[0m [4mhttps://squidfunk.github.io/mkdocs-material/blog/2026/02/18/mkdocs-2.0/[0m
|
||||
[0m
|
||||
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.
|
||||
|
After Width: | Height: | Size: 186 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 185 B |
|
After Width: | Height: | Size: 262 B |
|
After Width: | Height: | Size: 186 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 185 B |
|
After Width: | Height: | Size: 254 B |
|
After Width: | Height: | Size: 185 B |
|
After Width: | Height: | Size: 254 B |
|
After Width: | Height: | Size: 209 B |
|
After Width: | Height: | Size: 205 B |
|
After Width: | Height: | Size: 214 B |
|
After Width: | Height: | Size: 210 B |
|
After Width: | Height: | Size: 210 B |
|
After Width: | Height: | Size: 207 B |
|
After Width: | Height: | Size: 209 B |
|
After Width: | Height: | Size: 213 B |
|
After Width: | Height: | Size: 214 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 209 B |
|
After Width: | Height: | Size: 211 B |
|
After Width: | Height: | Size: 209 B |
|
After Width: | Height: | Size: 205 B |
|
After Width: | Height: | Size: 215 B |
|
After Width: | Height: | Size: 205 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 208 B |
|
After Width: | Height: | Size: 209 B |
|
After Width: | Height: | Size: 237 B |
|
After Width: | Height: | Size: 250 B |
|
After Width: | Height: | Size: 263 B |
|
After Width: | Height: | Size: 251 B |
|
After Width: | Height: | Size: 253 B |
|
After Width: | Height: | Size: 261 B |
|
After Width: | Height: | Size: 253 B |
|
After Width: | Height: | Size: 255 B |
|
After Width: | Height: | Size: 254 B |
|
After Width: | Height: | Size: 248 B |
|
After Width: | Height: | Size: 242 B |
|
After Width: | Height: | Size: 260 B |
|
After Width: | Height: | Size: 242 B |
|
After Width: | Height: | Size: 258 B |