From 6b243cfb0ba464887a9b8845897f81fa884c1558 Mon Sep 17 00:00:00 2001 From: ercmine Date: Sun, 5 Apr 2026 19:53:03 -0500 Subject: [PATCH] Implement Statement 10 shader pipeline foundation --- CMakeLists.txt | 12 +- cmake/ShaderCompilation.cmake | 23 +- cmake/compile_shaders.cmake | 240 +++++++++++++----- cmake/validate_package_layout.cmake | 15 ++ docs/build.md | 13 +- docs/rendering.md | 129 ++++------ engine/render/README.md | 6 + engine/render/shader_library.cpp | 157 ++++++++++++ engine/render/shader_library.hpp | 61 +++++ engine/shell/main.cpp | 27 +- shaders/README.md | 105 +++++++- shaders/debug/triangle/fs.sc | 9 + shaders/debug/triangle/vs.sc | 11 + shaders/includes/core/common.sh | 12 + shaders/includes/core/transform.sh | 11 + shaders/includes/debug/debug_color.sh | 13 + .../varying_color.def.sc} | 2 +- shaders/materials/unlit_color/fs.sc | 8 + shaders/materials/unlit_color/vs.sc | 11 + shaders/post/tonemap/fs.sc | 9 + shaders/shaders.cmake | 10 + shaders/src/fs_statement4.sc | 7 - shaders/src/vs_statement4.sc | 9 - tests/render/shader_pipeline_tests.cpp | 46 ++++ 24 files changed, 754 insertions(+), 192 deletions(-) create mode 100644 engine/render/shader_library.cpp create mode 100644 engine/render/shader_library.hpp create mode 100644 shaders/debug/triangle/fs.sc create mode 100644 shaders/debug/triangle/vs.sc create mode 100644 shaders/includes/core/common.sh create mode 100644 shaders/includes/core/transform.sh create mode 100644 shaders/includes/debug/debug_color.sh rename shaders/{src/varying.def.sc => includes/varying_color.def.sc} (67%) create mode 100644 shaders/materials/unlit_color/fs.sc create mode 100644 shaders/materials/unlit_color/vs.sc create mode 100644 shaders/post/tonemap/fs.sc create mode 100644 shaders/shaders.cmake delete mode 100644 shaders/src/fs_statement4.sc delete mode 100644 shaders/src/vs_statement4.sc create mode 100644 tests/render/shader_pipeline_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6796ed9..b4280af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,6 +114,8 @@ add_library(render_renderer STATIC engine/render/renderer_types.hpp engine/render/buffer_types.hpp engine/render/shader_types.hpp + engine/render/shader_library.hpp + engine/render/shader_library.cpp engine/render/texture_types.hpp engine/render/bgfx/renderer_bgfx.cpp ) @@ -186,6 +188,11 @@ if(RENDER_BUILD_TESTS) render_apply_project_options(render_renderer_lifecycle_tests) render_apply_warnings(render_renderer_lifecycle_tests) + add_executable(render_shader_pipeline_tests tests/render/shader_pipeline_tests.cpp) + target_link_libraries(render_shader_pipeline_tests PRIVATE render::renderer render::filesystem) + render_apply_project_options(render_shader_pipeline_tests) + render_apply_warnings(render_shader_pipeline_tests) + add_test(NAME unit.platform.types COMMAND render_platform_types_tests) set_tests_properties(unit.platform.types PROPERTIES LABELS "unit;platform") add_test(NAME unit.core.runtime COMMAND render_core_runtime_tests) @@ -196,12 +203,14 @@ if(RENDER_BUILD_TESTS) set_tests_properties(unit.filesystem.service PROPERTIES LABELS "unit;filesystem") add_test(NAME unit.renderer.lifecycle COMMAND render_renderer_lifecycle_tests) set_tests_properties(unit.renderer.lifecycle PROPERTIES LABELS "unit;renderer") + add_test(NAME unit.renderer.shader_pipeline COMMAND render_shader_pipeline_tests) + set_tests_properties(unit.renderer.shader_pipeline PROPERTIES LABELS "unit;renderer;shader") add_test(NAME headless.smoke.startup COMMAND render_headless_validation) set_tests_properties(headless.smoke.startup PROPERTIES LABELS "headless;smoke") add_custom_target(render_test_unit COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --label-regex unit - DEPENDS render_platform_types_tests render_core_runtime_tests render_serialization_tests render_filesystem_tests render_renderer_lifecycle_tests + DEPENDS render_platform_types_tests render_core_runtime_tests render_serialization_tests render_filesystem_tests render_renderer_lifecycle_tests render_shader_pipeline_tests WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" COMMENT "Running render unit tests" ) @@ -220,6 +229,7 @@ set(RENDER_PACKAGE_SHADER_RELATIVE_DIR "shaders/bin") install(TARGETS render_shell RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") install(DIRECTORY "${CMAKE_BINARY_DIR}/bin/shaders/bin/" DESTINATION "shaders/bin" OPTIONAL) +install(DIRECTORY "${CMAKE_BINARY_DIR}/bin/shaders/metadata/" DESTINATION "shaders/metadata" OPTIONAL) add_custom_target(render_package_stage COMMAND ${CMAKE_COMMAND} -E rm -rf "${RENDER_PACKAGE_STAGE_DIR}" diff --git a/cmake/ShaderCompilation.cmake b/cmake/ShaderCompilation.cmake index 7713903..d4e4c87 100644 --- a/cmake/ShaderCompilation.cmake +++ b/cmake/ShaderCompilation.cmake @@ -1,6 +1,8 @@ function(render_setup_shader_compilation) - set(RENDER_SHADER_OUTPUT_DIR "${CMAKE_BINARY_DIR}/bin/shaders/bin" CACHE PATH "Directory for compiled shader binaries") + set(RENDER_SHADER_OUTPUT_ROOT "${CMAKE_BINARY_DIR}/bin/shaders" CACHE PATH "Root directory for compiled shader artifacts") + set(RENDER_SHADER_MANIFEST "${CMAKE_SOURCE_DIR}/shaders/shaders.cmake" CACHE FILEPATH "Shader manifest file") set(RENDER_BGFX_SHADERC "" CACHE FILEPATH "Path to bgfx shaderc executable") + set(RENDER_SHADER_BACKENDS "" CACHE STRING "Semicolon-separated backend list override (dx11;spirv;glsl;metal)") option(RENDER_REQUIRE_SHADER_COMPILATION "Fail configure/build checks when shader compilation is unavailable" OFF) set(shaderc_candidate "${RENDER_BGFX_SHADERC}") @@ -8,17 +10,30 @@ function(render_setup_shader_compilation) set(shaderc_candidate "$") endif() + if(RENDER_SHADER_BACKENDS) + set(backend_list "${RENDER_SHADER_BACKENDS}") + elseif(WIN32) + set(backend_list "dx11") + elseif(APPLE) + set(backend_list "metal") + else() + set(backend_list "spirv;glsl") + endif() + add_custom_target(render_shaders - COMMAND ${CMAKE_COMMAND} -E make_directory "${RENDER_SHADER_OUTPUT_DIR}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${RENDER_SHADER_OUTPUT_ROOT}/bin" + COMMAND ${CMAKE_COMMAND} -E make_directory "${RENDER_SHADER_OUTPUT_ROOT}/metadata" COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/logs" COMMAND ${CMAKE_COMMAND} -DRENDER_SOURCE_DIR=${CMAKE_SOURCE_DIR} - -DRENDER_SHADER_OUTPUT_DIR=${RENDER_SHADER_OUTPUT_DIR} + -DRENDER_SHADER_OUTPUT_ROOT=${RENDER_SHADER_OUTPUT_ROOT} + -DRENDER_SHADER_MANIFEST=${RENDER_SHADER_MANIFEST} -DRENDER_BGFX_SHADERC=${shaderc_candidate} -DRENDER_REQUIRE_SHADER_COMPILATION=${RENDER_REQUIRE_SHADER_COMPILATION} + -DRENDER_SHADER_BACKENDS=${backend_list} -DRENDER_SHADER_LOG_FILE=${CMAKE_BINARY_DIR}/logs/shader_compilation.log -P "${CMAKE_SOURCE_DIR}/cmake/compile_shaders.cmake" - COMMENT "Compiling bgfx shaders" + COMMENT "Compiling render shader pipeline assets" VERBATIM ) diff --git a/cmake/compile_shaders.cmake b/cmake/compile_shaders.cmake index 90ac07c..cacc563 100644 --- a/cmake/compile_shaders.cmake +++ b/cmake/compile_shaders.cmake @@ -1,5 +1,9 @@ -if(NOT EXISTS "${RENDER_SOURCE_DIR}/shaders/src") - message(FATAL_ERROR "Shader source directory missing: ${RENDER_SOURCE_DIR}/shaders/src") +if(NOT EXISTS "${RENDER_SOURCE_DIR}/shaders") + message(FATAL_ERROR "Shader source directory missing: ${RENDER_SOURCE_DIR}/shaders") +endif() + +if(NOT EXISTS "${RENDER_SHADER_MANIFEST}") + message(FATAL_ERROR "Shader manifest missing: ${RENDER_SHADER_MANIFEST}") endif() if(NOT DEFINED RENDER_SHADER_LOG_FILE) @@ -20,7 +24,7 @@ if(NOT RENDER_BGFX_SHADERC) if(RENDER_REQUIRE_SHADER_COMPILATION) message(FATAL_ERROR "RENDER_BGFX_SHADERC not set and RENDER_REQUIRE_SHADER_COMPILATION=ON") endif() - message(WARNING "RENDER_BGFX_SHADERC not set; skipping shader compilation. Expected precompiled files in shaders/bin/.") + message(WARNING "RENDER_BGFX_SHADERC not set; skipping shader compilation. Expected staged files in ${RENDER_SHADER_OUTPUT_ROOT}.") render_shader_log("skipped: shaderc unavailable") return() endif() @@ -34,62 +38,182 @@ if(NOT EXISTS "${RENDER_BGFX_SHADERC}") return() endif() -set(SHADER_SOURCE_DIR "${RENDER_SOURCE_DIR}/shaders/src") -set(SHADER_OUTPUT_DIR "${RENDER_SHADER_OUTPUT_DIR}") -file(MAKE_DIRECTORY "${SHADER_OUTPUT_DIR}") +include("${RENDER_SHADER_MANIFEST}") + +set(SHADER_ROOT "${RENDER_SOURCE_DIR}/shaders") +set(SHADER_SOURCE_ROOT "${SHADER_ROOT}") +set(SHADER_OUTPUT_BIN_ROOT "${RENDER_SHADER_OUTPUT_ROOT}/bin") +set(SHADER_OUTPUT_META_ROOT "${RENDER_SHADER_OUTPUT_ROOT}/metadata") + +if(NOT RENDER_SHADER_BACKENDS) + message(FATAL_ERROR "RENDER_SHADER_BACKENDS is empty") +endif() + +set(RENDER_SHADER_STAGES vs fs cs) + +function(render_backend_profile backend stage out_platform_var out_profile_var) + if("${backend}" STREQUAL "spirv") + set(platform linux PARENT_SCOPE) + if("${stage}" STREQUAL "cs") + set(profile spirv15-12 PARENT_SCOPE) + else() + set(profile spirv PARENT_SCOPE) + endif() + elseif("${backend}" STREQUAL "glsl") + set(platform linux PARENT_SCOPE) + if("${stage}" STREQUAL "cs") + set(profile 430 PARENT_SCOPE) + else() + set(profile 330 PARENT_SCOPE) + endif() + elseif("${backend}" STREQUAL "metal") + set(platform osx PARENT_SCOPE) + set(profile metal PARENT_SCOPE) + elseif("${backend}" STREQUAL "dx11") + set(platform windows PARENT_SCOPE) + if("${stage}" STREQUAL "vs") + set(profile vs_5_0 PARENT_SCOPE) + elseif("${stage}" STREQUAL "fs") + set(profile ps_5_0 PARENT_SCOPE) + else() + set(profile cs_5_0 PARENT_SCOPE) + endif() + else() + message(FATAL_ERROR "Unsupported shader backend '${backend}'") + endif() +endfunction() + +function(render_compile_stage program category stage backend source_rel variant defines) + set(source_path "${SHADER_SOURCE_ROOT}/${source_rel}") + if(NOT EXISTS "${source_path}") + message(FATAL_ERROR "Shader source not found for ${program}/${stage}: ${source_path}") + endif() + + render_backend_profile("${backend}" "${stage}" platform profile) + + if("${stage}" STREQUAL "vs") + set(shader_type "vertex") + elseif("${stage}" STREQUAL "fs") + set(shader_type "fragment") + elseif("${stage}" STREQUAL "cs") + set(shader_type "compute") + else() + message(FATAL_ERROR "Unsupported shader stage '${stage}'") + endif() + + set(output_dir "${SHADER_OUTPUT_BIN_ROOT}/${backend}/${category}/${program}/${variant}") + file(MAKE_DIRECTORY "${output_dir}") + set(output_file "${output_dir}/${stage}.bin") + + set(stage_args + --type ${shader_type} + --platform ${platform} + --profile ${profile} + --varyingdef "${SHADER_ROOT}/includes/varying_color.def.sc" + --include-dir "${SHADER_ROOT}" + --include-dir "${SHADER_ROOT}/includes" + -f "${source_path}" + -o "${output_file}" + ) + + if(defines) + list(APPEND stage_args --define "${defines}") + endif() + + execute_process( + COMMAND "${RENDER_BGFX_SHADERC}" ${stage_args} + RESULT_VARIABLE shader_result + OUTPUT_VARIABLE shader_stdout + ERROR_VARIABLE shader_stderr + ) + + if(NOT shader_result EQUAL 0) + message(FATAL_ERROR + "Failed to compile shader\n" + " program=${program}\n" + " category=${category}\n" + " stage=${stage}\n" + " backend=${backend}\n" + " variant=${variant}\n" + " source=${source_rel}\n" + " defines=${defines}\n" + " stdout=${shader_stdout}\n" + " stderr=${shader_stderr}") + endif() + + file(SHA256 "${output_file}" artifact_hash) + + set(meta_dir "${SHADER_OUTPUT_META_ROOT}/${backend}/${category}/${program}/${variant}") + file(MAKE_DIRECTORY "${meta_dir}") + set(meta_file "${meta_dir}/${stage}.json") + + string(REPLACE "\"" "\\\"" safe_defines "${defines}") + + file(WRITE "${meta_file}" "{\n") + file(APPEND "${meta_file}" " \"schema_version\": 1,\n") + file(APPEND "${meta_file}" " \"program\": \"${program}\",\n") + file(APPEND "${meta_file}" " \"category\": \"${category}\",\n") + file(APPEND "${meta_file}" " \"variant\": \"${variant}\",\n") + file(APPEND "${meta_file}" " \"stage\": \"${stage}\",\n") + file(APPEND "${meta_file}" " \"backend\": \"${backend}\",\n") + file(APPEND "${meta_file}" " \"source\": \"${source_rel}\",\n") + file(APPEND "${meta_file}" " \"compiled_binary\": \"${backend}/${category}/${program}/${variant}/${stage}.bin\",\n") + file(APPEND "${meta_file}" " \"defines\": \"${safe_defines}\",\n") + file(APPEND "${meta_file}" " \"binary_sha256\": \"${artifact_hash}\"\n") + file(APPEND "${meta_file}" "}\n") + + render_shader_log("[ok] ${backend}/${category}/${program}/${variant}/${stage}.bin") +endfunction() + +function(render_emit_program_metadata program category backend variant defines) + set(meta_dir "${SHADER_OUTPUT_META_ROOT}/${backend}/${category}/${program}/${variant}") + file(MAKE_DIRECTORY "${meta_dir}") + set(program_file "${meta_dir}/program.json") + string(REPLACE "\"" "\\\"" safe_defines "${defines}") + + file(WRITE "${program_file}" "{\n") + file(APPEND "${program_file}" " \"schema_version\": 1,\n") + file(APPEND "${program_file}" " \"program\": \"${program}\",\n") + file(APPEND "${program_file}" " \"category\": \"${category}\",\n") + file(APPEND "${program_file}" " \"backend\": \"${backend}\",\n") + file(APPEND "${program_file}" " \"variant\": \"${variant}\",\n") + file(APPEND "${program_file}" " \"stages\": [\"vs\", \"fs\"],\n") + file(APPEND "${program_file}" " \"defines\": \"${safe_defines}\",\n") + file(APPEND "${program_file}" " \"vertex_binary\": \"${backend}/${category}/${program}/${variant}/vs.bin\",\n") + file(APPEND "${program_file}" " \"fragment_binary\": \"${backend}/${category}/${program}/${variant}/fs.bin\",\n") + file(APPEND "${program_file}" " \"reflection_files\": [\n") + file(APPEND "${program_file}" " \"${backend}/${category}/${program}/${variant}/vs.json\",\n") + file(APPEND "${program_file}" " \"${backend}/${category}/${program}/${variant}/fs.json\"\n") + file(APPEND "${program_file}" " ]\n") + file(APPEND "${program_file}" "}\n") + + render_shader_log("[meta] ${backend}/${category}/${program}/${variant}/program.json") +endfunction() + +file(MAKE_DIRECTORY "${SHADER_OUTPUT_BIN_ROOT}") +file(MAKE_DIRECTORY "${SHADER_OUTPUT_META_ROOT}") render_shader_log("shaderc: ${RENDER_BGFX_SHADERC}") -render_shader_log("output: ${SHADER_OUTPUT_DIR}") - -set(RENDER_BACKENDS spirv glsl metal dx11) -set(RENDER_SHADER_STAGES vs fs) -set(RENDER_SHADER_NAMES statement4) - -foreach(backend IN LISTS RENDER_BACKENDS) - file(MAKE_DIRECTORY "${SHADER_OUTPUT_DIR}/${backend}") - - foreach(stage IN LISTS RENDER_SHADER_STAGES) - foreach(name IN LISTS RENDER_SHADER_NAMES) - set(in_file "${SHADER_SOURCE_DIR}/${stage}_${name}.sc") - set(out_file "${SHADER_OUTPUT_DIR}/${backend}/${stage}_${name}.bin") - - if(stage STREQUAL "vs") - set(shader_type "vertex") - else() - set(shader_type "fragment") - endif() - - if(backend STREQUAL "spirv") - set(platform linux) - set(profile spirv) - elseif(backend STREQUAL "glsl") - set(platform linux) - set(profile 330) - elseif(backend STREQUAL "metal") - set(platform osx) - set(profile metal) - elseif(backend STREQUAL "dx11") - set(platform windows) - if(stage STREQUAL "vs") - set(profile vs_5_0) - else() - set(profile ps_5_0) - endif() - endif() - - execute_process( - COMMAND "${RENDER_BGFX_SHADERC}" --type ${shader_type} --platform ${platform} --profile ${profile} - --include-dir "${SHADER_SOURCE_DIR}" -f "${in_file}" -o "${out_file}" - RESULT_VARIABLE shader_result - OUTPUT_VARIABLE shader_stdout - ERROR_VARIABLE shader_stderr - ) - - if(NOT shader_result EQUAL 0) - message(FATAL_ERROR "Failed to compile ${in_file} for ${backend}:\n${shader_stdout}\n${shader_stderr}") - endif() - render_shader_log("[ok] ${backend}/${stage}_${name}.bin") - endforeach() +render_shader_log("output root: ${RENDER_SHADER_OUTPUT_ROOT}") + +foreach(backend IN LISTS RENDER_SHADER_BACKENDS) + foreach(entry IN LISTS RENDER_SHADER_PROGRAMS) + string(REPLACE "|" ";" entry_fields "${entry}") + list(LENGTH entry_fields entry_count) + if(NOT entry_count EQUAL 6) + message(FATAL_ERROR "Invalid shader manifest entry '${entry}'. Expected 6 fields") + endif() + + list(GET entry_fields 0 program) + list(GET entry_fields 1 category) + list(GET entry_fields 2 vs_source) + list(GET entry_fields 3 fs_source) + list(GET entry_fields 4 variant) + list(GET entry_fields 5 defines) + + render_compile_stage("${program}" "${category}" vs "${backend}" "${vs_source}" "${variant}" "${defines}") + render_compile_stage("${program}" "${category}" fs "${backend}" "${fs_source}" "${variant}" "${defines}") + render_emit_program_metadata("${program}" "${category}" "${backend}" "${variant}" "${defines}") endforeach() endforeach() -message(STATUS "Shader compilation complete: ${SHADER_OUTPUT_DIR}") +message(STATUS "Shader pipeline compilation complete: ${RENDER_SHADER_OUTPUT_ROOT}") diff --git a/cmake/validate_package_layout.cmake b/cmake/validate_package_layout.cmake index d9cf158..a393b2d 100644 --- a/cmake/validate_package_layout.cmake +++ b/cmake/validate_package_layout.cmake @@ -27,6 +27,7 @@ endif() set(stage_dir "${RENDER_PACKAGE_STAGE_DIR}") set(bin_dir "${stage_dir}/${RENDER_PACKAGE_BIN_SUBDIR}") set(shader_dir "${stage_dir}/${RENDER_PACKAGE_SHADER_RELATIVE_DIR}") +set(shader_metadata_dir "${stage_dir}/shaders/metadata") if(WIN32) set(shell_name "render_shell.exe") @@ -54,6 +55,20 @@ if(shader_count EQUAL 0) endif() render_package_log("[ok] compiled shader count: ${shader_count}") +if(NOT EXISTS "${shader_metadata_dir}") + message(FATAL_ERROR "Packaging validation failed: missing shader metadata directory ${shader_metadata_dir}") +endif() +render_package_log("[ok] shader metadata directory: ${shader_metadata_dir}") + +file(GLOB_RECURSE shader_metadata_files + "${shader_metadata_dir}/*.json" +) +list(LENGTH shader_metadata_files metadata_count) +if(metadata_count EQUAL 0) + message(FATAL_ERROR "Packaging validation failed: no shader metadata files found in ${shader_metadata_dir}") +endif() +render_package_log("[ok] shader metadata count: ${metadata_count}") + set(archive_base "${stage_dir}/../render-package") if(WIN32) set(archive_path "${archive_base}.zip") diff --git a/docs/build.md b/docs/build.md index 11a4761..23ad6fe 100644 --- a/docs/build.md +++ b/docs/build.md @@ -23,12 +23,17 @@ Runtime dependencies can come from: - SDL3 system package for SDL only (`find_package(SDL3 CONFIG)`) - FetchContent fallback (`-DRENDER_ALLOW_FETCHCONTENT=ON`) -## Shader compilation options - -- `render_shell` expects compiled shaders at runtime under `bin/shaders/bin//`. -- The `render_shader_check` target drives shader compilation and validation. +## Shader pipeline options + +- Shader sources and conventions live under `shaders/` with manifest-driven entries in `shaders/shaders.cmake`. +- `render_shaders` compiles binaries and reflection metadata under `/bin/shaders/`. +- Runtime expects: + - binaries: `bin/shaders/bin/////.bin` + - metadata: `bin/shaders/metadata/////*.json` +- `render_shader_check` drives the same shader pipeline used by shell/tests/packaging. - If `RENDER_BGFX_SHADERC` is not set, compilation is skipped unless `RENDER_REQUIRE_SHADER_COMPILATION=ON`. - Enable `RENDER_BGFX_BUILD_TOOLS=ON` to build bgfx tooling (including `shaderc`) when available. +- Use `RENDER_SHADER_BACKENDS` to override host-default backend list. ## Quick Start (Linux/macOS) diff --git a/docs/rendering.md b/docs/rendering.md index 4b1b132..cb5a09d 100644 --- a/docs/rendering.md +++ b/docs/rendering.md @@ -1,109 +1,84 @@ -# Renderer Runtime Lifecycle (Statement 9) +# Rendering + Shader Pipeline (Statements 9-10) ## Scope -Statement 9 hardens renderer runtime behavior around startup, backend selection, frame flow, resize handling, frame pacing, and recovery-oriented state management. +- Statement 9: renderer lifecycle hardening (startup/frame/resize/recovery). +- Statement 10: canonical shader source/build/runtime pipeline with variants, metadata, staging, and hot reload. -## Lifecycle model +## Engine-owned shader architecture -`render::rendering::Renderer` now owns an explicit lifecycle state machine: +The engine owns shader identity and runtime API through: -- `Uninitialized` -- `Initializing` -- `Ready` -- `Resizing` -- `Recovering` -- `ShuttingDown` -- `Failed` +- `ShaderProgramId { category, name, variant }` +- `ShaderPipelineLayout` (runtime binary + metadata roots) +- `ShaderProgramLibrary` (load + hot reload policy) -The runtime shell and future systems can query: +bgfx shader tools remain implementation detail in build scripts. -- `state()` -- `is_ready()` -- `can_render()` -- `status()` (`RendererStatus` snapshot including frame stats) +## Shader source organization -The renderer logs every lifecycle transition. +See `shaders/README.md` for full conventions. High-level structure: -## Startup and backend selection +- `shaders/includes/` shared include files and varying definitions +- `shaders/materials/` material-domain programs +- `shaders/post/` post-processing programs +- `shaders/debug/` debug visualization programs +- `shaders/shaders.cmake` manifest of program entries and variants -Startup is configuration-driven via `RendererConfig`: +This organization is designed to scale without introducing a custom shader DSL. -- `backend` (engine-owned enum, auto or explicit) -- `width`, `height` -- `debug` -- `vsync` -- `reset_flags` (engine-owned config mask) -- `min_frame_time_ms` (lightweight frame pacing floor) -- `allow_automatic_recovery` +## Build graph integration -Validation is explicit through `validate_renderer_config(...)`. +CMake target flow: -Backend behavior: +- `render_shaders` compiles all manifest entries and emits reflection metadata. +- `render_shader_check` depends on `render_shaders`. +- `render_shell` depends on `render_shaders` (runtime always uses generated artifacts). +- `render_package_stage` and `render_package_validate` include shader binaries and metadata. -1. Engine backend enum maps to bgfx renderer type internally. -2. Requested backend and actual backend are both logged. -3. Explicit requests that are not selected log a warning and continue with the chosen backend. +Primary knobs: -## Frame lifecycle contract +- `RENDER_BGFX_SHADERC` +- `RENDER_SHADER_BACKENDS` +- `RENDER_SHADER_OUTPUT_ROOT` +- `RENDER_REQUIRE_SHADER_COMPILATION` -Per-frame flow is now explicit and misuse-resistant: +## Backend/platform output handling -1. `begin_frame()` - - rejects invalid states - - detects begin/begin misuse - - applies deferred resize - - returns `false` when rendering should be skipped (minimized/invalid state) -2. app records view setup + submissions -3. `end_frame()` - - detects end without begin - - presents (`bgfx::frame()`) - - records frame stats and optional pacing sleep +Shader outputs are generated under: -This creates a stable contract for future view/pass expansion without redesigning loop ownership. +- `bin/shaders/bin/////.bin` +- `bin/shaders/metadata/////*.json` -## Resize policy +Backend mapping is explicit in compile scripts (`spirv`, `glsl`, `metal`, `dx11`) and runtime lookup uses the same mapping through `backend_shader_folder(...)`. -Resize is explicit and resilient: +## Runtime loading and hot reload -- shell forwards resize events with `request_resize(width, height)` -- resize requests are deduplicated and applied at frame boundary -- zero/minimized dimensions are treated as non-renderable and do not hard-fail the renderer -- rendering automatically resumes when a valid size arrives +`render_shell` now loads the debug triangle program through `ShaderProgramLibrary` instead of hardcoded shader file paths. -## Frame pacing policy (current stage) +Hot reload policy: -Current pacing is intentionally lightweight: +- monitor compiled vertex/fragment binaries and `program.json` +- if changed, attempt loading replacement program +- only swap handle after successful creation +- on failure, retain previous valid program and log warning -- primary pacing remains present/vsync (`vsync` + reset flags) -- optional minimum frame duration (`min_frame_time_ms`) prevents runaway present loops when desired -- no simulation scheduler or fixed-step governor is introduced at this stage +This keeps development iteration fast while avoiding renderer corruption on bad recompiles. -## Recovery/device-loss philosophy +## Reflection metadata foundation -A sane engine-owned recovery path is established: +The pipeline emits engine-facing JSON metadata: -- failed/recovery states are explicit -- `try_recover()` attempts an in-place reset path (`bgfx::reset`) using current backbuffer -- automatic recovery attempts are gated by `allow_automatic_recovery` -- recovery attempts and outcomes are logged +- stage metadata (`vs.json`, `fs.json`) with identity, source, defines, backend, and SHA256 +- `program.json` with stage list and binary/metadata references -This is a practical base for later full resource re-creation pipelines. +This is intentionally a foundation for later material binding validation and pipeline introspection. -## Runtime shell integration +## Current limits / follow-ups -`render_shell` now: +Deferred to later statements: -- forwards platform resize events through `request_resize` -- gates rendering work on `begin_frame()` success -- avoids undefined frame flow when minimized or otherwise non-renderable -- keeps shutdown ordered (`destroy resources` -> `renderer.shutdown()` -> `runtime.shutdown()`) - -## Current non-goals / deferred work - -Still deferred to later statements: - -- full multi-device-loss backend edge-case matrix -- explicit persistent resource registry for automated rebuild -- render graph and multi-pass scheduling -- scene/material-driven rendering orchestration +- richer uniform/resource reflection extraction beyond identity/hash fields +- compute shader runtime integration (manifest structure already reserves this) +- centralized material system on top of `ShaderProgramId` +- full editor/content pipeline tooling around variants and dependency graphs diff --git a/engine/render/README.md b/engine/render/README.md index ecae2eb..cb48d52 100644 --- a/engine/render/README.md +++ b/engine/render/README.md @@ -9,6 +9,12 @@ Engine-owned rendering interfaces and resource types. Higher-level engine and gameplay code should include only the public renderer headers. +## Shader pipeline boundary (Statement 10) + +- Engine-owned shader identity and lookup APIs live in `engine/render/shader_library.*`. +- Runtime systems should resolve shader programs through `ShaderProgramId` + `ShaderProgramLibrary`. +- bgfx shader compilation/tooling details remain in CMake scripts under `cmake/`. + ## Lifecycle contract (Statement 9) The public renderer API now exposes an explicit lifecycle and frame contract: diff --git a/engine/render/shader_library.cpp b/engine/render/shader_library.cpp new file mode 100644 index 0000000..c6b26e1 --- /dev/null +++ b/engine/render/shader_library.cpp @@ -0,0 +1,157 @@ +#include "engine/render/shader_library.hpp" + +#include "engine/platform/platform_log.hpp" + +#include + +namespace render::rendering { + +namespace { + +std::string make_watch_key(const ShaderProgramId& id) { + return id.category + ":" + id.name + ":" + id.variant; +} + +} // namespace + +std::string backend_shader_folder(const RendererBackend backend) { + switch (backend) { + case RendererBackend::Direct3D11: + case RendererBackend::Direct3D12: return "dx11"; + case RendererBackend::Metal: return "metal"; + case RendererBackend::Vulkan: return "spirv"; + case RendererBackend::OpenGL: return "glsl"; + case RendererBackend::Noop: + case RendererBackend::Auto: + default: return "spirv"; + } +} + +ShaderProgramPaths resolve_shader_program_paths( + const filesystem::FileSystemService& filesystem, + const RendererBackend backend, + const ShaderPipelineLayout& layout, + const ShaderProgramId& id) { + const std::filesystem::path backend_dir = backend_shader_folder(backend); + const std::filesystem::path shader_rel = backend_dir / id.category / id.name / id.variant; + + ShaderProgramPaths paths{}; + paths.vertex_binary = filesystem.root(filesystem::PathCategory::InstallRoot) / layout.binaries_relative_root / shader_rel / "vs.bin"; + paths.fragment_binary = filesystem.root(filesystem::PathCategory::InstallRoot) / layout.binaries_relative_root / shader_rel / "fs.bin"; + paths.program_metadata = filesystem.root(filesystem::PathCategory::InstallRoot) / layout.metadata_relative_root / shader_rel / "program.json"; + return paths; +} + +ShaderProgramLibrary::ShaderProgramLibrary( + Renderer& renderer, + const filesystem::FileSystemService& filesystem, + ShaderPipelineLayout layout) + : renderer_(renderer), + filesystem_(filesystem), + layout_(std::move(layout)) {} + +ProgramHandle ShaderProgramLibrary::load_program(const ShaderProgramId& id) { + const ShaderProgramPaths paths = resolve_shader_program_paths(filesystem_, renderer_.backend(), layout_, id); + + if (!filesystem_.is_regular_file(paths.vertex_binary) || !filesystem_.is_regular_file(paths.fragment_binary)) { + std::ostringstream error; + error << "Shader program binaries missing for " << id.category << "/" << id.name << " variant=" << id.variant + << " vs=" << paths.vertex_binary.string() << " fs=" << paths.fragment_binary.string(); + platform::log::error(error.str()); + return {}; + } + + if (!filesystem_.is_regular_file(paths.program_metadata)) { + platform::log::warn(std::string{"Shader metadata missing: "} + paths.program_metadata.string()); + } + + ShaderProgramDescription description{}; + description.vertex_shader_path = paths.vertex_binary; + description.fragment_shader_path = paths.fragment_binary; + description.debug_name = id.category + "/" + id.name + "/" + id.variant; + + ProgramHandle handle = renderer_.create_program(description); + if (handle.idx == kInvalidHandle) { + std::ostringstream error; + error << "Failed to create shader program for " << description.debug_name; + platform::log::error(error.str()); + return {}; + } + + ProgramWatchState state{}; + if (collect_write_times(paths, state)) { + watch_states_[make_watch_key(id)] = state; + } + + return handle; +} + +bool ShaderProgramLibrary::reload_if_stale(const ShaderProgramId& id, ProgramHandle& in_out_handle) { + const std::string key = make_watch_key(id); + const ShaderProgramPaths paths = resolve_shader_program_paths(filesystem_, renderer_.backend(), layout_, id); + + ProgramWatchState current{}; + if (!collect_write_times(paths, current)) { + return false; + } + + auto it = watch_states_.find(key); + if (it == watch_states_.end()) { + watch_states_.emplace(key, current); + return false; + } + + if (!is_newer(it->second, current)) { + return false; + } + + ProgramHandle replacement = load_program(id); + if (replacement.idx == kInvalidHandle) { + platform::log::warn("Shader hot reload failed; continuing with previous valid program"); + return false; + } + + if (in_out_handle.idx != kInvalidHandle) { + renderer_.destroy_program(in_out_handle); + } + in_out_handle = replacement; + watch_states_[key] = current; + platform::log::info(std::string{"Hot reloaded shader program: "} + key); + return true; +} + +bool ShaderProgramLibrary::collect_write_times(const ShaderProgramPaths& paths, ProgramWatchState& out_state) const { + if (!filesystem_.is_regular_file(paths.vertex_binary) || !filesystem_.is_regular_file(paths.fragment_binary)) { + return false; + } + + std::error_code ec; + out_state.vertex_write_time = std::filesystem::last_write_time(paths.vertex_binary, ec); + if (ec) { + platform::log::warn(std::string{"Failed to stat shader file: "} + paths.vertex_binary.string()); + return false; + } + + out_state.fragment_write_time = std::filesystem::last_write_time(paths.fragment_binary, ec); + if (ec) { + platform::log::warn(std::string{"Failed to stat shader file: "} + paths.fragment_binary.string()); + return false; + } + + if (filesystem_.is_regular_file(paths.program_metadata)) { + out_state.metadata_write_time = std::filesystem::last_write_time(paths.program_metadata, ec); + if (ec) { + platform::log::warn(std::string{"Failed to stat shader metadata file: "} + paths.program_metadata.string()); + return false; + } + } + + return true; +} + +bool ShaderProgramLibrary::is_newer(const ProgramWatchState& a, const ProgramWatchState& b) { + return b.vertex_write_time > a.vertex_write_time || b.fragment_write_time > a.fragment_write_time + || b.metadata_write_time > a.metadata_write_time; +} + +} // namespace render::rendering diff --git a/engine/render/shader_library.hpp b/engine/render/shader_library.hpp new file mode 100644 index 0000000..39f2b2f --- /dev/null +++ b/engine/render/shader_library.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include "engine/filesystem/filesystem.hpp" +#include "engine/render/renderer.hpp" + +#include +#include +#include +#include +#include + +namespace render::rendering { + +struct ShaderProgramId { + std::string category{}; + std::string name{}; + std::string variant{"default"}; +}; + +struct ShaderPipelineLayout { + std::filesystem::path binaries_relative_root{"shaders/bin"}; + std::filesystem::path metadata_relative_root{"shaders/metadata"}; +}; + +struct ShaderProgramPaths { + std::filesystem::path vertex_binary{}; + std::filesystem::path fragment_binary{}; + std::filesystem::path program_metadata{}; +}; + +[[nodiscard]] std::string backend_shader_folder(RendererBackend backend); +[[nodiscard]] ShaderProgramPaths resolve_shader_program_paths( + const filesystem::FileSystemService& filesystem, + RendererBackend backend, + const ShaderPipelineLayout& layout, + const ShaderProgramId& id); + +class ShaderProgramLibrary { +public: + ShaderProgramLibrary(Renderer& renderer, const filesystem::FileSystemService& filesystem, ShaderPipelineLayout layout = {}); + + [[nodiscard]] ProgramHandle load_program(const ShaderProgramId& id); + [[nodiscard]] bool reload_if_stale(const ShaderProgramId& id, ProgramHandle& in_out_handle); + +private: + struct ProgramWatchState { + std::filesystem::file_time_type vertex_write_time{}; + std::filesystem::file_time_type fragment_write_time{}; + std::filesystem::file_time_type metadata_write_time{}; + }; + + [[nodiscard]] bool collect_write_times(const ShaderProgramPaths& paths, ProgramWatchState& out_state) const; + [[nodiscard]] bool is_newer(const ProgramWatchState& a, const ProgramWatchState& b); + + Renderer& renderer_; + const filesystem::FileSystemService& filesystem_; + ShaderPipelineLayout layout_; + std::unordered_map watch_states_; +}; + +} // namespace render::rendering diff --git a/engine/shell/main.cpp b/engine/shell/main.cpp index 56c18de..c7af4d9 100644 --- a/engine/shell/main.cpp +++ b/engine/shell/main.cpp @@ -2,6 +2,7 @@ #include "engine/platform/platform_log.hpp" #include "engine/platform/platform_runtime.hpp" #include "engine/render/renderer.hpp" +#include "engine/render/shader_library.hpp" #include #include @@ -47,17 +48,6 @@ render::rendering::RendererBackend parse_renderer_backend_from_args(const int ar return render::rendering::RendererBackend::Auto; } -std::filesystem::path shader_backend_directory(const render::rendering::RendererBackend backend) { - switch (backend) { - case render::rendering::RendererBackend::Direct3D11: - case render::rendering::RendererBackend::Direct3D12: return "dx11"; - case render::rendering::RendererBackend::Metal: return "metal"; - case render::rendering::RendererBackend::Vulkan: return "spirv"; - case render::rendering::RendererBackend::OpenGL: return "glsl"; - default: return "spirv"; - } -} - std::array identity_matrix() { return { 1.0F, 0.0F, 0.0F, 0.0F, @@ -138,14 +128,13 @@ int main(int argc, char** argv) { const render::rendering::VertexBufferHandle vertex_buffer = renderer.create_vertex_buffer(vertex_buffer_desc); const render::rendering::IndexBufferHandle index_buffer = renderer.create_index_buffer(index_buffer_desc); - const std::filesystem::path shader_root = filesystem.root(render::filesystem::PathCategory::InstallRoot) - / "shaders/bin" / shader_backend_directory(renderer.backend()); - const render::rendering::ShaderProgramDescription program_desc{ - .vertex_shader_path = shader_root / "vs_statement4.bin", - .fragment_shader_path = shader_root / "fs_statement4.bin", - .debug_name = "statement4_program", + render::rendering::ShaderProgramLibrary shader_library{renderer, filesystem}; + const render::rendering::ShaderProgramId shader_id{ + .category = "debug", + .name = "debug_triangle", + .variant = "default", }; - const render::rendering::ProgramHandle program = renderer.create_program(program_desc); + render::rendering::ProgramHandle program = shader_library.load_program(shader_id); const auto identity = identity_matrix(); @@ -177,6 +166,8 @@ int main(int argc, char** argv) { renderer.set_view(kMainView, view); renderer.set_view_transform(kMainView, std::span{identity}, std::span{identity}); + shader_library.reload_if_stale(shader_id, program); + if (program.idx != render::rendering::kInvalidHandle) { render::rendering::MeshSubmission submission{}; submission.mesh = render::rendering::MeshHandle{ diff --git a/shaders/README.md b/shaders/README.md index 7707b20..126bf3a 100644 --- a/shaders/README.md +++ b/shaders/README.md @@ -1,20 +1,99 @@ -# shaders +# render shader pipeline -Shader sources for the internal renderer live under `shaders/src/`. +Statement 10 establishes the canonical, engine-owned shader workflow. -## Current layout +## Source tree -- `src/varying.def.sc` — shared varying declarations -- `src/vs_statement4.sc` — Statement 4 vertex shader -- `src/fs_statement4.sc` — Statement 4 fragment shader +- `shaders/includes/` shared include files (`*.sh`) and varying definitions (`*.def.sc`) + - `core/` common transforms/constants/macros used across shader families + - `debug/` debug-only helpers +- `shaders/materials//` material shader sources (`vs.sc`, `fs.sc`, optional `cs.sc`) +- `shaders/post//` post-process shader sources +- `shaders/debug//` debug visualization shader sources +- `shaders/shaders.cmake` canonical manifest of programs, variants, and defines -Compiled binaries are expected under `shaders/bin//` at runtime (copied/generated into the build output under `bin/shaders/bin//`). +Generated output root (build tree): -Supported backend output folders at this stage: +- `bin/shaders/bin/////.bin` +- `bin/shaders/metadata/////` + - `.json` stage metadata + - `program.json` engine-facing program reflection record -- `spirv` -- `glsl` -- `metal` -- `dx11` +Installed/staged runtime layout mirrors the same under `shaders/bin` and `shaders/metadata`. -Use the `render_shaders` CMake target with `RENDER_BGFX_SHADERC` set to compile shader binaries. +## Naming conventions + +- Stage files: `vs.sc`, `fs.sc`, `cs.sc`. +- Program identity is manifest-driven: `program`, `category`, `variant`. +- Include files use `*.sh`; varyings use `*.def.sc`. +- Stage entry point is `void main()`. +- Shared transform uniform is currently `u_modelViewProj`. + +## Include rules + +- Includes are resolved deterministically from: + 1. `shaders/` + 2. `shaders/includes/` +- Path style in source should be repo-relative from `shaders/` (e.g. `../includes/core/common.sh`). +- Layering rule: category shaders may include `includes/core/*`; debug shaders may also include `includes/debug/*`. + +## Variants and defines + +Variants are explicitly listed in `shaders/shaders.cmake` as manifest entries: + +`program|category|vertex_source|fragment_source|variant|defines` + +Current variant policy: + +- `default` for baseline behavior +- targeted debug variants (example: `debug_tint`) +- no free-form runtime define explosion; each variant must be declared in the manifest + +## Build integration + +Use `render_shaders` to compile all declared programs/variants for selected backends. + +Relevant CMake cache knobs: + +- `RENDER_BGFX_SHADERC`: explicit `shaderc` executable path +- `RENDER_SHADER_BACKENDS`: override backend list (`dx11;spirv;glsl;metal`) +- `RENDER_SHADER_OUTPUT_ROOT`: generated output root (`/bin/shaders`) +- `RENDER_REQUIRE_SHADER_COMPILATION`: fail hard when toolchain is unavailable + +`render_shader_check` depends on `render_shaders` and is used by validation/CI. + +## Backend/platform mapping + +Compilation mapping is explicit in `cmake/compile_shaders.cmake`: + +- `spirv` -> `--platform linux --profile spirv` +- `glsl` -> `--platform linux --profile 330` +- `metal` -> `--platform osx --profile metal` +- `dx11` -> `--platform windows --profile vs_5_0/ps_5_0` + +Host-default backends are selected by CMake (`dx11` on Windows, `metal` on macOS, `spirv;glsl` on Linux) and can be overridden. + +## Hot reload behavior + +`ShaderProgramLibrary` watches timestamps of: + +- vertex binary +- fragment binary +- `program.json` metadata + +When any changes during runtime, it attempts to rebuild the program handle. On failure, the previous valid handle stays active. + +## Reflection metadata ownership + +Metadata is generated by the shader pipeline and consumed via engine paths (`shaders/metadata/...`), not by exposing raw bgfx tool output as engine API. + +Current metadata captures: + +- schema version +- program/category/variant/backend/stage identity +- source path +- compiled binary relative path +- define string +- SHA256 of compiled stage binary + +This is the foundation for future uniform/resource binding validation. diff --git a/shaders/debug/triangle/fs.sc b/shaders/debug/triangle/fs.sc new file mode 100644 index 0000000..6beed79 --- /dev/null +++ b/shaders/debug/triangle/fs.sc @@ -0,0 +1,9 @@ +$input v_color0 + +#include "../includes/core/common.sh" +#include "../includes/debug/debug_color.sh" + +void main() +{ + gl_FragColor = render_debug_tint(v_color0); +} diff --git a/shaders/debug/triangle/vs.sc b/shaders/debug/triangle/vs.sc new file mode 100644 index 0000000..e8b9dff --- /dev/null +++ b/shaders/debug/triangle/vs.sc @@ -0,0 +1,11 @@ +$input a_position, a_color0 +$output v_color0 + +#include "../includes/core/common.sh" +#include "../includes/core/transform.sh" + +void main() +{ + gl_Position = render_transform_position(a_position); + v_color0 = render_pack_abgr(a_color0); +} diff --git a/shaders/includes/core/common.sh b/shaders/includes/core/common.sh new file mode 100644 index 0000000..d9ef38b --- /dev/null +++ b/shaders/includes/core/common.sh @@ -0,0 +1,12 @@ +#ifndef RENDER_COMMON_SH +#define RENDER_COMMON_SH + +#define RENDER_VARIANT_DEFAULT 0 +#define RENDER_VARIANT_DEBUG_WIREFRAME 1 + +vec4 render_pack_abgr(vec4 color) +{ + return color; +} + +#endif // RENDER_COMMON_SH diff --git a/shaders/includes/core/transform.sh b/shaders/includes/core/transform.sh new file mode 100644 index 0000000..3122d72 --- /dev/null +++ b/shaders/includes/core/transform.sh @@ -0,0 +1,11 @@ +#ifndef RENDER_TRANSFORM_SH +#define RENDER_TRANSFORM_SH + +uniform mat4 u_modelViewProj; + +vec4 render_transform_position(vec3 position) +{ + return mul(u_modelViewProj, vec4(position, 1.0)); +} + +#endif // RENDER_TRANSFORM_SH diff --git a/shaders/includes/debug/debug_color.sh b/shaders/includes/debug/debug_color.sh new file mode 100644 index 0000000..a685a94 --- /dev/null +++ b/shaders/includes/debug/debug_color.sh @@ -0,0 +1,13 @@ +#ifndef RENDER_DEBUG_COLOR_SH +#define RENDER_DEBUG_COLOR_SH + +vec4 render_debug_tint(vec4 color) +{ +#if defined(RENDER_DEBUG_TINT) + return color * vec4(1.0, 0.9, 0.9, 1.0); +#else + return color; +#endif +} + +#endif // RENDER_DEBUG_COLOR_SH diff --git a/shaders/src/varying.def.sc b/shaders/includes/varying_color.def.sc similarity index 67% rename from shaders/src/varying.def.sc rename to shaders/includes/varying_color.def.sc index 5e8a840..e334ee3 100644 --- a/shaders/src/varying.def.sc +++ b/shaders/includes/varying_color.def.sc @@ -1,4 +1,4 @@ vec3 a_position : POSITION; -vec4 a_color0 : COLOR0; +vec4 a_color0 : COLOR0; vec4 v_color0 : COLOR0; diff --git a/shaders/materials/unlit_color/fs.sc b/shaders/materials/unlit_color/fs.sc new file mode 100644 index 0000000..4677383 --- /dev/null +++ b/shaders/materials/unlit_color/fs.sc @@ -0,0 +1,8 @@ +$input v_color0 + +#include "../includes/core/common.sh" + +void main() +{ + gl_FragColor = v_color0; +} diff --git a/shaders/materials/unlit_color/vs.sc b/shaders/materials/unlit_color/vs.sc new file mode 100644 index 0000000..25ba5a0 --- /dev/null +++ b/shaders/materials/unlit_color/vs.sc @@ -0,0 +1,11 @@ +$input a_position, a_color0 +$output v_color0 + +#include "../includes/core/common.sh" +#include "../includes/core/transform.sh" + +void main() +{ + gl_Position = render_transform_position(a_position); + v_color0 = a_color0; +} diff --git a/shaders/post/tonemap/fs.sc b/shaders/post/tonemap/fs.sc new file mode 100644 index 0000000..b4dd6c0 --- /dev/null +++ b/shaders/post/tonemap/fs.sc @@ -0,0 +1,9 @@ +$input v_texcoord0 + +SAMPLER2D(s_color, 0); + +void main() +{ + vec4 color = texture2D(s_color, v_texcoord0); + gl_FragColor = color / (color + vec4(1.0)); +} diff --git a/shaders/shaders.cmake b/shaders/shaders.cmake new file mode 100644 index 0000000..9aee5c1 --- /dev/null +++ b/shaders/shaders.cmake @@ -0,0 +1,10 @@ +# Canonical shader manifest for render. +# Each entry: program|category|vertex_source|fragment_source|variant|defines +set(RENDER_SHADER_PROGRAMS + "debug_triangle|debug|debug/triangle/vs.sc|debug/triangle/fs.sc|default|" + "debug_triangle|debug|debug/triangle/vs.sc|debug/triangle/fs.sc|debug_tint|RENDER_DEBUG_TINT=1" + "material_unlit_color|materials|materials/unlit_color/vs.sc|materials/unlit_color/fs.sc|default|" +) + +# Optional future compute entries: name|category|compute_source|variant|defines +set(RENDER_SHADER_COMPUTE_PROGRAMS) diff --git a/shaders/src/fs_statement4.sc b/shaders/src/fs_statement4.sc deleted file mode 100644 index 3841c63..0000000 --- a/shaders/src/fs_statement4.sc +++ /dev/null @@ -1,7 +0,0 @@ -$input v_color0 - -#include "varying.def.sc" - -void main() { - gl_FragColor = v_color0; -} diff --git a/shaders/src/vs_statement4.sc b/shaders/src/vs_statement4.sc deleted file mode 100644 index 82c7908..0000000 --- a/shaders/src/vs_statement4.sc +++ /dev/null @@ -1,9 +0,0 @@ -$input a_position, a_color0 -$output v_color0 - -#include "varying.def.sc" - -void main() { - gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); - v_color0 = a_color0; -} diff --git a/tests/render/shader_pipeline_tests.cpp b/tests/render/shader_pipeline_tests.cpp new file mode 100644 index 0000000..0bc1b25 --- /dev/null +++ b/tests/render/shader_pipeline_tests.cpp @@ -0,0 +1,46 @@ +#include "engine/filesystem/filesystem.hpp" +#include "engine/render/shader_library.hpp" + +#include +#include + +namespace fs = std::filesystem; + +int main() { + using namespace render; + + const fs::path sandbox = fs::temp_directory_path() / "render_shader_pipeline_tests"; + std::error_code ec; + fs::remove_all(sandbox, ec); + + filesystem::StorageConfig config{}; + config.executable_path = sandbox / "bin" / "render_shell"; + config.source_root_override = sandbox / "source_data"; + config.writable_root_override = sandbox / "user"; + fs::create_directories(*config.source_root_override, ec); + + filesystem::FileSystemService file_system; + assert(file_system.initialize(config)); + + rendering::ShaderPipelineLayout layout{}; + rendering::ShaderProgramId id{ + .category = "debug", + .name = "debug_triangle", + .variant = "default", + }; + + const rendering::ShaderProgramPaths paths = + rendering::resolve_shader_program_paths(file_system, rendering::RendererBackend::Vulkan, layout, id); + + const fs::path expected_base = file_system.root(filesystem::PathCategory::InstallRoot) / "shaders"; + assert(paths.vertex_binary == expected_base / "bin" / "spirv" / "debug" / "debug_triangle" / "default" / "vs.bin"); + assert(paths.fragment_binary == expected_base / "bin" / "spirv" / "debug" / "debug_triangle" / "default" / "fs.bin"); + assert(paths.program_metadata == expected_base / "metadata" / "spirv" / "debug" / "debug_triangle" / "default" / "program.json"); + + assert(rendering::backend_shader_folder(rendering::RendererBackend::Direct3D11) == "dx11"); + assert(rendering::backend_shader_folder(rendering::RendererBackend::OpenGL) == "glsl"); + assert(rendering::backend_shader_folder(rendering::RendererBackend::Metal) == "metal"); + + fs::remove_all(sandbox, ec); + return 0; +}