diff --git a/.gitmodules b/.gitmodules index a177c249..4f902007 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,3 @@ -[submodule "Engine/cpp/ThirdParty/bx"] - path = Engine/cpp/ThirdParty/bx - url = https://github.com/bkaradzic/bx -[submodule "Engine/cpp/ThirdParty/bimg"] - path = Engine/cpp/ThirdParty/bimg - url = https://github.com/bkaradzic/bimg -[submodule "Engine/cpp/ThirdParty/bgfx"] - path = Engine/cpp/ThirdParty/bgfx - url = https://github.com/bkaradzic/bgfx -[submodule "Engine/cpp/ThirdParty/sdl"] +[submodule "Engine/cpp/thirdparty/sdl"] path = Engine/cpp/ThirdParty/sdl url = https://github.com/libsdl-org/SDL diff --git a/CMakeLists.txt b/CMakeLists.txt index dee67b71..8f95e25c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,7 @@ project(DraconicEngine LANGUAGES C CXX) find_package(Git QUIET) find_package(Threads REQUIRED) +find_package(Vulkan REQUIRED) if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git") message(STATUS "Updating submodules...") @@ -20,36 +21,12 @@ endif() list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") -option(BUILD_BENCHMARKS "Build micro-benchmarks (nanobench)" OFF) - include(CTest) include(Compiler) include(Modules) -include(Shaders) # Engine runtime (C++) add_subdirectory(Engine/cpp) # Samples (C++) add_subdirectory(Samples/cpp) - -# Aggregate benchmark targets: `benchmarks` builds them all, `run-benchmarks` -# builds and runs each in turn. -if(BUILD_BENCHMARKS) - get_property(DRACO_BENCH_TARGETS GLOBAL PROPERTY DRACO_BENCH_TARGETS) - if(DRACO_BENCH_TARGETS) - add_custom_target(benchmarks) - add_dependencies(benchmarks ${DRACO_BENCH_TARGETS}) - - set(BENCH_RUN_CMDS "") - foreach(BENCH_TARGET IN LISTS DRACO_BENCH_TARGETS) - list(APPEND BENCH_RUN_CMDS COMMAND $) - endforeach() - add_custom_target(run-benchmarks - ${BENCH_RUN_CMDS} - DEPENDS ${DRACO_BENCH_TARGETS} - USES_TERMINAL - VERBATIM - ) - endif() -endif() diff --git a/Engine/cpp/CMakeLists.txt b/Engine/cpp/CMakeLists.txt index 946eed00..1f0caec5 100644 --- a/Engine/cpp/CMakeLists.txt +++ b/Engine/cpp/CMakeLists.txt @@ -1,9 +1,6 @@ add_subdirectory(ThirdParty SYSTEM) add_modules_library(Runtime SHARED) -# Runtime exposes the whole engine through the `draconic` module. Shell (the single -# library holding both backends) is linked PUBLIC, so a consumer only links Runtime and -# does `import draconic;`; createShell() resolves from Shell. -target_link_libraries(Runtime PUBLIC Core Shell Scene Rendering) +target_link_libraries(Runtime PUBLIC Core) # TODO: binding library \ No newline at end of file diff --git a/Engine/cpp/Runtime/CMakeLists.txt b/Engine/cpp/Runtime/CMakeLists.txt index 9411c89c..6158d144 100644 --- a/Engine/cpp/Runtime/CMakeLists.txt +++ b/Engine/cpp/Runtime/CMakeLists.txt @@ -1,10 +1,18 @@ add_modules_library(Core PIC) add_modules_library(Shell) -add_modules_library(Rendering PIC) -add_modules_library(Scene) +add_modules_library(Rendering/Shaders) +add_modules_library(Rendering/RHI PIC) +add_modules_library(Rendering/RHI/Null) +add_modules_library(Rendering/RHI/Validation) +add_modules_library(Rendering/RHI/Vulkan) +if(WIN32) + add_modules_library(Rendering/RHI/DX12) +endif() +add_modules_library(Rendering/Shaders/System) +add_modules_library(Rendering/Graphics) +add_modules_library(Rendering/RenderGraph) target_link_libraries(Core PUBLIC Definitions Math IO Memory Logging) - # Shell is a single library: the interface plus both backends, separated only by folder # (Desktop/ = SDL3, Null/ = headless). SDL3 is a PRIVATE implementation detail; nothing # outside Shell sees it. createShell() dispatches to the backend chosen at compile time @@ -33,5 +41,40 @@ if(CMAKE_TESTING_ENABLED) add_test(NAME ${SHELL_TEST_TARGET} COMMAND ${SHELL_TEST_TARGET} --reporter junit --out "Testing/${SHELL_TEST_TARGET}.xml") endforeach() endif() -target_link_libraries(Rendering PUBLIC RHI RenderGraph Mesh Material QuadRenderer Renderer) -target_link_libraries(Scene PUBLIC Renderable TransformComponent Camera) +# Rendering: RHI is the backend-agnostic interface module; Null is the headless +# no-op backend (for tests/tools). GPU backends (Vulkan, DX12) come later. +# Shaders: HLSL compilation via DXC. DXC's headers come from Draco::DXCHeaders +# (which also carries DRACO_DXC_PATH); the runtime library is dlopen'd, so only +# the platform's dl library is linked. +target_link_libraries(Rendering_Shaders PUBLIC Core Draco::DXCHeaders PRIVATE ${CMAKE_DL_LIBS}) +# ShaderSystem: variant compile-on-demand + cache. Needs the RHI (to create GPU +# modules) on top of the RHI-free shaders module. Its test uses the Null backend. +target_link_libraries(Rendering_Shaders_System PUBLIC Rendering_Shaders Rendering_RHI Rendering_RHI_Null) +target_link_libraries(Rendering_RHI PUBLIC Core) +target_link_libraries(Rendering_RHI_Null PUBLIC Rendering_RHI) +# Validation wraps any backend; its test wraps the Null backend, so Null is +# PUBLIC here to give the test the rhi.null module interface. +target_link_libraries(Rendering_RHI_Validation PUBLIC Rendering_RHI Rendering_RHI_Null) +# Vulkan GPU backend. The Vulkan loader is PRIVATE: VkIncludes.h/vulkan.h live in +# the module's global fragment, so importers of rhi.vk don't need the headers, but +# the static lib's loader symbols still propagate to the final link. +target_link_libraries(Rendering_RHI_Vulkan PUBLIC Rendering_RHI PRIVATE Vulkan::Vulkan) +# DX12 GPU backend (Windows only). Links the D3D12/DXGI system libraries privately; +# IID_PPV_ARGS uses __uuidof, which Clang flags as a language extension. +if(WIN32) + target_link_libraries(Rendering_RHI_DX12 PUBLIC Rendering_RHI PRIVATE d3d12 dxgi dxguid d3dcompiler) + target_compile_options(Rendering_RHI_DX12 PRIVATE $<$:-Wno-language-extension-token>) +endif() +# Graphics render host: one target holding the graphics / graphics.null / graphics.gpu +# modules. The core `graphics` interface imports only base RHI + Shell; graphics.gpu +# pulls the backends. Shell is PUBLIC for the headless test. DX12 is added +# (with DRACO_HAS_DX12) only on Windows. +# RenderGraph: pass dependency resolution, transient aliasing, automatic barriers +# over the RHI. Backend-agnostic (base RHI only); Null is PUBLIC for its headless tests. +target_link_libraries(Rendering_RenderGraph PUBLIC Core Rendering_RHI Rendering_RHI_Null) +target_link_libraries(Rendering_Graphics PUBLIC + Rendering_RHI Rendering_RHI_Null Rendering_RHI_Vulkan Rendering_RHI_Validation Shell) +if(WIN32) + target_link_libraries(Rendering_Graphics PUBLIC Rendering_RHI_DX12) + target_compile_definitions(Rendering_Graphics PUBLIC DRACO_HAS_DX12) +endif() diff --git a/Engine/cpp/Runtime/Core/CMakeLists.txt b/Engine/cpp/Runtime/Core/CMakeLists.txt index cf2a4391..4478a693 100644 --- a/Engine/cpp/Runtime/Core/CMakeLists.txt +++ b/Engine/cpp/Runtime/Core/CMakeLists.txt @@ -1,12 +1,13 @@ -add_modules_library(Definitions) -add_modules_library(Math) -add_modules_library(IO) -add_modules_library(Memory) -add_modules_library(Logging) -add_modules_library(Containers) +# PIC: these are linked into the shared Runtime library (via Core), so their objects +# must be position-independent - a non-PIC static (e.g. Quaternion::identity) otherwise +# fails to relocate when making the shared object. +add_modules_library(Definitions PIC) +add_modules_library(Math PIC) +add_modules_library(IO PIC) +add_modules_library(Memory PIC) +add_modules_library(Logging PIC) -target_link_libraries(Math PUBLIC Definitions bx) +target_link_libraries(Math PUBLIC Definitions) target_link_libraries(IO PUBLIC Definitions stb_image) target_link_libraries(Memory PUBLIC Definitions Math) target_link_libraries(Logging PUBLIC Definitions) -target_link_libraries(Containers PUBLIC Definitions Memory Math) diff --git a/Engine/cpp/Runtime/Core/Definitions/Definitions.cppm b/Engine/cpp/Runtime/Core/Definitions/Definitions.cppm index a1f9ebf4..2c1d6d8b 100644 --- a/Engine/cpp/Runtime/Core/Definitions/Definitions.cppm +++ b/Engine/cpp/Runtime/Core/Definitions/Definitions.cppm @@ -4,6 +4,7 @@ module; export module core.defs; export import core.stdtypes; +export import core.status; static_assert(__cplusplus >= 202207L, "Minimum of C++23 required."); diff --git a/Engine/cpp/Runtime/Core/Definitions/Status.cppm b/Engine/cpp/Runtime/Core/Definitions/Status.cppm new file mode 100644 index 00000000..165808c1 --- /dev/null +++ b/Engine/cpp/Runtime/Core/Definitions/Status.cppm @@ -0,0 +1,38 @@ +// Status / ErrorCode - success or an error code, no payload. For operations +// that return a value-or-error, use std::expected instead. + +export module core.status; + +import core.stdtypes; + +export namespace draco +{ + enum class ErrorCode : u32 + { + Ok = 0, + Unknown, + InvalidArgument, + OutOfRange, + OutOfMemory, + NotFound, + NotSupported, + AlreadyExists, + Internal, + }; + + class Status + { + public: + constexpr Status() = default; + constexpr Status(ErrorCode code) : m_code(code) {} + + [[nodiscard]] constexpr ErrorCode code() const { return m_code; } + [[nodiscard]] constexpr bool isOk() const { return m_code == ErrorCode::Ok; } + [[nodiscard]] constexpr explicit operator bool() const { return isOk(); } + + friend constexpr bool operator==(Status a, Status b) { return a.m_code == b.m_code; } + + private: + ErrorCode m_code = ErrorCode::Ok; + }; +} diff --git a/Engine/cpp/Runtime/Core/Math/Functions.cppm b/Engine/cpp/Runtime/Core/Math/Functions.cppm index 6a411f06..52969c71 100644 --- a/Engine/cpp/Runtime/Core/Math/Functions.cppm +++ b/Engine/cpp/Runtime/Core/Math/Functions.cppm @@ -1,5 +1,6 @@ module; +#include #include #include #include @@ -114,6 +115,21 @@ export namespace draco::math { return static_cast(std::pow(x, y)); } + // Trigonometry (radians) and square root. + [[nodiscard]] inline f32 sin(f32 x) noexcept { return std::sin(x); } + [[nodiscard]] inline f32 cos(f32 x) noexcept { return std::cos(x); } + [[nodiscard]] inline f32 tan(f32 x) noexcept { return std::tan(x); } + [[nodiscard]] inline f32 acos(f32 x) noexcept { return std::acos(x); } + [[nodiscard]] inline f32 sqrt(f32 x) noexcept { return std::sqrt(x); } + + // Approximate comparisons against an epsilon (defaults to CMP_EPSILON). + [[nodiscard]] constexpr bool nearlyEqual(f32 a, f32 b, f32 epsilon = CMP_EPSILON) noexcept { + return abs(a - b) <= epsilon; + } + [[nodiscard]] constexpr bool nearlyZero(f32 v, f32 epsilon = CMP_EPSILON) noexcept { + return abs(v) <= epsilon; + } + template constexpr T lerp(T from, T to, T weight) noexcept { return std::lerp(from, to, weight); @@ -202,4 +218,4 @@ export namespace draco::math { T d = (control_1 - start) * T{3.} * omt2 + (control_2 - control_1) * T{6.} * omt * t + (end - control_2) * T{3.} * t2; return d; } -} +} \ No newline at end of file diff --git a/Engine/cpp/Runtime/Core/Math/Math.cppm b/Engine/cpp/Runtime/Core/Math/Math.cppm index bf983a28..275bf225 100644 --- a/Engine/cpp/Runtime/Core/Math/Math.cppm +++ b/Engine/cpp/Runtime/Core/Math/Math.cppm @@ -4,4 +4,6 @@ export import core.defs; export import core.math.constants; export import core.math.functions; export import core.math.types; +export import core.math.matrix4; +export import core.math.quaternion; export import core.math.transform; diff --git a/Engine/cpp/Runtime/Core/Math/Math.test.cpp b/Engine/cpp/Runtime/Core/Math/Math.test.cpp index 4b300766..55686ebe 100644 --- a/Engine/cpp/Runtime/Core/Math/Math.test.cpp +++ b/Engine/cpp/Runtime/Core/Math/Math.test.cpp @@ -1,4 +1,5 @@ #include +#include import core.math; @@ -449,28 +450,16 @@ TEST_SUITE("vector2") { ); } - TEST_CASE("approx_eq") { + TEST_CASE("nearlyEqual") { using math::Vector2; - using math::approx_eq; + using math::nearlyEqual; using math::CMP_EPSILON; static constexpr Vector2 v{1.0f, 2.0f}; - static constexpr Vector2 offset = Vector2::xAxis(CMP_EPSILON); - BASIC_R_SUBCASE("distance < epsilon", - ( approx_eq(v, v + offset * 0.5f) ), - ( true ) - ); - - BASIC_R_SUBCASE("distance == epsilon", - ( approx_eq(v, v + offset) ), - ( true ) - ); - - BASIC_R_SUBCASE("distance > epsilon", - ( approx_eq(v, v + offset * 2.0f) ), - ( false ) - ); + CHECK(nearlyEqual(v, v)); + CHECK(nearlyEqual(v, v + Vector2{0.5f * CMP_EPSILON, 0.0f})); // within epsilon + CHECK_FALSE(nearlyEqual(v, v + Vector2{2.0f * CMP_EPSILON, 0.0f})); // beyond epsilon } } @@ -904,28 +893,16 @@ TEST_SUITE("vector3") { ); } - TEST_CASE("approx_eq") { + TEST_CASE("nearlyEqual") { using math::Vector3; - using math::approx_eq; + using math::nearlyEqual; using math::CMP_EPSILON; static constexpr Vector3 v{1.0f, 2.0f, 3.0f}; - static constexpr Vector3 offset = Vector3::xAxis(CMP_EPSILON); - - BASIC_R_SUBCASE("distance < epsilon", - ( approx_eq(v, v + offset * 0.5f) ), - ( true ) - ); - - BASIC_R_SUBCASE("distance == epsilon", - ( approx_eq(v, v + offset) ), - ( true ) - ); - BASIC_R_SUBCASE("distance > epsilon", - ( approx_eq(v, v + offset * 2.0f) ), - ( false ) - ); + CHECK(nearlyEqual(v, v)); + CHECK(nearlyEqual(v, v + Vector3{0.0f, 0.0f, 0.5f * CMP_EPSILON})); // within epsilon + CHECK_FALSE(nearlyEqual(v, v + Vector3{0.0f, 0.0f, 2.0f * CMP_EPSILON})); // beyond epsilon (z lane) } TEST_CASE("cross") { @@ -1395,43 +1372,241 @@ TEST_SUITE("vector4") { ); } - TEST_CASE("approx_eq") { + TEST_CASE("nearlyEqual") { using math::Vector4; - using math::approx_eq; + using math::nearlyEqual; using math::CMP_EPSILON; static constexpr Vector4 v{1.0f, 2.0f, 3.0f, 4.0f}; - static constexpr Vector4 offset = Vector4::xAxis(CMP_EPSILON); - - BASIC_R_SUBCASE("distance < epsilon", - ( approx_eq(v, v + offset * 0.5f) ), - ( true ) - ); - BASIC_R_SUBCASE("distance == epsilon", - ( approx_eq(v, v + offset) ), - ( true ) - ); - - BASIC_R_SUBCASE("distance > epsilon", - ( approx_eq(v, v + offset * 2.0f) ), - ( false ) - ); + CHECK(nearlyEqual(v, v)); + CHECK(nearlyEqual(v, v + Vector4{0.0f, 0.0f, 0.0f, 0.5f * CMP_EPSILON})); // within epsilon + CHECK_FALSE(nearlyEqual(v, v + Vector4{0.0f, 0.0f, 0.0f, 2.0f * CMP_EPSILON})); // beyond epsilon (w lane) } TEST_CASE("Transform") { using math::Transform; static constexpr Transform transform; - STATIC_REQUIRE(transform.position[0] == 0.f); - STATIC_REQUIRE(transform.position[1] == 0.f); - STATIC_REQUIRE(transform.position[2] == 0.f); - STATIC_REQUIRE(transform.rotation[0] == 0.f); - STATIC_REQUIRE(transform.rotation[1] == 0.f); - STATIC_REQUIRE(transform.rotation[2] == 0.f); - STATIC_REQUIRE(transform.scale[0] == 1.f); - STATIC_REQUIRE(transform.scale[1] == 1.f); - STATIC_REQUIRE(transform.scale[2] == 1.f); + // Default: position 0, rotation identity, scale 1. + STATIC_REQUIRE(transform.position.x == 0.f); + STATIC_REQUIRE(transform.position.y == 0.f); + STATIC_REQUIRE(transform.position.z == 0.f); + STATIC_REQUIRE(transform.rotation.x == 0.f); + STATIC_REQUIRE(transform.rotation.y == 0.f); + STATIC_REQUIRE(transform.rotation.z == 0.f); + STATIC_REQUIRE(transform.rotation.w == 1.f); + STATIC_REQUIRE(transform.scale.x == 1.f); + STATIC_REQUIRE(transform.scale.y == 1.f); + STATIC_REQUIRE(transform.scale.z == 1.f); + } +} + +TEST_SUITE("matrix4") { + TEST_CASE("identity and multiply") { + using namespace draco::math; + const Matrix4 id = Matrix4::identity(); + const Matrix4 t = Matrix4::translation(Vector3{ 1.0f, 2.0f, 3.0f }); + + CHECK(nearlyEqual(id * t, t)); + CHECK(nearlyEqual(t * id, t)); + + static_assert(Matrix4::identity()[0, 0] == 1.0f); + static_assert(Matrix4::identity()[0, 1] == 0.0f); + } + + TEST_CASE("translation lives in the last row (row vectors)") { + using namespace draco::math; + const Matrix4 t = Matrix4::translation(Vector3{ 10.0f, 20.0f, 30.0f }); + CHECK(t.m[3][0] == 10.0f); + CHECK(t.m[3][1] == 20.0f); + CHECK(t.m[3][2] == 30.0f); + + const Vector3 p = transformPoint(Vector3{ 1.0f, 1.0f, 1.0f }, t); + CHECK(nearlyEqual(p, Vector3{ 11.0f, 21.0f, 31.0f })); + + // Directions ignore translation. + CHECK(nearlyEqual(transformDirection(Vector3{ 1.0f, 0.0f, 0.0f }, t), Vector3{ 1.0f, 0.0f, 0.0f })); + } + + TEST_CASE("rotationZ(90) maps +X to +Y (row vectors)") { + using namespace draco::math; + const Matrix4 rz = Matrix4::rotationZ(degToRad(90.0f)); + CHECK(nearlyEqual(transformDirection(Vector3::xAxis(), rz), Vector3::yAxis())); + + // rotationY(90) maps +Z to +X. + const Matrix4 ry = Matrix4::rotationY(degToRad(90.0f)); + CHECK(nearlyEqual(transformDirection(Vector3::zAxis(), ry), Vector3::xAxis())); + } + + TEST_CASE("2D affine helpers (transformPoint2D, operator==)") { + using namespace draco::math; + CHECK(Matrix4::identity() == Matrix4::identity()); + CHECK_FALSE(Matrix4::translation(Vector3{ 1, 0, 0 }) == Matrix4::identity()); + + const Matrix4 t = Matrix4::translation(Vector3{ 5.0f, 7.0f, 0.0f }); + CHECK(nearlyEqual(transformPoint2D(Vector2{ 1.0f, 2.0f }, t), Vector2{ 6.0f, 9.0f })); + + // Row vectors, left-to-right: v * (S * T). + const Matrix4 st = Matrix4::scale(Vector3{ 2.0f, 3.0f, 1.0f }) * Matrix4::translation(Vector3{ 1.0f, 1.0f, 0.0f }); + CHECK(nearlyEqual(transformPoint2D(Vector2{ 1.0f, 1.0f }, st), Vector2{ 3.0f, 4.0f })); + + const Matrix4 rz = Matrix4::rotationZ(degToRad(90.0f)); + CHECK(nearlyEqual(transformPoint2D(Vector2{ 1.0f, 0.0f }, rz), Vector2{ 0.0f, 1.0f })); + } + + TEST_CASE("composition reads left-to-right (scale then translate)") { + using namespace draco::math; + const Matrix4 st = Matrix4::scale(Vector3{ 2.0f, 2.0f, 2.0f }) * Matrix4::translation(Vector3{ 1.0f, 0.0f, 0.0f }); + const Vector3 p = transformPoint(Vector3{ 1.0f, 1.0f, 1.0f }, st); + CHECK(nearlyEqual(p, Vector3{ 3.0f, 2.0f, 2.0f })); + } + + TEST_CASE("perspective has the expected projective structure") { + using namespace draco::math; + const Matrix4 proj = Matrix4::perspectiveFovRH(degToRad(90.0f), 1.0f, 1.0f, 100.0f); + CHECK(proj.m[2][3] == -1.0f); // w' = -z (RH) + CHECK(nearlyEqual(proj.m[0][0], 1.0f)); // xScale = 1/tan(45) at aspect 1 + + // Depth mapping: the near plane (z = -zNear) maps to NDC depth 0, the far plane + // (z = -zFar) to NDC depth 1 - exercising the near/far-derived coefficients. + const auto ndcDepth = [&](f32 z) { + const Vector4 clip = Vector4{ 0.0f, 0.0f, z, 1.0f } * proj; + return clip.z / clip.w; + }; + CHECK(nearlyEqual(ndcDepth(-1.0f), 0.0f, 1.0e-4f)); + CHECK(nearlyEqual(ndcDepth(-100.0f), 1.0f, 1.0e-4f)); + } + + TEST_CASE("determinant") { + using namespace draco::math; + CHECK(nearlyEqual(determinant(Matrix4::identity()), 1.0f)); + CHECK(nearlyEqual(determinant(Matrix4::scale(Vector3{ 2.0f, 3.0f, 4.0f })), 24.0f)); + } + + TEST_CASE("inverse undoes the transform") { + using namespace draco::math; + Transform xform; + xform.scale = Vector3{ 2.0f, 0.5f, 3.0f }; + xform.rotation = Quaternion::fromAxisAngle(normalize(Vector3{ 1.0f, 2.0f, 3.0f }), degToRad(50.0f)); + xform.position = Vector3{ 5.0f, -2.0f, 1.0f }; + + const Matrix4 m = xform.toMatrix(); + const Matrix4 inv = inverse(m); + + CHECK(nearlyEqual(m * inv, Matrix4::identity(), 1.0e-3f)); + CHECK(nearlyEqual(inv * m, Matrix4::identity(), 1.0e-3f)); + + // Point transformed then inverse-transformed returns to itself. + const Vector3 p{ 3.0f, 4.0f, 5.0f }; + const Vector3 roundTrip = transformPoint(transformPoint(p, m), inv); + CHECK(nearlyEqual(roundTrip, p, 1.0e-3f)); + + // Singular matrix: tryInverse reports the failure explicitly (no silent identity). + CHECK_FALSE(tryInverse(Matrix4::scale(Vector3::zero)).has_value()); + + // A small-but-invertible scale must NOT be flagged singular by the scale-aware + // threshold (an absolute-epsilon determinant check would wrongly reject it). + const Vector3 tinyScale{ 1.0e-3f, 1.0e-3f, 1.0e-3f }; + const auto tiny = tryInverse(Matrix4::scale(tinyScale)); + REQUIRE(tiny.has_value()); + CHECK(nearlyEqual(Matrix4::scale(tinyScale) * *tiny, Matrix4::identity(), 1.0e-2f)); + } +} + +TEST_SUITE("quaternion") { + TEST_CASE("rotates vectors and agrees with its matrix") { + using namespace draco::math; + const Quaternion q = Quaternion::fromAxisAngle(Vector3::zAxis(), degToRad(90.0f)); + + // 90 deg about Z maps +X to +Y. + CHECK(nearlyEqual(rotateVector(q, Vector3::xAxis()), Vector3::yAxis())); + + // Quaternion rotation and its matrix agree. + const Matrix4 r = rotationMatrix(q); + CHECK(nearlyEqual(rotateVector(q, Vector3::xAxis()), transformDirection(Vector3::xAxis(), r))); + CHECK(nearlyEqual(rotateVector(q, Vector3{ 0.3f, -0.5f, 0.8f }), + transformDirection(Vector3{ 0.3f, -0.5f, 0.8f }, r))); + + // Identity does nothing. + CHECK(nearlyEqual(rotateVector(Quaternion::identity, Vector3{ 1.0f, 2.0f, 3.0f }), Vector3{ 1.0f, 2.0f, 3.0f })); + + // Composition: two 45-deg rotations == one 90-deg. + const Quaternion half = Quaternion::fromAxisAngle(Vector3::zAxis(), degToRad(45.0f)); + CHECK(nearlyEqual(rotateVector(half * half, Vector3::xAxis()), Vector3::yAxis())); + + // Composition is non-commutative: distinct-axis rotations depend on order, and + // q1 * q2 applies q2 first, then q1 (so it equals the sequential rotation). + const Quaternion qx = Quaternion::fromAxisAngle(Vector3::xAxis(), degToRad(90.0f)); + const Quaternion qy = Quaternion::fromAxisAngle(Vector3::yAxis(), degToRad(90.0f)); + const Vector3 zv{ 0.0f, 0.0f, 1.0f }; + CHECK(nearlyEqual(rotateVector(qx * qy, zv), rotateVector(qx, rotateVector(qy, zv)))); + CHECK_FALSE(nearlyEqual(rotateVector(qx * qy, zv), rotateVector(qy * qx, zv))); + + // A zero-length axis has no direction to rotate about: fall back to identity + // rather than divide by zero and produce a NaN quaternion. + CHECK(nearlyEqual(Quaternion::fromAxisAngle(Vector3::zero, degToRad(90.0f)), Quaternion::identity)); + + // q and -q are the same rotation, so nearlyEqual treats them as equal. + CHECK(nearlyEqual(q, Quaternion{ -q.x, -q.y, -q.z, -q.w })); + } + + TEST_CASE("slerp endpoints and midpoint") { + using namespace draco::math; + const Quaternion a = Quaternion::identity; + const Quaternion b = Quaternion::fromAxisAngle(Vector3::zAxis(), degToRad(90.0f)); + + CHECK(nearlyEqual(slerp(a, b, 0.0f), a)); + CHECK(nearlyEqual(slerp(a, b, 1.0f), b)); + + // Halfway 0..90 deg about Z is 45 deg: +X -> (cos45, sin45, 0). + const Quaternion mid = slerp(a, b, 0.5f); + const Vector3 rotated = rotateVector(mid, Vector3::xAxis()); + const f32 c = std::cos(degToRad(45.0f)); + CHECK(nearlyEqual(rotated, Vector3{ c, c, 0.0f }, 1.0e-4f)); + + // Antipodal endpoint: -b is the same rotation as b, so slerp's shortest-path sign + // correction gives a finite midpoint representing that same halfway rotation. + const Quaternion negB = Quaternion{ -b.x, -b.y, -b.z, -b.w }; + const Quaternion midAnti = slerp(a, negB, 0.5f); + CHECK(isFinite(midAnti.x)); + CHECK(nearlyEqual(rotateVector(midAnti, Vector3::xAxis()), Vector3{ c, c, 0.0f }, 1.0e-4f)); + } +} +TEST_SUITE("transform") { + TEST_CASE("composes scale, rotation, translation") { + using namespace draco::math; + Transform xform; + xform.scale = Vector3{ 2.0f, 2.0f, 2.0f }; + xform.rotation = Quaternion::fromAxisAngle(Vector3::zAxis(), degToRad(90.0f)); + xform.position = Vector3{ 5.0f, 0.0f, 0.0f }; + + const Matrix4 m = xform.toMatrix(); + + // (1,0,0) -> scale*2 -> (2,0,0) -> rot90Z -> (0,2,0) -> +translate -> (5,2,0) + const Vector3 p = transformPoint(Vector3::xAxis(), m); + CHECK(nearlyEqual(p, Vector3{ 5.0f, 2.0f, 0.0f })); + + // Identity transform is a no-op. + Transform id; + CHECK(nearlyEqual(transformPoint(Vector3{ 7.0f, 8.0f, 9.0f }, id.toMatrix()), + Vector3{ 7.0f, 8.0f, 9.0f })); + } + + TEST_CASE("lerp interpolates position, scale, and rotation") { + using namespace draco::math; + Transform a; + Transform b; + b.position = Vector3{ 10.0f, 0.0f, 0.0f }; + b.scale = Vector3{ 3.0f, 3.0f, 3.0f }; + b.rotation = Quaternion::fromAxisAngle(Vector3::zAxis(), degToRad(90.0f)); + + const Transform mid = Transform::lerp(a, b, 0.5f); + CHECK(nearlyEqual(mid.position, Vector3{ 5.0f, 0.0f, 0.0f })); + CHECK(nearlyEqual(mid.scale, Vector3{ 2.0f, 2.0f, 2.0f })); + // Rotation slerped to 45 deg about Z: +X -> (cos45, sin45, 0). + const f32 c = std::cos(degToRad(45.0f)); + CHECK(nearlyEqual(rotateVector(mid.rotation, Vector3::xAxis()), Vector3{ c, c, 0.0f }, 1.0e-4f)); } } \ No newline at end of file diff --git a/Engine/cpp/Runtime/Core/Math/Matrix4.cpp b/Engine/cpp/Runtime/Core/Math/Matrix4.cpp new file mode 100644 index 00000000..724ebb5b --- /dev/null +++ b/Engine/cpp/Runtime/Core/Math/Matrix4.cpp @@ -0,0 +1,72 @@ +// Matrix4 implementation: the 4x4 inverse routines. Kept out of the module interface +// so Matrix4.cppm carries no // - only these bodies need them. +module; + +#include +#include +#include +#include + +module core.math.matrix4; + +import core.stdtypes; +import core.math.constants; + +namespace draco::math +{ + std::optional tryInverse(const Matrix4& mat) noexcept + { + const f32* m = &mat.m[0][0]; + f32 inv[16]; + + inv[0] = m[5]*m[10]*m[15] - m[5]*m[11]*m[14] - m[9]*m[6]*m[15] + m[9]*m[7]*m[14] + m[13]*m[6]*m[11] - m[13]*m[7]*m[10]; + inv[4] = -m[4]*m[10]*m[15] + m[4]*m[11]*m[14] + m[8]*m[6]*m[15] - m[8]*m[7]*m[14] - m[12]*m[6]*m[11] + m[12]*m[7]*m[10]; + inv[8] = m[4]*m[9]*m[15] - m[4]*m[11]*m[13] - m[8]*m[5]*m[15] + m[8]*m[7]*m[13] + m[12]*m[5]*m[11] - m[12]*m[7]*m[9]; + inv[12] = -m[4]*m[9]*m[14] + m[4]*m[10]*m[13] + m[8]*m[5]*m[14] - m[8]*m[6]*m[13] - m[12]*m[5]*m[10] + m[12]*m[6]*m[9]; + inv[1] = -m[1]*m[10]*m[15] + m[1]*m[11]*m[14] + m[9]*m[2]*m[15] - m[9]*m[3]*m[14] - m[13]*m[2]*m[11] + m[13]*m[3]*m[10]; + inv[5] = m[0]*m[10]*m[15] - m[0]*m[11]*m[14] - m[8]*m[2]*m[15] + m[8]*m[3]*m[14] + m[12]*m[2]*m[11] - m[12]*m[3]*m[10]; + inv[9] = -m[0]*m[9]*m[15] + m[0]*m[11]*m[13] + m[8]*m[1]*m[15] - m[8]*m[3]*m[13] - m[12]*m[1]*m[11] + m[12]*m[3]*m[9]; + inv[13] = m[0]*m[9]*m[14] - m[0]*m[10]*m[13] - m[8]*m[1]*m[14] + m[8]*m[2]*m[13] + m[12]*m[1]*m[10] - m[12]*m[2]*m[9]; + inv[2] = m[1]*m[6]*m[15] - m[1]*m[7]*m[14] - m[5]*m[2]*m[15] + m[5]*m[3]*m[14] + m[13]*m[2]*m[7] - m[13]*m[3]*m[6]; + inv[6] = -m[0]*m[6]*m[15] + m[0]*m[7]*m[14] + m[4]*m[2]*m[15] - m[4]*m[3]*m[14] - m[12]*m[2]*m[7] + m[12]*m[3]*m[6]; + inv[10] = m[0]*m[5]*m[15] - m[0]*m[7]*m[13] - m[4]*m[1]*m[15] + m[4]*m[3]*m[13] + m[12]*m[1]*m[7] - m[12]*m[3]*m[5]; + inv[14] = -m[0]*m[5]*m[14] + m[0]*m[6]*m[13] + m[4]*m[1]*m[14] - m[4]*m[2]*m[13] - m[12]*m[1]*m[6] + m[12]*m[2]*m[5]; + inv[3] = -m[1]*m[6]*m[11] + m[1]*m[7]*m[10] + m[5]*m[2]*m[11] - m[5]*m[3]*m[10] - m[9]*m[2]*m[7] + m[9]*m[3]*m[6]; + inv[7] = m[0]*m[6]*m[11] - m[0]*m[7]*m[10] - m[4]*m[2]*m[11] + m[4]*m[3]*m[10] + m[8]*m[2]*m[7] - m[8]*m[3]*m[6]; + inv[11] = -m[0]*m[5]*m[11] + m[0]*m[7]*m[9] + m[4]*m[1]*m[11] - m[4]*m[3]*m[9] - m[8]*m[1]*m[7] + m[8]*m[3]*m[5]; + inv[15] = m[0]*m[5]*m[10] - m[0]*m[6]*m[9] - m[4]*m[1]*m[10] + m[4]*m[2]*m[9] + m[8]*m[1]*m[6] - m[8]*m[2]*m[5]; + + const f32 det = m[0]*inv[0] + m[1]*inv[4] + m[2]*inv[8] + m[3]*inv[12]; + + // Scale-aware singularity: by Hadamard's inequality |det| <= product of the + // row norms, so reject relative to that product, not an absolute epsilon. + const auto rowNorm = [&](usize r) { + const f32* row = m + r * 4; + return std::sqrt(row[0]*row[0] + row[1]*row[1] + row[2]*row[2] + row[3]*row[3]); + }; + const f32 normProduct = rowNorm(0) * rowNorm(1) * rowNorm(2) * rowNorm(3); + if (std::abs(det) <= CMP_EPSILON * normProduct) + { + return std::nullopt; + } + + const f32 invDet = 1.0f / det; + Matrix4 result{}; + f32* out = &result.m[0][0]; + for (usize i = 0; i < 16; ++i) + { + out[i] = inv[i] * invDet; + } + return result; + } + + Matrix4 inverse(const Matrix4& mat) noexcept + { + if (const std::optional inv = tryInverse(mat)) { return *inv; } + assert(false && "Matrix4 inverse(): singular matrix; use tryInverse() to handle it"); + Matrix4 result{}; + f32* out = &result.m[0][0]; + for (usize i = 0; i < 16; ++i) { out[i] = std::numeric_limits::quiet_NaN(); } + return result; + } +} diff --git a/Engine/cpp/Runtime/Core/Math/Matrix4.cppm b/Engine/cpp/Runtime/Core/Math/Matrix4.cppm new file mode 100644 index 00000000..5aa0e630 --- /dev/null +++ b/Engine/cpp/Runtime/Core/Math/Matrix4.cppm @@ -0,0 +1,242 @@ +module; + +#include // tryInverse() return type; the rest of the impl lives in Matrix4.cpp + +export module core.math.matrix4; + +import core.stdtypes; +import core.math.types; +import core.math.functions; +import core.math.constants; + +// Matrix4: 4x4 row-major matrix - transforms, projections (perspective/ortho/ +// lookAt RH), multiply, transpose/determinant/inverse, point/direction xform. +// +// Conventions: row-major storage m[row][col]; row vectors (v' = v * M); +// composition left-to-right; right-handed projections, NDC depth [0,1]; +// translation in the last row. + +export namespace draco::math +{ + // ======================================================================= + // Matrix4 - 4x4, row-major, row-vector convention. + // ======================================================================= + struct Matrix4 + { + f32 m[4][4]; + + // Raw row-major float pointer (16 contiguous floats), e.g. for GPU upload. + [[nodiscard]] const f32* data() const noexcept { return &m[0][0]; } + [[nodiscard]] f32* data() noexcept { return &m[0][0]; } + + [[nodiscard]] constexpr f32 operator[](usize row, usize col) const noexcept + { + return m[row][col]; + } + [[nodiscard]] constexpr f32& operator[](usize row, usize col) noexcept + { + return m[row][col]; + } + + [[nodiscard]] static constexpr Matrix4 identity() noexcept + { + return Matrix4{ { { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } } }; + } + + [[nodiscard]] static constexpr Matrix4 translation(Vector3 t) noexcept + { + return Matrix4{ { { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { t.x, t.y, t.z, 1.0f } } }; + } + + [[nodiscard]] static constexpr Matrix4 scale(Vector3 s) noexcept + { + return Matrix4{ { { s.x, 0.0f, 0.0f, 0.0f }, + { 0.0f, s.y, 0.0f, 0.0f }, + { 0.0f, 0.0f, s.z, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } } }; + } + + [[nodiscard]] static Matrix4 rotationX(f32 radians) noexcept + { + const f32 c = cos(radians); + const f32 s = sin(radians); + return Matrix4{ { { 1.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, c, s, 0.0f }, + { 0.0f, -s, c, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } } }; + } + + [[nodiscard]] static Matrix4 rotationY(f32 radians) noexcept + { + const f32 c = cos(radians); + const f32 s = sin(radians); + return Matrix4{ { { c, 0.0f, -s, 0.0f }, + { 0.0f, 1.0f, 0.0f, 0.0f }, + { s, 0.0f, c, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } } }; + } + + [[nodiscard]] static Matrix4 rotationZ(f32 radians) noexcept + { + const f32 c = cos(radians); + const f32 s = sin(radians); + return Matrix4{ { { c, s, 0.0f, 0.0f }, + { -s, c, 0.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } } }; + } + + // Right-handed perspective, NDC z in [0, 1]. + [[nodiscard]] static Matrix4 perspectiveFovRH(f32 fovYRadians, f32 aspect, f32 zNear, f32 zFar) noexcept + { + const f32 yScale = 1.0f / tan(fovYRadians * 0.5f); + const f32 xScale = yScale / aspect; + const f32 zRange = zFar / (zNear - zFar); + return Matrix4{ { { xScale, 0.0f, 0.0f, 0.0f }, + { 0.0f, yScale, 0.0f, 0.0f }, + { 0.0f, 0.0f, zRange, -1.0f }, + { 0.0f, 0.0f, zNear * zRange, 0.0f } } }; + } + + [[nodiscard]] static Matrix4 orthographicRH(f32 width, f32 height, f32 zNear, f32 zFar) noexcept + { + const f32 zRange = 1.0f / (zNear - zFar); + return Matrix4{ { { 2.0f / width, 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f / height, 0.0f, 0.0f }, + { 0.0f, 0.0f, zRange, 0.0f }, + { 0.0f, 0.0f, zNear * zRange, 1.0f } } }; + } + + [[nodiscard]] static Matrix4 lookAtRH(Vector3 eye, Vector3 target, Vector3 up) noexcept + { + const Vector3 zAxis = normalize(eye - target); // camera looks down -z + const Vector3 xAxis = normalize(cross(up, zAxis)); + const Vector3 yAxis = cross(zAxis, xAxis); + return Matrix4{ { { xAxis.x, yAxis.x, zAxis.x, 0.0f }, + { xAxis.y, yAxis.y, zAxis.y, 0.0f }, + { xAxis.z, yAxis.z, zAxis.z, 0.0f }, + { -dot(xAxis, eye), -dot(yAxis, eye), -dot(zAxis, eye), 1.0f } } }; + } + }; + + [[nodiscard]] constexpr Matrix4 operator*(const Matrix4& a, const Matrix4& b) noexcept + { + Matrix4 result{}; + for (usize row = 0; row < 4; ++row) + { + for (usize col = 0; col < 4; ++col) + { + f32 sum = 0.0f; + for (usize k = 0; k < 4; ++k) + { + sum += a.m[row][k] * b.m[k][col]; + } + result.m[row][col] = sum; + } + } + return result; + } + + // Row-vector transform: v' = v * M. + [[nodiscard]] constexpr Vector4 operator*(Vector4 v, const Matrix4& m) noexcept + { + return { v.x * m.m[0][0] + v.y * m.m[1][0] + v.z * m.m[2][0] + v.w * m.m[3][0], + v.x * m.m[0][1] + v.y * m.m[1][1] + v.z * m.m[2][1] + v.w * m.m[3][1], + v.x * m.m[0][2] + v.y * m.m[1][2] + v.z * m.m[2][2] + v.w * m.m[3][2], + v.x * m.m[0][3] + v.y * m.m[1][3] + v.z * m.m[2][3] + v.w * m.m[3][3] }; + } + + [[nodiscard]] constexpr Matrix4 transpose(const Matrix4& a) noexcept + { + Matrix4 result{}; + for (usize row = 0; row < 4; ++row) + { + for (usize col = 0; col < 4; ++col) + { + result.m[row][col] = a.m[col][row]; + } + } + return result; + } + + // Transforms a position (implicit w = 1, translation applied). + [[nodiscard]] constexpr Vector3 transformPoint(Vector3 p, const Matrix4& m) noexcept + { + return { p.x * m.m[0][0] + p.y * m.m[1][0] + p.z * m.m[2][0] + m.m[3][0], + p.x * m.m[0][1] + p.y * m.m[1][1] + p.z * m.m[2][1] + m.m[3][1], + p.x * m.m[0][2] + p.y * m.m[1][2] + p.z * m.m[2][2] + m.m[3][2] }; + } + + // Transforms a direction (implicit w = 0, translation ignored). + [[nodiscard]] constexpr Vector3 transformDirection(Vector3 d, const Matrix4& m) noexcept + { + return { d.x * m.m[0][0] + d.y * m.m[1][0] + d.z * m.m[2][0], + d.x * m.m[0][1] + d.y * m.m[1][1] + d.z * m.m[2][1], + d.x * m.m[0][2] + d.y * m.m[1][2] + d.z * m.m[2][2] }; + } + + // Transforms a 2D position (implicit z = 0, w = 1, translation applied; + // result projected back to 2D). For 2D affine transforms stored in a Matrix4. + [[nodiscard]] constexpr Vector2 transformPoint2D(Vector2 p, const Matrix4& m) noexcept + { + return { p.x * m.m[0][0] + p.y * m.m[1][0] + m.m[3][0], + p.x * m.m[0][1] + p.y * m.m[1][1] + m.m[3][1] }; + } + + // Exact element-wise equality (e.g. for an identity fast-path). + [[nodiscard]] constexpr bool operator==(const Matrix4& a, const Matrix4& b) noexcept + { + for (usize row = 0; row < 4; ++row) + for (usize col = 0; col < 4; ++col) + if (a.m[row][col] != b.m[row][col]) { return false; } + return true; + } + + [[nodiscard]] constexpr bool nearlyEqual(const Matrix4& a, const Matrix4& b, f32 epsilon = CMP_EPSILON) noexcept + { + for (usize row = 0; row < 4; ++row) + { + for (usize col = 0; col < 4; ++col) + { + if (!nearlyEqual(a.m[row][col], b.m[row][col], epsilon)) { return false; } + } + } + return true; + } + + [[nodiscard]] inline f32 determinant(const Matrix4& mat) noexcept + { + const f32* m = &mat.m[0][0]; + const f32 s0 = m[0] * m[5] - m[1] * m[4]; + const f32 s1 = m[0] * m[6] - m[2] * m[4]; + const f32 s2 = m[0] * m[7] - m[3] * m[4]; + const f32 s3 = m[1] * m[6] - m[2] * m[5]; + const f32 s4 = m[1] * m[7] - m[3] * m[5]; + const f32 s5 = m[2] * m[7] - m[3] * m[6]; + const f32 c5 = m[10] * m[15] - m[11] * m[14]; + const f32 c4 = m[9] * m[15] - m[11] * m[13]; + const f32 c3 = m[9] * m[14] - m[10] * m[13]; + const f32 c2 = m[8] * m[15] - m[11] * m[12]; + const f32 c1 = m[8] * m[14] - m[10] * m[12]; + const f32 c0 = m[8] * m[13] - m[9] * m[12]; + return s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0; + } + + // Full 4x4 inverse (adjugate / determinant). Returns nullopt for a singular + // matrix, judged with a scale-aware (Hadamard row-norm) threshold so a small- + // but-invertible matrix (e.g. a 1e-3 uniform scale) is not wrongly rejected. + // (Implementation in Matrix4.cpp.) + [[nodiscard]] std::optional tryInverse(const Matrix4& mat) noexcept; + + // Convenience inverse for matrices known to be invertible. On a singular matrix it + // does NOT silently return identity (which would hide the failure) - it asserts in + // debug and returns a not-a-number matrix so the error propagates visibly. Prefer + // tryInverse() whenever a matrix may be singular. (Implementation in Matrix4.cpp.) + [[nodiscard]] Matrix4 inverse(const Matrix4& mat) noexcept; +} diff --git a/Engine/cpp/Runtime/Core/Math/Quaternion.cppm b/Engine/cpp/Runtime/Core/Math/Quaternion.cppm new file mode 100644 index 00000000..07e86bb4 --- /dev/null +++ b/Engine/cpp/Runtime/Core/Math/Quaternion.cppm @@ -0,0 +1,135 @@ +export module core.math.quaternion; + +import core.stdtypes; +import core.math.types; +import core.math.matrix4; +import core.math.functions; +import core.math.constants; + +// Quaternion: unit quaternion rotation - fromAxisAngle, Hamilton product, +// conjugate/dot/normalize/slerp, rotateVector, and rotationMatrix (-> Matrix4). +// Row-vector convention (v' = v * M), matching Matrix4. + +export namespace draco::math +{ + // ======================================================================= + // Quaternion - unit quaternion rotation (x, y, z, w). + // ======================================================================= + struct Quaternion + { + f32 x = 0.0f; + f32 y = 0.0f; + f32 z = 0.0f; + f32 w = 1.0f; + + constexpr Quaternion() noexcept = default; + constexpr Quaternion(f32 inX, f32 inY, f32 inZ, f32 inW) noexcept : x(inX), y(inY), z(inZ), w(inW) {} + + [[nodiscard]] static Quaternion fromAxisAngle(Vector3 axis, f32 radians) noexcept + { + // A zero-length axis is not a rotation; normalize() would divide by zero, + // so fall back to identity rather than produce a NaN/degenerate quaternion. + if (nearlyZero(dot(axis, axis))) + { + return Quaternion{ 0.0f, 0.0f, 0.0f, 1.0f }; + } + const f32 half = radians * 0.5f; + const f32 s = sin(half); + const Vector3 a = normalize(axis); + return Quaternion{ a.x * s, a.y * s, a.z * s, cos(half) }; + } + + static const Quaternion identity; + }; + + inline constexpr Quaternion Quaternion::identity{ 0.0f, 0.0f, 0.0f, 1.0f }; + + // Hamilton product: applies `b` then `a` to a vector. + [[nodiscard]] constexpr Quaternion operator*(Quaternion a, Quaternion b) noexcept + { + return { a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y, + a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x, + a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w, + a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z }; + } + + [[nodiscard]] constexpr Quaternion conjugate(Quaternion q) noexcept { return { -q.x, -q.y, -q.z, q.w }; } + [[nodiscard]] constexpr f32 dot(Quaternion a, Quaternion b) noexcept { return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; } + + // General inverse (= conjugate / |q|^2). For unit quaternions this equals the conjugate. + [[nodiscard]] inline Quaternion inverse(Quaternion q) noexcept + { + const f32 lengthSq = dot(q, q); + if (lengthSq <= CMP_EPSILON * CMP_EPSILON) { return Quaternion::identity; } + const f32 inv = 1.0f / lengthSq; + return { -q.x * inv, -q.y * inv, -q.z * inv, q.w * inv }; + } + + [[nodiscard]] inline Quaternion normalize(Quaternion q) noexcept + { + const f32 lengthSq = dot(q, q); + if (lengthSq <= CMP_EPSILON * CMP_EPSILON) { return Quaternion::identity; } + const f32 inv = 1.0f / sqrt(lengthSq); + return { q.x * inv, q.y * inv, q.z * inv, q.w * inv }; + } + + [[nodiscard]] constexpr Vector3 rotateVector(Quaternion q, Vector3 v) noexcept + { + const Vector3 u{ q.x, q.y, q.z }; + const f32 s = q.w; + return u * (2.0f * dot(u, v)) + v * (s * s - dot(u, u)) + cross(u, v) * (2.0f * s); + } + + [[nodiscard]] inline bool nearlyEqual(Quaternion a, Quaternion b, f32 epsilon = CMP_EPSILON) noexcept + { + const bool same = nearlyEqual(a.x, b.x, epsilon) && nearlyEqual(a.y, b.y, epsilon) + && nearlyEqual(a.z, b.z, epsilon) && nearlyEqual(a.w, b.w, epsilon); + // q and -q represent the same rotation (and slerp/normalize may flip the sign), + // so accept the antipodal quaternion as equal too. + const bool antipodal = nearlyEqual(a.x, -b.x, epsilon) && nearlyEqual(a.y, -b.y, epsilon) + && nearlyEqual(a.z, -b.z, epsilon) && nearlyEqual(a.w, -b.w, epsilon); + return same || antipodal; + } + + // Spherical linear interpolation along the shortest arc; result is unit. + [[nodiscard]] inline Quaternion slerp(Quaternion a, Quaternion b, f32 t) noexcept + { + f32 cosTheta = dot(a, b); + if (cosTheta < 0.0f) // shortest path + { + b = Quaternion{ -b.x, -b.y, -b.z, -b.w }; + cosTheta = -cosTheta; + } + + if (cosTheta > 0.9995f) // nearly parallel - lerp + normalize + { + return normalize(Quaternion{ a.x + (b.x - a.x) * t, + a.y + (b.y - a.y) * t, + a.z + (b.z - a.z) * t, + a.w + (b.w - a.w) * t }); + } + + const f32 theta0 = acos(cosTheta); + const f32 theta = theta0 * t; + const f32 sinTheta = sin(theta); + const f32 sinTheta0 = sin(theta0); + const f32 s1 = sinTheta / sinTheta0; + const f32 s0 = cos(theta) - cosTheta * s1; + return Quaternion{ a.x * s0 + b.x * s1, + a.y * s0 + b.y * s1, + a.z * s0 + b.z * s1, + a.w * s0 + b.w * s1 }; + } + + // Rotation matrix for a unit quaternion (row-vector convention). + [[nodiscard]] constexpr Matrix4 rotationMatrix(Quaternion q) noexcept + { + const f32 xx = q.x * q.x, yy = q.y * q.y, zz = q.z * q.z; + const f32 xy = q.x * q.y, xz = q.x * q.z, yz = q.y * q.z; + const f32 wx = q.w * q.x, wy = q.w * q.y, wz = q.w * q.z; + return Matrix4{ { { 1.0f - 2.0f * (yy + zz), 2.0f * (xy + wz), 2.0f * (xz - wy), 0.0f }, + { 2.0f * (xy - wz), 1.0f - 2.0f * (xx + zz), 2.0f * (yz + wx), 0.0f }, + { 2.0f * (xz + wy), 2.0f * (yz - wx), 1.0f - 2.0f * (xx + yy), 0.0f }, + { 0.0f, 0.0f, 0.0f, 1.0f } } }; + } +} diff --git a/Engine/cpp/Runtime/Core/Math/Transform.cpp b/Engine/cpp/Runtime/Core/Math/Transform.cpp deleted file mode 100644 index 5e1029b4..00000000 --- a/Engine/cpp/Runtime/Core/Math/Transform.cpp +++ /dev/null @@ -1,38 +0,0 @@ -module; - -#include - -#include - -module core.math.transform; - -import core.stdtypes; - -namespace draco::math -{ - void Transform::toMatrix(f32 out[16]) const - { - f32 translation[16]; - f32 rx[16]; - f32 ry[16]; - f32 rz[16]; - f32 scale[16]; - f32 temp[16]; - - bx::mtxIdentity(out); - - bx::mtxScale(scale, scale[0], scale[1], scale[2]); - - bx::mtxRotateX(rx, rotation[0]); - bx::mtxRotateY(ry, rotation[1]); - bx::mtxRotateZ(rz, rotation[2]); - - bx::mtxTranslate(translation, position[0], position[1], position[2]); - - // scale * rotation * translation - bx::mtxMul(temp, scale, rx); - bx::mtxMul(temp, temp, ry); - bx::mtxMul(temp, temp, rz); - bx::mtxMul(out, temp, translation); - } -} diff --git a/Engine/cpp/Runtime/Core/Math/Transform.cppm b/Engine/cpp/Runtime/Core/Math/Transform.cppm index 176cbe7c..75843110 100644 --- a/Engine/cpp/Runtime/Core/Math/Transform.cppm +++ b/Engine/cpp/Runtime/Core/Math/Transform.cppm @@ -1,70 +1,44 @@ -module; - export module core.math.transform; -import core.math.types; + import core.stdtypes; +import core.math.types; +import core.math.matrix4; +import core.math.quaternion; export namespace draco::math { + // ======================================================================= + // Transform - position / rotation / scale, composed as S * R * T. + // ======================================================================= struct Transform { - f32 position[3] = { 0.0f, 0.0f, 0.0f }; - f32 rotation[3] = { 0.0f, 0.0f, 0.0f }; // Euler (radians) - f32 scale[3] = { 1.0f, 1.0f, 1.0f }; - - constexpr void setPosition(f32 x, f32 y, f32 z) noexcept { - position[0] = x; - position[1] = y; - position[2] = z; - } - - constexpr void setPosition(const Vector3& v) noexcept { - position[0] = v.x; - position[1] = v.y; - position[2] = v.z; - } - - [[nodiscard]] constexpr Vector3 getPosition() const noexcept { - return Vector3{position[0], position[1], position[2]}; - } - - constexpr void setRotation(const f32 x, const f32 y, const f32 z) noexcept - { - rotation[0] = x; - rotation[1] = y; - rotation[2] = z; - } + // Direct constexpr construction (not Vector3::zero / Quaternion::identity, which + // are static-const constants and so cannot seed a constexpr default member init). + Vector3 position{ 0.0f, 0.0f, 0.0f }; + Quaternion rotation{ 0.0f, 0.0f, 0.0f, 1.0f }; + Vector3 scale{ 1.0f, 1.0f, 1.0f }; - constexpr void setRotation(const Vector3& v) noexcept + [[nodiscard]] Matrix4 toMatrix() const noexcept { - rotation[0] = v.x; - rotation[1] = v.y; - rotation[2] = v.z; - } - - [[nodiscard]] constexpr Vector3 getRotation() const noexcept { - return Vector3{rotation[0], rotation[1], rotation[2]}; + Matrix4 result = Matrix4::scale(scale) * rotationMatrix(rotation); + result.m[3][0] = position.x; + result.m[3][1] = position.y; + result.m[3][2] = position.z; + return result; } - constexpr void setScale(const f32 x, const f32 y, const f32 z) noexcept + // Component-wise interpolation: position/scale lerp, rotation slerp. + [[nodiscard]] static Transform lerp(const Transform& a, const Transform& b, f32 t) noexcept { - scale[0] = x; - scale[1] = y; - scale[2] = z; + return Transform{ + draco::math::lerp(a.position, b.position, t), + slerp(a.rotation, b.rotation, t), + draco::math::lerp(a.scale, b.scale, t), + }; } - - constexpr void setScale(const Vector3& v) noexcept - { - scale[0] = v.x; - scale[1] = v.y; - scale[2] = v.z; - } - - [[nodiscard]] constexpr Vector3 getScale() const noexcept { - return Vector3{scale[0], scale[1], scale[2]}; - } - - // Compute column-major matrix from transform - void toMatrix(f32 out[16]) const; }; + + // Identity transform (position 0, rotation identity, scale 1) - the + // default-constructed value. + inline constexpr Transform identityTransform{}; } diff --git a/Engine/cpp/Runtime/Core/Math/TypesCommon.cppm b/Engine/cpp/Runtime/Core/Math/TypesCommon.cppm index c9df0996..773190a6 100644 --- a/Engine/cpp/Runtime/Core/Math/TypesCommon.cppm +++ b/Engine/cpp/Runtime/Core/Math/TypesCommon.cppm @@ -63,6 +63,10 @@ export namespace draco::math { [[nodiscard]] static Vector3 spherical(f32 azimuth, f32 inclination, f32 radius = 1.0f) noexcept; [[nodiscard]] static Vector3 cylindrical(f32 angle, f32 radius = 1.0f, f32 height = 0.0f) noexcept; + // constant vectors (defined in Vector3.cppm) + static const Vector3 zero; + static const Vector3 one; + // element access [[nodiscard]] constexpr f32& operator[](i32 i) noexcept; [[nodiscard]] constexpr const f32& operator[](i32 i) const noexcept; diff --git a/Engine/cpp/Runtime/Core/Math/Vector2.cppm b/Engine/cpp/Runtime/Core/Math/Vector2.cppm index cc1ccaa3..795625a9 100644 --- a/Engine/cpp/Runtime/Core/Math/Vector2.cppm +++ b/Engine/cpp/Runtime/Core/Math/Vector2.cppm @@ -413,9 +413,9 @@ export namespace draco::math { }; } - // Returns true if the vectors are approximately equal - [[nodiscard]] constexpr bool approx_eq(const Vector2& a, const Vector2& b) noexcept { - return distance_sq(a, b) < CMP_EPSILON2; + // Component-wise approximate equality against an epsilon. + [[nodiscard]] constexpr bool nearlyEqual(const Vector2& a, const Vector2& b, f32 epsilon = CMP_EPSILON) noexcept { + return nearlyEqual(a.x, b.x, epsilon) && nearlyEqual(a.y, b.y, epsilon); } } diff --git a/Engine/cpp/Runtime/Core/Math/Vector3.cppm b/Engine/cpp/Runtime/Core/Math/Vector3.cppm index b7bc2cef..9b5a25e2 100644 --- a/Engine/cpp/Runtime/Core/Math/Vector3.cppm +++ b/Engine/cpp/Runtime/Core/Math/Vector3.cppm @@ -441,15 +441,19 @@ export namespace draco::math { }; } - // Returns true if the vectors are approximately equal - [[nodiscard]] constexpr bool approx_eq(const Vector3& a, const Vector3& b) noexcept { - return distance_sq(a, b) < CMP_EPSILON2; + // Component-wise approximate equality against an epsilon. + [[nodiscard]] constexpr bool nearlyEqual(const Vector3& a, const Vector3& b, f32 epsilon = CMP_EPSILON) noexcept { + return nearlyEqual(a.x, b.x, epsilon) && nearlyEqual(a.y, b.y, epsilon) && nearlyEqual(a.z, b.z, epsilon); } // Returns cross product [[nodiscard]] constexpr Vector3 cross(const Vector3& a, const Vector3& b) noexcept { return { a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x }; } + + // constant vectors + inline constexpr Vector3 Vector3::zero{ 0.0f, 0.0f, 0.0f }; + inline constexpr Vector3 Vector3::one{ 1.0f, 1.0f, 1.0f }; } export namespace std { diff --git a/Engine/cpp/Runtime/Core/Math/Vector4.cppm b/Engine/cpp/Runtime/Core/Math/Vector4.cppm index c4b1fe41..9c55fea7 100644 --- a/Engine/cpp/Runtime/Core/Math/Vector4.cppm +++ b/Engine/cpp/Runtime/Core/Math/Vector4.cppm @@ -493,9 +493,10 @@ export namespace draco::math { }; } - // Returns true if the vectors are approximately equal - [[nodiscard]] constexpr bool approx_eq(const Vector4& a, const Vector4& b) noexcept { - return distance_sq(a, b) < CMP_EPSILON2; + // Component-wise approximate equality against an epsilon. + [[nodiscard]] constexpr bool nearlyEqual(const Vector4& a, const Vector4& b, f32 epsilon = CMP_EPSILON) noexcept { + return nearlyEqual(a.x, b.x, epsilon) && nearlyEqual(a.y, b.y, epsilon) + && nearlyEqual(a.z, b.z, epsilon) && nearlyEqual(a.w, b.w, epsilon); } } // namespace draco::math diff --git a/Engine/cpp/Runtime/Draconic.cppm b/Engine/cpp/Runtime/Draconic.cppm index 26f06e66..e246c36e 100644 --- a/Engine/cpp/Runtime/Draconic.cppm +++ b/Engine/cpp/Runtime/Draconic.cppm @@ -1,6 +1,3 @@ export module draconic; export import core; -export import shell; -export import scene; -export import rendering; diff --git a/Engine/cpp/Runtime/Rendering/CMakeLists.txt b/Engine/cpp/Runtime/Rendering/CMakeLists.txt deleted file mode 100644 index c26aa4a5..00000000 --- a/Engine/cpp/Runtime/Rendering/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -add_modules_library(RHI PIC) -add_modules_library(RenderGraph) -add_modules_library(Renderer PIC) -add_modules_library(Mesh) -add_modules_library(Material) -add_modules_library(QuadRenderer) - -target_link_libraries(RHI PUBLIC Core bgfx bx) -target_link_libraries(RenderGraph PUBLIC RHI bx) -target_link_libraries(Mesh PUBLIC RHI Core) -target_link_libraries(Material PUBLIC RHI) -target_link_libraries(QuadRenderer PUBLIC RHI RenderGraph bgfx bx) -target_link_libraries(Renderer PUBLIC Core RHI RenderGraph Mesh QuadRenderer Material bgfx bx) \ No newline at end of file diff --git a/Engine/cpp/Runtime/Rendering/Graphics/Graphics.cppm b/Engine/cpp/Runtime/Rendering/Graphics/Graphics.cppm new file mode 100644 index 00000000..4f5c6bad --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Graphics/Graphics.cppm @@ -0,0 +1,366 @@ +// The `graphics` module. +// +// The RHI render host, promoted out of the per-sample bring-up code so samples, +// the UI, and the renderer share one tested path. Two pieces: +// +// GraphicsDevice — the SHARED GPU: backend (validation-wrapped) + adapter + +// logical device + graphics queue, plus the CPU frame-in-flight ring index. +// Created once for the whole app. Hands out RenderWindows. +// +// RenderWindow — a single window's PRESENTATION target: surface + swapchain + +// a per-window ring of command pools/fences. Created/destroyed at runtime +// (the basis for detachable UI windows). The main window is just the first. +// +// A FrameContext is the per-window, per-frame hand-off the host gives a consumer: +// an acquired backbuffer + a host-created encoder. The host owns acquire / fence +// sync / submit / present and the backbuffer state transitions; the consumer +// records content (or calls beginBackbufferPass for the common clear+pass case). +// +// Multi-window is uniform: there is no "main window" special case here — an app +// renders a list of RenderWindows, each independent, sharing one GraphicsDevice. + +module; + +#include +#include +#include +#include +#include + +export module graphics; + +import core.stdtypes; +import core.status; +import rhi; +import shell; +// Backend factories live in sibling modules so this core host imports only the +// base RHI (keeps it GPU-backend-agnostic and avoids importing heavy backend +// modules into this interface — which GCC's module reader chokes on): +// graphics.null — createNullGraphicsDevice (headless) +// graphics.gpu — createGraphicsDevice (Vulkan/DX12) + +using namespace draco; +using namespace draco::shell; // IShell + input/window types +namespace rhi = draco::rhi; + +export namespace draco::graphics +{ + // Null is a real headless option (CI / servers / tests) — no GPU required. + enum class BackendType : u8 { Vulkan, DX12, Null }; + + struct GraphicsDeviceDesc + { + BackendType backend = BackendType::Vulkan; + // Validation (our RHI-layer wrapper AND the backend's own layers, e.g. Vulkan validation) + // defaults ON for dev builds and OFF for optimized/shipping builds (NDEBUG — set for + // Release/RelWithDebInfo/MinSizeRel). A profiling run (RelWithDebInfo) therefore measures + // the real cost, not the validation overhead. Override explicitly to force either way. +#if defined(NDEBUG) + bool enableValidation = false; +#else + bool enableValidation = true; +#endif + u32 framesInFlight = 2; // CPU-ahead ring depth + rhi::DeviceFeatures requiredFeatures = {}; + }; + + struct RenderWindowDesc + { + rhi::TextureFormat format = rhi::TextureFormat::BGRA8UnormSrgb; + rhi::PresentMode presentMode = rhi::PresentMode::Fifo; + u32 bufferCount = 2; // swapchain images + }; + + class GraphicsDevice; // forward (RenderWindow refs it; defined complete below) + class RenderWindow; // forward (FrameContext refs it) + + // Typed per-window payload. The UI layer stashes its {RootView, VGContext, + // VGRenderer} here without the host knowing the type — capability as*() idiom. + class IRenderWindowData + { + public: + virtual ~IRenderWindowData() = default; + }; + + // Per-window, per-frame hand-off. Returned by RenderWindow::beginFrame and + // passed to consumers (Subsystem::render / IApplicationModule::onRenderWindow). + struct FrameContext + { + bool valid = false; // false => skip (minimized/acquire failed) + RenderWindow* window = nullptr; // which window this frame targets + u32 frameIndex = 0; // device ring index (0..framesInFlight-1) + u32 width = 0; + u32 height = 0; + rhi::CommandEncoder* encoder = nullptr; // primary, host-created + rhi::CommandPool* pool = nullptr; // this frame's pool (extra encoders) + rhi::Texture* backbuffer = nullptr; + rhi::TextureView* backbufferView = nullptr; + + // Convenience for the common 2D/UI case: open a render pass that clears + // and targets the backbuffer (the Undefined->RenderTarget transition was + // already done by the host in beginFrame). A RenderGraph-driven renderer + // ignores this and records on `encoder` directly. + rhi::RenderPassEncoder* beginBackbufferPass(rhi::ClearColor clear) + { + rhi::ColorAttachment ca{}; + ca.view = backbufferView; + ca.loadOp = rhi::LoadOp::Clear; + ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = clear; + rhi::RenderPassDesc rpd{}; + rpd.colorAttachments.push_back(ca); + m_pass = (encoder != nullptr) ? encoder->beginRenderPass(rpd) : nullptr; + return m_pass; + } + void endBackbufferPass() { if (m_pass != nullptr) { m_pass->end(); m_pass = nullptr; } } + + // One-call clear of the backbuffer (open a clear pass, close it). For + // minimal apps that just want a visible, cleared window without touching + // RHI types. + void clear(f32 r, f32 g, f32 b, f32 a = 1.0f) + { + rhi::ClearColor c; c.r = r; c.g = g; c.b = b; c.a = a; + beginBackbufferPass(c); + endBackbufferPass(); + } + + private: + rhi::RenderPassEncoder* m_pass = nullptr; + }; + + // A window's presentation target. Owns its surface/swapchain and a per-frame + // ring of command pools + fences (per-window present sync). Destroyed via the + // GraphicsDevice that made it. + class RenderWindow + { + public: + // Built by GraphicsDevice::createRenderWindow; takes ownership of the RHI + // objects. Vectors are sized to framesInFlight. + RenderWindow(GraphicsDevice& device, IWindow& window, + rhi::Surface* surface, rhi::SwapChain* swapChain, + std::vector&& pools, std::vector&& fences) noexcept + : m_device(&device), m_window(&window), m_surface(surface), m_swapChain(swapChain), + m_pools(std::move(pools)), + m_fences(std::move(fences)), + m_width(window.width()), m_height(window.height()) + { + m_fenceValues.resize(m_fences.size(), 0ull); + } + + ~RenderWindow(); + + RenderWindow(const RenderWindow&) = delete; + RenderWindow& operator=(const RenderWindow&) = delete; + + [[nodiscard]] IWindow& window() noexcept { return *m_window; } + [[nodiscard]] rhi::SwapChain* swap() noexcept { return m_swapChain; } + + // Poll the window size; recreate the swapchain if it changed. Returns true + // when a resize happened. + bool syncSize(); + + // Acquire this window's backbuffer and open a host-created encoder from the + // current frame's pool (after the per-window fence guards reuse). The + // returned FrameContext is invalid when the window is minimized/zero-sized + // or acquisition fails — the caller skips rendering it this frame. + FrameContext beginFrame(); + + // Transition the backbuffer to Present, submit (signalling the per-window + // fence), and present. No-op for an invalid frame. + void endFrame(FrameContext& frame); + + [[nodiscard]] IRenderWindowData* data() noexcept { return m_data.get(); } + void setData(std::unique_ptr data) noexcept { m_data = std::move(data); } + + private: + GraphicsDevice* m_device; // borrowed + IWindow* m_window; // borrowed + rhi::Surface* m_surface; // owned + rhi::SwapChain* m_swapChain; // owned + std::vector m_pools; // owned, one per frame-in-flight + std::vector m_fences; // owned, one per frame-in-flight + std::vector m_fenceValues; + u32 m_width; + u32 m_height; + std::unique_ptr m_data; // optional typed payload + }; + + class GraphicsDevice + { + public: + // Build a GraphicsDevice from an already-created backend (takes + // ownership): enumerate adapters, create the logical device + graphics + // queue. Backend-agnostic — GPU backends (Vulkan/DX12) are built by the + // `graphics.gpu` factory, which then calls this. On failure the backend is + // destroyed. + static std::expected, ErrorCode> fromBackend( + rhi::Backend* backend, u32 framesInFlight, const rhi::DeviceFeatures& features = {}) + { + if (backend == nullptr) { return std::unexpected(ErrorCode::Unknown); } + + std::span adapters = backend->enumerateAdapters(); + if (adapters.size() == 0) { backend->destroy(); return std::unexpected(ErrorCode::Unknown); } + + rhi::DeviceDesc dd{}; + dd.graphicsQueueCount = 1; + dd.requiredFeatures = features; + rhi::Device* device = nullptr; + if (!adapters[0]->createDevice(dd, device).isOk()) { backend->destroy(); return std::unexpected(ErrorCode::Unknown); } + + rhi::Queue* queue = device->getQueue(rhi::QueueType::Graphics); + if (queue == nullptr) { device->destroy(); backend->destroy(); return std::unexpected(ErrorCode::Unknown); } + + const u32 frames = framesInFlight == 0 ? 1 : framesInFlight; + return std::make_unique(backend, device, queue, frames); + } + + GraphicsDevice(rhi::Backend* backend, rhi::Device* device, rhi::Queue* queue, u32 framesInFlight) noexcept + : m_backend(backend), m_device(device), m_queue(queue), m_framesInFlight(framesInFlight) {} + + ~GraphicsDevice() + { + if (m_device != nullptr) { m_device->waitIdle(); m_device->destroy(); m_device = nullptr; } + if (m_backend != nullptr) { m_backend->destroy(); m_backend = nullptr; } + } + + GraphicsDevice(const GraphicsDevice&) = delete; + GraphicsDevice& operator=(const GraphicsDevice&) = delete; + + // Create a presentation target (surface + swapchain + per-frame pools/ + // fences) for a window. The window must outlive the RenderWindow. + std::expected, ErrorCode> createRenderWindow(IWindow& window, const RenderWindowDesc& desc) + { + const NativeWindow nw = window.native(); + rhi::Surface* surface = nullptr; + if (!m_backend->createSurface(nw.window, nw.display, surface).isOk()) { return std::unexpected(ErrorCode::Unknown); } + + rhi::SwapChainDesc sd{}; + sd.width = window.width(); + sd.height = window.height(); + sd.format = desc.format; + sd.presentMode = desc.presentMode; + sd.bufferCount = desc.bufferCount; + sd.label = u8"renderwindow"; + rhi::SwapChain* swapChain = nullptr; + if (!m_device->createSwapChain(surface, sd, swapChain).isOk()) + { + m_device->destroySurface(surface); + return std::unexpected(ErrorCode::Unknown); + } + + std::vector pools; + std::vector fences; + for (u32 i = 0; i < m_framesInFlight; ++i) + { + rhi::CommandPool* pool = nullptr; + rhi::Fence* fence = nullptr; + if (!m_device->createCommandPool(rhi::QueueType::Graphics, pool).isOk() || + !m_device->createFence(0, fence).isOk()) + { + for (rhi::CommandPool* p : pools) { m_device->destroyCommandPool(p); } + for (rhi::Fence* f : fences) { m_device->destroyFence(f); } + if (pool != nullptr) { m_device->destroyCommandPool(pool); } + m_device->destroySwapChain(swapChain); + m_device->destroySurface(surface); + return std::unexpected(ErrorCode::Unknown); + } + pools.push_back(pool); + fences.push_back(fence); + } + + return std::make_unique(*this, window, surface, swapChain, + std::move(pools), std::move(fences)); + } + + [[nodiscard]] rhi::Device* raw() noexcept { return m_device; } + [[nodiscard]] rhi::Queue* gfxQueue() noexcept { return m_queue; } + [[nodiscard]] u32 framesInFlight() const noexcept { return m_framesInFlight; } + [[nodiscard]] u32 currentFrame() const noexcept { return m_currentFrame; } + + // Advance the CPU frame ring once per app frame (after all windows are + // rendered). Consumers key per-frame GPU resources on currentFrame(). + void advanceFrame() noexcept { m_currentFrame = (m_currentFrame + 1) % m_framesInFlight; } + + private: + rhi::Backend* m_backend; // owned + rhi::Device* m_device; // owned + rhi::Queue* m_queue; // borrowed from device + u32 m_framesInFlight; + u32 m_currentFrame = 0; + }; + + // ----- RenderWindow out-of-line defs (need GraphicsDevice complete) ----- + + inline RenderWindow::~RenderWindow() + { + rhi::Device* dev = m_device->raw(); + if (dev != nullptr) { dev->waitIdle(); } + for (rhi::CommandPool* p : m_pools) { if (p != nullptr) { dev->destroyCommandPool(p); } } + for (rhi::Fence* f : m_fences) { if (f != nullptr) { dev->destroyFence(f); } } + if (m_swapChain != nullptr) { dev->destroySwapChain(m_swapChain); m_swapChain = nullptr; } + if (m_surface != nullptr) { dev->destroySurface(m_surface); m_surface = nullptr; } + } + + inline bool RenderWindow::syncSize() + { + const u32 nw = m_window->width(); + const u32 nh = m_window->height(); + if (nw == 0 || nh == 0) { return false; } + if (nw == m_width && nh == m_height) { return false; } + m_width = nw; + m_height = nh; + m_device->raw()->waitIdle(); + m_swapChain->resize(nw, nh); + return true; + } + + inline FrameContext RenderWindow::beginFrame() + { + if (m_window->isMinimized() || m_window->width() == 0 || m_window->height() == 0) { return FrameContext{}; } + + const u32 fi = m_device->currentFrame(); + // Guard reuse of this slot's pool/backbuffer: wait the GPU's last + // submission against this window's fence at this ring slot. + if (m_fenceValues[fi] > 0) { m_fences[fi]->wait(m_fenceValues[fi], ~0ull); } + + if (!m_swapChain->acquireNextImage().isOk()) { return FrameContext{}; } + + m_pools[fi]->reset(); + rhi::CommandEncoder* enc = nullptr; + if (!m_pools[fi]->createEncoder(enc).isOk() || enc == nullptr) { return FrameContext{}; } + + // Host-managed backbuffer transition (Undefined -> RenderTarget). + enc->transitionTexture(m_swapChain->currentTexture(), + rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + + FrameContext f; + f.valid = true; + f.window = this; + f.frameIndex = fi; + f.width = m_window->width(); + f.height = m_window->height(); + f.encoder = enc; + f.pool = m_pools[fi]; + f.backbuffer = m_swapChain->currentTexture(); + f.backbufferView = m_swapChain->currentTextureView(); + return f; + } + + inline void RenderWindow::endFrame(FrameContext& frame) + { + if (!frame.valid) { return; } + const u32 fi = frame.frameIndex; + + frame.encoder->transitionTexture(m_swapChain->currentTexture(), + rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + rhi::CommandBuffer* cb = frame.encoder->finish(); + + ++m_fenceValues[fi]; + rhi::CommandBuffer* cbs[1] = { cb }; + m_device->gfxQueue()->submit(std::span(cbs, 1), m_fences[fi], m_fenceValues[fi]); + + m_swapChain->present(m_device->gfxQueue()); + m_pools[fi]->destroyEncoder(frame.encoder); + frame.encoder = nullptr; + } +} diff --git a/Engine/cpp/Runtime/Rendering/Graphics/GraphicsGpu.cppm b/Engine/cpp/Runtime/Rendering/Graphics/GraphicsGpu.cppm new file mode 100644 index 00000000..f7ea8d3c --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Graphics/GraphicsGpu.cppm @@ -0,0 +1,70 @@ +// The `graphics.gpu` module. +// +// The GPU-backend factory for GraphicsDevice: turns a GraphicsDeviceDesc into a +// live device on Vulkan or DX12 (validation-wrapped on request), then delegates +// the backend-agnostic bring-up to GraphicsDevice::fromBackend. This is the only +// Vulkan-coupled part of the render host, kept separate so the host types — and +// everything built on them (Application, the UI, the renderer) — stay GPU-backend +// agnostic and build headlessly. Null is delegated to createNullGraphicsDevice. + +module; + +#include +#include + +export module graphics.gpu; + +import core.stdtypes; +import core.status; +import rhi; +import rhi.vk; +#ifdef DRACO_HAS_DX12 +import rhi.dx12; +#endif +import rhi.validation; +import graphics; +import graphics.null; // Null backend delegation + +using namespace draco; +namespace rhi = draco::rhi; + +export namespace draco::graphics +{ + // Create a GraphicsDevice for the requested backend. Returns an error if the + // backend is unavailable (e.g. DX12 off this platform) or bring-up fails. + std::expected, ErrorCode> createGraphicsDevice(const GraphicsDeviceDesc& desc) + { + if (desc.backend == BackendType::Null) + { + return createNullGraphicsDevice(desc.framesInFlight); + } + + rhi::Backend* raw = nullptr; + switch (desc.backend) + { + case BackendType::Vulkan: + { + rhi::vk::VkBackendDesc bd{}; + bd.enableValidation = desc.enableValidation; + if (!rhi::vk::createBackend(bd, raw).isOk()) { return std::unexpected(ErrorCode::Unknown); } + break; + } + case BackendType::DX12: + { +#ifdef DRACO_HAS_DX12 + rhi::dx12::DxBackendDesc bd{}; + bd.enableValidation = desc.enableValidation; + if (!rhi::dx12::createDxBackend(bd, raw).isOk()) { return std::unexpected(ErrorCode::Unknown); } +#else + return std::unexpected(ErrorCode::Unknown); +#endif + break; + } + case BackendType::Null: + break; // handled above + } + + rhi::Backend* backend = desc.enableValidation ? rhi::validation::createValidatedBackend(raw) : raw; + return GraphicsDevice::fromBackend(backend, desc.framesInFlight, desc.requiredFeatures); + } +} diff --git a/Engine/cpp/Runtime/Rendering/Graphics/GraphicsNull.cppm b/Engine/cpp/Runtime/Rendering/Graphics/GraphicsNull.cppm new file mode 100644 index 00000000..6304ccfe --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Graphics/GraphicsNull.cppm @@ -0,0 +1,34 @@ +// The `graphics.null` module. +// +// Headless GraphicsDevice factory over the Null RHI backend (no GPU). For CI, +// servers, and tests, and the reference for what a real backend provides. Kept in +// its own module so the core host (graphics) imports only the base RHI — importing +// a backend module into the core interface trips GCC's module reader, and keeps +// the host GPU-backend-agnostic. + +module; + +#include +#include + +export module graphics.null; + +import core.stdtypes; +import core.status; +import rhi; +import rhi.null; +import graphics; + +using namespace draco; +namespace rhi = draco::rhi; + +export namespace draco::graphics +{ + // Create a headless GraphicsDevice backed by the Null RHI. No Vulkan required. + std::expected, ErrorCode> createNullGraphicsDevice(u32 framesInFlight = 2) + { + rhi::Backend* raw = nullptr; + if (!rhi::null::createNullBackend(raw).isOk()) { return std::unexpected(ErrorCode::Unknown); } + return GraphicsDevice::fromBackend(raw, framesInFlight); + } +} diff --git a/Engine/cpp/Runtime/Rendering/Graphics/GraphicsRhi.test.cpp b/Engine/cpp/Runtime/Rendering/Graphics/GraphicsRhi.test.cpp new file mode 100644 index 00000000..bf19016b --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Graphics/GraphicsRhi.test.cpp @@ -0,0 +1,131 @@ +// Headless tests for the RHI render host (GraphicsDevice + RenderWindow + +// FrameContext) over the Null RHI backend + null shell — no GPU required. +// Covers device bring-up, per-window frame begin/end, the frame-in-flight ring, +// multi-window rendering, resize, and the minimized-skip path. +#include + +import core; +import graphics; +import graphics.null; +import shell; +import shell.null; + +using namespace draco; +using namespace draco::graphics; +using namespace draco::shell; + +TEST_CASE("graphics: GraphicsDevice brings up over the null backend") +{ + auto created = createNullGraphicsDevice(); + REQUIRE(created.has_value()); + auto& gd = created.value(); + REQUIRE(gd.get() != nullptr); + CHECK(gd->raw() != nullptr); + CHECK(gd->gfxQueue() != nullptr); + CHECK(gd->framesInFlight() == 2u); + CHECK(gd->currentFrame() == 0u); +} + +TEST_CASE("graphics: a window renders, and the frame ring advances") +{ + NullShell shell; + auto created = createNullGraphicsDevice(2); + REQUIRE(created.has_value()); + auto& gd = created.value(); + + auto rwResult = gd->createRenderWindow(*shell.mainWindow(), RenderWindowDesc{}); + REQUIRE(rwResult.has_value()); + auto& rw = rwResult.value(); + + // Frame 0 + FrameContext f0 = rw->beginFrame(); + CHECK(f0.valid); + CHECK(f0.frameIndex == 0u); + CHECK(f0.window == rw.get()); + CHECK(f0.encoder != nullptr); + CHECK(f0.backbufferView != nullptr); + rw->endFrame(f0); + gd->advanceFrame(); + CHECK(gd->currentFrame() == 1u); + + // Frame 1 uses the next ring slot + FrameContext f1 = rw->beginFrame(); + CHECK(f1.valid); + CHECK(f1.frameIndex == 1u); + rw->endFrame(f1); + gd->advanceFrame(); + CHECK(gd->currentFrame() == 0u); // wraps with framesInFlight == 2 + + // Frame 2 wraps back to slot 0 and must wait the slot-0 fence cleanly. + FrameContext f2 = rw->beginFrame(); + CHECK(f2.valid); + CHECK(f2.frameIndex == 0u); + rw->endFrame(f2); +} + +TEST_CASE("graphics: two windows render independently in one app frame") +{ + NullShell shell; + IWindowManager* wm = shell.windowManager(); + auto second = wm->createWindow(WindowSettings{}); + REQUIRE(second.has_value()); + + auto created = createNullGraphicsDevice(2); + REQUIRE(created.has_value()); + auto& gd = created.value(); + + auto a = gd->createRenderWindow(*shell.mainWindow(), RenderWindowDesc{}); + auto b = gd->createRenderWindow(*second.value(), RenderWindowDesc{}); + REQUIRE(a.has_value()); + REQUIRE(b.has_value()); + + // Both windows render in the same app frame at the same ring index, then the + // device advances once. + FrameContext fa = a.value()->beginFrame(); + FrameContext fb = b.value()->beginFrame(); + CHECK(fa.valid); + CHECK(fb.valid); + CHECK(fa.frameIndex == fb.frameIndex); + CHECK(fa.window != fb.window); + a.value()->endFrame(fa); + b.value()->endFrame(fb); + gd->advanceFrame(); + CHECK(gd->currentFrame() == 1u); +} + +TEST_CASE("graphics: syncSize resizes the swapchain when the window changes") +{ + NullShell shell; + auto created = createNullGraphicsDevice(); + REQUIRE(created.has_value()); + auto& gd = created.value(); + + auto rwResult = gd->createRenderWindow(*shell.mainWindow(), RenderWindowDesc{}); + REQUIRE(rwResult.has_value()); + auto& rw = rwResult.value(); + + CHECK_FALSE(rw->syncSize()); // nothing changed yet + + static_cast(shell.mainWindow())->resize(1600, 900); + CHECK(rw->syncSize()); // picked up the change + CHECK(rw->swap()->width() == 1600u); + CHECK(rw->swap()->height() == 900u); + CHECK_FALSE(rw->syncSize()); // stable again +} + +TEST_CASE("graphics: a minimized window yields an invalid frame") +{ + NullShell shell; + auto created = createNullGraphicsDevice(); + REQUIRE(created.has_value()); + auto& gd = created.value(); + + auto rwResult = gd->createRenderWindow(*shell.mainWindow(), RenderWindowDesc{}); + REQUIRE(rwResult.has_value()); + auto& rw = rwResult.value(); + + static_cast(shell.mainWindow())->setMinimized(true); + FrameContext f = rw->beginFrame(); + CHECK_FALSE(f.valid); + rw->endFrame(f); // must be a harmless no-op +} diff --git a/Engine/cpp/Runtime/Rendering/Material/Material.cppm b/Engine/cpp/Runtime/Rendering/Material/Material.cppm deleted file mode 100644 index 20605bd9..00000000 --- a/Engine/cpp/Runtime/Rendering/Material/Material.cppm +++ /dev/null @@ -1,32 +0,0 @@ -module; - -#include - -export module rendering.material; - -import core.stdtypes; -import rendering.rhi; - -export namespace draco::rendering::material -{ - struct Uniform - { - u32 nameHash = 0; - const void* data = nullptr; - u16 count = 1; - }; - - struct Material - { - u32 shaderId = 0; - - rhi::PipelineHandle pipeline = rhi::InvalidPipeline; - - rhi::TextureHandle texture = rhi::InvalidTexture; - rhi::UniformHandle sampler = rhi::InvalidUniform; - - u8 texture_unit = 0; - - std::vector uniforms; - }; -} diff --git a/Engine/cpp/Runtime/Rendering/Mesh/Mesh.cpp b/Engine/cpp/Runtime/Rendering/Mesh/Mesh.cpp deleted file mode 100644 index 3f1e5b60..00000000 --- a/Engine/cpp/Runtime/Rendering/Mesh/Mesh.cpp +++ /dev/null @@ -1,431 +0,0 @@ -module; - -#include -#include -#include - -module rendering.mesh; - -import core.stdtypes; -import core.math.constants; -import core.memory; -import rendering.rhi; -import rendering.rhi.vertex; - -namespace draco::rendering::mesh -{ - using namespace draco::rendering; - - static std::unordered_map g_mesh_cache; - static core::memory::HandleRegistry g_meshes; - static rhi::LayoutHandle g_mesh_layout = rhi::InvalidLayout; - - static usize hashCombine(usize a, usize b) - { - return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2)); - } - - static usize hashMeshParams(int a, int b = 0, f32 c = 0.0f) - { - usize h1 = std::hash{}(a); - usize h2 = std::hash{}(b); - usize h3 = std::hash{}(c); - - return hashCombine(hashCombine(h1, h2), h3); - } - - static void ensureMeshLayout() - { - if (g_mesh_layout != rhi::InvalidLayout) - return; - - rhi::VertexLayoutDesc desc; - desc.elements = - { - { rhi::Attrib::Position, 3, rhi::AttribType::Float }, - { rhi::Attrib::Normal, 3, rhi::AttribType::Float }, - { rhi::Attrib::TexCoord0,2, rhi::AttribType::Float } - }; - - g_mesh_layout = rhi::createVertexLayout(desc); - } - - MeshHandle create(const void* vertex_data, u32 vertex_size, u32 vertex_count, const std::vector& indices, rhi::LayoutHandle layout) - { - Mesh mesh{}; - - mesh.vbh = rhi::createVertexBuffer(vertex_data, vertex_size, layout); - mesh.ibh = rhi::createIndexBuffer(indices.data(), static_cast(indices.size() * sizeof(u32))); - - mesh.layout = layout; - - mesh.vertexCount = vertex_count; - mesh.indexCount = static_cast(indices.size()); - - mesh.valid = (mesh.vbh != rhi::InvalidBuffer) && (mesh.ibh != rhi::InvalidBuffer); - - if (!mesh.valid) - { - if (mesh.vbh != rhi::InvalidBuffer) rhi::destroyBuffer(mesh.vbh); - if (mesh.ibh != rhi::InvalidBuffer) rhi::destroyBuffer(mesh.ibh); - return {}; - } - return g_meshes.create(mesh); - } - - MeshHandle createCube() - { - ensureMeshLayout(); - - usize key = 1; - - if (auto it = g_mesh_cache.find(key); it != g_mesh_cache.end()) - return it->second; - - auto v = gen::cubeVertices(); - auto i = gen::cubeIndices(); - - MeshHandle h = create(v.data(), v.size()*sizeof(Vertex), (u32)v.size(), i, g_mesh_layout); - - g_mesh_cache[key] = h; - return h; - } - - MeshHandle createPlane(f32 size) - { - ensureMeshLayout(); - - usize key = hashMeshParams(1000, 0, size); - - if (auto it = g_mesh_cache.find(key); it != g_mesh_cache.end()) - return it->second; - - auto v = gen::planeVertices(size); - auto i = gen::planeIndices(); - - MeshHandle h = create(v.data(), v.size()*sizeof(Vertex), (u32)v.size(), i, g_mesh_layout); - - g_mesh_cache[key] = h; - return h; - } - - MeshHandle createSphere(int segments, int rings) - { - if (segments < 3 || rings < 2) - return {}; - - ensureMeshLayout(); - - usize key = hashCombine(std::hash{}(segments), std::hash{}(rings)); - - if (auto it = g_mesh_cache.find(key); it != g_mesh_cache.end()) - return it->second; - - auto v = gen::sphereVertices(segments, rings); - auto i = gen::sphereIndices(segments, rings); - - MeshHandle h = create(v.data(), v.size()*sizeof(Vertex), (u32)v.size(), i, g_mesh_layout); - - g_mesh_cache[key] = h; - return h; - } - - MeshHandle createCylinder(int segments, f32 height) - { - if (segments < 3 || height < 0.0f) - return {}; - - ensureMeshLayout(); - - usize key = hashMeshParams(2000, segments, height); - - if (auto it = g_mesh_cache.find(key); it != g_mesh_cache.end()) - return it->second; - - auto v = gen::cylinderVertices(segments, height); - auto i = gen::cylinderIndices(segments); - - MeshHandle h = create(v.data(), v.size()*sizeof(Vertex), (u32)v.size(), i, g_mesh_layout); - - g_mesh_cache[key] = h; - return h; - } - - MeshHandle createCapsule(int segments, int rings, f32 height) - { - if (segments < 3 || rings < 2 || height < 0.0f) - return {}; - - ensureMeshLayout(); - usize key = hashCombine(hashCombine(std::hash{}(segments), std::hash{}(rings)), std::hash{}(height)); - - if (auto it = g_mesh_cache.find(key); it != g_mesh_cache.end()) - return it->second; - - auto v = gen::capsuleVertices(segments, rings, height); - auto i = gen::capsuleIndices(segments, rings); - - MeshHandle h = create(v.data(), v.size()*sizeof(Vertex), (u32)v.size(), i, g_mesh_layout); - - g_mesh_cache[key] = h; - return h; - } - - void destroy(MeshHandle handle) - { - auto* mesh = g_meshes.get(handle); - if (!mesh) return; - - rhi::destroyBuffer(mesh->vbh); - rhi::destroyBuffer(mesh->ibh); - - // Remove from cache - for (auto it = g_mesh_cache.begin(); it != g_mesh_cache.end(); ) - { - if (it->second == handle) - it = g_mesh_cache.erase(it); - else - ++it; - } - - g_meshes.destroy(handle); - } - - const Mesh* get(MeshHandle handle) - { - return g_meshes.get(handle); - } -} - -namespace draco::rendering::mesh::gen -{ - Vertex make(f32 px, f32 py, f32 pz, f32 nx, f32 ny, f32 nz, f32 u, f32 v) - { - return { px, py, pz, nx, ny, nz, u, v }; - } - - std::vector cubeVertices() - { - return { - make(-1,-1, 1, 0,0,1, 0,0), - make( 1,-1, 1, 0,0,1, 1,0), - make( 1, 1, 1, 0,0,1, 1,1), - make(-1, 1, 1, 0,0,1, 0,1), - - make( 1,-1,-1, 0,0,-1, 0,0), - make(-1,-1,-1, 0,0,-1, 1,0), - make(-1, 1,-1, 0,0,-1, 1,1), - make( 1, 1,-1, 0,0,-1, 0,1), - - make(-1,-1,-1,-1,0,0, 0,0), - make(-1,-1, 1,-1,0,0, 1,0), - make(-1, 1, 1,-1,0,0, 1,1), - make(-1, 1,-1,-1,0,0, 0,1), - - make( 1,-1, 1, 1,0,0, 0,0), - make( 1,-1,-1, 1,0,0, 1,0), - make( 1, 1,-1, 1,0,0, 1,1), - make( 1, 1, 1, 1,0,0, 0,1), - - make(-1, 1, 1, 0,1,0, 0,0), - make( 1, 1, 1, 0,1,0, 1,0), - make( 1, 1,-1, 0,1,0, 1,1), - make(-1, 1,-1, 0,1,0, 0,1), - - make(-1,-1,-1, 0,-1,0, 0,0), - make( 1,-1,-1, 0,-1,0, 1,0), - make( 1,-1, 1, 0,-1,0, 1,1), - make(-1,-1, 1, 0,-1,0, 0,1), - }; - } - - std::vector cubeIndices() - { - return { - 0,1,2, 2,3,0, - 4,5,6, 6,7,4, - 8,9,10, 10,11,8, - 12,13,14, 14,15,12, - 16,17,18, 18,19,16, - 20,21,22, 22,23,20 - }; - } - - std::vector planeVertices(f32 size) - { - f32 s = size * 0.5f; - - return { - make(-s,0,-s, 0,1,0, 0,0), - make( s,0,-s, 0,1,0, 1,0), - make( s,0, s, 0,1,0, 1,1), - make(-s,0, s, 0,1,0, 0,1), - }; - } - - std::vector planeIndices() - { - return { 0,1,2, 2,3,0 }; - } - - std::vector sphereVertices(int segments, int rings) - { - std::vector v; - - for (int y = 0; y <= rings; y++) - { - f32 v01 = (f32)y / rings; - f32 theta = v01 * draco::math::PI; - - for (int x = 0; x <= segments; x++) - { - f32 u01 = (f32)x / segments; - f32 phi = u01 * 2.0f * draco::math::PI; - - f32 px = sinf(theta) * cosf(phi); - f32 py = cosf(theta); - f32 pz = sinf(theta) * sinf(phi); - - v.push_back(make(px,py,pz, px,py,pz, u01,v01)); - } - } - - return v; - } - - std::vector sphereIndices(int segments, int rings) - { - std::vector i; - - for (int y = 0; y < rings; y++) - { - for (int x = 0; x < segments; x++) - { - int a = y * (segments + 1) + x; - int b = a + segments + 1; - - i.push_back(a); - i.push_back(b); - i.push_back(a + 1); - - i.push_back(b); - i.push_back(b + 1); - i.push_back(a + 1); - } - } - - return i; - } - - std::vector cylinderVertices(int segments, f32 height) - { - std::vector v; - f32 half = height * 0.5f; - - // Side walls (Outward normals) - for (int y = 0; y <= 1; y++) { - f32 py = (y ? half : -half); - for (int x = 0; x <= segments; x++) { - f32 t = (f32)x / segments; - f32 a = t * 2.0f * draco::math::PI; - f32 cx = cosf(a); - f32 cz = sinf(a); - // Normal is strictly horizontal for side walls - v.push_back(make(cx, py, cz, cx, 0, cz, t, (f32)y)); - } - } - - // Top cap (Upward normals) - // Center vertex - v.push_back(make(0, half, 0, 0, 1, 0, 0.5f, 0.5f)); - for (int x = 0; x <= segments; x++) { - f32 t = (f32)x / segments; - f32 a = t * 2.0f * draco::math::PI; - v.push_back(make(cosf(a), half, sinf(a), 0, 1, 0, (cosf(a)+1)*0.5f, (sinf(a)+1)*0.5f)); - } - - // Bottom cap (Downward Normals) - // Center vertex - v.push_back(make(0, -half, 0, 0, -1, 0, 0.5f, 0.5f)); - for (int x = 0; x <= segments; x++) { - f32 t = (f32)x / segments; - f32 a = t * 2.0f * draco::math::PI; - v.push_back(make(cosf(a), -half, sinf(a), 0, -1, 0, (cosf(a)+1)*0.5f, (sinf(a)+1)*0.5f)); - } - - return v; - } - - std::vector cylinderIndices(int segments) - { - std::vector i; - int side_start = 0; - int top_start = (segments + 1) * 2; - int bottom_start = top_start + (segments + 2); - - // Sides - for (int s = 0; s < segments; s++) { - int b0 = s; int b1 = s + 1; - int t0 = b0 + segments + 1; int t1 = b1 + segments + 1; - i.push_back(b0); i.push_back(t0); i.push_back(t1); - i.push_back(b0); i.push_back(t1); i.push_back(b1); - } - - // Top Cap (Triangle Fan style) - for (int s = 0; s < segments; s++) { - i.push_back(top_start); // Center - i.push_back(top_start + s + 2); - i.push_back(top_start + s + 1); - } - - // Bottom Cap - for (int s = 0; s < segments; s++) { - i.push_back(bottom_start); // Center - i.push_back(bottom_start + s + 1); - i.push_back(bottom_start + s + 2); - } - return i; - } - - std::vector capsuleVertices(int segments, int rings, f32 height) - { - std::vector v; - f32 half = height * 0.5f; - - // One continuous loop from bottom pole to top pole - // Total rings for a capsule = rings (bottom cap) + rings (top cap) - for (int r = 0; r <= rings; r++) { - f32 v_uv = (f32)r / rings; - f32 theta = v_uv * draco::math::PI; // 0 to draco::math::PI - - // Adjust Y for the cylinder section - f32 y_offset = (theta < draco::math::PI * 0.5f) ? half : -half; - - for (int s = 0; s <= segments; s++) { - f32 u_uv = (f32)s / segments; - f32 phi = u_uv * 2.0f * draco::math::PI; - - f32 nx = sinf(theta) * cosf(phi); - f32 ny = cosf(theta); - f32 nz = sinf(theta) * sinf(phi); - - v.push_back(make(nx, ny + y_offset, nz, nx, ny, nz, u_uv, v_uv)); - } - } - return v; - } - - std::vector capsuleIndices(int segments, int rings) - { - std::vector i; - for (int r = 0; r < rings; r++) { - for (int s = 0; s < segments; s++) { - int a = r * (segments + 1) + s; - int b = a + segments + 1; - i.push_back(a); i.push_back(b); i.push_back(a + 1); - i.push_back(b); i.push_back(b + 1); i.push_back(a + 1); - } - } - return i; - } -} diff --git a/Engine/cpp/Runtime/Rendering/Mesh/Mesh.cppm b/Engine/cpp/Runtime/Rendering/Mesh/Mesh.cppm deleted file mode 100644 index fac985a9..00000000 --- a/Engine/cpp/Runtime/Rendering/Mesh/Mesh.cppm +++ /dev/null @@ -1,71 +0,0 @@ -module; - -#include - -export module rendering.mesh; - -import core.stdtypes; -import core.memory; -import rendering.rhi; - -export namespace draco::rendering::mesh -{ - struct MeshTag {}; - - using MeshHandle = core::memory::Handle; - - struct Vertex - { - f32 px, py, pz; - f32 nx, ny, nz; - f32 u, v; - }; - - struct Mesh - { - rhi::BufferHandle vbh; - rhi::BufferHandle ibh; - - rhi::LayoutHandle layout; - - u32 vertexCount = 0; - u32 indexCount = 0; - - bool valid = false; - }; - - MeshHandle create( - const void* vertexData, - u32 vertexSize, - u32 vertexCount, - const std::vector& indices, - rhi::LayoutHandle layout - ); - - MeshHandle createCube(); - MeshHandle createPlane(float size); - MeshHandle createSphere(int segments, int rings); - MeshHandle createCylinder(int segments, float height); - MeshHandle createCapsule(int segments, int rings, float height); - - void destroy(MeshHandle mesh); - const Mesh* get(MeshHandle mesh); -} - -export namespace draco::rendering::mesh::gen -{ - std::vector cubeVertices(); - std::vector cubeIndices(); - - std::vector planeVertices(float size); - std::vector planeIndices(); - - std::vector sphereVertices(int segments, int rings); - std::vector sphereIndices(int segments, int rings); - - std::vector cylinderVertices(int segments, float height); - std::vector cylinderIndices(int segments); - - std::vector capsuleVertices(int segments, int rings, float height); - std::vector capsuleIndices(int segments, int rings); -} diff --git a/Engine/cpp/Runtime/Rendering/QuadRenderer/QuadRenderer.cpp b/Engine/cpp/Runtime/Rendering/QuadRenderer/QuadRenderer.cpp deleted file mode 100644 index ff67e855..00000000 --- a/Engine/cpp/Runtime/Rendering/QuadRenderer/QuadRenderer.cpp +++ /dev/null @@ -1,186 +0,0 @@ -module; - -#include -#include - -#include -#include - -module rendering.quad; - -import core.stdtypes; -import rendering.rhi; -import rendering.rhi.vertex; -import rendering.rendergraph; - -namespace draco::rendering::quad { - - static constexpr f32 QuadUV[4][2] = { - {0.0f, 0.0f}, - {1.0f, 0.0f}, - {1.0f, 1.0f}, - {0.0f, 1.0f} - }; - - void QuadRenderer::init(rhi::PipelineHandle pipeline) - { - using namespace draco::rendering::rhi; - - VertexLayoutDesc layout{}; - layout.elements.push_back({Attrib::Position, 3, AttribType::Float}); - layout.elements.push_back({Attrib::TexCoord0, 2, AttribType::Float}); - layout.elements.push_back({Attrib::Color0, 4, AttribType::Uint8, true}); - - m_pipeline = pipeline; - m_layout = createVertexLayout(layout); - - // Allocating dynamic streaming buffers - m_vb = createDynamicVertexBuffer(sizeof(TexturedVertex) * MaxVertices, m_layout); - - // Pass BGFX_BUFFER_NONE implicitly to match tracking - m_ib = createDynamicIndexBuffer(MaxIndices * sizeof(u16), BGFX_BUFFER_NONE); - - m_sampler = createUniform("s_texColor", UniformType::Sampler); - } - - void QuadRenderer::begin() - { - m_vertices.clear(); - m_indices.clear(); - - m_quad_count = 0; - - m_batch_key = {}; - } - - void QuadRenderer::submit(const QuadCommand& cmd) - { - if (m_quad_count >= MaxQuads) - return; - - BatchKey new_key{cmd.texture, m_pipeline, draco::rendering::rhi::InvalidSampler}; - - if (m_batch_key.texture == draco::rendering::rhi::InvalidTexture) - { - m_batch_key = new_key; - } - - bool state_change = !(new_key == m_batch_key); - - if (state_change) - { - // TODO: Flush current batch automatically - - return; - } - - pushQuad(cmd); - - m_quad_count++; - } - - void QuadRenderer::pushQuad(const QuadCommand& cmd) - { - f32 hw = cmd.width * 0.5f; - f32 hh = cmd.height * 0.5f; - - f32 c = cosf(cmd.rotation); - f32 s = sinf(cmd.rotation); - - f32 corners[4][2] = { - {-hw, -hh}, - { hw, -hh}, - { hw, hh}, - {-hw, hh} - }; - - u16 start = static_cast(m_vertices.size()); - - for (int i = 0; i < 4; i++) - { - f32 rx = corners[i][0] * c - corners[i][1] * s; - - f32 ry = corners[i][0] * s + corners[i][1] * c; - - draco::rendering::rhi::TexturedVertex v{}; - - v.x = cmd.x + rx; - v.y = cmd.y + ry; - v.z = cmd.z; - - v.u = QuadUV[i][0]; - v.v = QuadUV[i][1]; - - v.color = cmd.color; - - m_vertices.push_back(v); - } - - m_indices.push_back(start + 0); - m_indices.push_back(start + 1); - m_indices.push_back(start + 2); - - m_indices.push_back(start + 2); - m_indices.push_back(start + 3); - m_indices.push_back(start + 0); - } - - void QuadRenderer::flushToPass(draco::rendering::rendergraph::Pass& pass) - { - using namespace draco::rendering::rhi; - - if (m_vertices.empty()) - return; - - // Upload only the exact slices we are using this frame - updateDynamicVertexBuffer(m_vb, 0, m_vertices.data(), static_cast(m_vertices.size() * sizeof(TexturedVertex))); - updateDynamicIndexBuffer(m_ib, 0, m_indices.data(), static_cast(m_indices.size() * sizeof(u16))); - - RenderPacket pkt{}; - pkt.vertexBuffer = m_vb; - pkt.indexBuffer = m_ib; - pkt.pipeline = m_pipeline; - pkt.textureHandle = m_batch_key.texture; - pkt.samplerUniform = m_sampler; - - pkt.vertexCount = static_cast(m_vertices.size()); - pkt.indexCount = static_cast(m_indices.size()); - - pkt.sortKey = makeSortKey(0, 0, static_cast(m_pipeline.value), static_cast(m_batch_key.texture.value), 0); - - bx::mtxIdentity(pkt.model); - - pass.packets.push_back(pkt); - - m_vertices.clear(); - m_indices.clear(); - } - - void QuadRenderer::shutdown() - { - using namespace draco::rendering::rhi; - - destroyBuffer(m_vb); - destroyBuffer(m_ib); - - destroyUniform(m_sampler); - } - - void QuadRenderer::buildOrtho(OrthoCamera& cam, f32 width, f32 height) - { - using namespace draco::rendering::rhi; - - identityMatrix(cam.view); - identityMatrix(cam.proj); - - f32 rl = std::max(width, 1.0f); - f32 tb = std::max(height, 1.0f); - - cam.proj[0] = 2.0f / rl; - cam.proj[5] = -2.0f / tb; - cam.proj[10] = -1.0f; - - cam.proj[12] = -1.0f; - cam.proj[13] = 1.0f; - } -} diff --git a/Engine/cpp/Runtime/Rendering/QuadRenderer/QuadRenderer.cppm b/Engine/cpp/Runtime/Rendering/QuadRenderer/QuadRenderer.cppm deleted file mode 100644 index e3f72bc8..00000000 --- a/Engine/cpp/Runtime/Rendering/QuadRenderer/QuadRenderer.cppm +++ /dev/null @@ -1,81 +0,0 @@ -module; - -#include - -export module rendering.quad; - -import core.stdtypes; -import rendering.rhi; -import rendering.rhi.vertex; -import rendering.rendergraph; - -export namespace draco::rendering::quad { - - struct BatchKey { - rhi::TextureHandle texture = rhi::InvalidTexture; - - rhi::PipelineHandle pipeline = rhi::InvalidPipeline; - - rhi::SamplerHandle sampler = rhi::InvalidSampler; - - [[nodiscard]] bool operator==(const BatchKey&) const = default; - }; - - struct QuadCommand { - rhi::TextureHandle texture = rhi::InvalidTexture; - - f32 x = 0.0f; - f32 y = 0.0f; - f32 z = 0.0f; - - f32 width = 1.0f; - f32 height = 1.0f; - - f32 rotation = 0.0f; - - u32 color = 0xffffffff; - }; - - struct OrthoCamera { - f32 view[16]; - f32 proj[16]; - - f32 x = 0.0f; - f32 y = 0.0f; - f32 zoom = 1.0f; - }; - - class QuadRenderer { - public: - static constexpr u32 MaxQuads = 10000; - static constexpr u32 MaxVertices = MaxQuads * 4; - static constexpr u32 MaxIndices = MaxQuads * 6; - - void init(draco::rendering::rhi::PipelineHandle pipeline); - - void begin(); - - void submit(const QuadCommand& cmd); - - void flushToPass(rendergraph::Pass& pass); - - void shutdown(); - - static void buildOrtho(OrthoCamera& cam, f32 width, f32 height); - - private: - void pushQuad(const QuadCommand& cmd); - - BatchKey m_batch_key{}; - - std::vector m_vertices; - std::vector m_indices; - - rhi::BufferHandle m_vb = rhi::InvalidBuffer; - rhi::BufferHandle m_ib = rhi::InvalidBuffer; - rhi::LayoutHandle m_layout = rhi::InvalidLayout; - rhi::PipelineHandle m_pipeline = rhi::InvalidPipeline; - rhi::UniformHandle m_sampler = rhi::InvalidUniform; - u32 m_quad_count = 0; - }; -} diff --git a/Engine/cpp/Runtime/Rendering/RHI/Buffers.cpp b/Engine/cpp/Runtime/Rendering/RHI/Buffers.cpp deleted file mode 100644 index fa65dac5..00000000 --- a/Engine/cpp/Runtime/Rendering/RHI/Buffers.cpp +++ /dev/null @@ -1,102 +0,0 @@ -module; - -#include -#include "macros.h" - -module rendering.rhi; - -import core.stdtypes; -import core.math.constants; - -namespace draco::rendering::rhi -{ - BufferHandle createVertexBuffer(const void* data, u32 size, LayoutHandle layout_h) - { - RHI_ASSERT(data != nullptr, "Vertex buffer data is null"); - RHI_ASSERT(size > 0, "Vertex buffer size is zero"); - - auto* layout = getChecked(g_layouts, layout_h, "Layout"); - - RHI_ASSERT(layout, "Invalid vertex layout"); - - auto vbh = bgfx::createVertexBuffer(bgfx::copy(data, size), layout->layout); - - Buffer buf; - buf.vbh = vbh; - - return g_buffers.create(buf); - } - - BufferHandle createIndexBuffer(const void* data, u32 size) - { - RHI_ASSERT(data != nullptr, "Index buffer data is null"); - RHI_ASSERT(size > 0, "Index buffer size is zero"); - - bgfx::IndexBufferHandle ibh = bgfx::createIndexBuffer(bgfx::copy(data, size), BGFX_BUFFER_INDEX32); - - Buffer buf; // Idk why I named it this, it just sounds funny ;) - buf.ibh = ibh; - buf.isIndex = true; - - return g_buffers.create(buf); - } - - BufferHandle createDynamicVertexBuffer(u32 size, LayoutHandle layout_h) - { - auto* layout = getChecked(g_layouts, layout_h, "Layout"); - RHI_ASSERT(layout, "Invalid layout"); - - bgfx::DynamicVertexBufferHandle dvbh = bgfx::createDynamicVertexBuffer(size, layout->layout); - - RHI_ASSERT(bgfx::isValid(dvbh), "Failed to create dynamic vertex buffer"); - - Buffer buf; - buf.dvbh = dvbh; - buf.isDynamic = true; - - return g_buffers.create(buf); - } - - void updateDynamicVertexBuffer(BufferHandle handle, u32 start_vertex, const void* data, u32 size) - { - auto* buf = getChecked(g_buffers, handle, "Buffer"); - - if (!buf) - return; - - RHI_ASSERT(buf->isDynamic && !buf->isIndex, "Not a dynamic vertex buffer"); - RHI_ASSERT(bgfx::isValid(buf->dvbh), "Invalid dynamic vertex buffer handle"); - - const bgfx::Memory* mem = bgfx::copy(data, size); - - bgfx::update(buf->dvbh, start_vertex, mem); - } - - BufferHandle createDynamicIndexBuffer(u32 size, u16 flags) - { - bgfx::DynamicIndexBufferHandle ibh = bgfx::createDynamicIndexBuffer(size, flags); - - RHI_ASSERT(bgfx::isValid(ibh), "Invalid dynamic index buffer handle"); - - Buffer buf{}; - buf.isDynamic = true; - buf.isIndex = true; - buf.dibh = ibh; - - return g_buffers.create(buf); - } - - void updateDynamicIndexBuffer(BufferHandle handle, u32 start_index, const void* data, u32 size) - { - auto* buf = getChecked(g_buffers, handle, "DynamicIndexBuffer"); - - if (!buf) - return; - - RHI_ASSERT(buf->isDynamic && buf->isIndex, "Not a dynamic index buffer"); - - const bgfx::Memory* mem = bgfx::copy(data, size); - - bgfx::update(buf->dibh, start_index, mem); - } -} diff --git a/Engine/cpp/Runtime/Rendering/RHI/Commands.cpp b/Engine/cpp/Runtime/Rendering/RHI/Commands.cpp deleted file mode 100644 index c751d14c..00000000 --- a/Engine/cpp/Runtime/Rendering/RHI/Commands.cpp +++ /dev/null @@ -1,150 +0,0 @@ -module; - -#include -#include -#include -#include "macros.h" - -module rendering.rhi; - -import core.stdtypes; -import core.math.constants; - -namespace draco::rendering::rhi -{ - void perspective(f32* out, f32 fov, f32 aspect, f32 nearp, f32 farp) - { - bx::mtxProj(out, fov, aspect, nearp, farp, bgfx::getCaps()->homogeneousDepth); - } - - void lookAt(f32* out, const f32* eye, const f32* at, const f32* up) - { - bx::Vec3 eye_v { eye[0], eye[1], eye[2] }; - bx::Vec3 at_v { at[0], at[1], at[2] }; - bx::Vec3 up_v { up[0], up[1], up[2] }; - - bx::mtxLookAt(out, eye_v, at_v, up_v); - } - - // Note: Internal use only, use apply_view() instead - void setViewRect(ViewID view, u16 x, u16 y, u16 w, u16 h) - { - bgfx::setViewRect(view, x, y, w, h); - } - - // Note: Internal use only, use apply_view() instead - void setViewFramebuffer(ViewID view, FramebufferHandle h) - { - auto* fb = getChecked(g_framebuffers, h, "Framebuffer"); - - if (!fb) - return; - - bgfx::setViewFrameBuffer(view, fb->fbh); - } - - void setViewProjection(ViewID view, const f32* view_mtx, const f32* proj_mtx) - { - bgfx::setViewTransform(view, view_mtx, proj_mtx); - } - - void setScissor(const ScissorRect& r) - { - if (!r.enabled) - bgfx::setScissor(math::UINT16_MAX_VAL); - else - bgfx::setScissor(r.x, r.y, r.w, r.h); - } - - void setStencil(u32 fstencil, u32 bstencil) - { - bgfx::setStencil(fstencil, bstencil); - } - - void applyView(ViewID view, const ViewDesc& desc) - { - if (desc.fb != InvalidFramebuffer) - { - auto* fb = getChecked(g_framebuffers, desc.fb, "Framebuffer"); - - if (fb && bgfx::isValid(fb->fbh)) - { - bgfx::setViewFrameBuffer(view, fb->fbh); - } - else - { - RHI_WARN(false, "Framebuffer invalid at apply_view"); - } - } - - bgfx::setViewRect(view, desc.x, desc.y, desc.w, desc.h); - - if (desc.clearFlags != 0) - { - bgfx::setViewClear(view, desc.clearFlags, desc.clearColor); - } - } - - void identityMatrix(f32* mtx) - { - bx::mtxIdentity(mtx); - } - - void submit(const RenderPacket& p, ViewID view) - { - auto* pipeline = getChecked(g_pipelines, p.pipeline, "Pipeline"); - auto* vb = getChecked(g_buffers, p.vertexBuffer, "VertexBuffer"); - Buffer* ib = nullptr; - - if (!pipeline || !vb) - return; - - if (p.indexBuffer != InvalidBuffer) - ib = getChecked(g_buffers, p.indexBuffer, "IndexBuffer"); - - // Transform matrix (model) - bgfx::setTransform(p.model); - - // Vertex buffer binding with explicit range control - if (vb->isDynamic) - { - // If count is UINT32_MAX, bgfx will fallback to drawing the full buffer automatically - bgfx::setVertexBuffer(0, vb->dvbh, 0, p.vertexCount); - } else { - bgfx::setVertexBuffer(0, vb->vbh, 0, p.vertexCount); - } - - // Index buffer binding with explicit range control - if (ib && ib->isIndex) - { - if (ib->isDynamic) - { - bgfx::setIndexBuffer(ib->dibh, 0, p.indexCount); - } else { - bgfx::setIndexBuffer(ib->ibh, 0, p.indexCount); - } - } - - // Uniforms - for (const auto& u : p.uniforms) - { - if (auto* handle = getChecked(g_uniforms, u.handle, "UniformBind")) - { - bgfx::setUniform(*handle, u.data, u.num); - } - } - - // Texture binding - if (auto* tex = getChecked(g_textures, p.textureHandle, "Texture")) - { - if (auto* sampler = getChecked(g_uniforms, p.samplerUniform, "Sampler")) - { - bgfx::setTexture(p.textureUnit, *sampler, *tex, p.samplerFlags); - } - } - - // Apply pipeline state & submit draw call - bgfx::setState(pipeline->state); - bgfx::submit(view, pipeline->program); - } -} diff --git a/Engine/cpp/Runtime/Rendering/RHI/Commands.cppm b/Engine/cpp/Runtime/Rendering/RHI/Commands.cppm new file mode 100644 index 00000000..c576a7de --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Commands.cppm @@ -0,0 +1,236 @@ +/// Abstract command recording interfaces: CommandPool, CommandEncoder, +/// RenderPassEncoder, ComputePassEncoder, TransferBatch. + +module; + +#include +#include + +export module rhi:commands; + +import core.stdtypes; +import core.status; +import :forward; +import :enums; +import :types; +import :descriptors; +import :resources; + +using namespace draco; + +export namespace draco::rhi { + +// ---- Render Command Encoder (shared draw-recording surface) ---- + +/// The draw-recording commands common to a render pass and a render bundle. A `Renderer` that +/// takes a `RenderCommandEncoder*` records identically whether it targets a live pass (inline) +/// or an off-thread `RenderBundleEncoder` - which is what makes parallel command recording +/// fall out (split a draw list into N bundles recorded on N threads, then executeBundles). +/// This is exactly the subset valid inside a WebGPU render bundle: no pass-level dynamic state +/// (viewport / scissor / blend constant / stencil ref are inherited from the pass), no queries. +class RenderCommandEncoder { +public: + virtual ~RenderCommandEncoder() = default; + + /// Bind a graphics (rasterization) pipeline. + virtual void setPipeline(RenderPipeline* pipeline) = 0; + /// Bind a resource group at the given index. + virtual void setBindGroup(u32 index, BindGroup* group, std::span dynamicOffsets = {}) = 0; + /// Upload push constant data. + virtual void setPushConstants(ShaderStage stages, u32 offset, u32 size, const void* data) = 0; + + /// Bind a vertex buffer to a slot. + virtual void setVertexBuffer(u32 slot, Buffer* buffer, u64 offset = 0) = 0; + /// Bind an index buffer. + virtual void setIndexBuffer(Buffer* buffer, IndexFormat format, u64 offset = 0) = 0; + + /// Issue a non-indexed draw call. + virtual void draw(u32 vertexCount, u32 instanceCount = 1, u32 firstVertex = 0, u32 firstInstance = 0) = 0; + /// Issue an indexed draw call. + virtual void drawIndexed(u32 indexCount, u32 instanceCount = 1, u32 firstIndex = 0, i32 baseVertex = 0, u32 firstInstance = 0) = 0; + /// Issue an indirect draw call. + virtual void drawIndirect(Buffer* buffer, u64 offset, u32 drawCount = 1, u32 stride = 0) = 0; + /// Issue an indexed indirect draw call. + virtual void drawIndexedIndirect(Buffer* buffer, u64 offset, u32 drawCount = 1, u32 stride = 0) = 0; +}; + +// ---- Render Pass Encoder ---- + +/// Records draw commands within a render pass: the shared recording surface plus the pass-level +/// dynamic state, queries, bundle execution, and pass control. +class RenderPassEncoder : public RenderCommandEncoder { +public: + /// Cross-query for the mesh-shader extension (replacement for a sideways + /// dynamic_cast). Encoders that support it return `this`. + [[nodiscard]] virtual MeshShaderPassExt* asMeshShaderExt() noexcept { return nullptr; } + + /// Set the viewport rectangle and depth range. + virtual void setViewport(f32 x, f32 y, f32 w, f32 h, f32 minDepth = 0.0f, f32 maxDepth = 1.0f) = 0; + /// Set the scissor rectangle. + virtual void setScissor(i32 x, i32 y, u32 w, u32 h) = 0; + /// Set the blend constant color. + virtual void setBlendConstant(f32 r, f32 g, f32 b, f32 a) = 0; + /// Set the stencil reference value. + virtual void setStencilReference(u32 reference) = 0; + + /// Execute pre-recorded render bundles in order (replays their draws into this pass). The + /// pass must have been begun with RenderPassContents::SecondaryCommandBuffers. + virtual void executeBundles(std::span bundles) = 0; + + /// Write a timestamp query. + virtual void writeTimestamp(QuerySet* querySet, u32 index) = 0; + /// Begin an occlusion query. + virtual void beginOcclusionQuery(QuerySet* querySet, u32 index) = 0; + /// End an occlusion query. + virtual void endOcclusionQuery(QuerySet* querySet, u32 index) = 0; + + /// End the render pass. + virtual void end() = 0; +}; + +// ---- Render Bundle ---- + +/// An immutable, pre-recorded sequence of draw commands, replayable into any compatible render +/// pass (matching attachment formats) via RenderPassEncoder::executeBundles. Valid until its +/// owning command pool is reset. Recorded off the main thread for parallel command recording. +class RenderBundle { +public: + virtual ~RenderBundle() = default; +}; + +/// Records draws into a render bundle (the shared recording surface only - no pass-level state). +class RenderBundleEncoder : public RenderCommandEncoder { +public: + /// Finish recording and return the immutable bundle (owned by the command pool). + [[nodiscard]] virtual RenderBundle* finish() = 0; +}; + +// ---- Compute Pass Encoder ---- + +/// Records compute dispatch commands within a compute pass. +class ComputePassEncoder { +public: + virtual ~ComputePassEncoder() = default; + + virtual void setPipeline(ComputePipeline* pipeline) = 0; + virtual void setBindGroup(u32 index, BindGroup* group, std::span dynamicOffsets = {}) = 0; + virtual void setPushConstants(ShaderStage stages, u32 offset, u32 size, const void* data) = 0; + + /// Dispatch compute work groups. + virtual void dispatch(u32 x, u32 y = 1, u32 z = 1) = 0; + /// Dispatch compute work groups via an indirect buffer. + virtual void dispatchIndirect(Buffer* buffer, u64 offset) = 0; + + /// Insert a compute-to-compute memory barrier. + virtual void computeBarrier() = 0; + + virtual void writeTimestamp(QuerySet* querySet, u32 index) = 0; + virtual void end() = 0; +}; + +// ---- Command Encoder ---- + +/// Records GPU commands: render/compute passes, barriers, copies, queries. +class CommandEncoder { +public: + virtual ~CommandEncoder() = default; + + /// Cross-query for the ray-tracing extension (replacement for a sideways + /// dynamic_cast). Encoders that support it return `this`. + [[nodiscard]] virtual RayTracingEncoderExt* asRayTracingExt() noexcept { return nullptr; } + + /// Begin a render pass. Returns the encoder for recording draw commands. + [[nodiscard]] virtual RenderPassEncoder* beginRenderPass(const RenderPassDesc& desc) = 0; + /// Begin a compute pass. + [[nodiscard]] virtual ComputePassEncoder* beginComputePass(std::u8string_view label = {}) = 0; + /// Begin recording a render bundle (a reusable, off-thread-recordable draw sequence + /// replayable into passes matching `desc`'s attachment signature). Returns null if the + /// backend does not support bundles. + [[nodiscard]] virtual RenderBundleEncoder* createRenderBundleEncoder(const RenderBundleDesc& desc) = 0; + + /// Insert resource barriers. + virtual void barrier(const BarrierGroup& group) = 0; + + /// Convenience: transition a single texture between resource states. + void transitionTexture(Texture* tex, ResourceState oldState, ResourceState newState) { + TextureBarrier tb{}; tb.texture = tex; tb.oldState = oldState; tb.newState = newState; + BarrierGroup g{}; g.textureBarriers = std::span(&tb, 1); + barrier(g); + } + + /// Convenience: transition a single buffer between resource states. + void transitionBuffer(Buffer* buf, ResourceState oldState, ResourceState newState) { + BufferBarrier bb{}; bb.buffer = buf; bb.oldState = oldState; bb.newState = newState; + BarrierGroup g{}; g.bufferBarriers = std::span(&bb, 1); + barrier(g); + } + + /// Copy operations. + virtual void copyBufferToBuffer(Buffer* src, u64 srcOffset, Buffer* dst, u64 dstOffset, u64 size) = 0; + virtual void copyBufferToTexture(Buffer* src, Texture* dst, const BufferTextureCopyRegion& region) = 0; + virtual void copyTextureToBuffer(Texture* src, Buffer* dst, const BufferTextureCopyRegion& region) = 0; + virtual void copyTextureToTexture(Texture* src, Texture* dst, const TextureCopyRegion& region) = 0; + + /// Blit (scaled copy) from one texture to another. + virtual void blit(Texture* src, Texture* dst) = 0; + /// Generate mipmaps for a texture. + virtual void generateMipmaps(Texture* texture) = 0; + /// Resolve a multisampled texture to a single-sampled texture. + virtual void resolveTexture(Texture* src, Texture* dst) = 0; + + /// Query operations. + virtual void resetQuerySet(QuerySet* querySet, u32 first, u32 count) = 0; + virtual void writeTimestamp(QuerySet* querySet, u32 index) = 0; + virtual void resolveQuerySet(QuerySet* querySet, u32 first, u32 count, Buffer* dst, u64 dstOffset) = 0; + + /// Debug labels. + virtual void beginDebugLabel(std::u8string_view label, f32 r = 0, f32 g = 0, f32 b = 0, f32 a = 1) = 0; + virtual void endDebugLabel() = 0; + virtual void insertDebugLabel(std::u8string_view label, f32 r = 0, f32 g = 0, f32 b = 0, f32 a = 1) = 0; + + /// Finish recording and return an immutable command buffer. + [[nodiscard]] virtual CommandBuffer* finish() = 0; +}; + +// ---- Command Pool ---- + +/// Manages command buffer memory for a single queue type. +/// One pool per thread per queue type. +class CommandPool { +public: + virtual ~CommandPool() = default; + + /// Create a new command encoder for recording. + virtual Status createEncoder(CommandEncoder*& out) = 0; + /// Destroy a command encoder. + virtual void destroyEncoder(CommandEncoder*& encoder) = 0; + /// Reset all command buffers allocated from this pool. + virtual void reset() = 0; +}; + +// ---- Transfer Batch ---- + +/// Batches staging upload operations (CPU->GPU buffer/texture writes). +class TransferBatch { +public: + virtual ~TransferBatch() = default; + + /// Stage a buffer write. + virtual void writeBuffer(Buffer* dst, u64 dstOffset, std::span data) = 0; + /// Stage a texture write. + virtual void writeTexture(Texture* dst, std::span data, + const TextureDataLayout& layout, Extent3D extent, + u32 mipLevel = 0, u32 arrayLayer = 0) = 0; + + /// Submit all staged writes synchronously. + virtual Status submit() = 0; + /// Submit all staged writes, signaling a fence on completion. + virtual Status submitAsync(Fence* fence, u64 signalValue) = 0; + + /// Reset the batch for reuse. + virtual void reset() = 0; + /// Destroy the batch. + virtual void destroy() = 0; +}; + +} // namespace draco::rhi diff --git a/Engine/cpp/Runtime/Rendering/RHI/Core.cpp b/Engine/cpp/Runtime/Rendering/RHI/Core.cpp deleted file mode 100644 index e8529435..00000000 --- a/Engine/cpp/Runtime/Rendering/RHI/Core.cpp +++ /dev/null @@ -1,334 +0,0 @@ -module; - -#include -#include -#include -#include -#include -#include -#include -#include -#include "macros.h" - -module rendering.rhi; - -import core.stdtypes; -import core.math.constants; - -namespace draco::rendering::rhi -{ - using namespace draco::core::memory; - - HandleRegistry g_buffers; - HandleRegistry g_pipelines; - HandleRegistry g_uniforms; - HandleRegistry g_textures; - HandleRegistry g_framebuffers; - HandleRegistry g_shaders; - HandleRegistry g_layouts; - - std::vector g_deletion_queue; - u16 g_width = 0; - u16 g_height = 0; - - void queueDestruction(std::function cb) - { - g_deletion_queue.push_back({ - bgfx::getStats()->gpuFrameNum, - std::move(cb) - }); - } - - // Explicit overloads for each bgfx resource - void destroyLater(bgfx::ShaderHandle handle) - { - queueDestruction([handle]() { - bgfx::destroy(handle); - }); - } - - void destroyLater(bgfx::UniformHandle handle) - { - queueDestruction([handle]() { - bgfx::destroy(handle); - }); - } - - void destroyLater(bgfx::VertexBufferHandle handle) - { - queueDestruction([handle]() { - bgfx::destroy(handle); - }); - } - - void destroyLater(bgfx::IndexBufferHandle handle) - { - queueDestruction([handle]() { - bgfx::destroy(handle); - }); - } - - void destroyLater(bgfx::DynamicVertexBufferHandle handle) - { - queueDestruction([handle]() { - bgfx::destroy(handle); - }); - } - - void destroyLater(bgfx::DynamicIndexBufferHandle handle) - { - queueDestruction([handle]() { - bgfx::destroy(handle); - }); - } - - void destroyLater(bgfx::TextureHandle handle) - { - queueDestruction([handle]() { - bgfx::destroy(handle); - }); - } - - void destroyLater(bgfx::FrameBufferHandle handle) - { - queueDestruction([handle]() { - bgfx::destroy(handle); - }); - } - - void processDeletions() - { - u64 frame = bgfx::getStats()->gpuFrameNum; - - std::erase_if(g_deletion_queue, [frame](const auto& d) - { - if (frame >= d.frame + 2) - { - d.cleanup(); - return true; - } - return false; - }); - } - - bool init(void* display_type, void* window_handle, NativeWindowType window_type, u16 width, u16 height) - { - g_width = width; - g_height = height; - - bgfx::Init init{}; - init.type = bgfx::RendererType::Count; - - init.platformData.ndt = display_type; - init.platformData.nwh = window_handle; - - // Map our internal window type to bgfx's native window handle type - if (window_type == NativeWindowType::Wayland) - { - init.platformData.type = bgfx::NativeWindowHandleType::Wayland; - } - else - { - // Others can work fine with the default type - init.platformData.type = bgfx::NativeWindowHandleType::Default; - } - - init.resolution.width = width; - init.resolution.height = height; - init.resolution.reset = BGFX_RESET_VSYNC; - - if (!bgfx::init(init)) - { - RHI_WARN(false, "bgfx initialization failed"); - return false; - } - - bgfx::setDebug(BGFX_DEBUG_TEXT); - return true; - } - - void resize(u16 width, u16 height) - { - if(width == 0 || height == 0) - return; // Minimized window safety - - if(width == g_width && height == g_height) - return; // No need to resize - - g_width = width; - g_height = height; - - bgfx::reset(width, height, BGFX_RESET_VSYNC); - } - - void shutdown() - { - // Walk all registries and destroy live GPU objects - for (auto& slot : g_buffers.internal().raw()) - { - if (!slot.alive) continue; - - if (bgfx::isValid(slot.value.vbh)) - bgfx::destroy(slot.value.vbh); - - if (bgfx::isValid(slot.value.ibh)) - bgfx::destroy(slot.value.ibh); - - if (bgfx::isValid(slot.value.dvbh)) - bgfx::destroy(slot.value.dvbh); - } - - for (auto& slot : g_pipelines.internal().raw()) - { - if (!slot.alive) continue; - - if (bgfx::isValid(slot.value.program)) - bgfx::destroy(slot.value.program); - } - - for (auto& slot : g_uniforms.internal().raw()) - { - if (!slot.alive) continue; - - if (bgfx::isValid(slot.value)) - bgfx::destroy(slot.value); - } - - for (auto& slot : g_textures.internal().raw()) - { - if (!slot.alive) continue; - - if (bgfx::isValid(slot.value)) - bgfx::destroy(slot.value); - } - - bgfx::shutdown(); - } - - void destroyBuffer(BufferHandle h) - { - auto* buf = getChecked(g_buffers, h, "Buffer"); - - if (!buf) - return; - - if (bgfx::isValid(buf->vbh)) - destroyLater(buf->vbh); - - if (bgfx::isValid(buf->ibh)) - destroyLater(buf->ibh); - - if (bgfx::isValid(buf->dvbh)) - destroyLater(buf->dvbh); - - if (bgfx::isValid(buf->dibh)) - destroyLater(buf->dibh); - - g_buffers.destroy(h); - } - - u64 mapState(PipelineState s, BlendMode blend, DepthTest depth, CullMode cull, bool depth_write) - { - u64 state = 0; - - if ((s & PipelineState::WriteRGB) != PipelineState::Default) - state |= BGFX_STATE_WRITE_RGB; - - if ((s & PipelineState::WriteAlpha) != PipelineState::Default) - state |= BGFX_STATE_WRITE_A; - - if (depth_write) - state |= BGFX_STATE_WRITE_Z; - - switch (depth) - { - case DepthTest::Less: state |= BGFX_STATE_DEPTH_TEST_LESS; break; - case DepthTest::Equal: state |= BGFX_STATE_DEPTH_TEST_EQUAL; break; - case DepthTest::Always: state |= BGFX_STATE_DEPTH_TEST_ALWAYS; break; - case DepthTest::None: break; - } - - - switch (cull) - { - case CullMode::CW: state |= BGFX_STATE_CULL_CW; break; - case CullMode::CCW: state |= BGFX_STATE_CULL_CCW; break; - case CullMode::None: break; - } - - switch (blend) - { - case BlendMode::Alpha: - state |= BGFX_STATE_BLEND_ALPHA; - break; - - case BlendMode::Additive: - state |= BGFX_STATE_BLEND_ADD; - break; - - case BlendMode::Multiply: - state |= BGFX_STATE_BLEND_MULTIPLY; - break; - - case BlendMode::None: - break; - } - - if ((s & PipelineState::MSAA) != PipelineState::Default) - state |= BGFX_STATE_MSAA; - - if ((s & PipelineState::PrimitiveTriStrip) != PipelineState::Default) - state |= BGFX_STATE_PT_TRISTRIP; - - return state; - } - - bgfx::UniformType::Enum map_uniform_type(UniformType t) - { - switch (t) - { - case UniformType::Sampler: return bgfx::UniformType::Sampler; - case UniformType::Vec4: return bgfx::UniformType::Vec4; - case UniformType::Mat3: return bgfx::UniformType::Mat3; - case UniformType::Mat4: return bgfx::UniformType::Mat4; - } - return bgfx::UniformType::Vec4; - } - - bgfx::Attrib::Enum map_attrib(Attrib a) - { - switch (a) - { - case Attrib::Position: return bgfx::Attrib::Position; - case Attrib::Color0: return bgfx::Attrib::Color0; - case Attrib::TexCoord0: return bgfx::Attrib::TexCoord0; - case Attrib::Normal: return bgfx::Attrib::Normal; - case Attrib::Tangent: return bgfx::Attrib::Tangent; - } - - return bgfx::Attrib::Position; - } - - bgfx::AttribType::Enum map_attrib_type(AttribType t) - { - switch (t) - { - case AttribType::Float: return bgfx::AttribType::Float; - case AttribType::Uint8: return bgfx::AttribType::Uint8; - } - - return bgfx::AttribType::Float; - } - - void beginFrame() - { - // Clean up GPU resources safely - processDeletions(); - } - - void endFrame() - { - // Submit frame to GPU - bgfx::frame(); - } -} diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxAccelStruct.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxAccelStruct.cppm new file mode 100644 index 00000000..358c7d83 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxAccelStruct.cppm @@ -0,0 +1,58 @@ +/// DX12 implementation of AccelStruct. +/// Wraps a D3D12 buffer resource in RAYTRACING_ACCELERATION_STRUCTURE state. + +module; + +#include "DxIncludes.h" + +export module rhi.dx12:accel_struct; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxAccelStructImpl : public AccelStruct { +public: + Status init(ID3D12Device* device, const AccelStructDesc& d) { + m_type = d.type; + + D3D12_HEAP_PROPERTIES hp{}; hp.Type = D3D12_HEAP_TYPE_DEFAULT; + D3D12_RESOURCE_DESC rd{}; + rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + rd.Width = 256 * 1024; // 256 KB default, grown at build time + rd.Height = 1; + rd.DepthOrArraySize = 1; + rd.MipLevels = 1; + rd.Format = DXGI_FORMAT_UNKNOWN; + rd.SampleDesc = { 1, 0 }; + rd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + rd.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + + HRESULT hr = device->CreateCommittedResource( + &hp, D3D12_HEAP_FLAG_NONE, &rd, + D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE, nullptr, + IID_PPV_ARGS(&m_resource)); + if (FAILED(hr)) return ErrorCode::Unknown; + + m_gpuAddr = m_resource->GetGPUVirtualAddress(); + return ErrorCode::Ok; + } + + AccelStructType type() const override { return m_type; } + u64 deviceAddress() const override { return m_gpuAddr; } + + void cleanup() { m_resource.Reset(); } + + [[nodiscard]] ID3D12Resource* handle() const { return m_resource.Get(); } + +private: + AccelStructType m_type = AccelStructType::BottomLevel; + u64 m_gpuAddr = 0; + ComPtr m_resource; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxAdapter.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxAdapter.cppm new file mode 100644 index 00000000..0f9bd638 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxAdapter.cppm @@ -0,0 +1,122 @@ +/// DX12 implementation of Adapter. +/// Wraps IDXGIAdapter1, queries device features, creates DxDevice. + +module; + +#include "DxIncludes.h" + +#include +#include + +export module rhi.dx12:adapter; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxDeviceImpl; // forward + +/// Convert a null-terminated UTF-16 string (as DXGI reports device names) to UTF-8. +inline std::u8string wideToUtf8(const wchar_t* w) { + if (!w) return {}; + const int len = ::WideCharToMultiByte(CP_UTF8, 0, w, -1, nullptr, 0, nullptr, nullptr); + if (len <= 1) return {}; + std::u8string out(static_cast(len - 1), u8'\0'); + ::WideCharToMultiByte(CP_UTF8, 0, w, -1, reinterpret_cast(out.data()), len, nullptr, nullptr); + return out; +} + +class DxAdapterImpl : public Adapter { +public: + DxAdapterImpl(IDXGIAdapter1* adapter, IDXGIFactory4* factory) + : m_adapter(adapter), m_factory(factory) + { + m_adapter->GetDesc1(&m_desc); + } + + ~DxAdapterImpl() override { + if (m_adapter) { m_adapter->Release(); m_adapter = nullptr; } + } + + // ---- Adapter interface ---- + + void getInfo(AdapterInfo& out) override { + // DXGI Description is a WCHAR[] (UTF-16) - transcode to UTF-8. + out.name = wideToUtf8(m_desc.Description); + out.vendorId = m_desc.VendorId; + out.deviceId = m_desc.DeviceId; + out.type = (m_desc.DedicatedVideoMemory > 0) ? AdapterType::DiscreteGpu : AdapterType::IntegratedGpu; + out.supportedFeatures = buildFeatures(); + } + + DeviceFeatures buildFeatures() { + // Create a temporary device to query features. + ComPtr tempDevice; + HRESULT hr = D3D12CreateDevice(m_adapter, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&tempDevice)); + if (FAILED(hr) || !tempDevice) return {}; + + DeviceFeatures f{}; + + // Check feature support. + D3D12_FEATURE_DATA_D3D12_OPTIONS options{}; + if (SUCCEEDED(tempDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &options, sizeof(options)))) { + f.bindlessDescriptors = true; // DX12 always supports descriptor indexing + f.timestampQueries = true; + f.multiDrawIndirect = true; + f.depthClamp = true; + f.fillModeWireframe = true; + f.textureCompressionBC = true; + f.textureCompressionASTC = false; + f.independentBlend = true; + f.multiViewport = true; + f.pipelineStatisticsQueries = true; + } + + // Check mesh shader support. + D3D12_FEATURE_DATA_D3D12_OPTIONS7 options7{}; + if (SUCCEEDED(tempDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS7, &options7, sizeof(options7)))) { + f.meshShaders = (options7.MeshShaderTier != D3D12_MESH_SHADER_TIER_NOT_SUPPORTED); + } + + // Check ray tracing support. + D3D12_FEATURE_DATA_D3D12_OPTIONS5 options5{}; + if (SUCCEEDED(tempDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &options5, sizeof(options5)))) { + f.rayTracing = (options5.RaytracingTier != D3D12_RAYTRACING_TIER_NOT_SUPPORTED); + } + + // Conservative limits for D3D12 feature level 12.0. + f.maxBindGroups = 32; + f.maxBindingsPerGroup = 1000000; + f.maxPushConstantSize = 128; + f.maxTextureDimension2D = 16384; + f.maxTextureArrayLayers = 2048; + f.maxComputeWorkgroupSizeX = 1024; + f.maxComputeWorkgroupSizeY = 1024; + f.maxComputeWorkgroupSizeZ = 64; + f.maxComputeWorkgroupsPerDimension = 65535; + f.maxBufferSize = static_cast(m_desc.DedicatedVideoMemory); + f.minUniformBufferOffsetAlignment = 256; + f.minStorageBufferOffsetAlignment = 16; + f.timestampPeriodNs = 1; // DX12 timestamps in ticks, period queried at runtime + + return f; + } + + Status createDevice(const DeviceDesc& desc, Device*& out) override; + + // ---- Internal ---- + [[nodiscard]] IDXGIAdapter1* handle() const { return m_adapter; } + [[nodiscard]] IDXGIFactory4* factory() const { return m_factory; } + [[nodiscard]] DXGI_ADAPTER_DESC1 adapterDesc() const { return m_desc; } + +private: + IDXGIAdapter1* m_adapter = nullptr; // owned, released in destructor + IDXGIFactory4* m_factory = nullptr; // not owned (Backend owns it) + DXGI_ADAPTER_DESC1 m_desc{}; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxBackend.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxBackend.cppm new file mode 100644 index 00000000..56e56084 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxBackend.cppm @@ -0,0 +1,138 @@ +/// DX12 implementation of Backend. +/// Creates DXGI factory, enumerates adapters, creates surfaces. + +module; + +#include "DxIncludes.h" +#include +#include + +#include + +export module rhi.dx12:backend; + +import core.stdtypes; +import core.status; +import rhi; +import :surface; +import :adapter; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +/// Configuration for DX12 backend creation. +struct DxBackendDesc { + bool enableValidation = false; +}; + +/// DX12 implementation of Backend. +class DxBackendImpl : public Backend { +public: + ~DxBackendImpl() override { destroyImpl(); } + + // ---- Backend interface ---- + + std::span enumerateAdapters() override { + return std::span(m_adapterPtrs.data(), m_adapterPtrs.size()); + } + + Status createSurface(void* windowHandle, void* /*displayHandle*/, Surface*& out) override { + out = nullptr; + if (!windowHandle) { + logError("DxBackend: window handle is null"); + return ErrorCode::InvalidArgument; + } + out = new DxSurfaceImpl(reinterpret_cast(windowHandle)); + return ErrorCode::Ok; + } + + void destroy() override { + destroyImpl(); + delete this; + } + + // ---- Internal ---- + [[nodiscard]] IDXGIFactory4* factory() const { return m_factory.Get(); } + [[nodiscard]] bool validationEnabled() const { return m_validationEnabled; } + +private: + friend Status createDxBackend(const DxBackendDesc& desc, Backend*& out); + + Status init(bool enableValidation) { + m_validationEnabled = enableValidation; + + // Enable debug layer before device creation. + if (m_validationEnabled) { + ComPtr debugController; + if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)))) { + debugController->EnableDebugLayer(); + } + } + + // Create DXGI factory. + UINT factoryFlags = m_validationEnabled ? DXGI_CREATE_FACTORY_DEBUG : 0; + HRESULT hr = CreateDXGIFactory2(factoryFlags, IID_PPV_ARGS(&m_factory)); + if (FAILED(hr)) { + logErrorf("DxBackend: CreateDXGIFactory2 failed (0x%08X)", static_cast(hr)); + return ErrorCode::Unknown; + } + + enumerateAdaptersInternal(); + isInitialized = true; + return ErrorCode::Ok; + } + + void enumerateAdaptersInternal() { + ComPtr adapter; + for (UINT i = 0; m_factory->EnumAdapters1(i, &adapter) != DXGI_ERROR_NOT_FOUND; ++i) { + DXGI_ADAPTER_DESC1 desc{}; + adapter->GetDesc1(&desc); + + // Skip software adapters. + if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) { + adapter.Reset(); + continue; + } + + // Check D3D12 feature level 12.0 support. + if (SUCCEEDED(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_12_0, __uuidof(ID3D12Device), nullptr))) { + auto* a = new DxAdapterImpl(adapter.Detach(), m_factory.Get()); + m_adapters.push_back(a); + m_adapterPtrs.push_back(a); + } + + adapter.Reset(); + } + + // Expose adapters best-GPU-first; callers take [0]. See Backend::enumerateAdapters. + sortAdaptersByPreference(m_adapterPtrs); + } + + void destroyImpl() { + for (auto* a : m_adapters) delete a; + m_adapters.clear(); + m_adapterPtrs.clear(); + m_factory.Reset(); + } + + ComPtr m_factory; + bool m_validationEnabled = false; + std::vector m_adapters; + std::vector m_adapterPtrs; +}; + +/// Creates a DX12 backend. Caller owns the returned pointer - dispose via destroy(). +[[nodiscard]] Status createDxBackend(const DxBackendDesc& desc, Backend*& out) { + out = nullptr; + auto* b = new DxBackendImpl(); + Status r = b->init(desc.enableValidation); + if (r != ErrorCode::Ok) { + delete b; + return r; + } + out = b; + return ErrorCode::Ok; +} + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxBindGroup.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxBindGroup.cppm new file mode 100644 index 00000000..8ffde992 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxBindGroup.cppm @@ -0,0 +1,213 @@ +/// DX12 implementation of BindGroup. +/// Allocates contiguous descriptor ranges in CPU-visible heaps and writes descriptors. + +module; + +#include "DxIncludes.h" +#include +#include + +export module rhi.dx12:bind_group; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :bind_group_layout; +import :gpu_descriptor_heap; +import :buffer; +import :texture; +import :texture_view; +import :sampler; +import :accel_struct; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxAccelStructImpl; // forward + +class DxBindGroupImpl : public BindGroup { +public: + Status init(ID3D12Device* device, const BindGroupDesc& d, + DxGpuDescriptorHeap* cpuSrvHeap, DxGpuDescriptorHeap* cpuSamplerHeap) { + m_device = device; + m_layout = static_cast(d.layout); + m_cpuSrvHeap = cpuSrvHeap; + m_cpuSamplerHeap = cpuSamplerHeap; + if (!m_layout) return ErrorCode::Unknown; + + // Cache counts so cleanup doesn't need to access m_layout (which may be destroyed first). + m_cachedCbvSrvUavCount = m_layout->cbvSrvUavCount(); + m_cachedSamplerCount = m_layout->samplerCount(); + + if (m_cachedCbvSrvUavCount > 0) { + m_cbvSrvUavOffset = cpuSrvHeap->allocate(m_cachedCbvSrvUavCount); + if (m_cbvSrvUavOffset < 0) return ErrorCode::Unknown; + } + if (m_cachedSamplerCount > 0) { + m_samplerOffset = cpuSamplerHeap->allocate(m_cachedSamplerCount); + if (m_samplerOffset < 0) return ErrorCode::Unknown; + } + + writeDescriptors(d); + return ErrorCode::Ok; + } + + BindGroupLayout* layout() override { return m_layout; } + + void updateBindless(std::span entries) override { + auto ranges = m_layout->ranges(); + for (usize i = 0; i < entries.size(); ++i) { + const auto& e = entries[i]; + if (e.layoutIndex >= static_cast(ranges.size())) continue; + const auto& r = ranges[e.layoutIndex]; + + BindGroupEntry bgEntry{}; + bgEntry.buffer = e.buffer; + bgEntry.bufferOffset= e.bufferOffset; + bgEntry.bufferSize = e.bufferSize; + bgEntry.textureView = e.textureView; + bgEntry.sampler = e.sampler; + + if (r.isSampler) + writeSampler(bgEntry, r, e.arrayIndex); + else + writeCbvSrvUav(bgEntry, r, e.arrayIndex); + } + } + + void cleanup() { + if (m_cbvSrvUavOffset >= 0 && m_cachedCbvSrvUavCount > 0) + m_cpuSrvHeap->free(static_cast(m_cbvSrvUavOffset), m_cachedCbvSrvUavCount); + if (m_samplerOffset >= 0 && m_cachedSamplerCount > 0) + m_cpuSamplerHeap->free(static_cast(m_samplerOffset), m_cachedSamplerCount); + m_cbvSrvUavOffset = -1; m_samplerOffset = -1; + } + + [[nodiscard]] i32 cbvSrvUavOffset() const { return m_cbvSrvUavOffset; } + [[nodiscard]] i32 samplerOffset() const { return m_samplerOffset; } + [[nodiscard]] std::span dynamicGpuAddresses() const { return { m_dynAddrs.data(), m_dynAddrs.size() }; } + +private: + void writeDescriptors(const BindGroupDesc& d) { + auto ranges = m_layout->ranges(); + usize entryIdx = 0; + for (usize i = 0; i < ranges.size(); ++i) { + const auto& r = ranges[i]; + switch (r.type) { + case BindingType::BindlessTextures: case BindingType::BindlessSamplers: + case BindingType::BindlessStorageBuffers: case BindingType::BindlessStorageTextures: + continue; + default: break; + } + if (entryIdx >= d.entries.size()) break; + const auto& e = d.entries[entryIdx++]; + + if (r.hasDynamicOffset) { + if (auto* buf = static_cast(e.buffer)) + m_dynAddrs.push_back(buf->gpuAddress() + e.bufferOffset); + else + m_dynAddrs.push_back(0); + continue; + } + if (r.isSampler) writeSampler(e, r); + else writeCbvSrvUav(e, r); + } + } + + void writeCbvSrvUav(const BindGroupEntry& e, const DxBindingRangeInfo& r, u32 arrayIdx = 0) { + u32 off = static_cast(m_cbvSrvUavOffset) + r.heapOffset + arrayIdx; + D3D12_CPU_DESCRIPTOR_HANDLE dest = m_cpuSrvHeap->getCpuHandle(off); + + switch (r.type) { + case BindingType::UniformBuffer: + if (auto* buf = static_cast(e.buffer)) { + D3D12_CONSTANT_BUFFER_VIEW_DESC cbv{}; + cbv.BufferLocation = buf->gpuAddress() + e.bufferOffset; + u64 sz = (e.bufferSize > 0) ? e.bufferSize : buf->desc.size; + cbv.SizeInBytes = static_cast((sz + 255) & ~u64(255)); + m_device->CreateConstantBufferView(&cbv, dest); + } + break; + case BindingType::StorageBufferReadOnly: + if (auto* buf = static_cast(e.buffer)) { + u64 sz = (e.bufferSize > 0) ? e.bufferSize : buf->desc.size; + D3D12_SHADER_RESOURCE_VIEW_DESC srv{}; + srv.ViewDimension = D3D12_SRV_DIMENSION_BUFFER; + srv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + if (r.storageBufferStride > 0) { + srv.Format = DXGI_FORMAT_UNKNOWN; + srv.Buffer.FirstElement = static_cast(e.bufferOffset / r.storageBufferStride); + srv.Buffer.NumElements = static_cast(sz / r.storageBufferStride); + srv.Buffer.StructureByteStride = r.storageBufferStride; + } else { + srv.Format = DXGI_FORMAT_R32_TYPELESS; + srv.Buffer.FirstElement = static_cast(e.bufferOffset / 4); + srv.Buffer.NumElements = static_cast(sz / 4); + srv.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_RAW; + } + m_device->CreateShaderResourceView(buf->handle(), &srv, dest); + } + break; + case BindingType::StorageBufferReadWrite: + if (auto* buf = static_cast(e.buffer)) { + u64 sz = (e.bufferSize > 0) ? e.bufferSize : buf->desc.size; + D3D12_UNORDERED_ACCESS_VIEW_DESC uav{}; + uav.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; + if (r.storageBufferStride > 0) { + uav.Format = DXGI_FORMAT_UNKNOWN; + uav.Buffer.FirstElement = static_cast(e.bufferOffset / r.storageBufferStride); + uav.Buffer.NumElements = static_cast(sz / r.storageBufferStride); + uav.Buffer.StructureByteStride = r.storageBufferStride; + } else { + uav.Format = DXGI_FORMAT_R32_TYPELESS; + uav.Buffer.FirstElement = static_cast(e.bufferOffset / 4); + uav.Buffer.NumElements = static_cast(sz / 4); + uav.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_RAW; + } + m_device->CreateUnorderedAccessView(buf->handle(), nullptr, &uav, dest); + } + break; + case BindingType::SampledTexture: case BindingType::BindlessTextures: + if (auto* v = static_cast(e.textureView)) + m_device->CopyDescriptorsSimple(1, dest, v->getSrv(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + break; + case BindingType::StorageTextureReadOnly: case BindingType::StorageTextureReadWrite: + case BindingType::BindlessStorageTextures: + if (auto* v = static_cast(e.textureView)) + m_device->CopyDescriptorsSimple(1, dest, v->getUav(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + break; + case BindingType::AccelerationStructure: + if (auto* dxAs = static_cast(e.accelStruct)) { + D3D12_SHADER_RESOURCE_VIEW_DESC asSrv{}; + asSrv.Format = DXGI_FORMAT_UNKNOWN; + asSrv.ViewDimension = D3D12_SRV_DIMENSION_RAYTRACING_ACCELERATION_STRUCTURE; + asSrv.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + asSrv.RaytracingAccelerationStructure.Location = dxAs->deviceAddress(); + m_device->CreateShaderResourceView(nullptr, &asSrv, dest); + } + break; + default: break; + } + } + + void writeSampler(const BindGroupEntry& e, const DxBindingRangeInfo& r, u32 arrayIdx = 0) { + u32 off = static_cast(m_samplerOffset) + r.heapOffset + arrayIdx; + D3D12_CPU_DESCRIPTOR_HANDLE dest = m_cpuSamplerHeap->getCpuHandle(off); + if (auto* s = static_cast(e.sampler)) + m_device->CopyDescriptorsSimple(1, dest, s->handle(), D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); + } + + ID3D12Device* m_device = nullptr; + DxBindGroupLayoutImpl* m_layout = nullptr; + DxGpuDescriptorHeap* m_cpuSrvHeap = nullptr; + DxGpuDescriptorHeap* m_cpuSamplerHeap = nullptr; + u32 m_cachedCbvSrvUavCount = 0; + u32 m_cachedSamplerCount = 0; + i32 m_cbvSrvUavOffset = -1; + i32 m_samplerOffset = -1; + std::vector m_dynAddrs; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxBindGroupLayout.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxBindGroupLayout.cppm new file mode 100644 index 00000000..d889eaf3 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxBindGroupLayout.cppm @@ -0,0 +1,86 @@ +/// DX12 implementation of BindGroupLayout. +/// Tracks descriptor range definitions and heap offset info. + +module; + +#include +#include + +#include + +export module rhi.dx12:bind_group_layout; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +struct DxBindingRangeInfo { + u32 binding = 0; + BindingType type = BindingType::UniformBuffer; + u32 count = 0; + u32 heapOffset = 0; + bool isSampler = false; + bool hasDynamicOffset = false; + u32 storageBufferStride = 0; +}; + +class DxBindGroupLayoutImpl : public BindGroupLayout { +public: + Status init(const BindGroupLayoutDesc& d) { + u32 csvUavOff = 0, sampOff = 0; + + for (usize i = 0; i < d.entries.size(); ++i) { + const auto& e = d.entries[i]; + m_entries.push_back(e); + + bool sampler = isSamplerBinding(e.type); + u32 cnt = e.count; + if (cnt == ~0u) { cnt = 1024 * 16; m_hasBindless = true; } + + DxBindingRangeInfo r{}; + r.binding = e.binding; + r.type = e.type; + r.count = cnt; + r.isSampler = sampler; + r.hasDynamicOffset = e.hasDynamicOffset; + r.storageBufferStride = e.storageBufferStride; + + if (e.hasDynamicOffset) { + r.heapOffset = 0; + ++m_dynamicOffsetCount; + } else if (sampler) { + r.heapOffset = sampOff; + sampOff += cnt; + } else { + r.heapOffset = csvUavOff; + csvUavOff += cnt; + } + m_ranges.push_back(r); + } + m_cbvSrvUavCount = csvUavOff; + m_samplerCount = sampOff; + return ErrorCode::Ok; + } + + [[nodiscard]] std::span entries() const { return { m_entries.data(), m_entries.size() }; } + [[nodiscard]] std::span ranges() const { return { m_ranges.data(), m_ranges.size() }; } + [[nodiscard]] u32 cbvSrvUavCount() const { return m_cbvSrvUavCount; } + [[nodiscard]] u32 samplerCount() const { return m_samplerCount; } + [[nodiscard]] u32 dynamicOffsetCount()const { return m_dynamicOffsetCount; } + [[nodiscard]] bool hasBindless() const { return m_hasBindless; } + +private: + std::vector m_entries; + std::vector m_ranges; + u32 m_cbvSrvUavCount = 0; + u32 m_samplerCount = 0; + u32 m_dynamicOffsetCount= 0; + bool m_hasBindless = false; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxBuffer.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxBuffer.cppm new file mode 100644 index 00000000..d2745a48 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxBuffer.cppm @@ -0,0 +1,94 @@ +/// DX12 implementation of Buffer. + +module; + +#include "DxIncludes.h" + +export module rhi.dx12:buffer; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxDeviceImpl; // forward + +class DxBufferImpl : public Buffer { +public: + Status init(ID3D12Device* device, const BufferDesc& d) { + desc = d; + + auto heapType = toHeapType(d.memory); + auto flags = toBufferResourceFlags(d.usage); + + // Initial state based on heap type. + m_state = D3D12_RESOURCE_STATE_COMMON; + if (heapType == D3D12_HEAP_TYPE_UPLOAD) m_state = D3D12_RESOURCE_STATE_GENERIC_READ; + if (heapType == D3D12_HEAP_TYPE_READBACK) m_state = D3D12_RESOURCE_STATE_COPY_DEST; + + u64 alignedSize = (d.size + 255) & ~u64(255); // 256-byte alignment for CBVs + + D3D12_HEAP_PROPERTIES heapProps{}; + heapProps.Type = heapType; + + D3D12_RESOURCE_DESC rd{}; + rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + rd.Width = alignedSize; + rd.Height = 1; + rd.DepthOrArraySize = 1; + rd.MipLevels = 1; + rd.Format = DXGI_FORMAT_UNKNOWN; + rd.SampleDesc = { 1, 0 }; + rd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + rd.Flags = flags; + + HRESULT hr = device->CreateCommittedResource( + &heapProps, D3D12_HEAP_FLAG_NONE, + &rd, m_state, nullptr, + IID_PPV_ARGS(&m_resource)); + if (FAILED(hr)) { + logErrorf("DxBuffer: CreateCommittedResource failed (0x%08X)", static_cast(hr)); + return ErrorCode::Unknown; + } + + // Persistently map upload/readback buffers. + if (heapType == D3D12_HEAP_TYPE_UPLOAD || heapType == D3D12_HEAP_TYPE_READBACK) + m_resource->Map(0, nullptr, &m_persistentMap); + + return ErrorCode::Ok; + } + + void* map() override { + if (m_persistentMap) return m_persistentMap; + void* ptr = nullptr; + if (SUCCEEDED(m_resource->Map(0, nullptr, &ptr))) return ptr; + return nullptr; + } + + void unmap() override { + if (m_persistentMap) return; // don't unmap persistently mapped buffers + m_resource->Unmap(0, nullptr); + } + + void cleanup() { + if (m_persistentMap) { m_resource->Unmap(0, nullptr); m_persistentMap = nullptr; } + m_resource.Reset(); + } + + // ---- Internal ---- + [[nodiscard]] ID3D12Resource* handle() const { return m_resource.Get(); } + [[nodiscard]] D3D12_RESOURCE_STATES currentState() const { return m_state; } + void setState(D3D12_RESOURCE_STATES s) { m_state = s; } + [[nodiscard]] D3D12_GPU_VIRTUAL_ADDRESS gpuAddress() const { return m_resource ? m_resource->GetGPUVirtualAddress() : 0; } + +private: + ComPtr m_resource; + D3D12_RESOURCE_STATES m_state = D3D12_RESOURCE_STATE_COMMON; + void* m_persistentMap = nullptr; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxCommandBuffer.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxCommandBuffer.cppm new file mode 100644 index 00000000..723bb906 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxCommandBuffer.cppm @@ -0,0 +1,32 @@ +/// DX12 implementation of CommandBuffer. +/// Simple wrapper for a closed ID3D12GraphicsCommandList. + +module; + +#include "DxIncludes.h" + +export module rhi.dx12:command_buffer; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxCommandBufferImpl : public CommandBuffer { +public: + explicit DxCommandBufferImpl(ID3D12GraphicsCommandList* cmdList) : m_cmdList(cmdList) {} + + [[nodiscard]] ID3D12GraphicsCommandList* handle() const { return m_cmdList; } + + void release() { + if (m_cmdList) { m_cmdList->Release(); m_cmdList = nullptr; } + } + +private: + ID3D12GraphicsCommandList* m_cmdList = nullptr; // raw, released via release() +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxCommandEncoder.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxCommandEncoder.cppm new file mode 100644 index 00000000..f85c4cd9 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxCommandEncoder.cppm @@ -0,0 +1,927 @@ +/// DX12 implementation of CommandEncoder + RayTracingEncoderExt. +/// Wraps an ID3D12GraphicsCommandList for recording commands. + +module; + +#include "DxIncludes.h" +#include +#include +#include + +#include +#include + +export module rhi.dx12:command_encoder; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :buffer; +import :texture; +import :texture_view; +import :bind_group; +import :bind_group_layout; +import :pipeline_layout; +import :query_set; +import :command_buffer; +import :command_pool; +import :descriptor_heap; +import :gpu_descriptor_heap; +import :descriptor_staging; +import :render_pipeline; +import :render_bundle_encoder; +import :compute_pipeline; +import :accel_struct; +import :ray_tracing_pipeline; +import :render_pass_encoder; +import :compute_pass_encoder; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxDeviceImpl; // forward + +/// DX12 implementation of CommandEncoder and RayTracingEncoderExt. +/// Wraps an ID3D12GraphicsCommandList for recording commands outside of +/// render/compute passes (barriers, copies, queries, RT builds). +class DxCommandEncoderImpl : public CommandEncoder, public RayTracingEncoderExt { +public: + RayTracingEncoderExt* asRayTracingExt() noexcept override { return this; } + DxCommandEncoderImpl(DxDeviceImpl* device, ID3D12GraphicsCommandList* cmdList, + DxCommandPoolImpl* pool, const DxRenderPassContext& rpeCtx, + const DxComputePassContext& cpeCtx) + : m_device(device), m_cmdList(cmdList), m_pool(pool), + m_gpuSrvHeap(rpeCtx.gpuSrvHeap), m_gpuSamplerHeap(rpeCtx.gpuSamplerHeap), + m_rpe(rpeCtx), m_cpe(cpeCtx), m_rpeCtx(rpeCtx) {} + + ~DxCommandEncoderImpl() override { for (auto* e : m_bundleEncoders) delete e; } + + // ================================================================ + // CommandEncoder interface + // ================================================================ + + // ---- Render Pass ---- + + RenderPassEncoder* beginRenderPass(const RenderPassDesc& desc) override { + ensureDescriptorHeaps(); + + // Timestamp at pass begin. + if (desc.timestampQuerySet) { + if (auto* qs = static_cast(desc.timestampQuerySet)) + m_cmdList->EndQuery(qs->handle(), D3D12_QUERY_TYPE_TIMESTAMP, desc.beginTimestampIndex); + } + + // Collect render target views. + D3D12_CPU_DESCRIPTOR_HANDLE rtvHandles[8]{}; + u32 rtvCount = 0; + const auto& colorAtts = desc.colorAttachments; + for (usize i = 0; i < colorAtts.size() && i < 8; ++i) { + const auto& att = colorAtts[i]; + if (auto* dxView = static_cast(att.view)) { + rtvHandles[i] = dxView->getRtv(); + ++rtvCount; + } + } + + // Depth/stencil view. + D3D12_CPU_DESCRIPTOR_HANDLE dsvStorage{}; + D3D12_CPU_DESCRIPTOR_HANDLE* dsvPtr = nullptr; + if (desc.depthStencilAttachment.has_value()) { + const auto& dsAttach = *desc.depthStencilAttachment; + if (auto* dxView = static_cast(dsAttach.view)) { + dsvStorage = dxView->getDsv(); + dsvPtr = &dsvStorage; + } + } + + m_cmdList->OMSetRenderTargets(rtvCount, rtvHandles, FALSE, dsvPtr); + + // Clear render targets. + for (usize i = 0; i < colorAtts.size() && i < 8; ++i) { + const auto& att = colorAtts[i]; + if (att.loadOp == LoadOp::Clear) { + FLOAT color[4] = { att.clearValue.r, att.clearValue.g, att.clearValue.b, att.clearValue.a }; + m_cmdList->ClearRenderTargetView(rtvHandles[i], color, 0, nullptr); + } + } + + // Clear depth/stencil. + if (desc.depthStencilAttachment.has_value() && dsvPtr) { + const auto& dsAttach = *desc.depthStencilAttachment; + D3D12_CLEAR_FLAGS clearFlags = static_cast(0); + bool needsClear = false; + if (dsAttach.depthLoadOp == LoadOp::Clear) { + clearFlags = static_cast( + static_cast(clearFlags) | static_cast(D3D12_CLEAR_FLAG_DEPTH)); + needsClear = true; + } + if (dsAttach.stencilLoadOp == LoadOp::Clear) { + clearFlags = static_cast( + static_cast(clearFlags) | static_cast(D3D12_CLEAR_FLAG_STENCIL)); + needsClear = true; + } + if (needsClear) + m_cmdList->ClearDepthStencilView(*dsvPtr, clearFlags, + dsAttach.depthClearValue, static_cast(dsAttach.stencilClearValue), 0, nullptr); + } + + m_rpe.begin(desc); + return &m_rpe; + } + + // ---- Compute Pass ---- + + ComputePassEncoder* beginComputePass(std::u8string_view) override { + ensureDescriptorHeaps(); + m_cpe.begin(); + return &m_cpe; + } + + RenderBundleEncoder* createRenderBundleEncoder(const RenderBundleDesc& /*desc*/) override { + ComPtr dev; + m_cmdList->GetDevice(IID_PPV_ARGS(&dev)); + if (!dev) return nullptr; + ComPtr alloc; + if (FAILED(dev->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_BUNDLE, IID_PPV_ARGS(&alloc)))) return nullptr; + ComPtr list; + if (FAILED(dev->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_BUNDLE, alloc.Get(), nullptr, IID_PPV_ARGS(&list)))) + return nullptr; + + // Bundles inherit the parent's descriptor heaps; bind matching heaps on the bundle. + ensureDescriptorHeaps(); + ID3D12DescriptorHeap* heaps[2]; UINT n = 0; + if (m_gpuSrvHeap) heaps[n++] = m_gpuSrvHeap->heap(); + if (m_gpuSamplerHeap) heaps[n++] = m_gpuSamplerHeap->heap(); + if (n > 0) list->SetDescriptorHeaps(n, heaps); + + DxRenderPassContext ctx = m_rpeCtx; + ctx.cmdList = list.Get(); + auto* enc = new DxRenderBundleEncoderImpl(ctx, list, alloc); + m_bundleEncoders.push_back(enc); // owned: freed in this encoder's destructor + return enc; + } + + // ---- Barriers ---- + + void barrier(const BarrierGroup& group) override { + usize totalBarriers = group.bufferBarriers.size() + group.textureBarriers.size() + + group.memoryBarriers.size(); + if (totalBarriers == 0) return; + + std::vector dxBarriers; + dxBarriers.reserve(totalBarriers); + + // Buffer barriers. + for (usize i = 0; i < group.bufferBarriers.size(); ++i) { + const auto& bb = group.bufferBarriers[i]; + auto* dxBuf = static_cast(bb.buffer); + if (!dxBuf) continue; + + auto oldState = toResourceStates(bb.oldState); + auto newState = toResourceStates(bb.newState); + if (oldState == newState) continue; + + D3D12_RESOURCE_BARRIER b{}; + b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + b.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + b.Transition.pResource = dxBuf->handle(); + b.Transition.StateBefore = oldState; + b.Transition.StateAfter = newState; + b.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + dxBuf->setState(newState); + dxBarriers.push_back(b); + } + + // Texture barriers: coalesce chained transitions per (resource, subresource). + // The barrier solver can emit multiple barriers for the same texture in one + // batch (e.g. ParticlePass declares both ReadDepth and ReadTexture on SceneDepth, + // producing DEPTH_WRITE->DEPTH_READ then DEPTH_READ->SHADER_READ). D3D12 processes + // all barriers in one ResourceBarrier() call simultaneously, so the second barrier's + // StateBefore won't match reality. Fix: coalesce A->B, B->C into a single A->C. + { + struct CoalescedEntry { + ID3D12Resource* resource; + u32 subresource; + D3D12_RESOURCE_STATES firstBefore; + D3D12_RESOURCE_STATES lastAfter; + }; + std::vector coalesced; + coalesced.reserve(group.textureBarriers.size()); + + for (usize i = 0; i < group.textureBarriers.size(); ++i) { + const auto& tb = group.textureBarriers[i]; + auto* dxTex = static_cast(tb.texture); + if (!dxTex) continue; + + auto newState = toResourceStates(tb.newState, dxTex->desc.format); + bool isWholeResource = (tb.mipLevelCount == ~0u) && (tb.arrayLayerCount == ~0u); + + if (isWholeResource) { + auto resolvedOldState = dxTex->currentState(); + if (resolvedOldState == newState) continue; + + // Check if we already have an entry for this resource+subresource. + bool found = false; + for (auto& entry : coalesced) { + if (entry.resource == dxTex->handle() + && entry.subresource == D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES) { + entry.lastAfter = newState; + found = true; + break; + } + } + if (!found) + coalesced.push_back({ dxTex->handle(), D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, + resolvedOldState, newState }); + + dxTex->setState(newState); + } else { + // Per-subresource: expand range and coalesce each subresource individually. + u32 mipCount = dxTex->desc.mipLevelCount; + u32 layerCount = std::max( + (dxTex->desc.dimension == TextureDimension::Texture3D) + ? dxTex->desc.depth : dxTex->desc.arrayLayerCount, 1u); + u32 baseMip = tb.baseMipLevel; + u32 mipEnd = std::min(baseMip + tb.mipLevelCount, mipCount); + u32 baseLayer= tb.baseArrayLayer; + u32 layerEnd = std::min(baseLayer + tb.arrayLayerCount, layerCount); + + for (u32 layer = baseLayer; layer < layerEnd; ++layer) { + for (u32 mip = baseMip; mip < mipEnd; ++mip) { + auto resolvedOldState = dxTex->getSubresourceState(mip, layer); + u32 sub = mip + layer * mipCount; + if (resolvedOldState == newState) continue; + + bool found = false; + for (auto& entry : coalesced) { + if (entry.resource == dxTex->handle() && entry.subresource == sub) { + entry.lastAfter = newState; + found = true; + break; + } + } + if (!found) + coalesced.push_back({ dxTex->handle(), sub, resolvedOldState, newState }); + } + } + dxTex->setSubresourceState(baseMip, tb.mipLevelCount, baseLayer, tb.arrayLayerCount, newState); + } + } + + // Phase 2: emit one D3D12 barrier per coalesced entry. + for (const auto& entry : coalesced) { + if (entry.firstBefore == entry.lastAfter) continue; // A->B->A cancelled out + D3D12_RESOURCE_BARRIER b{}; + b.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + b.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + b.Transition.pResource = entry.resource; + b.Transition.StateBefore = entry.firstBefore; + b.Transition.StateAfter = entry.lastAfter; + b.Transition.Subresource = entry.subresource; + dxBarriers.push_back(b); + } + } + + // Memory barriers -> UAV barriers. + for (usize i = 0; i < group.memoryBarriers.size(); ++i) { + D3D12_RESOURCE_BARRIER b{}; + b.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; + b.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + b.UAV.pResource = nullptr; // global UAV barrier + dxBarriers.push_back(b); + } + + if (!dxBarriers.empty()) + m_cmdList->ResourceBarrier(static_cast(dxBarriers.size()), dxBarriers.data()); + } + + // ---- Copy Operations ---- + + void copyBufferToBuffer(Buffer* src, u64 srcOffset, Buffer* dst, u64 dstOffset, u64 size) override { + auto* dxSrc = static_cast(src); + auto* dxDst = static_cast(dst); + if (!dxSrc || !dxDst) return; + m_cmdList->CopyBufferRegion(dxDst->handle(), dstOffset, dxSrc->handle(), srcOffset, size); + } + + void copyBufferToTexture(Buffer* src, Texture* dst, const BufferTextureCopyRegion& region) override { + auto* dxSrc = static_cast(src); + auto* dxTex = static_cast(dst); + if (!dxSrc || !dxTex) return; + + u32 subresource = region.textureMipLevel + region.textureArrayLayer * dxTex->desc.mipLevelCount; + + D3D12_TEXTURE_COPY_LOCATION srcLoc{}; + srcLoc.pResource = dxSrc->handle(); + srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + srcLoc.PlacedFootprint.Offset = region.bufferOffset; + srcLoc.PlacedFootprint.Footprint.Format = toDxgiFormat(dxTex->desc.format); + srcLoc.PlacedFootprint.Footprint.Width = region.textureExtent.width; + srcLoc.PlacedFootprint.Footprint.Height = region.textureExtent.height; + srcLoc.PlacedFootprint.Footprint.Depth = region.textureExtent.depth; + srcLoc.PlacedFootprint.Footprint.RowPitch = region.bytesPerRow; + + D3D12_TEXTURE_COPY_LOCATION dstLoc{}; + dstLoc.pResource = dxTex->handle(); + dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dstLoc.SubresourceIndex = subresource; + + m_cmdList->CopyTextureRegion(&dstLoc, + region.textureOrigin.x, region.textureOrigin.y, region.textureOrigin.z, + &srcLoc, nullptr); + } + + void copyTextureToBuffer(Texture* src, Buffer* dst, const BufferTextureCopyRegion& region) override { + auto* dxTex = static_cast(src); + auto* dxDst = static_cast(dst); + if (!dxTex || !dxDst) return; + + u32 subresource = region.textureMipLevel + region.textureArrayLayer * dxTex->desc.mipLevelCount; + + D3D12_TEXTURE_COPY_LOCATION srcLoc{}; + srcLoc.pResource = dxTex->handle(); + srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + srcLoc.SubresourceIndex = subresource; + + D3D12_TEXTURE_COPY_LOCATION dstLoc{}; + dstLoc.pResource = dxDst->handle(); + dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + dstLoc.PlacedFootprint.Offset = region.bufferOffset; + dstLoc.PlacedFootprint.Footprint.Format = toDxgiFormat(dxTex->desc.format); + dstLoc.PlacedFootprint.Footprint.Width = region.textureExtent.width; + dstLoc.PlacedFootprint.Footprint.Height = region.textureExtent.height; + dstLoc.PlacedFootprint.Footprint.Depth = region.textureExtent.depth; + dstLoc.PlacedFootprint.Footprint.RowPitch = region.bytesPerRow; + + D3D12_BOX srcBox{}; + srcBox.left = region.textureOrigin.x; + srcBox.top = region.textureOrigin.y; + srcBox.front = region.textureOrigin.z; + srcBox.right = region.textureOrigin.x + region.textureExtent.width; + srcBox.bottom = region.textureOrigin.y + region.textureExtent.height; + srcBox.back = region.textureOrigin.z + region.textureExtent.depth; + + m_cmdList->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, &srcBox); + } + + void copyTextureToTexture(Texture* src, Texture* dst, const TextureCopyRegion& region) override { + auto* dxSrc = static_cast(src); + auto* dxDst = static_cast(dst); + if (!dxSrc || !dxDst) return; + + u32 srcSub = region.srcMipLevel + region.srcArrayLayer * dxSrc->desc.mipLevelCount; + u32 dstSub = region.dstMipLevel + region.dstArrayLayer * dxDst->desc.mipLevelCount; + + D3D12_TEXTURE_COPY_LOCATION srcLoc{}; + srcLoc.pResource = dxSrc->handle(); + srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + srcLoc.SubresourceIndex = srcSub; + + D3D12_TEXTURE_COPY_LOCATION dstLoc{}; + dstLoc.pResource = dxDst->handle(); + dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dstLoc.SubresourceIndex = dstSub; + + D3D12_BOX srcBox{}; + srcBox.left = 0; + srcBox.top = 0; + srcBox.front = 0; + srcBox.right = region.extent.width; + srcBox.bottom = region.extent.height; + srcBox.back = region.extent.depth; + + m_cmdList->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, &srcBox); + } + + // ---- Blit & Mipmap Generation ---- + + void blit(Texture* src, Texture* dst) override { + auto* dxSrc = static_cast(src); + auto* dxDst = static_cast(dst); + if (!dxSrc || !dxDst) return; + + DXGI_FORMAT dxgiFormat = toDxgiFormat(dxDst->desc.format); + + // Caller has set textures to CopySrc/CopyDst. Internally transition to SRV/RTV for blit. + D3D12_RESOURCE_BARRIER barriers[2]{}; + + barriers[0].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barriers[0].Transition.pResource = dxSrc->handle(); + barriers[0].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE; + barriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE + | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; + + barriers[1].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barriers[1].Transition.pResource = dxDst->handle(); + barriers[1].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barriers[1].Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; + barriers[1].Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; + + m_cmdList->ResourceBarrier(2, barriers); + + blitSubresource(dxSrc, 0, dxDst, 0, dxDst->desc.width, dxDst->desc.height, dxgiFormat); + + // Transition back to CopySrc/CopyDst. + barriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE + | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; + barriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE; + barriers[1].Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; + barriers[1].Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST; + + m_cmdList->ResourceBarrier(2, barriers); + } + + void generateMipmaps(Texture* texture) override { + auto* dxTex = static_cast(texture); + if (!dxTex) return; + + const auto& d = dxTex->desc; + if (d.mipLevelCount <= 1) return; + + DXGI_FORMAT dxgiFormat = toDxgiFormat(d.format); + + // Texture enters in CopySrc+CopyDst state (DX12: all subresources in COPY_SOURCE). + // For each mip: transition src->SRV, dst->RTV, blit, restore both->COPY_SOURCE. + for (u32 mip = 1; mip < d.mipLevelCount; ++mip) { + u32 dstWidth = std::max(1u, d.width >> mip); + u32 dstHeight = std::max(1u, d.height >> mip); + + D3D12_RESOURCE_BARRIER barriers[2]{}; + + barriers[0].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barriers[0].Transition.pResource = dxTex->handle(); + barriers[0].Transition.Subresource = mip - 1; + barriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE; + barriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE + | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; + + barriers[1].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barriers[1].Transition.pResource = dxTex->handle(); + barriers[1].Transition.Subresource = mip; + barriers[1].Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE; + barriers[1].Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; + + m_cmdList->ResourceBarrier(2, barriers); + + blitSubresource(dxTex, mip - 1, dxTex, mip, dstWidth, dstHeight, dxgiFormat); + + // Post-blit: restore both to COPY_SOURCE. + barriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE + | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE; + barriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE; + barriers[1].Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; + barriers[1].Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE; + + m_cmdList->ResourceBarrier(2, barriers); + } + } + + // ---- MSAA Resolve ---- + + void resolveTexture(Texture* src, Texture* dst) override { + auto* dxSrc = static_cast(src); + auto* dxDst = static_cast(dst); + if (!dxSrc || !dxDst) return; + + DXGI_FORMAT dxgiFormat = toDxgiFormat(dxDst->desc.format); + + // Transition src -> RESOLVE_SOURCE, dst -> RESOLVE_DEST. + D3D12_RESOURCE_BARRIER barriers[2]{}; + + barriers[0].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barriers[0].Transition.pResource = dxSrc->handle(); + barriers[0].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_SOURCE; + barriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_RESOLVE_SOURCE; + + barriers[1].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barriers[1].Transition.pResource = dxDst->handle(); + barriers[1].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + barriers[1].Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; + barriers[1].Transition.StateAfter = D3D12_RESOURCE_STATE_RESOLVE_DEST; + + m_cmdList->ResourceBarrier(2, barriers); + + m_cmdList->ResolveSubresource(dxDst->handle(), 0, dxSrc->handle(), 0, dxgiFormat); + + // Transition back. + barriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_RESOLVE_SOURCE; + barriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_SOURCE; + barriers[1].Transition.StateBefore = D3D12_RESOURCE_STATE_RESOLVE_DEST; + barriers[1].Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST; + + m_cmdList->ResourceBarrier(2, barriers); + } + + // ---- Queries ---- + + void resetQuerySet(QuerySet*, u32, u32) override { + // DX12 does not require explicit query reset -- queries are implicitly reset when written. + } + + void writeTimestamp(QuerySet* querySet, u32 index) override { + if (auto* qs = static_cast(querySet)) + m_cmdList->EndQuery(qs->handle(), D3D12_QUERY_TYPE_TIMESTAMP, index); + } + + void resolveQuerySet(QuerySet* querySet, u32 first, u32 count, Buffer* dst, u64 dstOffset) override { + auto* qs = static_cast(querySet); + auto* dxDst = static_cast(dst); + if (!qs || !dxDst) return; + m_cmdList->ResolveQueryData(qs->handle(), DxQuerySetImpl::toDxQueryType(qs->type), + first, count, dxDst->handle(), dstOffset); + } + + // ---- Debug Labels ---- + + void beginDebugLabel(std::u8string_view, f32, f32, f32, f32) override { + // PIX events would go here; no-op without PIX runtime. + } + + void endDebugLabel() override {} + + void insertDebugLabel(std::u8string_view, f32, f32, f32, f32) override {} + + // ---- Finish ---- + + CommandBuffer* finish() override { + m_cmdList->Close(); + auto* cb = new DxCommandBufferImpl(m_cmdList); + m_pool->trackCommandBuffer(cb); + return cb; + } + + // ================================================================ + // RayTracingEncoderExt interface + // ================================================================ + + void buildBottomLevelAccelStruct(AccelStruct* dst, Buffer* scratchBuffer, u64 scratchOffset, + std::span tris, + std::span aabbs) override + { + auto* dxAs = static_cast(dst); + auto* dxScratch = static_cast(scratchBuffer); + if (!dxAs || !dxScratch) return; + + // Query ID3D12GraphicsCommandList4 for RT support. + ComPtr cmdList4; + if (FAILED(m_cmdList->QueryInterface(IID_PPV_ARGS(&cmdList4))) || !cmdList4) return; + + usize totalGeoms = tris.size() + aabbs.size(); + std::vector geomDescs(totalGeoms); + usize idx = 0; + + for (usize i = 0; i < tris.size(); ++i) { + const auto& t = tris[i]; + geomDescs[idx] = {}; + geomDescs[idx].Type = D3D12_RAYTRACING_GEOMETRY_TYPE_TRIANGLES; + geomDescs[idx].Flags = toGeometryFlags(t.flags); + + auto& tri = geomDescs[idx].Triangles; + + if (auto* vb = static_cast(t.vertexBuffer)) { + tri.VertexBuffer.StartAddress = vb->gpuAddress() + t.vertexOffset; + tri.VertexBuffer.StrideInBytes = t.vertexStride; + tri.VertexCount = t.vertexCount; + tri.VertexFormat = toDxgiVertexFormat(t.vertexFormat); + } + + if (t.indexBuffer) { + if (auto* ib = static_cast(t.indexBuffer)) { + tri.IndexBuffer = ib->gpuAddress() + t.indexOffset; + tri.IndexCount = t.indexCount; + tri.IndexFormat = (t.indexFormat == IndexFormat::UInt16) + ? DXGI_FORMAT_R16_UINT : DXGI_FORMAT_R32_UINT; + } + } else { + tri.IndexFormat = DXGI_FORMAT_UNKNOWN; + } + + if (t.transformBuffer) { + if (auto* tb = static_cast(t.transformBuffer)) + tri.Transform3x4 = tb->gpuAddress() + t.transformOffset; + } + + ++idx; + } + + for (usize i = 0; i < aabbs.size(); ++i) { + const auto& a = aabbs[i]; + geomDescs[idx] = {}; + geomDescs[idx].Type = D3D12_RAYTRACING_GEOMETRY_TYPE_PROCEDURAL_PRIMITIVE_AABBS; + geomDescs[idx].Flags = toGeometryFlags(a.flags); + + if (auto* ab = static_cast(a.aabbBuffer)) { + geomDescs[idx].AABBs.AABBs.StartAddress = ab->gpuAddress() + a.offset; + geomDescs[idx].AABBs.AABBs.StrideInBytes = a.stride; + geomDescs[idx].AABBs.AABBCount = a.count; + } + + ++idx; + } + + D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC buildDesc{}; + buildDesc.DestAccelerationStructureData = dxAs->deviceAddress(); + buildDesc.ScratchAccelerationStructureData = dxScratch->gpuAddress() + scratchOffset; + buildDesc.Inputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL; + buildDesc.Inputs.Flags = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_TRACE; + buildDesc.Inputs.NumDescs = static_cast(totalGeoms); + buildDesc.Inputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY; + buildDesc.Inputs.pGeometryDescs = geomDescs.data(); + + cmdList4->BuildRaytracingAccelerationStructure(&buildDesc, 0, nullptr); + } + + void buildTopLevelAccelStruct(AccelStruct* dst, Buffer* scratchBuffer, u64 scratchOffset, + Buffer* instanceBuffer, u64 instanceOffset, u32 instanceCount) override + { + auto* dxAs = static_cast(dst); + auto* dxScratch = static_cast(scratchBuffer); + auto* dxInstances = static_cast(instanceBuffer); + if (!dxAs || !dxScratch || !dxInstances) return; + + ComPtr cmdList4; + if (FAILED(m_cmdList->QueryInterface(IID_PPV_ARGS(&cmdList4))) || !cmdList4) return; + + D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC buildDesc{}; + buildDesc.DestAccelerationStructureData = dxAs->deviceAddress(); + buildDesc.ScratchAccelerationStructureData = dxScratch->gpuAddress() + scratchOffset; + buildDesc.Inputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL; + buildDesc.Inputs.Flags = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_TRACE; + buildDesc.Inputs.NumDescs = instanceCount; + buildDesc.Inputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY; + buildDesc.Inputs.InstanceDescs = dxInstances->gpuAddress() + instanceOffset; + + cmdList4->BuildRaytracingAccelerationStructure(&buildDesc, 0, nullptr); + } + + void setRayTracingPipeline(RayTracingPipeline* pipeline) override { + m_currentRtPipeline = static_cast(pipeline); + if (!m_currentRtPipeline) return; + + ensureDescriptorHeaps(); + + ComPtr cmdList4; + if (SUCCEEDED(m_cmdList->QueryInterface(IID_PPV_ARGS(&cmdList4))) && cmdList4) + cmdList4->SetPipelineState1(m_currentRtPipeline->handle()); + + // RT uses compute root signature binding. + if (auto* layout = m_currentRtPipeline->pipelineLayout()) + m_cmdList->SetComputeRootSignature(layout->handle()); + } + + void setBindGroup(u32 index, BindGroup* group, std::span dynamicOffsets) override { + auto* dxGroup = static_cast(group); + if (!dxGroup || !m_currentRtPipeline) return; + + auto* layout = m_currentRtPipeline->pipelineLayout(); + if (!layout) return; + + auto* dxLayout = static_cast(dxGroup->layout()); + + // RT uses compute root signature binding -- copy-on-bind staging. + if (dxGroup->cbvSrvUavOffset() >= 0 && dxLayout && dxLayout->cbvSrvUavCount() > 0) { + i32 rootIdx = layout->getCbvSrvUavRootIndex(index); + if (rootIdx >= 0) { + i32 stagedOffset = m_pool->srvStaging()->copyFrom( + static_cast(dxGroup->cbvSrvUavOffset()), dxLayout->cbvSrvUavCount()); + if (stagedOffset >= 0) { + auto gpuHandle = m_gpuSrvHeap->getGpuHandle(static_cast(stagedOffset)); + m_cmdList->SetComputeRootDescriptorTable(static_cast(rootIdx), gpuHandle); + } + } + } + + if (dxGroup->samplerOffset() >= 0 && dxLayout && dxLayout->samplerCount() > 0) { + i32 rootIdx = layout->getSamplerRootIndex(index); + if (rootIdx >= 0) { + i32 stagedOffset = m_pool->samplerStaging()->copyFrom( + static_cast(dxGroup->samplerOffset()), dxLayout->samplerCount()); + if (stagedOffset >= 0) { + auto gpuHandle = m_gpuSamplerHeap->getGpuHandle(static_cast(stagedOffset)); + m_cmdList->SetComputeRootDescriptorTable(static_cast(rootIdx), gpuHandle); + } + } + } + + // Dynamic root entries. + auto dynEntries = layout->dynamicRootEntries(); + auto dynAddrs = dxGroup->dynamicGpuAddresses(); + usize dynOffsetIdx = 0; + for (usize i = 0; i < dynEntries.size(); ++i) { + const auto& entry = dynEntries[i]; + if (entry.groupIndex != index) continue; + if (entry.dynamicIndex >= dynAddrs.size()) continue; + + u64 gpuAddr = dynAddrs[entry.dynamicIndex]; + if (dynOffsetIdx < dynamicOffsets.size()) + gpuAddr += static_cast(dynamicOffsets[dynOffsetIdx]); + ++dynOffsetIdx; + + switch (entry.paramType) { + case D3D12_ROOT_PARAMETER_TYPE_CBV: + m_cmdList->SetComputeRootConstantBufferView(static_cast(entry.rootParamIndex), gpuAddr); + break; + case D3D12_ROOT_PARAMETER_TYPE_SRV: + m_cmdList->SetComputeRootShaderResourceView(static_cast(entry.rootParamIndex), gpuAddr); + break; + case D3D12_ROOT_PARAMETER_TYPE_UAV: + m_cmdList->SetComputeRootUnorderedAccessView(static_cast(entry.rootParamIndex), gpuAddr); + break; + default: break; + } + } + } + + void setPushConstants(ShaderStage, u32 offset, u32 size, const void* data) override { + if (!m_currentRtPipeline) return; + auto* layout = m_currentRtPipeline->pipelineLayout(); + if (!layout || layout->pushConstantRootIndex() < 0) return; + + m_cmdList->SetComputeRoot32BitConstants( + static_cast(layout->pushConstantRootIndex()), + size / 4, data, offset / 4); + } + + void traceRays(Buffer* raygenSBT, u64 raygenOffset, u64 raygenStride, + Buffer* missSBT, u64 missOffset, u64 missStride, + Buffer* hitSBT, u64 hitOffset, u64 hitStride, + u32 width, u32 height, u32 depth) override + { + ComPtr cmdList4; + if (FAILED(m_cmdList->QueryInterface(IID_PPV_ARGS(&cmdList4))) || !cmdList4) return; + + D3D12_DISPATCH_RAYS_DESC dispatchDesc{}; + + // Raygen -- single record. + if (auto* dxBuf = static_cast(raygenSBT)) { + dispatchDesc.RayGenerationShaderRecord.StartAddress = dxBuf->gpuAddress() + raygenOffset; + dispatchDesc.RayGenerationShaderRecord.SizeInBytes = raygenStride; + } + + // Miss. + if (missSBT) { + if (auto* dxBuf = static_cast(missSBT)) { + dispatchDesc.MissShaderTable.StartAddress = dxBuf->gpuAddress() + missOffset; + dispatchDesc.MissShaderTable.StrideInBytes = missStride; + dispatchDesc.MissShaderTable.SizeInBytes = missStride; // assume single entry + } + } + + // Hit group. + if (hitSBT) { + if (auto* dxBuf = static_cast(hitSBT)) { + dispatchDesc.HitGroupTable.StartAddress = dxBuf->gpuAddress() + hitOffset; + dispatchDesc.HitGroupTable.StrideInBytes = hitStride; + dispatchDesc.HitGroupTable.SizeInBytes = hitStride; // assume single entry + } + } + + dispatchDesc.Width = width; + dispatchDesc.Height = height; + dispatchDesc.Depth = depth; + + cmdList4->DispatchRays(&dispatchDesc); + } + + // ================================================================ + // Internal accessors + // ================================================================ + + [[nodiscard]] ID3D12GraphicsCommandList* cmdList() const { return m_cmdList; } + [[nodiscard]] DxDeviceImpl* ownerDevice() const { return m_device; } + [[nodiscard]] DxDescriptorStaging* srvStaging() { return m_pool->srvStaging(); } + [[nodiscard]] DxDescriptorStaging* samplerStaging() { return m_pool->samplerStaging(); } + + // ================================================================ + // Static helpers + // ================================================================ + + /// Convert RHI ResourceState to D3D12_RESOURCE_STATES. + static D3D12_RESOURCE_STATES toResourceStates(ResourceState state) { + return toResourceStates(state, TextureFormat::Undefined); + } + + /// Convert RHI ResourceState to D3D12_RESOURCE_STATES, considering the texture format. + /// For depth formats, ShaderRead maps to DEPTH_READ instead of PIXEL_SHADER_RESOURCE. + static D3D12_RESOURCE_STATES toResourceStates(ResourceState state, TextureFormat format) { + if (state == ResourceState::Undefined) + return D3D12_RESOURCE_STATE_COMMON; + + D3D12_RESOURCE_STATES result = D3D12_RESOURCE_STATE_COMMON; + + if (hasFlag(state, ResourceState::VertexBuffer)) + result = static_cast(result | D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER); + if (hasFlag(state, ResourceState::IndexBuffer)) + result = static_cast(result | D3D12_RESOURCE_STATE_INDEX_BUFFER); + if (hasFlag(state, ResourceState::UniformBuffer)) + result = static_cast(result | D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER); + if (hasFlag(state, ResourceState::ShaderRead)) { + // Depth textures use DEPTH_READ when sampled, not PIXEL_SHADER_RESOURCE. + if (isDepthFormat(format)) + result = static_cast(result | D3D12_RESOURCE_STATE_DEPTH_READ); + else + result = static_cast(result | D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE + | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); + } + if (hasFlag(state, ResourceState::ShaderWrite)) + result = static_cast(result | D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + if (hasFlag(state, ResourceState::RenderTarget)) + result = static_cast(result | D3D12_RESOURCE_STATE_RENDER_TARGET); + if (hasFlag(state, ResourceState::DepthStencilWrite)) + result = static_cast(result | D3D12_RESOURCE_STATE_DEPTH_WRITE); + if (hasFlag(state, ResourceState::DepthStencilRead)) + result = static_cast(result | D3D12_RESOURCE_STATE_DEPTH_READ); + if (hasFlag(state, ResourceState::IndirectArgument)) + result = static_cast(result | D3D12_RESOURCE_STATE_INDIRECT_ARGUMENT); + if (hasFlag(state, ResourceState::CopySrc)) + result = static_cast(result | D3D12_RESOURCE_STATE_COPY_SOURCE); + if (hasFlag(state, ResourceState::CopyDst)) + result = static_cast(result | D3D12_RESOURCE_STATE_COPY_DEST); + if (hasFlag(state, ResourceState::Present)) + result = static_cast(result | D3D12_RESOURCE_STATE_PRESENT); + if (hasFlag(state, ResourceState::General)) + result = static_cast(result | D3D12_RESOURCE_STATE_COMMON); + if (hasFlag(state, ResourceState::AccelStructRead)) + result = static_cast(result | D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE); + if (hasFlag(state, ResourceState::AccelStructWrite)) + result = static_cast(result | D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + + return result; + } + +private: + // ---- Blit helper ---- + + /// Blits one subresource using the device's fullscreen triangle pipeline. + /// Expects src subresource in PIXEL_SHADER_RESOURCE state, dst subresource in RENDER_TARGET state. + /// Implementation defined out-of-line (requires DxDeviceImpl for blit PSO and descriptor heaps). + void blitSubresource(DxTextureImpl* srcTex, u32 srcMip, DxTextureImpl* dstTex, u32 dstMip, + u32 dstWidth, u32 dstHeight, DXGI_FORMAT dxgiFormat); + + // ---- Descriptor heap management ---- + + void ensureDescriptorHeaps() { + if (m_descriptorHeapsSet) return; + m_descriptorHeapsSet = true; + + ID3D12DescriptorHeap* heaps[2] = { + m_gpuSrvHeap->heap(), + m_gpuSamplerHeap->heap() + }; + m_cmdList->SetDescriptorHeaps(2, heaps); + } + + static D3D12_RAYTRACING_GEOMETRY_FLAGS toGeometryFlags(GeometryFlags flags) { + D3D12_RAYTRACING_GEOMETRY_FLAGS result = D3D12_RAYTRACING_GEOMETRY_FLAG_NONE; + if (static_cast(flags) & static_cast(GeometryFlags::Opaque)) + result = static_cast( + result | D3D12_RAYTRACING_GEOMETRY_FLAG_OPAQUE); + if (static_cast(flags) & static_cast(GeometryFlags::NoDuplicateAnyHitInvocation)) + result = static_cast( + result | D3D12_RAYTRACING_GEOMETRY_FLAG_NO_DUPLICATE_ANYHIT_INVOCATION); + return result; + } + + static bool hasFlag(ResourceState state, ResourceState flag) { + return (static_cast(state) & static_cast(flag)) != 0; + } + + // ---- Members ---- + + DxDeviceImpl* m_device = nullptr; + ID3D12GraphicsCommandList* m_cmdList = nullptr; + DxCommandPoolImpl* m_pool = nullptr; + DxRayTracingPipelineImpl* m_currentRtPipeline = nullptr; + bool m_descriptorHeapsSet = false; + + // GPU descriptor heaps (cached from device at construction). + DxGpuDescriptorHeap* m_gpuSrvHeap = nullptr; + DxGpuDescriptorHeap* m_gpuSamplerHeap = nullptr; + + // Embedded sub-encoders using context-based decoupling (see DxRenderPassEncoder.cppm, + // DxComputePassEncoder.cppm). Contexts carry the pointers the sub-encoders need. + DxRenderPassEncoderImpl m_rpe; + DxComputePassEncoderImpl m_cpe; + DxRenderPassContext m_rpeCtx; // cloned for bundle encoders + std::vector m_bundleEncoders; // owned wrappers (freed in dtor) +}; + +// ---- Deferred DxCommandPoolImpl method implementations ---- + +// createEncoder and destroyEncoder are defined out-of-line here because they +// need the full DxCommandEncoderImpl definition. The DxDeviceImpl will call +// pool->init() with the GPU descriptor heap pointers so the pool can build +// the context structs. For now, these are left as declarations to be resolved +// when DxDeviceImpl is defined. +// +// The DxDeviceImpl (not yet ported) will wire up: +// DxRenderPassContext rpeCtx { cmdList, device, srvStaging, samplerStaging, +// gpuSrvHeap, gpuSamplerHeap, drawSig, drawIdxSig, meshSig }; +// DxComputePassContext cpeCtx { cmdList, srvStaging, samplerStaging, +// gpuSrvHeap, gpuSamplerHeap, dispatchSig }; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxCommandPool.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxCommandPool.cppm new file mode 100644 index 00000000..d9a0d115 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxCommandPool.cppm @@ -0,0 +1,93 @@ +/// DX12 implementation of CommandPool. +/// Wraps an ID3D12CommandAllocator. + +module; + +#include "DxIncludes.h" +#include + +export module rhi.dx12:command_pool; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :command_buffer; +import :descriptor_staging; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxDeviceImpl; // forward +class DxCommandEncoderImpl; // forward + +class DxCommandPoolImpl : public CommandPool { +public: + Status init(DxDeviceImpl* device, ID3D12Device* d3dDevice, QueueType queueType, + DxGpuDescriptorHeap* cpuSrvHeap, DxGpuDescriptorHeap* gpuSrvHeap, + DxGpuDescriptorHeap* cpuSamplerHeap, DxGpuDescriptorHeap* gpuSamplerHeap) { + m_device = device; + m_d3dDevice = d3dDevice; + m_type = toCommandListType(queueType); + + HRESULT hr = d3dDevice->CreateCommandAllocator(m_type, IID_PPV_ARGS(&m_allocator)); + if (FAILED(hr)) return ErrorCode::Unknown; + + // Create descriptor staging (shared by all encoders from this pool). + m_srvStaging.init(cpuSrvHeap, gpuSrvHeap, d3dDevice, + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, 1024); + m_samplerStaging.init(cpuSamplerHeap, gpuSamplerHeap, d3dDevice, + D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, 64); + + return ErrorCode::Ok; + } + + // ---- CommandPool interface ---- + Status createEncoder(CommandEncoder*& out) override; + void destroyEncoder(CommandEncoder*& encoder) override; + + void reset() override { + releaseCommandBuffers(); + // Reset descriptor staging -- GPU is done (fence waited), so staging + // bump pointers can safely return to start. + m_srvStaging.reset(); + m_samplerStaging.reset(); + m_allocator->Reset(); + } + + void cleanup() { + releaseCommandBuffers(); + m_srvStaging.destroy(); + m_samplerStaging.destroy(); + m_allocator.Reset(); + } + + // ---- Internal ---- + [[nodiscard]] ID3D12CommandAllocator* handle() const { return m_allocator.Get(); } + [[nodiscard]] DxDeviceImpl* ownerDevice() const { return m_device; } + [[nodiscard]] DxDescriptorStaging* srvStaging() { return &m_srvStaging; } + [[nodiscard]] DxDescriptorStaging* samplerStaging() { return &m_samplerStaging; } + + /// Called by DxCommandEncoderImpl::finish() to register a command buffer with this pool. + void trackCommandBuffer(DxCommandBufferImpl* cb) { m_trackedBuffers.push_back(cb); } + +private: + void releaseCommandBuffers() { + for (auto* cb : m_trackedBuffers) { + cb->release(); + delete cb; + } + m_trackedBuffers.clear(); + } + + ComPtr m_allocator; + ID3D12Device* m_d3dDevice = nullptr; + DxDeviceImpl* m_device = nullptr; + D3D12_COMMAND_LIST_TYPE m_type = D3D12_COMMAND_LIST_TYPE_DIRECT; + std::vector m_trackedBuffers; + DxDescriptorStaging m_srvStaging; + DxDescriptorStaging m_samplerStaging; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxComputePassEncoder.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxComputePassEncoder.cppm new file mode 100644 index 00000000..f869eb1c --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxComputePassEncoder.cppm @@ -0,0 +1,177 @@ +/// DX12 implementation of ComputePassEncoder. +/// Records compute dispatch commands into the parent command encoder's command list. + +module; + +#include "DxIncludes.h" +#include + +export module rhi.dx12:compute_pass_encoder; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :buffer; +import :bind_group; +import :bind_group_layout; +import :compute_pipeline; +import :pipeline_layout; +import :query_set; +import :descriptor_staging; +import :gpu_descriptor_heap; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +/// Pointers needed by the compute pass encoder, provided by the command encoder. +struct DxComputePassContext { + ID3D12GraphicsCommandList* cmdList = nullptr; + DxDescriptorStaging* srvStaging = nullptr; + DxDescriptorStaging* samplerStaging = nullptr; + DxGpuDescriptorHeap* gpuSrvHeap = nullptr; + DxGpuDescriptorHeap* gpuSamplerHeap = nullptr; + // Indirect command signature (cached on device). + ID3D12CommandSignature* dispatchSig = nullptr; +}; + +class DxComputePassEncoderImpl : public ComputePassEncoder { +public: + explicit DxComputePassEncoderImpl(const DxComputePassContext& ctx) + : m_ctx(ctx) {} + + void begin() { + m_currentPipeline = nullptr; + } + + // ---- Pipeline & Binding ---- + + void setPipeline(ComputePipeline* pipeline) override { + auto* dxPipeline = static_cast(pipeline); + if (!dxPipeline) return; + m_currentPipeline = dxPipeline; + + auto* cmdList = m_ctx.cmdList; + cmdList->SetPipelineState(dxPipeline->handle()); + cmdList->SetComputeRootSignature(dxPipeline->pipelineLayout()->handle()); + } + + void setBindGroup(u32 index, BindGroup* group, std::span dynamicOffsets) override { + auto* dxGroup = static_cast(group); + if (!dxGroup || !m_currentPipeline) return; + + auto* layout = m_currentPipeline->pipelineLayout(); + if (!layout) return; + + auto* cmdList = m_ctx.cmdList; + auto* dxLayout = static_cast(dxGroup->layout()); + + // Copy-on-bind: copy into encoder's staging region, bind from staging offset. + if (dxGroup->cbvSrvUavOffset() >= 0 && dxLayout && dxLayout->cbvSrvUavCount() > 0) { + i32 rootIdx = layout->getCbvSrvUavRootIndex(index); + if (rootIdx >= 0) { + i32 stagedOffset = m_ctx.srvStaging->copyFrom( + static_cast(dxGroup->cbvSrvUavOffset()), dxLayout->cbvSrvUavCount()); + if (stagedOffset >= 0) { + auto gpuHandle = m_ctx.gpuSrvHeap->getGpuHandle(static_cast(stagedOffset)); + cmdList->SetComputeRootDescriptorTable(static_cast(rootIdx), gpuHandle); + } + } + } + + if (dxGroup->samplerOffset() >= 0 && dxLayout && dxLayout->samplerCount() > 0) { + i32 rootIdx = layout->getSamplerRootIndex(index); + if (rootIdx >= 0) { + i32 stagedOffset = m_ctx.samplerStaging->copyFrom( + static_cast(dxGroup->samplerOffset()), dxLayout->samplerCount()); + if (stagedOffset >= 0) { + auto gpuHandle = m_ctx.gpuSamplerHeap->getGpuHandle(static_cast(stagedOffset)); + cmdList->SetComputeRootDescriptorTable(static_cast(rootIdx), gpuHandle); + } + } + } + + // Bind dynamic offset root descriptors (not staged -- uses GPU virtual addresses). + auto dynAddrs = dxGroup->dynamicGpuAddresses(); + usize dynOffsetIdx = 0; + for (usize i = 0; i < layout->dynamicRootEntries().size(); ++i) { + const auto& entry = layout->dynamicRootEntries()[i]; + if (entry.groupIndex != index) continue; + if (entry.dynamicIndex >= static_cast(dynAddrs.size())) continue; + + u64 gpuAddr = dynAddrs[entry.dynamicIndex]; + if (dynOffsetIdx < dynamicOffsets.size()) + gpuAddr += static_cast(dynamicOffsets[dynOffsetIdx]); + ++dynOffsetIdx; + + switch (entry.paramType) { + case D3D12_ROOT_PARAMETER_TYPE_CBV: + cmdList->SetComputeRootConstantBufferView(static_cast(entry.rootParamIndex), gpuAddr); + break; + case D3D12_ROOT_PARAMETER_TYPE_SRV: + cmdList->SetComputeRootShaderResourceView(static_cast(entry.rootParamIndex), gpuAddr); + break; + case D3D12_ROOT_PARAMETER_TYPE_UAV: + cmdList->SetComputeRootUnorderedAccessView(static_cast(entry.rootParamIndex), gpuAddr); + break; + default: break; + } + } + } + + void setPushConstants(ShaderStage /*stages*/, u32 offset, u32 size, const void* data) override { + if (!m_currentPipeline) return; + auto* layout = m_currentPipeline->pipelineLayout(); + if (!layout || layout->pushConstantRootIndex() < 0) return; + + m_ctx.cmdList->SetComputeRoot32BitConstants( + static_cast(layout->pushConstantRootIndex()), + size / 4, data, offset / 4); + } + + // ---- Dispatch ---- + + void dispatch(u32 x, u32 y, u32 z) override { + m_ctx.cmdList->Dispatch(x, y, z); + } + + void dispatchIndirect(Buffer* buffer, u64 offset) override { + auto* dxBuf = static_cast(buffer); + if (!dxBuf) return; + + auto* sig = m_ctx.dispatchSig; + if (!sig) return; + + m_ctx.cmdList->ExecuteIndirect(sig, 1, dxBuf->handle(), offset, nullptr, 0); + } + + // ---- Barrier ---- + + void computeBarrier() override { + D3D12_RESOURCE_BARRIER barrier{}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV; + barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE; + barrier.UAV.pResource = nullptr; // global UAV barrier + m_ctx.cmdList->ResourceBarrier(1, &barrier); + } + + // ---- Queries ---- + + void writeTimestamp(QuerySet* querySet, u32 index) override { + auto* qs = static_cast(querySet); + if (qs) m_ctx.cmdList->EndQuery(qs->handle(), D3D12_QUERY_TYPE_TIMESTAMP, index); + } + + // ---- End ---- + + void end() override { + m_currentPipeline = nullptr; + } + +private: + DxComputePassContext m_ctx; + DxComputePipelineImpl* m_currentPipeline = nullptr; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxComputePipeline.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxComputePipeline.cppm new file mode 100644 index 00000000..5aac0e8e --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxComputePipeline.cppm @@ -0,0 +1,50 @@ +/// DX12 implementation of ComputePipeline. + +module; + +#include "DxIncludes.h" + +export module rhi.dx12:compute_pipeline; + +import core.stdtypes; +import core.status; +import rhi; +import :pipeline_layout; +import :shader_module; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxComputePipelineImpl : public ComputePipeline { +public: + Status init(ID3D12Device* device, const ComputePipelineDesc& d) { + m_layout = static_cast(d.layout); + if (!m_layout) return ErrorCode::Unknown; + auto* csMod = static_cast(d.compute.module); + if (!csMod) return ErrorCode::Unknown; + + auto cs = csMod->bytecode(); + D3D12_COMPUTE_PIPELINE_STATE_DESC pso{}; + pso.pRootSignature = m_layout->handle(); + pso.CS = { cs.data(), cs.size() }; + + HRESULT hr = device->CreateComputePipelineState(&pso, IID_PPV_ARGS(&m_pipelineState)); + if (FAILED(hr)) { + logErrorf("DxComputePipeline: CreateComputePipelineState failed (0x%08X)", static_cast(hr)); + return ErrorCode::Unknown; + } + return ErrorCode::Ok; + } + + void cleanup() { m_pipelineState.Reset(); } + + [[nodiscard]] ID3D12PipelineState* handle() const { return m_pipelineState.Get(); } + [[nodiscard]] DxPipelineLayoutImpl* pipelineLayout() const { return m_layout; } + +private: + ComPtr m_pipelineState; + DxPipelineLayoutImpl* m_layout = nullptr; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxConversions.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxConversions.cppm new file mode 100644 index 00000000..85afec72 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxConversions.cppm @@ -0,0 +1,380 @@ +/// Conversion utilities between draco::rhi enums and DX12/DXGI enums. + +module; + +#include "DxIncludes.h" + +export module rhi.dx12:conversions; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +inline DXGI_FORMAT toDxgiFormat(TextureFormat f) { + switch (f) { + case TextureFormat::Undefined: return DXGI_FORMAT_UNKNOWN; + case TextureFormat::R8Unorm: return DXGI_FORMAT_R8_UNORM; + case TextureFormat::R8Snorm: return DXGI_FORMAT_R8_SNORM; + case TextureFormat::R8Uint: return DXGI_FORMAT_R8_UINT; + case TextureFormat::R8Sint: return DXGI_FORMAT_R8_SINT; + case TextureFormat::R16Uint: return DXGI_FORMAT_R16_UINT; + case TextureFormat::R16Sint: return DXGI_FORMAT_R16_SINT; + case TextureFormat::R16Float: return DXGI_FORMAT_R16_FLOAT; + case TextureFormat::RG8Unorm: return DXGI_FORMAT_R8G8_UNORM; + case TextureFormat::RG8Snorm: return DXGI_FORMAT_R8G8_SNORM; + case TextureFormat::RG8Uint: return DXGI_FORMAT_R8G8_UINT; + case TextureFormat::RG8Sint: return DXGI_FORMAT_R8G8_SINT; + case TextureFormat::R32Uint: return DXGI_FORMAT_R32_UINT; + case TextureFormat::R32Sint: return DXGI_FORMAT_R32_SINT; + case TextureFormat::R32Float: return DXGI_FORMAT_R32_FLOAT; + case TextureFormat::RG16Uint: return DXGI_FORMAT_R16G16_UINT; + case TextureFormat::RG16Sint: return DXGI_FORMAT_R16G16_SINT; + case TextureFormat::RG16Float: return DXGI_FORMAT_R16G16_FLOAT; + case TextureFormat::RGBA8Unorm: return DXGI_FORMAT_R8G8B8A8_UNORM; + case TextureFormat::RGBA8UnormSrgb: return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; + case TextureFormat::RGBA8Snorm: return DXGI_FORMAT_R8G8B8A8_SNORM; + case TextureFormat::RGBA8Uint: return DXGI_FORMAT_R8G8B8A8_UINT; + case TextureFormat::RGBA8Sint: return DXGI_FORMAT_R8G8B8A8_SINT; + case TextureFormat::BGRA8Unorm: return DXGI_FORMAT_B8G8R8A8_UNORM; + case TextureFormat::BGRA8UnormSrgb: return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; + case TextureFormat::RGB10A2Unorm: return DXGI_FORMAT_R10G10B10A2_UNORM; + case TextureFormat::RGB10A2Uint: return DXGI_FORMAT_R10G10B10A2_UINT; + case TextureFormat::RG11B10Float: return DXGI_FORMAT_R11G11B10_FLOAT; + case TextureFormat::RGB9E5Float: return DXGI_FORMAT_R9G9B9E5_SHAREDEXP; + case TextureFormat::RG32Uint: return DXGI_FORMAT_R32G32_UINT; + case TextureFormat::RG32Sint: return DXGI_FORMAT_R32G32_SINT; + case TextureFormat::RG32Float: return DXGI_FORMAT_R32G32_FLOAT; + case TextureFormat::RGBA16Uint: return DXGI_FORMAT_R16G16B16A16_UINT; + case TextureFormat::RGBA16Sint: return DXGI_FORMAT_R16G16B16A16_SINT; + case TextureFormat::RGBA16Float: return DXGI_FORMAT_R16G16B16A16_FLOAT; + case TextureFormat::RGBA16Unorm: return DXGI_FORMAT_R16G16B16A16_UNORM; + case TextureFormat::RGBA16Snorm: return DXGI_FORMAT_R16G16B16A16_SNORM; + case TextureFormat::RGBA32Uint: return DXGI_FORMAT_R32G32B32A32_UINT; + case TextureFormat::RGBA32Sint: return DXGI_FORMAT_R32G32B32A32_SINT; + case TextureFormat::RGBA32Float: return DXGI_FORMAT_R32G32B32A32_FLOAT; + case TextureFormat::Depth16Unorm: return DXGI_FORMAT_D16_UNORM; + case TextureFormat::Depth24Plus: return DXGI_FORMAT_D24_UNORM_S8_UINT; + case TextureFormat::Depth24PlusStencil8:return DXGI_FORMAT_D24_UNORM_S8_UINT; + case TextureFormat::Depth32Float: return DXGI_FORMAT_D32_FLOAT; + case TextureFormat::Depth32FloatStencil8:return DXGI_FORMAT_D32_FLOAT_S8X24_UINT; + case TextureFormat::Stencil8: return DXGI_FORMAT_R8_UINT; + case TextureFormat::BC1RGBAUnorm: return DXGI_FORMAT_BC1_UNORM; + case TextureFormat::BC1RGBAUnormSrgb: return DXGI_FORMAT_BC1_UNORM_SRGB; + case TextureFormat::BC2RGBAUnorm: return DXGI_FORMAT_BC2_UNORM; + case TextureFormat::BC2RGBAUnormSrgb: return DXGI_FORMAT_BC2_UNORM_SRGB; + case TextureFormat::BC3RGBAUnorm: return DXGI_FORMAT_BC3_UNORM; + case TextureFormat::BC3RGBAUnormSrgb: return DXGI_FORMAT_BC3_UNORM_SRGB; + case TextureFormat::BC4RUnorm: return DXGI_FORMAT_BC4_UNORM; + case TextureFormat::BC4RSnorm: return DXGI_FORMAT_BC4_SNORM; + case TextureFormat::BC5RGUnorm: return DXGI_FORMAT_BC5_UNORM; + case TextureFormat::BC5RGSnorm: return DXGI_FORMAT_BC5_SNORM; + case TextureFormat::BC6HRGBUfloat: return DXGI_FORMAT_BC6H_UF16; + case TextureFormat::BC6HRGBFloat: return DXGI_FORMAT_BC6H_SF16; + case TextureFormat::BC7RGBAUnorm: return DXGI_FORMAT_BC7_UNORM; + case TextureFormat::BC7RGBAUnormSrgb: return DXGI_FORMAT_BC7_UNORM_SRGB; + default: return DXGI_FORMAT_UNKNOWN; + } +} + +inline TextureFormat fromDxgiFormat(DXGI_FORMAT f) { + switch (f) { + case DXGI_FORMAT_R8G8B8A8_UNORM: return TextureFormat::RGBA8Unorm; + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: return TextureFormat::RGBA8UnormSrgb; + case DXGI_FORMAT_B8G8R8A8_UNORM: return TextureFormat::BGRA8Unorm; + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: return TextureFormat::BGRA8UnormSrgb; + case DXGI_FORMAT_R16G16B16A16_FLOAT: return TextureFormat::RGBA16Float; + case DXGI_FORMAT_R10G10B10A2_UNORM: return TextureFormat::RGB10A2Unorm; + case DXGI_FORMAT_R32G32B32A32_FLOAT: return TextureFormat::RGBA32Float; + default: return TextureFormat::Undefined; + } +} + +inline DXGI_FORMAT toDxgiVertexFormat(VertexFormat f) { + switch (f) { + case VertexFormat::Uint8x2: return DXGI_FORMAT_R8G8_UINT; + case VertexFormat::Uint8x4: return DXGI_FORMAT_R8G8B8A8_UINT; + case VertexFormat::Sint8x2: return DXGI_FORMAT_R8G8_SINT; + case VertexFormat::Sint8x4: return DXGI_FORMAT_R8G8B8A8_SINT; + case VertexFormat::Unorm8x2: return DXGI_FORMAT_R8G8_UNORM; + case VertexFormat::Unorm8x4: return DXGI_FORMAT_R8G8B8A8_UNORM; + case VertexFormat::Snorm8x2: return DXGI_FORMAT_R8G8_SNORM; + case VertexFormat::Snorm8x4: return DXGI_FORMAT_R8G8B8A8_SNORM; + case VertexFormat::Uint16x2: return DXGI_FORMAT_R16G16_UINT; + case VertexFormat::Uint16x4: return DXGI_FORMAT_R16G16B16A16_UINT; + case VertexFormat::Sint16x2: return DXGI_FORMAT_R16G16_SINT; + case VertexFormat::Sint16x4: return DXGI_FORMAT_R16G16B16A16_SINT; + case VertexFormat::Unorm16x2: return DXGI_FORMAT_R16G16_UNORM; + case VertexFormat::Unorm16x4: return DXGI_FORMAT_R16G16B16A16_UNORM; + case VertexFormat::Snorm16x2: return DXGI_FORMAT_R16G16_SNORM; + case VertexFormat::Snorm16x4: return DXGI_FORMAT_R16G16B16A16_SNORM; + case VertexFormat::Float16x2: return DXGI_FORMAT_R16G16_FLOAT; + case VertexFormat::Float16x4: return DXGI_FORMAT_R16G16B16A16_FLOAT; + case VertexFormat::Float32: return DXGI_FORMAT_R32_FLOAT; + case VertexFormat::Float32x2: return DXGI_FORMAT_R32G32_FLOAT; + case VertexFormat::Float32x3: return DXGI_FORMAT_R32G32B32_FLOAT; + case VertexFormat::Float32x4: return DXGI_FORMAT_R32G32B32A32_FLOAT; + case VertexFormat::Uint32: return DXGI_FORMAT_R32_UINT; + case VertexFormat::Uint32x2: return DXGI_FORMAT_R32G32_UINT; + case VertexFormat::Uint32x3: return DXGI_FORMAT_R32G32B32_UINT; + case VertexFormat::Uint32x4: return DXGI_FORMAT_R32G32B32A32_UINT; + case VertexFormat::Sint32: return DXGI_FORMAT_R32_SINT; + case VertexFormat::Sint32x2: return DXGI_FORMAT_R32G32_SINT; + case VertexFormat::Sint32x3: return DXGI_FORMAT_R32G32B32_SINT; + case VertexFormat::Sint32x4: return DXGI_FORMAT_R32G32B32A32_SINT; + default: return DXGI_FORMAT_UNKNOWN; + } +} + +inline DXGI_FORMAT toDxgiIndexFormat(IndexFormat f) { + switch (f) { + case IndexFormat::UInt16: return DXGI_FORMAT_R16_UINT; + case IndexFormat::UInt32: return DXGI_FORMAT_R32_UINT; + } + return DXGI_FORMAT_R16_UINT; +} + +inline D3D12_COMMAND_LIST_TYPE toCommandListType(QueueType t) { + switch (t) { + case QueueType::Graphics: return D3D12_COMMAND_LIST_TYPE_DIRECT; + case QueueType::Compute: return D3D12_COMMAND_LIST_TYPE_COMPUTE; + case QueueType::Transfer: return D3D12_COMMAND_LIST_TYPE_COPY; + } + return D3D12_COMMAND_LIST_TYPE_DIRECT; +} + +inline D3D12_HEAP_TYPE toHeapType(MemoryLocation loc) { + switch (loc) { + case MemoryLocation::GpuOnly: return D3D12_HEAP_TYPE_DEFAULT; + case MemoryLocation::CpuToGpu: return D3D12_HEAP_TYPE_UPLOAD; + case MemoryLocation::GpuToCpu: return D3D12_HEAP_TYPE_READBACK; + case MemoryLocation::Auto: return D3D12_HEAP_TYPE_DEFAULT; + } + return D3D12_HEAP_TYPE_DEFAULT; +} + +inline D3D12_RESOURCE_FLAGS toTextureResourceFlags(TextureUsage usage) { + D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE; + if (static_cast(usage & TextureUsage::RenderTarget)) flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + if (static_cast(usage & TextureUsage::DepthStencil)) flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; + if (static_cast(usage & TextureUsage::Storage)) flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + return flags; +} + +inline D3D12_RESOURCE_FLAGS toBufferResourceFlags(BufferUsage usage) { + D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE; + if (static_cast(usage & BufferUsage::Storage)) flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + if (static_cast(usage & BufferUsage::AccelStructScratch)) flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + return flags; +} + +inline D3D12_RESOURCE_DIMENSION toResourceDimension(TextureDimension d) { + switch (d) { + case TextureDimension::Texture1D: return D3D12_RESOURCE_DIMENSION_TEXTURE1D; + case TextureDimension::Texture2D: return D3D12_RESOURCE_DIMENSION_TEXTURE2D; + case TextureDimension::Texture3D: return D3D12_RESOURCE_DIMENSION_TEXTURE3D; + } + return D3D12_RESOURCE_DIMENSION_TEXTURE2D; +} + +inline D3D12_COMPARISON_FUNC toComparisonFunc(CompareFunction f) { + switch (f) { + case CompareFunction::Never: return D3D12_COMPARISON_FUNC_NEVER; + case CompareFunction::Less: return D3D12_COMPARISON_FUNC_LESS; + case CompareFunction::Equal: return D3D12_COMPARISON_FUNC_EQUAL; + case CompareFunction::LessEqual: return D3D12_COMPARISON_FUNC_LESS_EQUAL; + case CompareFunction::Greater: return D3D12_COMPARISON_FUNC_GREATER; + case CompareFunction::NotEqual: return D3D12_COMPARISON_FUNC_NOT_EQUAL; + case CompareFunction::GreaterEqual: return D3D12_COMPARISON_FUNC_GREATER_EQUAL; + case CompareFunction::Always: return D3D12_COMPARISON_FUNC_ALWAYS; + } + return D3D12_COMPARISON_FUNC_ALWAYS; +} + +inline D3D12_TEXTURE_ADDRESS_MODE toAddressMode(AddressMode m) { + switch (m) { + case AddressMode::Repeat: return D3D12_TEXTURE_ADDRESS_MODE_WRAP; + case AddressMode::MirrorRepeat: return D3D12_TEXTURE_ADDRESS_MODE_MIRROR; + case AddressMode::ClampToEdge: return D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + case AddressMode::ClampToBorder: return D3D12_TEXTURE_ADDRESS_MODE_BORDER; + } + return D3D12_TEXTURE_ADDRESS_MODE_WRAP; +} + +inline D3D12_FILTER toFilter(FilterMode min, FilterMode mag, MipmapFilterMode mip, bool comparison) { + bool minL = min == FilterMode::Linear; + bool magL = mag == FilterMode::Linear; + bool mipL = mip == MipmapFilterMode::Linear; + if (comparison) { + if (!minL && !magL && !mipL) return D3D12_FILTER_COMPARISON_MIN_MAG_MIP_POINT; + if (!minL && !magL && mipL) return D3D12_FILTER_COMPARISON_MIN_MAG_POINT_MIP_LINEAR; + if (!minL && magL && !mipL) return D3D12_FILTER_COMPARISON_MIN_POINT_MAG_LINEAR_MIP_POINT; + if (!minL && magL && mipL) return D3D12_FILTER_COMPARISON_MIN_POINT_MAG_MIP_LINEAR; + if ( minL && !magL && !mipL) return D3D12_FILTER_COMPARISON_MIN_LINEAR_MAG_MIP_POINT; + if ( minL && !magL && mipL) return D3D12_FILTER_COMPARISON_MIN_LINEAR_MAG_POINT_MIP_LINEAR; + if ( minL && magL && !mipL) return D3D12_FILTER_COMPARISON_MIN_MAG_LINEAR_MIP_POINT; + return D3D12_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR; + } + if (!minL && !magL && !mipL) return D3D12_FILTER_MIN_MAG_MIP_POINT; + if (!minL && !magL && mipL) return D3D12_FILTER_MIN_MAG_POINT_MIP_LINEAR; + if (!minL && magL && !mipL) return D3D12_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT; + if (!minL && magL && mipL) return D3D12_FILTER_MIN_POINT_MAG_MIP_LINEAR; + if ( minL && !magL && !mipL) return D3D12_FILTER_MIN_LINEAR_MAG_MIP_POINT; + if ( minL && !magL && mipL) return D3D12_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR; + if ( minL && magL && !mipL) return D3D12_FILTER_MIN_MAG_LINEAR_MIP_POINT; + return D3D12_FILTER_MIN_MAG_MIP_LINEAR; +} + +inline DXGI_FORMAT toTypelessDepthFormat(TextureFormat f) { + switch (f) { + case TextureFormat::Depth16Unorm: return DXGI_FORMAT_R16_TYPELESS; + case TextureFormat::Depth24Plus: + case TextureFormat::Depth24PlusStencil8: return DXGI_FORMAT_R24G8_TYPELESS; + case TextureFormat::Depth32Float: return DXGI_FORMAT_R32_TYPELESS; + case TextureFormat::Depth32FloatStencil8:return DXGI_FORMAT_R32G8X24_TYPELESS; + default: return toDxgiFormat(f); + } +} + +inline DXGI_FORMAT toDepthSrvFormat(TextureFormat f) { + switch (f) { + case TextureFormat::Depth16Unorm: return DXGI_FORMAT_R16_UNORM; + case TextureFormat::Depth24Plus: + case TextureFormat::Depth24PlusStencil8: return DXGI_FORMAT_R24_UNORM_X8_TYPELESS; + case TextureFormat::Depth32Float: return DXGI_FORMAT_R32_FLOAT; + case TextureFormat::Depth32FloatStencil8:return DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS; + default: return toDxgiFormat(f); + } +} + +inline DXGI_FORMAT toStencilSrvFormat(TextureFormat f) { + switch (f) { + case TextureFormat::Depth24PlusStencil8: return DXGI_FORMAT_X24_TYPELESS_G8_UINT; + case TextureFormat::Depth32FloatStencil8:return DXGI_FORMAT_X32_TYPELESS_G8X24_UINT; + default: return toDxgiFormat(f); + } +} + +inline D3D12_PRIMITIVE_TOPOLOGY_TYPE toPrimitiveTopologyType(PrimitiveTopology t) { + switch (t) { + case PrimitiveTopology::PointList: return D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT; + case PrimitiveTopology::LineList: + case PrimitiveTopology::LineStrip: return D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE; + case PrimitiveTopology::TriangleList: + case PrimitiveTopology::TriangleStrip: return D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + } + return D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; +} + +inline D3D_PRIMITIVE_TOPOLOGY toPrimitiveTopology(PrimitiveTopology t) { + switch (t) { + case PrimitiveTopology::PointList: return D3D_PRIMITIVE_TOPOLOGY_POINTLIST; + case PrimitiveTopology::LineList: return D3D_PRIMITIVE_TOPOLOGY_LINELIST; + case PrimitiveTopology::LineStrip: return D3D_PRIMITIVE_TOPOLOGY_LINESTRIP; + case PrimitiveTopology::TriangleList: return D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; + case PrimitiveTopology::TriangleStrip: return D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP; + } + return D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; +} + +inline D3D12_CULL_MODE toCullMode(CullMode m) { + switch (m) { + case CullMode::None: return D3D12_CULL_MODE_NONE; + case CullMode::Front: return D3D12_CULL_MODE_FRONT; + case CullMode::Back: return D3D12_CULL_MODE_BACK; + } + return D3D12_CULL_MODE_NONE; +} + +inline D3D12_FILL_MODE toFillMode(FillMode m) { + switch (m) { + case FillMode::Solid: return D3D12_FILL_MODE_SOLID; + case FillMode::Wireframe: return D3D12_FILL_MODE_WIREFRAME; + } + return D3D12_FILL_MODE_SOLID; +} + +inline D3D12_BLEND toBlendFactor(BlendFactor f) { + switch (f) { + case BlendFactor::Zero: return D3D12_BLEND_ZERO; + case BlendFactor::One: return D3D12_BLEND_ONE; + case BlendFactor::Src: return D3D12_BLEND_SRC_COLOR; + case BlendFactor::OneMinusSrc: return D3D12_BLEND_INV_SRC_COLOR; + case BlendFactor::SrcAlpha: return D3D12_BLEND_SRC_ALPHA; + case BlendFactor::OneMinusSrcAlpha: return D3D12_BLEND_INV_SRC_ALPHA; + case BlendFactor::Dst: return D3D12_BLEND_DEST_COLOR; + case BlendFactor::OneMinusDst: return D3D12_BLEND_INV_DEST_COLOR; + case BlendFactor::DstAlpha: return D3D12_BLEND_DEST_ALPHA; + case BlendFactor::OneMinusDstAlpha: return D3D12_BLEND_INV_DEST_ALPHA; + case BlendFactor::SrcAlphaSaturated: return D3D12_BLEND_SRC_ALPHA_SAT; + case BlendFactor::Constant: return D3D12_BLEND_BLEND_FACTOR; + case BlendFactor::OneMinusConstant: return D3D12_BLEND_INV_BLEND_FACTOR; + } + return D3D12_BLEND_ONE; +} + +inline D3D12_BLEND_OP toBlendOp(BlendOperation op) { + switch (op) { + case BlendOperation::Add: return D3D12_BLEND_OP_ADD; + case BlendOperation::Subtract: return D3D12_BLEND_OP_SUBTRACT; + case BlendOperation::ReverseSubtract: return D3D12_BLEND_OP_REV_SUBTRACT; + case BlendOperation::Min: return D3D12_BLEND_OP_MIN; + case BlendOperation::Max: return D3D12_BLEND_OP_MAX; + } + return D3D12_BLEND_OP_ADD; +} + +inline D3D12_STENCIL_OP toStencilOp(StencilOperation op) { + switch (op) { + case StencilOperation::Keep: return D3D12_STENCIL_OP_KEEP; + case StencilOperation::Zero: return D3D12_STENCIL_OP_ZERO; + case StencilOperation::Replace: return D3D12_STENCIL_OP_REPLACE; + case StencilOperation::IncrementClamp: return D3D12_STENCIL_OP_INCR_SAT; + case StencilOperation::DecrementClamp: return D3D12_STENCIL_OP_DECR_SAT; + case StencilOperation::Invert: return D3D12_STENCIL_OP_INVERT; + case StencilOperation::IncrementWrap: return D3D12_STENCIL_OP_INCR; + case StencilOperation::DecrementWrap: return D3D12_STENCIL_OP_DECR; + } + return D3D12_STENCIL_OP_KEEP; +} + +inline D3D12_DESCRIPTOR_RANGE_TYPE toDescriptorRangeType(BindingType t) { + switch (t) { + case BindingType::UniformBuffer: return D3D12_DESCRIPTOR_RANGE_TYPE_CBV; + case BindingType::StorageBufferReadOnly: return D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + case BindingType::StorageBufferReadWrite: return D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + case BindingType::SampledTexture: return D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + case BindingType::StorageTextureReadOnly: return D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + case BindingType::StorageTextureReadWrite: return D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + case BindingType::Sampler: return D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER; + case BindingType::ComparisonSampler: return D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER; + case BindingType::BindlessTextures: return D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + case BindingType::BindlessSamplers: return D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER; + case BindingType::BindlessStorageBuffers: return D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + case BindingType::BindlessStorageTextures: return D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + case BindingType::AccelerationStructure: return D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + } + return D3D12_DESCRIPTOR_RANGE_TYPE_SRV; +} + +inline bool isSamplerBinding(BindingType t) { + return t == BindingType::Sampler || t == BindingType::ComparisonSampler || t == BindingType::BindlessSamplers; +} + +/// Strips sRGB from a DXGI format (needed for DXGI flip model swap chains). +inline DXGI_FORMAT stripSrgb(DXGI_FORMAT f) { + switch (f) { + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: return DXGI_FORMAT_R8G8B8A8_UNORM; + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: return DXGI_FORMAT_B8G8R8A8_UNORM; + case DXGI_FORMAT_BC1_UNORM_SRGB: return DXGI_FORMAT_BC1_UNORM; + case DXGI_FORMAT_BC2_UNORM_SRGB: return DXGI_FORMAT_BC2_UNORM; + case DXGI_FORMAT_BC3_UNORM_SRGB: return DXGI_FORMAT_BC3_UNORM; + case DXGI_FORMAT_BC7_UNORM_SRGB: return DXGI_FORMAT_BC7_UNORM; + default: return f; + } +} + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxDescriptorHeap.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxDescriptorHeap.cppm new file mode 100644 index 00000000..1aaf3a80 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxDescriptorHeap.cppm @@ -0,0 +1,80 @@ +/// Simple CPU-side descriptor heap allocator with free-list. + +module; + +#include "DxIncludes.h" +#include + +export module rhi.dx12:descriptor_heap; + +import core.stdtypes; +import core.status; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxDescriptorHeapAllocator { +public: + DxDescriptorHeapAllocator() = default; + + Status init(ID3D12Device* device, D3D12_DESCRIPTOR_HEAP_TYPE type, u32 maxCount, + D3D12_DESCRIPTOR_HEAP_FLAGS flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE) { + m_maxCount = maxCount; + m_alive.resize(maxCount, static_cast(0)); + + D3D12_DESCRIPTOR_HEAP_DESC hd{}; + hd.Type = type; + hd.NumDescriptors = maxCount; + hd.Flags = flags; + HRESULT hr = device->CreateDescriptorHeap(&hd, IID_PPV_ARGS(&m_heap)); + if (FAILED(hr)) return ErrorCode::Unknown; + + m_heapStart = m_heap->GetCPUDescriptorHandleForHeapStart(); + m_descriptorSize = device->GetDescriptorHandleIncrementSize(type); + return ErrorCode::Ok; + } + + D3D12_CPU_DESCRIPTOR_HANDLE allocate() { + for (u32 i = 0; i < m_maxCount; ++i) { + u32 idx = (m_searchStart + i) % m_maxCount; + if (!m_alive[idx]) { + m_alive[idx] = true; + ++m_allocCount; + m_searchStart = (idx + 1) % m_maxCount; + D3D12_CPU_DESCRIPTOR_HANDLE h{}; + h.ptr = m_heapStart.ptr + static_cast(idx) * m_descriptorSize; + return h; + } + } + return {}; // heap full + } + + void free(D3D12_CPU_DESCRIPTOR_HANDLE handle) { + if (handle.ptr < m_heapStart.ptr) return; + u32 offset = static_cast((handle.ptr - m_heapStart.ptr) / m_descriptorSize); + if (offset < m_maxCount && m_alive[offset]) { + m_alive[offset] = false; + --m_allocCount; + } + } + + void destroy() { + m_heap.Reset(); + m_alive.clear(); + } + + [[nodiscard]] ID3D12DescriptorHeap* heap() const { return m_heap.Get(); } + [[nodiscard]] u32 descriptorSize() const { return m_descriptorSize; } + +private: + ComPtr m_heap; + D3D12_CPU_DESCRIPTOR_HANDLE m_heapStart{}; + u32 m_descriptorSize = 0; + u32 m_maxCount = 0; + u32 m_allocCount = 0; + u32 m_searchStart = 0; + std::vector m_alive; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxDescriptorStaging.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxDescriptorStaging.cppm new file mode 100644 index 00000000..86e79661 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxDescriptorStaging.cppm @@ -0,0 +1,98 @@ +/// Bump-allocating staging region within a GPU-visible descriptor heap. +/// Copies bind group descriptors from CPU heap into GPU heap at bind time. + +module; + +#include "DxIncludes.h" +#include + +#include + +export module rhi.dx12:descriptor_staging; + +import core.stdtypes; +import core.status; +import :gpu_descriptor_heap; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxDescriptorStaging { +public: + DxDescriptorStaging() = default; + + void init(DxGpuDescriptorHeap* cpuHeap, DxGpuDescriptorHeap* gpuHeap, + ID3D12Device* device, D3D12_DESCRIPTOR_HEAP_TYPE heapType, u32 initialCapacity) { + m_cpuHeap = cpuHeap; + m_gpuHeap = gpuHeap; + m_device = device; + m_heapType = heapType; + m_capacity = initialCapacity; + } + + /// Copies `count` descriptors from `srcOffset` in CPU heap into GPU staging. + /// Returns the staging offset in the GPU heap, or -1 on failure. + i32 copyFrom(u32 srcOffset, u32 count) { + if (count == 0) return -1; + + // Lazy allocation. + if (m_blockOffset < 0) { + m_blockOffset = m_gpuHeap->allocate(m_capacity); + if (m_blockOffset < 0) return -1; + m_current = 0; + } + + // Grow if needed: retire current block, allocate bigger. + if (m_current + count > m_capacity) { + u32 newCap = std::max(m_capacity * 2, m_current + count); + i32 newBlock = m_gpuHeap->allocate(newCap); + if (newBlock < 0) return -1; + m_retiredBlocks.push_back({ m_blockOffset, m_capacity }); + m_blockOffset = newBlock; + m_capacity = newCap; + m_current = 0; + } + + u32 dstOffset = static_cast(m_blockOffset) + m_current; + m_device->CopyDescriptorsSimple(count, + m_gpuHeap->getCpuHandle(dstOffset), + m_cpuHeap->getCpuHandle(srcOffset), + m_heapType); + m_current += count; + return static_cast(dstOffset); + } + + /// Resets bump pointer. Called when command pool resets after fence wait. + void reset() { + m_current = 0; + for (auto& b : m_retiredBlocks) + m_gpuHeap->free(static_cast(b.offset), b.capacity); + m_retiredBlocks.clear(); + } + + /// Frees all blocks. + void destroy() { + if (m_blockOffset >= 0) { + m_gpuHeap->free(static_cast(m_blockOffset), m_capacity); + m_blockOffset = -1; + } + for (auto& b : m_retiredBlocks) + m_gpuHeap->free(static_cast(b.offset), b.capacity); + m_retiredBlocks.clear(); + } + +private: + struct RetiredBlock { i32 offset; u32 capacity; }; + + DxGpuDescriptorHeap* m_cpuHeap = nullptr; + DxGpuDescriptorHeap* m_gpuHeap = nullptr; + ID3D12Device* m_device = nullptr; + D3D12_DESCRIPTOR_HEAP_TYPE m_heapType = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + i32 m_blockOffset = -1; + u32 m_capacity = 0; + u32 m_current = 0; + std::vector m_retiredBlocks; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxDevice.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxDevice.cppm new file mode 100644 index 00000000..36e5e678 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxDevice.cppm @@ -0,0 +1,926 @@ +/// DX12 implementation of Device. +/// Creates ID3D12Device, manages descriptor heaps, queues, command signatures, +/// and an internal blit pipeline for texture copy / mipmap generation. + +module; + +#include "DxIncludes.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +export module rhi.dx12:device; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :adapter; +import :surface; +import :descriptor_heap; +import :gpu_descriptor_heap; +import :buffer; +import :texture; +import :texture_view; +import :sampler; +import :shader_module; +import :fence; +import :query_set; +import :bind_group_layout; +import :bind_group; +import :pipeline_layout; +import :pipeline_cache; +import :render_pipeline; +import :compute_pipeline; +import :mesh_pipeline; +import :accel_struct; +import :ray_tracing_pipeline; +import :command_pool; +import :command_encoder; +import :render_pass_encoder; +import :compute_pass_encoder; +import :queue; +import :swap_chain; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxDeviceImpl : public Device { +public: + Status init(DxAdapterImpl* adapter, const DeviceDesc& desc) { + m_adapter = adapter; + + // Create device at feature level 12.0. + HRESULT hr = D3D12CreateDevice( + adapter->handle(), D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&m_device)); + if (FAILED(hr)) { + logErrorf("DxDevice: D3D12CreateDevice failed (0x%08X)", static_cast(hr)); + return ErrorCode::Unknown; + } + + // Suppress noisy debug layer warnings. + { + ComPtr infoQueue; + if (SUCCEEDED(m_device->QueryInterface(IID_PPV_ARGS(&infoQueue)))) { + D3D12_MESSAGE_ID suppressIds[] = { + D3D12_MESSAGE_ID_CLEARRENDERTARGETVIEW_MISMATCHINGCLEARVALUE, + D3D12_MESSAGE_ID_CLEARDEPTHSTENCILVIEW_MISMATCHINGCLEARVALUE, + }; + D3D12_INFO_QUEUE_FILTER filter{}; + filter.DenyList.NumIDs = static_cast(std::size(suppressIds)); + filter.DenyList.pIDList = suppressIds; + infoQueue->AddStorageFilterEntries(&filter); + m_infoQueue = infoQueue; + } + } + + // --- Descriptor heap allocators (CPU-side, for staging) --- + m_rtvHeap.init(m_device.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_RTV, 256); + m_dsvHeap.init(m_device.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_DSV, 64); + m_srvHeap.init(m_device.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, 4096); + m_samplerHeap.init(m_device.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, 256); + + // --- GPU-visible descriptor heaps (shader-visible) --- + m_gpuSrvHeap.init(m_device.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, 65536, true); + m_gpuSamplerHeap.init(m_device.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, 2048, true); + + // --- CPU-visible descriptor heaps (non-shader-visible, bind groups write here) --- + m_cpuSrvHeap.init(m_device.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, 65536, false); + m_cpuSamplerHeap.init(m_device.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER, 2048, false); + + // --- Create queues --- + u32 graphicsCount = std::max(desc.graphicsQueueCount, 1u); + for (u32 i = 0; i < graphicsCount; ++i) { + auto* q = new DxQueueImpl(); + if (q->init(m_device.Get(), QueueType::Graphics, this) != ErrorCode::Ok) { + delete q; break; + } + m_graphicsQueues.push_back(q); + } + for (u32 i = 0; i < desc.computeQueueCount; ++i) { + auto* q = new DxQueueImpl(); + if (q->init(m_device.Get(), QueueType::Compute, this) != ErrorCode::Ok) { + delete q; break; + } + m_computeQueues.push_back(q); + } + for (u32 i = 0; i < desc.transferQueueCount; ++i) { + auto* q = new DxQueueImpl(); + if (q->init(m_device.Get(), QueueType::Transfer, this) != ErrorCode::Ok) { + delete q; break; + } + m_transferQueues.push_back(q); + } + + // --- Cached command signatures for indirect execution --- + createIndirectCommandSignatures(); + + // --- Internal blit pipeline --- + createBlitPipeline(); + + // --- Detect mesh shader & ray tracing support --- + detectExtensionSupport(); + + // --- Populate features --- + type = DeviceType::DX12; + features = adapter->buildFeatures(); + + // --- RT handle properties (DX12 constants) --- + if (m_rtEnabled) { + shaderGroupHandleSize = D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES; // 32 + shaderGroupHandleAlignment = D3D12_RAYTRACING_SHADER_RECORD_BYTE_ALIGNMENT; // 32 + shaderGroupBaseAlignment = D3D12_RAYTRACING_SHADER_TABLE_BYTE_ALIGNMENT; // 64 + } + + return ErrorCode::Ok; + } + + // ================================================================== + // Device interface -- Queues + // ================================================================== + + Queue* getQueue(QueueType t, u32 index) override { + switch (t) { + case QueueType::Graphics: return index < m_graphicsQueues.size() ? m_graphicsQueues[index] : nullptr; + case QueueType::Compute: return index < m_computeQueues.size() ? m_computeQueues[index] : nullptr; + case QueueType::Transfer: return index < m_transferQueues.size() ? m_transferQueues[index] : nullptr; + } + return nullptr; + } + + u32 getQueueCount(QueueType t) override { + switch (t) { + case QueueType::Graphics: return static_cast(m_graphicsQueues.size()); + case QueueType::Compute: return static_cast(m_computeQueues.size()); + case QueueType::Transfer: return static_cast(m_transferQueues.size()); + } + return 0; + } + + FormatSupport getFormatSupport(TextureFormat /*format*/) override { + // DX12 supports D24_S8 on all hardware and most formats broadly. + // A full implementation would call CheckFeatureSupport(D3D12_FEATURE_FORMAT_SUPPORT). + return FormatSupport::Texture | FormatSupport::ColorAttachment | + FormatSupport::DepthStencil | FormatSupport::Buffer | + FormatSupport::VertexBuffer | FormatSupport::BlendableColor | + FormatSupport::LinearFilter; + } + + // ================================================================== + // Device interface -- Resource creation + // ================================================================== + + Status createBuffer(const BufferDesc& d, Buffer*& out) override { + auto* b = new DxBufferImpl(); + if (b->init(m_device.Get(), d) != ErrorCode::Ok) { delete b; out = nullptr; return ErrorCode::Unknown; } + setDebugName(b->handle(), d.label); + out = b; + return ErrorCode::Ok; + } + + Status createTexture(const TextureDesc& d, Texture*& out) override { + auto* t = new DxTextureImpl(); + if (t->init(m_device.Get(), d) != ErrorCode::Ok) { delete t; out = nullptr; return ErrorCode::Unknown; } + setDebugName(t->handle(), d.label); + out = t; + return ErrorCode::Ok; + } + + Status createTextureView(Texture* tex, const TextureViewDesc& d, TextureView*& out) override { + auto* dxTex = static_cast(tex); + if (!dxTex) { + logError("DxDevice: cast to DxTextureImpl failed"); + out = nullptr; + return ErrorCode::Unknown; + } + auto* v = new DxTextureViewImpl(); + if (v->init(m_device.Get(), dxTex, d, &m_srvHeap, &m_rtvHeap, &m_dsvHeap) != ErrorCode::Ok) { + delete v; out = nullptr; return ErrorCode::Unknown; + } + out = v; + return ErrorCode::Ok; + } + + Status createSampler(const SamplerDesc& d, Sampler*& out) override { + auto* s = new DxSamplerImpl(); + if (s->init(m_device.Get(), d, &m_samplerHeap) != ErrorCode::Ok) { + delete s; out = nullptr; return ErrorCode::Unknown; + } + out = s; + return ErrorCode::Ok; + } + + Status createShaderModule(const ShaderModuleDesc& d, ShaderModule*& out) override { + auto* m = new DxShaderModuleImpl(); + if (m->init(d) != ErrorCode::Ok) { delete m; out = nullptr; return ErrorCode::Unknown; } + out = m; + return ErrorCode::Ok; + } + + // ================================================================== + // Binding & Pipelines + // ================================================================== + + Status createBindGroupLayout(const BindGroupLayoutDesc& d, BindGroupLayout*& out) override { + auto* l = new DxBindGroupLayoutImpl(); + if (l->init(d) != ErrorCode::Ok) { delete l; out = nullptr; return ErrorCode::Unknown; } + out = l; + return ErrorCode::Ok; + } + + Status createBindGroup(const BindGroupDesc& d, BindGroup*& out) override { + auto* g = new DxBindGroupImpl(); + if (g->init(m_device.Get(), d, &m_cpuSrvHeap, &m_cpuSamplerHeap) != ErrorCode::Ok) { + delete g; out = nullptr; return ErrorCode::Unknown; + } + out = g; + return ErrorCode::Ok; + } + + Status createPipelineLayout(const PipelineLayoutDesc& d, PipelineLayout*& out) override { + auto* l = new DxPipelineLayoutImpl(); + if (l->init(m_device.Get(), d) != ErrorCode::Ok) { + logError("DxDevice: createPipelineLayout failed"); + delete l; out = nullptr; return ErrorCode::Unknown; + } + setDebugName(l->handle(), d.label); + out = l; + return ErrorCode::Ok; + } + + Status createPipelineCache(const PipelineCacheDesc& d, PipelineCache*& out) override { + auto* c = new DxPipelineCacheImpl(); + if (c->init(m_device.Get(), d) != ErrorCode::Ok) { delete c; out = nullptr; return ErrorCode::Unknown; } + if (c->handle()) setDebugName(c->handle(), d.label); + out = c; + return ErrorCode::Ok; + } + + Status createRenderPipeline(const RenderPipelineDesc& d, RenderPipeline*& out) override { + auto* p = new DxRenderPipelineImpl(); + if (p->init(m_device.Get(), d) != ErrorCode::Ok) { delete p; out = nullptr; return ErrorCode::Unknown; } + setDebugName(p->handle(), d.label); + out = p; + return ErrorCode::Ok; + } + + Status createComputePipeline(const ComputePipelineDesc& d, ComputePipeline*& out) override { + auto* p = new DxComputePipelineImpl(); + if (p->init(m_device.Get(), d) != ErrorCode::Ok) { delete p; out = nullptr; return ErrorCode::Unknown; } + setDebugName(p->handle(), d.label); + out = p; + return ErrorCode::Ok; + } + + // ================================================================== + // Mesh shader (folded in) + // ================================================================== + + Status createMeshPipeline(const MeshPipelineDesc& d, MeshPipeline*& out) override { + if (!m_meshEnabled) { out = nullptr; return ErrorCode::NotSupported; } + auto* p = new DxMeshPipelineImpl(); + if (p->init(m_device.Get(), d) != ErrorCode::Ok) { delete p; out = nullptr; return ErrorCode::Unknown; } + setDebugName(p->handle(), d.label); + out = p; + return ErrorCode::Ok; + } + + void destroyMeshPipeline(MeshPipeline*& p) override { + if (p) { static_cast(p)->cleanup(); delete p; p = nullptr; } + } + + // ================================================================== + // Ray tracing (folded in) + // ================================================================== + + Status createAccelStruct(const AccelStructDesc& d, AccelStruct*& out) override { + if (!m_rtEnabled) { out = nullptr; return ErrorCode::NotSupported; } + auto* a = new DxAccelStructImpl(); + if (a->init(m_device.Get(), d) != ErrorCode::Ok) { delete a; out = nullptr; return ErrorCode::Unknown; } + out = a; + return ErrorCode::Ok; + } + + void destroyAccelStruct(AccelStruct*& a) override { + if (a) { static_cast(a)->cleanup(); delete a; a = nullptr; } + } + + Status createRayTracingPipeline(const RayTracingPipelineDesc& d, RayTracingPipeline*& out) override { + if (!m_rtEnabled) { out = nullptr; return ErrorCode::NotSupported; } + auto* p = new DxRayTracingPipelineImpl(); + if (p->init(m_device.Get(), d) != ErrorCode::Ok) { delete p; out = nullptr; return ErrorCode::Unknown; } + out = p; + return ErrorCode::Ok; + } + + void destroyRayTracingPipeline(RayTracingPipeline*& p) override { + if (p) { static_cast(p)->cleanup(); delete p; p = nullptr; } + } + + Status getShaderGroupHandles(RayTracingPipeline* pipeline, u32 firstGroup, + u32 groupCount, std::span outData) override { + if (!m_rtEnabled) return ErrorCode::NotSupported; + auto* dxPipeline = static_cast(pipeline); + if (!dxPipeline || !dxPipeline->properties()) { + logError("DxDevice: pipeline or properties is null"); + return ErrorCode::Unknown; + } + + constexpr u32 handleSize = D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES; // 32 + if (outData.size() < static_cast(groupCount * handleSize)) { + logError("DxDevice: output buffer too small for shader group handles"); + return ErrorCode::Unknown; + } + + auto exportNames = dxPipeline->groupExportNames(); + for (u32 i = 0; i < groupCount; ++i) { + u32 groupIdx = firstGroup + i; + if (groupIdx >= exportNames.size()) { + logError("DxDevice: shader group index out of range"); + return ErrorCode::Unknown; + } + const auto& exportName = exportNames[groupIdx]; + void* identifier = dxPipeline->properties()->GetShaderIdentifier(exportName.c_str()); + if (!identifier) { + logError("DxDevice: GetShaderIdentifier returned null"); + return ErrorCode::Unknown; + } + std::memcpy(outData.data() + (i * handleSize), identifier, handleSize); + } + return ErrorCode::Ok; + } + + // ================================================================== + // Commands + // ================================================================== + + Status createCommandPool(QueueType qt, CommandPool*& out) override { + auto* p = new DxCommandPoolImpl(); + if (p->init(this, m_device.Get(), qt, + &m_cpuSrvHeap, &m_gpuSrvHeap, + &m_cpuSamplerHeap, &m_gpuSamplerHeap) != ErrorCode::Ok) { + delete p; out = nullptr; return ErrorCode::Unknown; + } + out = p; + return ErrorCode::Ok; + } + + // ================================================================== + // Synchronization + // ================================================================== + + Status createFence(u64 initialValue, Fence*& out) override { + auto* f = new DxFenceImpl(); + if (f->init(m_device.Get(), initialValue) != ErrorCode::Ok) { delete f; out = nullptr; return ErrorCode::Unknown; } + out = f; + return ErrorCode::Ok; + } + + // ================================================================== + // Queries + // ================================================================== + + Status createQuerySet(const QuerySetDesc& d, QuerySet*& out) override { + auto* q = new DxQuerySetImpl(); + if (q->init(m_device.Get(), d) != ErrorCode::Ok) { delete q; out = nullptr; return ErrorCode::Unknown; } + setDebugName(q->handle(), d.label); + out = q; + return ErrorCode::Ok; + } + + // ================================================================== + // Presentation + // ================================================================== + + Status createSwapChain(Surface* surface, const SwapChainDesc& d, SwapChain*& out) override { + auto* dxSurface = static_cast(surface); + if (!dxSurface) { + logError("DxDevice: cast to DxSurfaceImpl failed"); + out = nullptr; + return ErrorCode::Unknown; + } + + // Need a graphics queue for swap chain. + if (m_graphicsQueues.empty()) { out = nullptr; return ErrorCode::Unknown; } + + auto* sc = new DxSwapChainImpl(); + if (sc->init(m_device.Get(), m_adapter->factory(), + m_graphicsQueues[0]->handle(), + dxSurface, d, + &m_srvHeap, &m_rtvHeap, &m_dsvHeap) != ErrorCode::Ok) { + delete sc; out = nullptr; return ErrorCode::Unknown; + } + out = sc; + return ErrorCode::Ok; + } + + // ================================================================== + // Resource destruction + // ================================================================== + + void destroyBuffer(Buffer*& b) override { if (b) { static_cast(b)->cleanup(); delete b; b = nullptr; } } + void destroyTexture(Texture*& t) override { if (t) { static_cast(t)->cleanup(); delete t; t = nullptr; } } + void destroyTextureView(TextureView*& v) override { if (v) { static_cast(v)->cleanup(); delete v; v = nullptr; } } + void destroySampler(Sampler*& s) override { if (s) { static_cast(s)->cleanup(); delete s; s = nullptr; } } + void destroyShaderModule(ShaderModule*& m) override { if (m) { static_cast(m)->cleanup(); delete m; m = nullptr; } } + void destroyBindGroupLayout(BindGroupLayout*& l) override { if (l) { delete l; l = nullptr; } } + void destroyBindGroup(BindGroup*& g) override { if (g) { static_cast(g)->cleanup(); delete g; g = nullptr; } } + void destroyPipelineLayout(PipelineLayout*& l) override { if (l) { static_cast(l)->cleanup(); delete l; l = nullptr; } } + void destroyPipelineCache(PipelineCache*& c) override { if (c) { static_cast(c)->cleanup(); delete c; c = nullptr; } } + void destroyRenderPipeline(RenderPipeline*& p) override { if (p) { static_cast(p)->cleanup(); delete p; p = nullptr; } } + void destroyComputePipeline(ComputePipeline*& p) override { if (p) { static_cast(p)->cleanup(); delete p; p = nullptr; } } + void destroyCommandPool(CommandPool*& p) override { if (p) { static_cast(p)->cleanup(); delete p; p = nullptr; } } + void destroyFence(Fence*& f) override { if (f) { static_cast(f)->cleanup(); delete f; f = nullptr; } } + void destroyQuerySet(QuerySet*& q) override { if (q) { static_cast(q)->cleanup(); delete q; q = nullptr; } } + void destroySwapChain(SwapChain*& sc) override { if (sc) { static_cast(sc)->cleanup(); delete sc; sc = nullptr; } } + void destroySurface(Surface*& s) override { if (s) { delete s; s = nullptr; } } + + // ================================================================== + // Lifecycle + // ================================================================== + + void waitIdle() override { + for (auto* q : m_graphicsQueues) q->waitIdle(); + for (auto* q : m_computeQueues) q->waitIdle(); + for (auto* q : m_transferQueues) q->waitIdle(); + drainDebugMessages(); + } + + void drainDebugMessages() { + if (!m_infoQueue) return; + UINT64 count = m_infoQueue->GetNumStoredMessages(); + for (UINT64 i = 0; i < count; ++i) { + SIZE_T len = 0; + m_infoQueue->GetMessage(i, nullptr, &len); + if (len == 0) continue; + auto* msg = static_cast(std::malloc(len)); + if (m_infoQueue->GetMessage(i, msg, &len) == S_OK) { + if (msg->Severity <= D3D12_MESSAGE_SEVERITY_WARNING) + std::fprintf(stderr, "[DX12 %s] %.*s\n", + msg->Severity == D3D12_MESSAGE_SEVERITY_ERROR ? "ERROR" : + msg->Severity == D3D12_MESSAGE_SEVERITY_WARNING ? "WARN" : "CORRUPT", + static_cast(msg->DescriptionByteLength), msg->pDescription); + } + std::free(msg); + } + m_infoQueue->ClearStoredMessages(); + } + + void destroy() override { + waitIdle(); + + // Queues. + for (auto* q : m_graphicsQueues) { q->cleanup(); delete q; } + for (auto* q : m_computeQueues) { q->cleanup(); delete q; } + for (auto* q : m_transferQueues) { q->cleanup(); delete q; } + m_graphicsQueues.clear(); + m_computeQueues.clear(); + m_transferQueues.clear(); + + // Blit pipeline. + for (auto& [fmt, pso] : m_blitPsoCache) + pso.Reset(); + m_blitPsoCache.clear(); + m_blitVsBlob.Reset(); + m_blitPsBlob.Reset(); + m_blitRootSignature.Reset(); + + // Command signatures. + m_drawSignature.Reset(); + m_drawIndexedSignature.Reset(); + m_dispatchSignature.Reset(); + m_dispatchMeshSignature.Reset(); + + // Descriptor heaps. + m_cpuSrvHeap.destroy(); + m_cpuSamplerHeap.destroy(); + m_gpuSrvHeap.destroy(); + m_gpuSamplerHeap.destroy(); + m_rtvHeap.destroy(); + m_dsvHeap.destroy(); + m_srvHeap.destroy(); + m_samplerHeap.destroy(); + + // Report live objects in debug builds. +#ifdef _DEBUG + { + ComPtr debugDevice; + if (SUCCEEDED(m_device->QueryInterface(IID_PPV_ARGS(&debugDevice)))) { + debugDevice->ReportLiveDeviceObjects( + static_cast(D3D12_RLDO_DETAIL | D3D12_RLDO_IGNORE_INTERNAL)); + } + } +#endif + + m_device.Reset(); + delete this; + } + + // ================================================================== + // Internal accessors (encoders, swap chain, etc. need these) + // ================================================================== + + [[nodiscard]] ID3D12Device* handle() const { return m_device.Get(); } + [[nodiscard]] DxAdapterImpl* adapter() const { return m_adapter; } + + [[nodiscard]] DxDescriptorHeapAllocator* rtvHeap() { return &m_rtvHeap; } + [[nodiscard]] DxDescriptorHeapAllocator* dsvHeap() { return &m_dsvHeap; } + [[nodiscard]] DxDescriptorHeapAllocator* srvHeap() { return &m_srvHeap; } + [[nodiscard]] DxDescriptorHeapAllocator* samplerHeap() { return &m_samplerHeap; } + + [[nodiscard]] DxGpuDescriptorHeap* gpuSrvHeap() { return &m_gpuSrvHeap; } + [[nodiscard]] DxGpuDescriptorHeap* gpuSamplerHeap() { return &m_gpuSamplerHeap; } + [[nodiscard]] DxGpuDescriptorHeap* cpuSrvHeap() { return &m_cpuSrvHeap; } + [[nodiscard]] DxGpuDescriptorHeap* cpuSamplerHeap() { return &m_cpuSamplerHeap; } + + [[nodiscard]] ID3D12CommandSignature* drawSignature() const { return m_drawSignature.Get(); } + [[nodiscard]] ID3D12CommandSignature* drawIndexedSignature() const { return m_drawIndexedSignature.Get(); } + [[nodiscard]] ID3D12CommandSignature* dispatchSignature() const { return m_dispatchSignature.Get(); } + [[nodiscard]] ID3D12CommandSignature* dispatchMeshSignature() const { return m_dispatchMeshSignature.Get(); } + [[nodiscard]] ID3D12RootSignature* blitRootSignature() const { return m_blitRootSignature.Get(); } + + [[nodiscard]] bool meshEnabled() const { return m_meshEnabled; } + [[nodiscard]] bool rtEnabled() const { return m_rtEnabled; } + + /// Gets or creates a blit PSO for the given render target format. + ID3D12PipelineState* getOrCreateBlitPSO(DXGI_FORMAT format) { + if (!m_blitRootSignature) return nullptr; + + std::lock_guard lock(m_blitMutex); + + auto it = m_blitPsoCache.find(format); + if (it != m_blitPsoCache.end()) return it->second.Get(); + + D3D12_GRAPHICS_PIPELINE_STATE_DESC psd{}; + psd.pRootSignature = m_blitRootSignature.Get(); + psd.VS = m_blitVsBytecode; + psd.PS = m_blitPsBytecode; + psd.InputLayout = { nullptr, 0 }; + psd.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + psd.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID; + psd.RasterizerState.CullMode = D3D12_CULL_MODE_NONE; + psd.RasterizerState.DepthClipEnable = FALSE; + psd.BlendState.RenderTarget[0].BlendEnable = FALSE; + psd.BlendState.RenderTarget[0].RenderTargetWriteMask = 0x0F; + psd.DepthStencilState.DepthEnable = FALSE; + psd.DepthStencilState.StencilEnable = FALSE; + psd.DSVFormat = DXGI_FORMAT_UNKNOWN; + psd.NumRenderTargets = 1; + psd.RTVFormats[0] = format; + psd.SampleDesc.Count = 1; + psd.SampleMask = UINT_MAX; + + ComPtr newPso; + if (SUCCEEDED(m_device->CreateGraphicsPipelineState(&psd, IID_PPV_ARGS(&newPso)))) { + auto* raw = newPso.Get(); + m_blitPsoCache[format] = std::move(newPso); + return raw; + } + return nullptr; + } + + /// Sets a debug name on a DX12 object (visible in PIX, VS Graphics Debugger, etc.). + /// Works with any type that inherits from ID3D12Object (Resource, PSO, QueryHeap, etc.). + template + static void setDebugName(T* obj, std::u8string_view name) { + if (!obj || name.empty()) return; + // Convert narrow to wide. + std::wstring wide; + wide.reserve(name.size()); + for (usize i = 0; i < name.size(); ++i) + wide.push_back(static_cast(name[i])); + obj->SetName(wide.c_str()); + } + +private: + // ------------------------------------------------------------------ + // Indirect command signatures + // ------------------------------------------------------------------ + + void createIndirectCommandSignatures() { + D3D12_INDIRECT_ARGUMENT_DESC argDesc{}; + D3D12_COMMAND_SIGNATURE_DESC sigDesc{}; + sigDesc.NumArgumentDescs = 1; + sigDesc.pArgumentDescs = &argDesc; + sigDesc.NodeMask = 0; + + // Draw. + argDesc.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW; + sigDesc.ByteStride = 16; // sizeof(D3D12_DRAW_ARGUMENTS): 4 x uint32 + m_device->CreateCommandSignature(&sigDesc, nullptr, IID_PPV_ARGS(&m_drawSignature)); + + // DrawIndexed. + argDesc.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED; + sigDesc.ByteStride = 20; // sizeof(D3D12_DRAW_INDEXED_ARGUMENTS): 5 x uint32 + m_device->CreateCommandSignature(&sigDesc, nullptr, IID_PPV_ARGS(&m_drawIndexedSignature)); + + // Dispatch. + argDesc.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH; + sigDesc.ByteStride = 12; // sizeof(D3D12_DISPATCH_ARGUMENTS): 3 x uint32 + m_device->CreateCommandSignature(&sigDesc, nullptr, IID_PPV_ARGS(&m_dispatchSignature)); + } + + // ------------------------------------------------------------------ + // Extension support detection (mesh shader, ray tracing) + // ------------------------------------------------------------------ + + void detectExtensionSupport() { + // Mesh shaders -- requires D3D12_OPTIONS7. + D3D12_FEATURE_DATA_D3D12_OPTIONS7 options7{}; + HRESULT hr = m_device->CheckFeatureSupport( + D3D12_FEATURE_D3D12_OPTIONS7, &options7, sizeof(options7)); + if (SUCCEEDED(hr) && options7.MeshShaderTier != D3D12_MESH_SHADER_TIER_NOT_SUPPORTED) { + m_meshEnabled = true; + + // DispatchMesh command signature. + D3D12_INDIRECT_ARGUMENT_DESC argDesc{}; + argDesc.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH_MESH; + D3D12_COMMAND_SIGNATURE_DESC sigDesc{}; + sigDesc.ByteStride = 12; // sizeof(D3D12_DISPATCH_MESH_ARGUMENTS): 3 x uint32 + sigDesc.NumArgumentDescs = 1; + sigDesc.pArgumentDescs = &argDesc; + sigDesc.NodeMask = 0; + m_device->CreateCommandSignature(&sigDesc, nullptr, IID_PPV_ARGS(&m_dispatchMeshSignature)); + } + + // Ray tracing -- requires D3D12_OPTIONS5. + D3D12_FEATURE_DATA_D3D12_OPTIONS5 options5{}; + hr = m_device->CheckFeatureSupport( + D3D12_FEATURE_D3D12_OPTIONS5, &options5, sizeof(options5)); + if (SUCCEEDED(hr) && options5.RaytracingTier != D3D12_RAYTRACING_TIER_NOT_SUPPORTED) { + m_rtEnabled = true; + } + } + + // ------------------------------------------------------------------ + // Internal blit pipeline (fullscreen triangle VS + texture sample PS) + // ------------------------------------------------------------------ + + void createBlitPipeline() { + // TODO: Blit pipeline requires D3DCompile from d3dcompiler.lib. + // Add d3dcompiler to target_link_libraries and uncomment the code below + // once d3dcompiler linkage is available in this project. + // + // The blit pipeline is used for Blit and GenerateMipmaps operations. + const char vsSource[] = R"( + struct VSOutput { + float4 Position : SV_Position; + float2 UV : TEXCOORD0; + }; + VSOutput main(uint vertexId : SV_VertexID) { + VSOutput output; + output.UV = float2((vertexId << 1) & 2, vertexId & 2); + output.Position = float4(output.UV * float2(2, -2) + float2(-1, 1), 0, 1); + return output; + } + )"; + + const char psSource[] = R"( + Texture2D srcTexture : register(t0); + SamplerState srcSampler : register(s0); + float4 main(float4 pos : SV_Position, float2 uv : TEXCOORD0) : SV_Target { + return srcTexture.Sample(srcSampler, uv); + } + )"; + + ComPtr errorBlob; + + // Compile VS. + HRESULT hr = D3DCompile(vsSource, sizeof(vsSource) - 1, nullptr, nullptr, nullptr, + "main", "vs_5_0", 0, 0, &m_blitVsBlob, &errorBlob); + if (FAILED(hr)) { + if (errorBlob) logErrorf("DxDevice: blit VS compile error: %s", + static_cast(errorBlob->GetBufferPointer())); + return; + } + errorBlob.Reset(); + + // Compile PS. + hr = D3DCompile(psSource, sizeof(psSource) - 1, nullptr, nullptr, nullptr, + "main", "ps_5_0", 0, 0, &m_blitPsBlob, &errorBlob); + if (FAILED(hr)) { + if (errorBlob) logErrorf("DxDevice: blit PS compile error: %s", + static_cast(errorBlob->GetBufferPointer())); + m_blitVsBlob.Reset(); + return; + } + errorBlob.Reset(); + + m_blitVsBytecode = { m_blitVsBlob->GetBufferPointer(), m_blitVsBlob->GetBufferSize() }; + m_blitPsBytecode = { m_blitPsBlob->GetBufferPointer(), m_blitPsBlob->GetBufferSize() }; + + // Root signature: 1 SRV descriptor table (t0) + 1 static linear sampler (s0). + D3D12_DESCRIPTOR_RANGE srvRange{}; + srvRange.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + srvRange.NumDescriptors = 1; + srvRange.BaseShaderRegister = 0; + srvRange.RegisterSpace = 0; + srvRange.OffsetInDescriptorsFromTableStart = 0; + + D3D12_ROOT_PARAMETER rootParam{}; + rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + rootParam.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; + rootParam.DescriptorTable.NumDescriptorRanges = 1; + rootParam.DescriptorTable.pDescriptorRanges = &srvRange; + + D3D12_STATIC_SAMPLER_DESC staticSampler{}; + staticSampler.Filter = D3D12_FILTER_MIN_MAG_MIP_LINEAR; + staticSampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + staticSampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + staticSampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP; + staticSampler.MaxAnisotropy = 1; + staticSampler.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER; + staticSampler.MinLOD = 0; + staticSampler.MaxLOD = D3D12_FLOAT32_MAX; + staticSampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; + + D3D12_ROOT_SIGNATURE_DESC rsDesc{}; + rsDesc.NumParameters = 1; + rsDesc.pParameters = &rootParam; + rsDesc.NumStaticSamplers = 1; + rsDesc.pStaticSamplers = &staticSampler; + + ComPtr signatureBlob; + hr = D3D12SerializeRootSignature(&rsDesc, D3D_ROOT_SIGNATURE_VERSION_1, + &signatureBlob, &errorBlob); + if (FAILED(hr)) { + if (errorBlob) logErrorf("DxDevice: blit root sig serialize error: %s", + static_cast(errorBlob->GetBufferPointer())); + return; + } + + m_device->CreateRootSignature(0, signatureBlob->GetBufferPointer(), + signatureBlob->GetBufferSize(), + IID_PPV_ARGS(&m_blitRootSignature)); + } + + // ------------------------------------------------------------------ + // Member data + // ------------------------------------------------------------------ + + ComPtr m_device; + ComPtr m_infoQueue; + DxAdapterImpl* m_adapter = nullptr; + + // Queues. + std::vector m_graphicsQueues; + std::vector m_computeQueues; + std::vector m_transferQueues; + + // Descriptor heap allocators (CPU-side for staging). + DxDescriptorHeapAllocator m_rtvHeap; + DxDescriptorHeapAllocator m_dsvHeap; + DxDescriptorHeapAllocator m_srvHeap; + DxDescriptorHeapAllocator m_samplerHeap; + + // GPU-visible descriptor heaps (shader-visible, for command buffer binding). + DxGpuDescriptorHeap m_gpuSrvHeap; + DxGpuDescriptorHeap m_gpuSamplerHeap; + + // CPU-visible descriptor heaps (non-shader-visible, bind groups write here). + DxGpuDescriptorHeap m_cpuSrvHeap; + DxGpuDescriptorHeap m_cpuSamplerHeap; + + // Cached command signatures for indirect execution. + ComPtr m_drawSignature; + ComPtr m_drawIndexedSignature; + ComPtr m_dispatchSignature; + ComPtr m_dispatchMeshSignature; + + // Internal blit pipeline. + ComPtr m_blitRootSignature; + D3D12_SHADER_BYTECODE m_blitVsBytecode{}; + D3D12_SHADER_BYTECODE m_blitPsBytecode{}; + ComPtr m_blitVsBlob; + ComPtr m_blitPsBlob; + std::unordered_map> m_blitPsoCache; + std::mutex m_blitMutex; + + // Extension flags. + bool m_meshEnabled = false; + bool m_rtEnabled = false; +}; + +// ================================================================== +// Adapter::CreateDevice implementation +// ================================================================== + +Status DxAdapterImpl::createDevice(const DeviceDesc& desc, Device*& out) { + auto* dev = new DxDeviceImpl(); + if (dev->init(this, desc) != ErrorCode::Ok) { + delete dev; out = nullptr; return ErrorCode::Unknown; + } + out = dev; + return ErrorCode::Ok; +} + +// ---- CommandEncoder out-of-line: blitSubresource (needs DxDeviceImpl) ---- + +void DxCommandEncoderImpl::blitSubresource(DxTextureImpl* srcTex, u32 srcMip, + DxTextureImpl* dstTex, u32 dstMip, u32 dstWidth, u32 dstHeight, DXGI_FORMAT dxgiFormat) { + + auto* blitRootSig = m_device->blitRootSignature(); + if (!blitRootSig) return; + auto* blitPso = m_device->getOrCreateBlitPSO(dxgiFormat); + if (!blitPso) return; + + // Allocate temp RTV for destination mip. + auto rtvHandle = m_device->rtvHeap()->allocate(); + + D3D12_RENDER_TARGET_VIEW_DESC rtvDesc{}; + rtvDesc.Format = dxgiFormat; + rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; + rtvDesc.Texture2D.MipSlice = dstMip; + m_device->handle()->CreateRenderTargetView(dstTex->handle(), &rtvDesc, rtvHandle); + + // Allocate temp SRV in CPU heap, write, then stage-copy to GPU heap. + i32 tempSrvOff = m_device->cpuSrvHeap()->allocate(1); + if (tempSrvOff < 0) { m_device->rtvHeap()->free(rtvHandle); return; } + + auto tempCpuHandle = m_device->cpuSrvHeap()->getCpuHandle(static_cast(tempSrvOff)); + + D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{}; + srvDesc.Format = dxgiFormat; + srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + srvDesc.Texture2D.MostDetailedMip = srcMip; + srvDesc.Texture2D.MipLevels = 1; + m_device->handle()->CreateShaderResourceView(srcTex->handle(), &srvDesc, tempCpuHandle); + + // Copy from CPU heap into GPU staging, then free CPU temp slot. + i32 stagedOff = m_pool->srvStaging()->copyFrom(static_cast(tempSrvOff), 1); + m_device->cpuSrvHeap()->free(static_cast(tempSrvOff), 1); + if (stagedOff < 0) { m_device->rtvHeap()->free(rtvHandle); return; } + + auto srvGpuHandle = m_device->gpuSrvHeap()->getGpuHandle(static_cast(stagedOff)); + + ensureDescriptorHeaps(); + + // Set blit pipeline. + m_cmdList->SetGraphicsRootSignature(blitRootSig); + m_cmdList->SetPipelineState(blitPso); + m_cmdList->SetGraphicsRootDescriptorTable(0, srvGpuHandle); + m_cmdList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr); + + D3D12_VIEWPORT vp{}; vp.Width = static_cast(dstWidth); vp.Height = static_cast(dstHeight); vp.MaxDepth = 1.0f; + m_cmdList->RSSetViewports(1, &vp); + D3D12_RECT sc{}; sc.right = static_cast(dstWidth); sc.bottom = static_cast(dstHeight); + m_cmdList->RSSetScissorRects(1, &sc); + + m_cmdList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + m_cmdList->DrawInstanced(3, 1, 0, 0); + + m_device->rtvHeap()->free(rtvHandle); +} + +// ---- CommandPool out-of-line methods (need DxCommandEncoderImpl + context structs) ---- + +Status DxCommandPoolImpl::createEncoder(CommandEncoder*& out) { + out = nullptr; + + ComPtr d3dDev; + m_allocator->GetDevice(IID_PPV_ARGS(&d3dDev)); + if (!d3dDev) return ErrorCode::Unknown; + + ID3D12GraphicsCommandList* cmdList = nullptr; + HRESULT hr = d3dDev->CreateCommandList(0, m_type, + m_allocator.Get(), nullptr, IID_PPV_ARGS(&cmdList)); + if (FAILED(hr)) return ErrorCode::Unknown; + + DxRenderPassContext rpeCtx{}; + rpeCtx.cmdList = cmdList; + rpeCtx.srvStaging = &m_srvStaging; + rpeCtx.samplerStaging = &m_samplerStaging; + rpeCtx.gpuSrvHeap = m_device->gpuSrvHeap(); + rpeCtx.gpuSamplerHeap = m_device->gpuSamplerHeap(); + rpeCtx.drawSig = m_device->drawSignature(); + rpeCtx.drawIndexedSig = m_device->drawIndexedSignature(); + rpeCtx.dispatchMeshSig = m_device->dispatchMeshSignature(); + + DxComputePassContext cpeCtx{}; + cpeCtx.cmdList = cmdList; + cpeCtx.srvStaging = &m_srvStaging; + cpeCtx.samplerStaging = &m_samplerStaging; + cpeCtx.gpuSrvHeap = m_device->gpuSrvHeap(); + cpeCtx.gpuSamplerHeap = m_device->gpuSamplerHeap(); + cpeCtx.dispatchSig = m_device->dispatchSignature(); + + auto* enc = new DxCommandEncoderImpl(m_device, cmdList, this, rpeCtx, cpeCtx); + out = enc; + return ErrorCode::Ok; +} + +void DxCommandPoolImpl::destroyEncoder(CommandEncoder*& encoder) { + if (auto* dx = static_cast(encoder)) delete dx; + encoder = nullptr; +} + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxFence.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxFence.cppm new file mode 100644 index 00000000..f10855b3 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxFence.cppm @@ -0,0 +1,57 @@ +/// DX12 implementation of Fence using ID3D12Fence. + +module; + +#include "DxIncludes.h" + +export module rhi.dx12:fence; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxFenceImpl : public Fence { +public: + Status init(ID3D12Device* device, u64 initialValue) { + HRESULT hr = device->CreateFence(initialValue, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence)); + if (FAILED(hr)) { + logErrorf("DxFence: CreateFence failed (0x%08X)", static_cast(hr)); + return ErrorCode::Unknown; + } + m_event = CreateEventW(nullptr, FALSE, FALSE, nullptr); + return ErrorCode::Ok; + } + + u64 completedValue() override { + return m_fence->GetCompletedValue(); + } + + bool wait(u64 value, u64 timeoutNs) override { + if (m_fence->GetCompletedValue() >= value) return true; + m_fence->SetEventOnCompletion(value, m_event); + DWORD timeoutMs = (timeoutNs == ~0ull) ? INFINITE : static_cast(timeoutNs / 1000000); + return WaitForSingleObject(m_event, timeoutMs) == WAIT_OBJECT_0; + } + + void cleanup() { + if (m_event) { CloseHandle(m_event); m_event = nullptr; } + m_fence.Reset(); + } + + // ---- Internal ---- + [[nodiscard]] ID3D12Fence* handle() const { return m_fence.Get(); } + + void signal(ID3D12CommandQueue* queue, u64 value) { + queue->Signal(m_fence.Get(), value); + } + +private: + ComPtr m_fence; + HANDLE m_event = nullptr; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxGpuDescriptorHeap.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxGpuDescriptorHeap.cppm new file mode 100644 index 00000000..30499d15 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxGpuDescriptorHeap.cppm @@ -0,0 +1,108 @@ +/// GPU-visible descriptor heap with contiguous block allocation. +/// Used for CBV/SRV/UAV and Sampler heaps that are shader-visible. + +module; + +#include "DxIncludes.h" +#include + +export module rhi.dx12:gpu_descriptor_heap; + +import core.stdtypes; +import core.status; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxGpuDescriptorHeap { +public: + DxGpuDescriptorHeap() = default; + + Status init(ID3D12Device* device, D3D12_DESCRIPTOR_HEAP_TYPE type, u32 capacity, + bool shaderVisible = true) { + m_capacity = capacity; + + D3D12_DESCRIPTOR_HEAP_DESC hd{}; + hd.Type = type; + hd.NumDescriptors = capacity; + hd.Flags = shaderVisible ? D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE + : D3D12_DESCRIPTOR_HEAP_FLAG_NONE; + HRESULT hr = device->CreateDescriptorHeap(&hd, IID_PPV_ARGS(&m_heap)); + if (FAILED(hr)) return ErrorCode::Unknown; + + m_cpuStart = m_heap->GetCPUDescriptorHandleForHeapStart(); + if (shaderVisible) + m_gpuStart = m_heap->GetGPUDescriptorHandleForHeapStart(); + m_incrementSize = device->GetDescriptorHandleIncrementSize(type); + return ErrorCode::Ok; + } + + /// Allocate a contiguous block. Returns offset or -1. + i32 allocate(u32 count) { + if (count == 0) return -1; + // First-fit from free list. + for (usize i = 0; i < m_freeBlocks.size(); ++i) { + auto& b = m_freeBlocks[i]; + if (b.count >= count) { + u32 off = b.offset; + if (b.count == count) + m_freeBlocks.erase(m_freeBlocks.begin() + i); + else + b = { b.offset + count, b.count - count }; + return static_cast(off); + } + } + // Bump allocate. + if (m_nextFree + count <= m_capacity) { + u32 off = m_nextFree; + m_nextFree += count; + return static_cast(off); + } + return -1; + } + + /// Free a block with coalescing. + void free(u32 offset, u32 count) { + if (count == 0) return; + u32 mOff = offset, mCnt = count; + for (usize i = 0; i < m_freeBlocks.size(); ) { + if (m_freeBlocks[i].offset + m_freeBlocks[i].count == mOff) { + mOff = m_freeBlocks[i].offset; mCnt += m_freeBlocks[i].count; + m_freeBlocks.erase(m_freeBlocks.begin() + i); + } else if (mOff + mCnt == m_freeBlocks[i].offset) { + mCnt += m_freeBlocks[i].count; + m_freeBlocks.erase(m_freeBlocks.begin() + i); + } else ++i; + } + if (mOff + mCnt == m_nextFree) + m_nextFree = mOff; + else + m_freeBlocks.push_back({ mOff, mCnt }); + } + + D3D12_CPU_DESCRIPTOR_HANDLE getCpuHandle(u32 offset) const { + D3D12_CPU_DESCRIPTOR_HANDLE h{}; h.ptr = m_cpuStart.ptr + static_cast(offset) * m_incrementSize; return h; + } + D3D12_GPU_DESCRIPTOR_HANDLE getGpuHandle(u32 offset) const { + D3D12_GPU_DESCRIPTOR_HANDLE h{}; h.ptr = m_gpuStart.ptr + static_cast(offset) * m_incrementSize; return h; + } + + void destroy() { m_heap.Reset(); m_freeBlocks.clear(); } + + [[nodiscard]] ID3D12DescriptorHeap* heap() const { return m_heap.Get(); } + [[nodiscard]] u32 incrementSize() const { return m_incrementSize; } + +private: + struct FreeBlock { u32 offset; u32 count; }; + + ComPtr m_heap; + D3D12_CPU_DESCRIPTOR_HANDLE m_cpuStart{}; + D3D12_GPU_DESCRIPTOR_HANDLE m_gpuStart{}; + u32 m_incrementSize = 0; + u32 m_capacity = 0; + u32 m_nextFree = 0; + std::vector m_freeBlocks; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxIncludes.h b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxIncludes.h new file mode 100644 index 00000000..b984153b --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxIncludes.h @@ -0,0 +1,31 @@ +#ifndef DRACO_RHI_DX12_INCLUDES_H_ +#define DRACO_RHI_DX12_INCLUDES_H_ + +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +# define NOMINMAX +#endif + +// DX12 headers use __uuidof (MSVC extension) via IID_PPV_ARGS. Clang supports +// it but warns under -Wlanguage-extension-token; suppress for all DX12 code. +// NOTE: We push but do NOT pop here - the suppression must remain active for +// IID_PPV_ARGS expansions in our .cppm files. The diagnostic state is scoped +// to each translation unit, so it won't leak. +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wlanguage-extension-token" +#endif + +#include +#include // ComPtr +#include +#include +#include + +// Helper alias. +template +using ComPtr = Microsoft::WRL::ComPtr; + +#endif // DRACO_RHI_DX12_INCLUDES_H_ diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxMeshPipeline.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxMeshPipeline.cppm new file mode 100644 index 00000000..0e6225f9 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxMeshPipeline.cppm @@ -0,0 +1,221 @@ +/// DX12 implementation of MeshPipeline. +/// Uses pipeline state stream (ID3D12Device2::CreatePipelineState) since +/// mesh shader pipelines cannot use the traditional D3D12_GRAPHICS_PIPELINE_STATE_DESC. + +module; + +#include "DxIncludes.h" + +#include +#include +#include + +export module rhi.dx12:mesh_pipeline; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :pipeline_layout; +import :shader_module; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxMeshPipelineImpl : public MeshPipeline { +public: + Status init(ID3D12Device* device, const MeshPipelineDesc& desc) { + m_layout = static_cast(desc.layout); + if (!m_layout) { + logErrorf("DxMeshPipeline: pipeline layout is null"); + return ErrorCode::Unknown; + } + layout = desc.layout; + + // Query ID3D12Device2 for CreatePipelineState (pipeline state stream API). + ComPtr device2; + HRESULT hr = device->QueryInterface(IID_PPV_ARGS(&device2)); + if (FAILED(hr) || !device2) { + logErrorf("DxMeshPipeline: QueryInterface for ID3D12Device2 failed (0x%08X)", static_cast(hr)); + return ErrorCode::Unknown; + } + + // Build pipeline state stream as raw bytes. + // Subobjects packed sequentially: root sig, MS, [AS], [PS], blend, + // sample mask, rasterizer, [depth/stencil], [DS format], RT formats, sample desc. + alignas(8) u8 streamBuffer[2048]{}; + usize offset = 0; + + // Root signature (manual - pointer type cannot use writeSubobject). + { + offset = (offset + 7) & ~usize(7); + std::memcpy(&streamBuffer[offset], &(const D3D12_PIPELINE_STATE_SUBOBJECT_TYPE&) + (D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_ROOT_SIGNATURE), sizeof(D3D12_PIPELINE_STATE_SUBOBJECT_TYPE)); + offset += sizeof(D3D12_PIPELINE_STATE_SUBOBJECT_TYPE); + offset = (offset + 7) & ~usize(7); // pointer-align + auto* rootSig = m_layout->handle(); + std::memcpy(&streamBuffer[offset], &rootSig, sizeof(rootSig)); + offset += sizeof(rootSig); + } + + // Mesh shader (required). + auto* msMod = static_cast(desc.mesh.module); + if (!msMod) { + logErrorf("DxMeshPipeline: mesh shader module is null"); + return ErrorCode::Unknown; + } + { + auto msCode = msMod->bytecode(); + D3D12_SHADER_BYTECODE msBC{ msCode.data(), msCode.size() }; + writeSubobject(streamBuffer, offset, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_MS, msBC); + } + + // Task/amplification shader (optional). + if (desc.task.has_value()) { + if (auto* asMod = static_cast(desc.task->module)) { + auto asCode = asMod->bytecode(); + D3D12_SHADER_BYTECODE asBC{ asCode.data(), asCode.size() }; + writeSubobject(streamBuffer, offset, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_AS, asBC); + } + } + + // Fragment/pixel shader (optional). + if (desc.fragment.has_value()) { + if (auto* psMod = static_cast(desc.fragment->shader.module)) { + auto psCode = psMod->bytecode(); + D3D12_SHADER_BYTECODE psBC{ psCode.data(), psCode.size() }; + writeSubobject(streamBuffer, offset, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_PS, psBC); + } + } + + // Blend state. + auto colorTargets = desc.colorTargets; + D3D12_BLEND_DESC blendDesc{}; + blendDesc.AlphaToCoverageEnable = desc.multisample.alphaToCoverageEnabled ? TRUE : FALSE; + blendDesc.IndependentBlendEnable = (colorTargets.size() > 1) ? TRUE : FALSE; + for (usize i = 0; i < colorTargets.size() && i < 8; ++i) { + const auto& t = colorTargets[i]; + auto& rt = blendDesc.RenderTarget[i]; + rt.RenderTargetWriteMask = static_cast(t.writeMask); + if (t.blend.has_value()) { + rt.BlendEnable = TRUE; + rt.SrcBlend = toBlendFactor(t.blend->color.srcFactor); + rt.DestBlend = toBlendFactor(t.blend->color.dstFactor); + rt.BlendOp = toBlendOp(t.blend->color.operation); + rt.SrcBlendAlpha = toBlendFactor(t.blend->alpha.srcFactor); + rt.DestBlendAlpha = toBlendFactor(t.blend->alpha.dstFactor); + rt.BlendOpAlpha = toBlendOp(t.blend->alpha.operation); + } + } + writeSubobject(streamBuffer, offset, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_BLEND, blendDesc); + + // Sample mask. + UINT sampleMask = (desc.multisample.mask != 0) ? desc.multisample.mask : ~0u; + writeSubobject(streamBuffer, offset, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_MASK, sampleMask); + + // Rasterizer state. + D3D12_RASTERIZER_DESC rasterDesc{}; + rasterDesc.FillMode = toFillMode(desc.primitive.fillMode); + rasterDesc.CullMode = toCullMode(desc.primitive.cullMode); + rasterDesc.FrontCounterClockwise = (desc.primitive.frontFace == FrontFace::CCW) ? TRUE : FALSE; + rasterDesc.DepthClipEnable = desc.primitive.depthClipEnabled ? TRUE : FALSE; + rasterDesc.MultisampleEnable = (desc.multisample.count > 1) ? TRUE : FALSE; + + if (desc.depthStencil.has_value()) { + rasterDesc.DepthBias = desc.depthStencil->depthBias; + rasterDesc.DepthBiasClamp = desc.depthStencil->depthBiasClamp; + rasterDesc.SlopeScaledDepthBias = desc.depthStencil->depthBiasSlopeScale; + } + + writeSubobject(streamBuffer, offset, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RASTERIZER, rasterDesc); + + // Depth/stencil state. + if (desc.depthStencil.has_value()) { + const auto& ds = *desc.depthStencil; + + D3D12_DEPTH_STENCIL_DESC dsDesc{}; + dsDesc.DepthEnable = ds.depthTestEnabled ? TRUE : FALSE; + dsDesc.DepthWriteMask = ds.depthWriteEnabled ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO; + dsDesc.DepthFunc = toComparisonFunc(ds.depthCompare); + dsDesc.StencilEnable = ds.stencilEnabled ? TRUE : FALSE; + dsDesc.StencilReadMask = ds.stencilReadMask; + dsDesc.StencilWriteMask = ds.stencilWriteMask; + + auto& ff = dsDesc.FrontFace; + ff.StencilFailOp = toStencilOp(ds.stencilFront.failOp); + ff.StencilDepthFailOp = toStencilOp(ds.stencilFront.depthFailOp); + ff.StencilPassOp = toStencilOp(ds.stencilFront.passOp); + ff.StencilFunc = toComparisonFunc(ds.stencilFront.compare); + + auto& bf = dsDesc.BackFace; + bf.StencilFailOp = toStencilOp(ds.stencilBack.failOp); + bf.StencilDepthFailOp = toStencilOp(ds.stencilBack.depthFailOp); + bf.StencilPassOp = toStencilOp(ds.stencilBack.passOp); + bf.StencilFunc = toComparisonFunc(ds.stencilBack.compare); + + writeSubobject(streamBuffer, offset, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL, dsDesc); + + // Depth/stencil format. + DXGI_FORMAT dsFormat = toDxgiFormat(ds.format); + writeSubobject(streamBuffer, offset, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_DEPTH_STENCIL_FORMAT, dsFormat); + } + + // Render target formats. + D3D12_RT_FORMAT_ARRAY rtFormats{}; + rtFormats.NumRenderTargets = static_cast(std::min(colorTargets.size(), usize(8))); + for (usize i = 0; i < colorTargets.size() && i < 8; ++i) + rtFormats.RTFormats[i] = toDxgiFormat(colorTargets[i].format); + writeSubobject(streamBuffer, offset, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_RENDER_TARGET_FORMATS, rtFormats); + + // Sample desc. + DXGI_SAMPLE_DESC sampleDesc{}; + sampleDesc.Count = std::max(desc.multisample.count, 1u); + sampleDesc.Quality = 0; + writeSubobject(streamBuffer, offset, D3D12_PIPELINE_STATE_SUBOBJECT_TYPE_SAMPLE_DESC, sampleDesc); + + // Create pipeline state via stream API. + D3D12_PIPELINE_STATE_STREAM_DESC streamDesc{}; + streamDesc.SizeInBytes = static_cast(offset); + streamDesc.pPipelineStateSubobjectStream = streamBuffer; + + hr = device2->CreatePipelineState(&streamDesc, IID_PPV_ARGS(&m_pipelineState)); + if (FAILED(hr)) { + logErrorf("DxMeshPipeline: CreatePipelineState failed (0x%08X)", static_cast(hr)); + return ErrorCode::Unknown; + } + return ErrorCode::Ok; + } + + void cleanup() { m_pipelineState.Reset(); } + + [[nodiscard]] ID3D12PipelineState* handle() const { return m_pipelineState.Get(); } + [[nodiscard]] DxPipelineLayoutImpl* pipelineLayout() const { return m_layout; } + +private: + /// Writes a pipeline state stream subobject into the buffer. + /// Each subobject is: { type (aligned to 8), padding to value alignment, value }. + template + static void writeSubobject(u8* buffer, usize& offset, + D3D12_PIPELINE_STATE_SUBOBJECT_TYPE type, const T& value) { + // Align subobject start to pointer size (8 bytes on 64-bit). + offset = (offset + 7) & ~usize(7); + + // Write type. + std::memcpy(&buffer[offset], &type, sizeof(type)); + offset += sizeof(D3D12_PIPELINE_STATE_SUBOBJECT_TYPE); + + // Pad to natural alignment of value within the subobject. + constexpr usize valueAlign = alignof(T); + offset = (offset + valueAlign - 1) & ~(valueAlign - 1); + + // Write value. + std::memcpy(&buffer[offset], &value, sizeof(T)); + offset += sizeof(T); + } + + ComPtr m_pipelineState; + DxPipelineLayoutImpl* m_layout = nullptr; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxModule.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxModule.cppm new file mode 100644 index 00000000..a84256c3 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxModule.cppm @@ -0,0 +1,38 @@ +/// Primary module for rhi.dx12. Re-exports all partitions. +/// DX12 backend - Windows only. + +export module rhi.dx12; + +export import :conversions; +export import :surface; +export import :descriptor_heap; +export import :adapter; +export import :backend; +export import :buffer; +export import :texture; +export import :texture_view; +export import :sampler; +export import :shader_module; +export import :fence; +export import :query_set; +export import :gpu_descriptor_heap; +export import :descriptor_staging; +export import :bind_group_layout; +export import :pipeline_cache; +export import :pipeline_layout; +export import :bind_group; +export import :render_pipeline; +export import :compute_pipeline; +export import :command_buffer; +export import :command_pool; +export import :render_pass_encoder; +export import :render_bundle_encoder; +export import :compute_pass_encoder; +export import :command_encoder; +export import :transfer_batch; +export import :swap_chain; +export import :queue; +export import :accel_struct; +export import :mesh_pipeline; +export import :ray_tracing_pipeline; +export import :device; diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxPipelineCache.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxPipelineCache.cppm new file mode 100644 index 00000000..d83470cd --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxPipelineCache.cppm @@ -0,0 +1,53 @@ +/// DX12 implementation of PipelineCache via ID3D12PipelineLibrary. + +module; + +#include "DxIncludes.h" +#include + +export module rhi.dx12:pipeline_cache; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxPipelineCacheImpl : public PipelineCache { +public: + Status init(ID3D12Device* device, const PipelineCacheDesc& d) { + ComPtr device1; + if (FAILED(device->QueryInterface(IID_PPV_ARGS(&device1)))) return ErrorCode::Unknown; + + HRESULT hr; + if (d.initialData.size() > 0) + hr = device1->CreatePipelineLibrary(d.initialData.data(), d.initialData.size(), IID_PPV_ARGS(&m_library)); + else + hr = device1->CreatePipelineLibrary(nullptr, 0, IID_PPV_ARGS(&m_library)); + + return SUCCEEDED(hr) ? ErrorCode::Ok : ErrorCode::Unknown; + } + + u32 getDataSize() override { + if (!m_library) return 0; + return static_cast(m_library->GetSerializedSize()); + } + + Status getData(std::span outData) override { + if (!m_library) return ErrorCode::Unknown; + auto size = m_library->GetSerializedSize(); + if (outData.size() < size) return ErrorCode::Unknown; + return SUCCEEDED(m_library->Serialize(outData.data(), size)) ? ErrorCode::Ok : ErrorCode::Unknown; + } + + void cleanup() { m_library.Reset(); } + + [[nodiscard]] ID3D12PipelineLibrary* handle() const { return m_library.Get(); } + +private: + ComPtr m_library; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxPipelineLayout.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxPipelineLayout.cppm new file mode 100644 index 00000000..65b3c54b --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxPipelineLayout.cppm @@ -0,0 +1,154 @@ +/// DX12 implementation of PipelineLayout. +/// Creates ID3D12RootSignature from bind group layouts + push constant ranges. + +module; + +#include "DxIncludes.h" +#include +#include + +export module rhi.dx12:pipeline_layout; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :bind_group_layout; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +struct DynamicRootEntry { + u32 groupIndex; + u32 dynamicIndex; + i32 rootParamIndex; + D3D12_ROOT_PARAMETER_TYPE paramType; +}; + +class DxPipelineLayoutImpl : public PipelineLayout { +public: + Status init(ID3D12Device* device, const PipelineLayoutDesc& d) { + m_numBindGroups = static_cast(d.bindGroupLayouts.size()); + + std::vector rootParams; + // Storage for descriptor ranges (must outlive SerializeRootSignature). + std::vector> rangeStorage; + + m_rootParamMap.resize(d.bindGroupLayouts.size() * 2, -1); + + for (usize gi = 0; gi < d.bindGroupLayouts.size(); ++gi) { + auto* layout = static_cast(d.bindGroupLayouts[gi]); + if (!layout) return ErrorCode::Unknown; + + std::vector csvRanges, sampRanges; + u32 dynIdx = 0; + + auto ranges = layout->ranges(); + for (usize ri = 0; ri < ranges.size(); ++ri) { + const auto& r = ranges[ri]; + + if (r.hasDynamicOffset) { + D3D12_ROOT_PARAMETER_TYPE pt; + switch (r.type) { + case BindingType::UniformBuffer: pt = D3D12_ROOT_PARAMETER_TYPE_CBV; break; + case BindingType::StorageBufferReadOnly: pt = D3D12_ROOT_PARAMETER_TYPE_SRV; break; + case BindingType::StorageBufferReadWrite: pt = D3D12_ROOT_PARAMETER_TYPE_UAV; break; + default: continue; + } + D3D12_ROOT_PARAMETER p{}; p.ParameterType = pt; + p.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + p.Descriptor.ShaderRegister = r.binding; + p.Descriptor.RegisterSpace = static_cast(gi); + + m_dynamicRootEntries.push_back({ static_cast(gi), dynIdx, + static_cast(rootParams.size()), pt }); + rootParams.push_back(p); + ++dynIdx; + continue; + } + + D3D12_DESCRIPTOR_RANGE dr{}; + dr.RangeType = toDescriptorRangeType(r.type); + dr.NumDescriptors = r.count; + dr.BaseShaderRegister = r.binding; + dr.RegisterSpace = static_cast(gi); + dr.OffsetInDescriptorsFromTableStart = r.heapOffset; + + if (r.isSampler) sampRanges.push_back(dr); + else csvRanges.push_back(dr); + } + + if (!csvRanges.empty()) { + rangeStorage.push_back(static_cast&&>(csvRanges)); + auto& stored = rangeStorage.back(); + D3D12_ROOT_PARAMETER p{}; p.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + p.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + p.DescriptorTable.NumDescriptorRanges = static_cast(stored.size()); + p.DescriptorTable.pDescriptorRanges = stored.data(); + m_rootParamMap[gi * 2] = static_cast(rootParams.size()); + rootParams.push_back(p); + } + if (!sampRanges.empty()) { + rangeStorage.push_back(static_cast&&>(sampRanges)); + auto& stored = rangeStorage.back(); + D3D12_ROOT_PARAMETER p{}; p.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + p.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + p.DescriptorTable.NumDescriptorRanges = static_cast(stored.size()); + p.DescriptorTable.pDescriptorRanges = stored.data(); + m_rootParamMap[gi * 2 + 1] = static_cast(rootParams.size()); + rootParams.push_back(p); + } + } + + // Push constants → root 32-bit constants. + for (usize i = 0; i < d.pushConstantRanges.size(); ++i) { + const auto& pc = d.pushConstantRanges[i]; + if (m_pushConstantRootIndex < 0) + m_pushConstantRootIndex = static_cast(rootParams.size()); + + D3D12_ROOT_PARAMETER p{}; p.ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS; + p.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + p.Constants.ShaderRegister = pc.offset / 4; + p.Constants.RegisterSpace = m_numBindGroups; + p.Constants.Num32BitValues = pc.size / 4; + rootParams.push_back(p); + } + + // Serialize and create root signature. + D3D12_ROOT_SIGNATURE_DESC rsDesc{}; + rsDesc.NumParameters = static_cast(rootParams.size()); + rsDesc.pParameters = rootParams.data(); + rsDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; + + ComPtr sigBlob, errBlob; + HRESULT hr = D3D12SerializeRootSignature(&rsDesc, D3D_ROOT_SIGNATURE_VERSION_1, + &sigBlob, &errBlob); + if (FAILED(hr)) { + if (errBlob) logErrorf("DxPipelineLayout: %s", static_cast(errBlob->GetBufferPointer())); + return ErrorCode::Unknown; + } + + hr = device->CreateRootSignature(0, sigBlob->GetBufferPointer(), sigBlob->GetBufferSize(), + IID_PPV_ARGS(&m_rootSig)); + return SUCCEEDED(hr) ? ErrorCode::Ok : ErrorCode::Unknown; + } + + void cleanup() { m_rootSig.Reset(); } + + [[nodiscard]] ID3D12RootSignature* handle() const { return m_rootSig.Get(); } + [[nodiscard]] i32 getCbvSrvUavRootIndex(u32 gi) const { return (gi*2 < m_rootParamMap.size()) ? m_rootParamMap[gi*2] : -1; } + [[nodiscard]] i32 getSamplerRootIndex(u32 gi) const { return (gi*2+1 < m_rootParamMap.size()) ? m_rootParamMap[gi*2+1] : -1; } + [[nodiscard]] i32 pushConstantRootIndex() const { return m_pushConstantRootIndex; } + [[nodiscard]] u32 numBindGroups() const { return m_numBindGroups; } + [[nodiscard]] std::span dynamicRootEntries() const { return { m_dynamicRootEntries.data(), m_dynamicRootEntries.size() }; } + +private: + ComPtr m_rootSig; + std::vector m_rootParamMap; + std::vector m_dynamicRootEntries; + i32 m_pushConstantRootIndex = -1; + u32 m_numBindGroups = 0; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxQuerySet.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxQuerySet.cppm new file mode 100644 index 00000000..3f0e9bfd --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxQuerySet.cppm @@ -0,0 +1,60 @@ +/// DX12 implementation of QuerySet. Wraps ID3D12QueryHeap. + +module; + +#include "DxIncludes.h" + +export module rhi.dx12:query_set; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxQuerySetImpl : public QuerySet { +public: + Status init(ID3D12Device* device, const QuerySetDesc& d) { + type = d.type; + count = d.count; + + D3D12_QUERY_HEAP_DESC hd{}; + hd.Type = toQueryHeapType(d.type); + hd.Count = d.count; + HRESULT hr = device->CreateQueryHeap(&hd, IID_PPV_ARGS(&m_heap)); + if (FAILED(hr)) { + logErrorf("DxQuerySet: CreateQueryHeap failed (0x%08X)", static_cast(hr)); + return ErrorCode::Unknown; + } + return ErrorCode::Ok; + } + + void cleanup() { m_heap.Reset(); } + + [[nodiscard]] ID3D12QueryHeap* handle() const { return m_heap.Get(); } + + static D3D12_QUERY_HEAP_TYPE toQueryHeapType(QueryType t) { + switch (t) { + case QueryType::Timestamp: return D3D12_QUERY_HEAP_TYPE_TIMESTAMP; + case QueryType::Occlusion: return D3D12_QUERY_HEAP_TYPE_OCCLUSION; + case QueryType::PipelineStatistics: return D3D12_QUERY_HEAP_TYPE_PIPELINE_STATISTICS; + } + return D3D12_QUERY_HEAP_TYPE_TIMESTAMP; + } + + static D3D12_QUERY_TYPE toDxQueryType(QueryType t) { + switch (t) { + case QueryType::Timestamp: return D3D12_QUERY_TYPE_TIMESTAMP; + case QueryType::Occlusion: return D3D12_QUERY_TYPE_OCCLUSION; + case QueryType::PipelineStatistics: return D3D12_QUERY_TYPE_PIPELINE_STATISTICS; + } + return D3D12_QUERY_TYPE_TIMESTAMP; + } + +private: + ComPtr m_heap; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxQueue.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxQueue.cppm new file mode 100644 index 00000000..c12766d0 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxQueue.cppm @@ -0,0 +1,127 @@ +/// DX12 implementation of Queue. + +module; + +#include "DxIncludes.h" +#include +#include + +export module rhi.dx12:queue; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :command_buffer; +import :fence; +import :transfer_batch; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxDeviceImpl; // forward + +class DxQueueImpl : public Queue { +public: + Status init(ID3D12Device* device, QueueType type, DxDeviceImpl* owner) { + queueType = type; + m_device = owner; + m_d3dDevice = device; + + D3D12_COMMAND_QUEUE_DESC qd{}; + qd.Type = toCommandListType(type); + HRESULT hr = device->CreateCommandQueue(&qd, IID_PPV_ARGS(&m_queue)); + if (FAILED(hr)) return ErrorCode::Unknown; + + hr = device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_internalFence)); + if (FAILED(hr)) return ErrorCode::Unknown; + m_fenceEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + + // Query timestamp frequency. + UINT64 freq = 0; + m_queue->GetTimestampFrequency(&freq); + m_tsPeriod = (freq > 0) ? (1e9f / static_cast(freq)) : 1.0f; + + return ErrorCode::Ok; + } + + // ---- Queue interface ---- + + void submit(std::span cmdBufs) override { + if (cmdBufs.size() == 0) return; + std::vector lists(cmdBufs.size()); + for (usize i = 0; i < cmdBufs.size(); ++i) { + if (auto* dxCb = static_cast(cmdBufs[i])) + lists[i] = dxCb->handle(); + } + m_queue->ExecuteCommandLists(static_cast(lists.size()), lists.data()); + } + + void submit(std::span cmdBufs, Fence* signalFence, u64 signalValue) override { + submit(cmdBufs); + if (auto* f = static_cast(signalFence)) + m_queue->Signal(f->handle(), signalValue); + } + + void submit(std::span cmdBufs, + std::span waitFences, std::span waitValues, + Fence* signalFence, u64 signalValue) override { + for (usize i = 0; i < waitFences.size(); ++i) + if (auto* f = static_cast(waitFences[i])) + m_queue->Wait(f->handle(), waitValues[i]); + submit(cmdBufs); + if (auto* f = static_cast(signalFence)) + m_queue->Signal(f->handle(), signalValue); + } + + void waitIdle() override { + ++m_fenceValue; + m_queue->Signal(m_internalFence.Get(), m_fenceValue); + if (m_internalFence->GetCompletedValue() < m_fenceValue) { + m_internalFence->SetEventOnCompletion(m_fenceValue, m_fenceEvent); + WaitForSingleObject(m_fenceEvent, INFINITE); + } + } + + Status createTransferBatch(TransferBatch*& out) override { + auto* batch = new DxTransferBatchImpl(); + if (batch->init(m_d3dDevice, m_queue.Get(), queueType) != ErrorCode::Ok) { + delete batch; + return ErrorCode::Unknown; + } + out = batch; + return ErrorCode::Ok; + } + + void destroyTransferBatch(TransferBatch*& batch) override { + if (auto* dx = static_cast(batch)) { + dx->destroy(); + delete dx; + } + batch = nullptr; + } + + f32 timestampPeriod() const override { return m_tsPeriod; } + + void cleanup() { + if (m_fenceEvent) { CloseHandle(m_fenceEvent); m_fenceEvent = nullptr; } + m_internalFence.Reset(); + m_queue.Reset(); + } + + // ---- Internal ---- + [[nodiscard]] ID3D12CommandQueue* handle() const { return m_queue.Get(); } + [[nodiscard]] DxDeviceImpl* owner() const { return m_device; } + +private: + ComPtr m_queue; + ComPtr m_internalFence; + HANDLE m_fenceEvent = nullptr; + u64 m_fenceValue = 0; + f32 m_tsPeriod = 1.0f; + DxDeviceImpl* m_device = nullptr; + ID3D12Device* m_d3dDevice = nullptr; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxRayTracingPipeline.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxRayTracingPipeline.cppm new file mode 100644 index 00000000..92140aa0 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxRayTracingPipeline.cppm @@ -0,0 +1,236 @@ +/// DX12 implementation of RayTracingPipeline. +/// Wraps a D3D12 state object created via ID3D12Device5::CreateStateObject. + +module; + +#include "DxIncludes.h" +#include +#include + +#include +#include + +export module rhi.dx12:ray_tracing_pipeline; + +import core.stdtypes; +import core.status; +import rhi; +import :shader_module; +import :pipeline_layout; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxRayTracingPipelineImpl : public RayTracingPipeline { +public: + Status init(ID3D12Device* device, const RayTracingPipelineDesc& desc) { + m_layout = static_cast(desc.layout); + if (!m_layout) { + logErrorf("DxRayTracingPipeline: pipeline layout is null"); + return ErrorCode::Unknown; + } + layout = desc.layout; + + // Query ID3D12Device5 for CreateStateObject. + ComPtr device5; + HRESULT hr = device->QueryInterface(IID_PPV_ARGS(&device5)); + if (FAILED(hr) || !device5) { + logErrorf("DxRayTracingPipeline: QueryInterface for ID3D12Device5 failed (0x%08X)", + static_cast(hr)); + return ErrorCode::Unknown; + } + + // Collect entry point names and per-stage export descriptors. + // Export names are UTF-8 std::u8string_view; DX12 wants LPCWSTR, so widen them + // (ASCII-safe for shader export names). + std::vector entryWide; + std::vector exports; + + for (usize i = 0; i < desc.stages.size(); ++i) { + const auto& stage = desc.stages[i]; + auto* dxMod = static_cast(stage.module); + if (!dxMod) continue; + + entryWide.emplace_back( + reinterpret_cast(stage.entryPoint.data()), + stage.entryPoint.size()); + } + + exports.resize(entryWide.size()); + for (usize i = 0; i < entryWide.size(); ++i) { + exports[i] = {}; + exports[i].Name = entryWide[i].c_str(); + exports[i].Flags = D3D12_EXPORT_FLAG_NONE; + } + + // Build one DXIL library subobject per stage. + std::vector libraries; + for (usize i = 0; i < desc.stages.size(); ++i) { + auto* dxMod = static_cast(desc.stages[i].module); + if (!dxMod) continue; + + auto bc = dxMod->bytecode(); + D3D12_DXIL_LIBRARY_DESC lib{}; + lib.DXILLibrary.pShaderBytecode = bc.data(); + lib.DXILLibrary.BytecodeLength = bc.size(); + lib.NumExports = 1; + lib.pExports = &exports[i]; + libraries.push_back(lib); + } + + // Count hit groups. + u32 numHitGroups = 0; + for (usize i = 0; i < desc.groups.size(); ++i) { + if (desc.groups[i].type == RayTracingShaderGroup::Type::TrianglesHitGroup || + desc.groups[i].type == RayTracingShaderGroup::Type::ProceduralHitGroup) + ++numHitGroups; + } + + // Total subobjects: libraries + hit groups + shader config + pipeline config + global root sig. + usize subobjectCount = libraries.size() + numHitGroups + 3; + std::vector subobjects(subobjectCount); + usize soIdx = 0; + + // --- DXIL library subobjects --- + for (usize i = 0; i < libraries.size(); ++i) { + subobjects[soIdx].Type = D3D12_STATE_SUBOBJECT_TYPE_DXIL_LIBRARY; + subobjects[soIdx].pDesc = &libraries[i]; + ++soIdx; + } + + // --- Hit groups --- + std::vector hitGroups; + std::vector hitGroupNamesWide; + + for (usize i = 0; i < desc.groups.size(); ++i) { + const auto& group = desc.groups[i]; + if (group.type != RayTracingShaderGroup::Type::TrianglesHitGroup && + group.type != RayTracingShaderGroup::Type::ProceduralHitGroup) + continue; + + // Format "HitGroupN" where N is the group index. + std::wstring hgName = L"HitGroup" + std::to_wstring(i); + hitGroupNamesWide.push_back(hgName); + + D3D12_HIT_GROUP_DESC hg{}; + hg.HitGroupExport = hitGroupNamesWide.back().c_str(); + hg.Type = (group.type == RayTracingShaderGroup::Type::TrianglesHitGroup) + ? D3D12_HIT_GROUP_TYPE_TRIANGLES + : D3D12_HIT_GROUP_TYPE_PROCEDURAL_PRIMITIVE; + + if (group.closestHitShaderIndex != ~0u && group.closestHitShaderIndex < entryWide.size()) + hg.ClosestHitShaderImport = entryWide[group.closestHitShaderIndex].c_str(); + + if (group.anyHitShaderIndex != ~0u && group.anyHitShaderIndex < entryWide.size()) + hg.AnyHitShaderImport = entryWide[group.anyHitShaderIndex].c_str(); + + if (group.intersectionShaderIndex != ~0u && group.intersectionShaderIndex < entryWide.size()) + hg.IntersectionShaderImport = entryWide[group.intersectionShaderIndex].c_str(); + + hitGroups.push_back(hg); + } + + for (usize i = 0; i < hitGroups.size(); ++i) { + subobjects[soIdx].Type = D3D12_STATE_SUBOBJECT_TYPE_HIT_GROUP; + subobjects[soIdx].pDesc = &hitGroups[i]; + ++soIdx; + } + + // --- Build group-to-export-name mapping --- + // For each group in desc.Groups order, store the DX12 export name + // used to retrieve its shader identifier. + for (usize i = 0; i < desc.groups.size(); ++i) { + const auto& group = desc.groups[i]; + if (group.type == RayTracingShaderGroup::Type::General) { + // General groups (raygen/miss/callable) use the entry point name. + if (group.generalShaderIndex != ~0u && group.generalShaderIndex < desc.stages.size()) { + auto ep = desc.stages[group.generalShaderIndex].entryPoint; + m_groupExportNames.emplace_back( + reinterpret_cast(ep.data()), ep.size()); + } else { + m_groupExportNames.emplace_back(); + } + } else { + // Hit groups use "HitGroupN" where N is the group index. + m_groupExportNames.push_back(L"HitGroup" + std::to_wstring(i)); + } + } + + // --- Shader config --- + D3D12_RAYTRACING_SHADER_CONFIG shaderConfig{}; + shaderConfig.MaxPayloadSizeInBytes = (desc.maxPayloadSize > 0) ? desc.maxPayloadSize : 32; + shaderConfig.MaxAttributeSizeInBytes = (desc.maxAttributeSize > 0) ? desc.maxAttributeSize : 8; + subobjects[soIdx].Type = D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_SHADER_CONFIG; + subobjects[soIdx].pDesc = &shaderConfig; + ++soIdx; + + // --- Pipeline config --- + D3D12_RAYTRACING_PIPELINE_CONFIG pipelineConfig{}; + pipelineConfig.MaxTraceRecursionDepth = desc.maxRecursionDepth; + subobjects[soIdx].Type = D3D12_STATE_SUBOBJECT_TYPE_RAYTRACING_PIPELINE_CONFIG; + subobjects[soIdx].pDesc = &pipelineConfig; + ++soIdx; + + // --- Global root signature --- + D3D12_GLOBAL_ROOT_SIGNATURE globalRootSig{}; + globalRootSig.pGlobalRootSignature = m_layout->handle(); + subobjects[soIdx].Type = D3D12_STATE_SUBOBJECT_TYPE_GLOBAL_ROOT_SIGNATURE; + subobjects[soIdx].pDesc = &globalRootSig; + ++soIdx; + + // --- Create state object --- + D3D12_STATE_OBJECT_DESC stateObjDesc{}; + stateObjDesc.Type = D3D12_STATE_OBJECT_TYPE_RAYTRACING_PIPELINE; + stateObjDesc.NumSubobjects = static_cast(soIdx); + stateObjDesc.pSubobjects = subobjects.data(); + + hr = device5->CreateStateObject(&stateObjDesc, IID_PPV_ARGS(&m_stateObject)); + if (FAILED(hr) || !m_stateObject) { + logErrorf("DxRayTracingPipeline: CreateStateObject failed (0x%08X)", + static_cast(hr)); + return ErrorCode::Unknown; + } + + // Query properties for shader identifier lookup. + hr = m_stateObject->QueryInterface(IID_PPV_ARGS(&m_properties)); + if (FAILED(hr)) m_properties.Reset(); + + return ErrorCode::Ok; + } + + /// Gets the shader identifier for an export name. + /// Returns a pointer to D3D12_SHADER_IDENTIFIER_SIZE_IN_BYTES (32) bytes, + /// or nullptr on failure. + [[nodiscard]] void* getShaderIdentifier(std::u8string_view exportName) const { + if (!m_properties) return nullptr; + + // Convert narrow string to wide (ASCII-safe for shader entry points). + std::wstring wide; + wide.reserve(exportName.size()); + for (usize i = 0; i < exportName.size(); ++i) + wide.push_back(static_cast(exportName[i])); + + return m_properties->GetShaderIdentifier(wide.c_str()); + } + + void cleanup() { + m_properties.Reset(); + m_stateObject.Reset(); + } + + [[nodiscard]] ID3D12StateObject* handle() const { return m_stateObject.Get(); } + [[nodiscard]] ID3D12StateObjectProperties* properties() const { return m_properties.Get(); } + [[nodiscard]] DxPipelineLayoutImpl* pipelineLayout() const { return m_layout; } + [[nodiscard]] std::span groupExportNames() const { + return { m_groupExportNames.data(), m_groupExportNames.size() }; + } + +private: + ComPtr m_stateObject; + ComPtr m_properties; + DxPipelineLayoutImpl* m_layout = nullptr; + std::vector m_groupExportNames; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxRenderBundleEncoder.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxRenderBundleEncoder.cppm new file mode 100644 index 00000000..d55bb089 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxRenderBundleEncoder.cppm @@ -0,0 +1,69 @@ +/// DX12 implementation of RenderBundleEncoder + RenderBundle. +/// +/// A render bundle is an ID3D12GraphicsCommandList of type D3D12_COMMAND_LIST_TYPE_BUNDLE, +/// recorded once and replayed into a direct command list via ExecuteBundle +/// (RenderPassEncoder::ExecuteBundles). DX12 bundles inherit the parent's descriptor heaps, +/// viewport, scissor, and render targets - but NOT pipeline state / topology, which the bundle +/// sets itself (handled by SetPipeline). Draw recording is delegated to a DxRenderPassEncoderImpl +/// whose context points at the bundle list, reusing the full root-signature / descriptor-table +/// binding logic. +/// +/// BEST-EFFORT (authored without a Windows toolchain to compile against - shake out on Windows): +/// the structure + DX12 bundle semantics are in place; details (heap inheritance, signature +/// caching, lifetime vs frames-in-flight) may need adjustment. + +module; + +#include "DxIncludes.h" +#include +#include + +export module rhi.dx12:render_bundle_encoder; + +import core.stdtypes; +import core.status; +import rhi; +import :render_pass_encoder; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +// DxRenderBundleImpl lives in :render_pass_encoder (so ExecuteBundles can use it without a +// module cycle); this partition imports it from there. + +// Records draws into a bundle command list by delegating to a render-pass encoder pointed at it. +class DxRenderBundleEncoderImpl : public RenderBundleEncoder { +public: + DxRenderBundleEncoderImpl(const DxRenderPassContext& ctx, + ComPtr list, ComPtr alloc) + : m_rec(ctx), m_list(std::move(list)), m_alloc(std::move(alloc)) { + m_rec.begin(RenderPassDesc{}); // reset pipeline-tracking state (no pass attachments needed) + } + ~DxRenderBundleEncoderImpl() override { delete m_bundle; } + + void setPipeline(RenderPipeline* p) override { m_rec.setPipeline(p); } + void setBindGroup(u32 i, BindGroup* g, std::span d) override { m_rec.setBindGroup(i, g, d); } + void setPushConstants(ShaderStage s, u32 o, u32 sz, const void* d) override { m_rec.setPushConstants(s, o, sz, d); } + void setVertexBuffer(u32 slot, Buffer* b, u64 o) override { m_rec.setVertexBuffer(slot, b, o); } + void setIndexBuffer(Buffer* b, IndexFormat f, u64 o) override { m_rec.setIndexBuffer(b, f, o); } + void draw(u32 v, u32 inst, u32 fv, u32 fi) override { m_rec.draw(v, inst, fv, fi); } + void drawIndexed(u32 ic, u32 inst, u32 fi, i32 bv, u32 finst) override { m_rec.drawIndexed(ic, inst, fi, bv, finst); } + void drawIndirect(Buffer* b, u64 o, u32 dc, u32 st) override { m_rec.drawIndirect(b, o, dc, st); } + void drawIndexedIndirect(Buffer* b, u64 o, u32 dc, u32 st) override { m_rec.drawIndexedIndirect(b, o, dc, st); } + + RenderBundle* finish() override { + if (m_bundle) return m_bundle; + m_list->Close(); + m_bundle = new DxRenderBundleImpl(m_list, m_alloc, m_rec.currentRootSig(), m_rec.currentPso()); + return m_bundle; + } + +private: + DxRenderPassEncoderImpl m_rec; + ComPtr m_list; + ComPtr m_alloc; + DxRenderBundleImpl* m_bundle = nullptr; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxRenderPassEncoder.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxRenderPassEncoder.cppm new file mode 100644 index 00000000..22b364b7 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxRenderPassEncoder.cppm @@ -0,0 +1,444 @@ +/// DX12 implementation of RenderPassEncoder + MeshShaderPassExt. +/// Records render pass commands into the parent command encoder's command list. + +module; + +#include "DxIncludes.h" +#include +#include + +export module rhi.dx12:render_pass_encoder; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :buffer; +import :bind_group; +import :bind_group_layout; +import :render_pipeline; +import :pipeline_layout; +import :query_set; +import :texture; +import :texture_view; +import :descriptor_staging; +import :gpu_descriptor_heap; +import :mesh_pipeline; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +// A recorded bundle command list (+ its allocator, which must outlive GPU execution). Lives +// here (not in :render_bundle_encoder) so ExecuteBundles can use it without a module cycle. +class DxRenderBundleImpl : public RenderBundle { +public: + DxRenderBundleImpl(ComPtr list, ComPtr alloc, + ID3D12RootSignature* rootSig = nullptr, ID3D12PipelineState* pso = nullptr) + : m_list(std::move(list)), m_alloc(std::move(alloc)), m_rootSig(rootSig), m_pso(pso) {} + [[nodiscard]] ID3D12GraphicsCommandList* handle() const { return m_list.Get(); } + [[nodiscard]] ID3D12RootSignature* rootSig() const { return m_rootSig; } + [[nodiscard]] ID3D12PipelineState* pso() const { return m_pso; } +private: + ComPtr m_list; + ComPtr m_alloc; + ID3D12RootSignature* m_rootSig = nullptr; + ID3D12PipelineState* m_pso = nullptr; +}; + +/// Pointers needed by the render pass encoder, provided by the command encoder. +/// Avoids coupling to DxCommandEncoderImpl directly. +struct DxRenderPassContext { + ID3D12GraphicsCommandList* cmdList = nullptr; + DxDescriptorStaging* srvStaging = nullptr; + DxDescriptorStaging* samplerStaging = nullptr; + DxGpuDescriptorHeap* gpuSrvHeap = nullptr; + DxGpuDescriptorHeap* gpuSamplerHeap = nullptr; + // Indirect command signatures (cached on device). + ID3D12CommandSignature* drawSig = nullptr; + ID3D12CommandSignature* drawIndexedSig = nullptr; + ID3D12CommandSignature* dispatchMeshSig = nullptr; +}; + +class DxRenderPassEncoderImpl : public RenderPassEncoder, public MeshShaderPassExt { +public: + MeshShaderPassExt* asMeshShaderExt() noexcept override { return this; } + explicit DxRenderPassEncoderImpl(const DxRenderPassContext& ctx) + : m_ctx(ctx) {} + + void begin(const RenderPassDesc& desc) { + m_desc = desc; + m_currentPipeline = nullptr; + m_currentMeshPipeline = nullptr; + } + + // ---- RenderPassEncoder: Pipeline & Binding ---- + + void setPipeline(RenderPipeline* pipeline) override { + auto* dxPipeline = static_cast(pipeline); + if (!dxPipeline) return; + m_currentPipeline = dxPipeline; + m_currentMeshPipeline = nullptr; + + auto* cmdList = m_ctx.cmdList; + cmdList->SetPipelineState(dxPipeline->handle()); + cmdList->SetGraphicsRootSignature(dxPipeline->pipelineLayout()->handle()); + cmdList->IASetPrimitiveTopology(dxPipeline->topology()); + + // Re-apply cached vertex buffers with correct strides from the new pipeline. + // DX12 vertex buffer views include stride, which comes from the pipeline. + // If setVertexBuffer was called before setPipeline, the stride was 0. + for (u32 slot = 0; slot < m_cachedVbCount; ++slot) { + if (m_cachedVbs[slot].BufferLocation != 0) { + m_cachedVbs[slot].StrideInBytes = dxPipeline->getVertexStride(slot); + cmdList->IASetVertexBuffers(slot, 1, &m_cachedVbs[slot]); + } + } + } + + void setBindGroup(u32 index, BindGroup* group, std::span dynamicOffsets) override { + auto* dxGroup = static_cast(group); + if (!dxGroup) return; + + auto* layout = getCurrentLayout(); + if (!layout) return; + + auto* cmdList = m_ctx.cmdList; + auto* dxLayout = static_cast(dxGroup->layout()); + + // Copy-on-bind: copy bind group's descriptors into encoder's staging region, + // then bind from the staging offset. This makes bind group destruction safe + // during command recording -- the GPU only references the staging copy. + + // Bind CBV/SRV/UAV table (staged). + if (dxGroup->cbvSrvUavOffset() >= 0 && dxLayout && dxLayout->cbvSrvUavCount() > 0) { + i32 rootIdx = layout->getCbvSrvUavRootIndex(index); + if (rootIdx >= 0) { + i32 stagedOffset = m_ctx.srvStaging->copyFrom( + static_cast(dxGroup->cbvSrvUavOffset()), dxLayout->cbvSrvUavCount()); + if (stagedOffset >= 0) { + auto gpuHandle = m_ctx.gpuSrvHeap->getGpuHandle(static_cast(stagedOffset)); + cmdList->SetGraphicsRootDescriptorTable(static_cast(rootIdx), gpuHandle); + } + } + } + + // Bind sampler table (staged). + if (dxGroup->samplerOffset() >= 0 && dxLayout && dxLayout->samplerCount() > 0) { + i32 rootIdx = layout->getSamplerRootIndex(index); + if (rootIdx >= 0) { + i32 stagedOffset = m_ctx.samplerStaging->copyFrom( + static_cast(dxGroup->samplerOffset()), dxLayout->samplerCount()); + if (stagedOffset >= 0) { + auto gpuHandle = m_ctx.gpuSamplerHeap->getGpuHandle(static_cast(stagedOffset)); + cmdList->SetGraphicsRootDescriptorTable(static_cast(rootIdx), gpuHandle); + } + } + } + + // Bind dynamic offset root descriptors (not staged -- uses GPU virtual addresses). + auto dynAddrs = dxGroup->dynamicGpuAddresses(); + usize dynOffsetIdx = 0; + for (usize i = 0; i < layout->dynamicRootEntries().size(); ++i) { + const auto& entry = layout->dynamicRootEntries()[i]; + if (entry.groupIndex != index) continue; + if (entry.dynamicIndex >= static_cast(dynAddrs.size())) continue; + + u64 gpuAddr = dynAddrs[entry.dynamicIndex]; + if (dynOffsetIdx < dynamicOffsets.size()) + gpuAddr += static_cast(dynamicOffsets[dynOffsetIdx]); + ++dynOffsetIdx; + + switch (entry.paramType) { + case D3D12_ROOT_PARAMETER_TYPE_CBV: + cmdList->SetGraphicsRootConstantBufferView(static_cast(entry.rootParamIndex), gpuAddr); + break; + case D3D12_ROOT_PARAMETER_TYPE_SRV: + cmdList->SetGraphicsRootShaderResourceView(static_cast(entry.rootParamIndex), gpuAddr); + break; + case D3D12_ROOT_PARAMETER_TYPE_UAV: + cmdList->SetGraphicsRootUnorderedAccessView(static_cast(entry.rootParamIndex), gpuAddr); + break; + default: break; + } + } + } + + void setPushConstants(ShaderStage /*stages*/, u32 offset, u32 size, const void* data) override { + auto* layout = getCurrentLayout(); + if (!layout || layout->pushConstantRootIndex() < 0) return; + + m_ctx.cmdList->SetGraphicsRoot32BitConstants( + static_cast(layout->pushConstantRootIndex()), + size / 4, data, offset / 4); + } + + // ---- Vertex & Index Buffers ---- + + void setVertexBuffer(u32 slot, Buffer* buffer, u64 offset) override { + auto* dxBuf = static_cast(buffer); + if (!dxBuf || slot >= 8) return; + + u32 stride = m_currentPipeline ? m_currentPipeline->getVertexStride(slot) : 0; + + D3D12_VERTEX_BUFFER_VIEW view{}; + view.BufferLocation = dxBuf->gpuAddress() + offset; + view.SizeInBytes = static_cast(dxBuf->desc.size - offset); + view.StrideInBytes = stride; + + // Cache for re-application when pipeline changes. + m_cachedVbs[slot] = view; + if (slot >= m_cachedVbCount) m_cachedVbCount = slot + 1; + + m_ctx.cmdList->IASetVertexBuffers(slot, 1, &view); + } + + void setIndexBuffer(Buffer* buffer, IndexFormat format, u64 offset) override { + auto* dxBuf = static_cast(buffer); + if (!dxBuf) return; + + D3D12_INDEX_BUFFER_VIEW view{}; + view.BufferLocation = dxBuf->gpuAddress() + offset; + view.SizeInBytes = static_cast(dxBuf->desc.size - offset); + view.Format = toDxgiIndexFormat(format); + + m_ctx.cmdList->IASetIndexBuffer(&view); + } + + // ---- Dynamic State ---- + + void setViewport(f32 x, f32 y, f32 w, f32 h, f32 minDepth, f32 maxDepth) override { + D3D12_VIEWPORT viewport{}; + viewport.TopLeftX = x; + viewport.TopLeftY = y; + viewport.Width = w; + viewport.Height = h; + viewport.MinDepth = minDepth; + viewport.MaxDepth = maxDepth; + + m_ctx.cmdList->RSSetViewports(1, &viewport); + } + + void setScissor(i32 x, i32 y, u32 w, u32 h) override { + D3D12_RECT rect{}; + rect.left = x; + rect.top = y; + rect.right = x + static_cast(w); + rect.bottom = y + static_cast(h); + + m_ctx.cmdList->RSSetScissorRects(1, &rect); + } + + void setBlendConstant(f32 r, f32 g, f32 b, f32 a) override { + f32 color[4] = { r, g, b, a }; + m_ctx.cmdList->OMSetBlendFactor(color); + } + + void setStencilReference(u32 reference) override { + m_ctx.cmdList->OMSetStencilRef(reference); + } + + // ---- Draw Commands ---- + + void draw(u32 vertexCount, u32 instanceCount, u32 firstVertex, u32 firstInstance) override { + m_ctx.cmdList->DrawInstanced(vertexCount, instanceCount, firstVertex, firstInstance); + } + + void drawIndexed(u32 indexCount, u32 instanceCount, u32 firstIndex, i32 baseVertex, u32 firstInstance) override { + m_ctx.cmdList->DrawIndexedInstanced(indexCount, instanceCount, firstIndex, baseVertex, firstInstance); + } + + void drawIndirect(Buffer* buffer, u64 offset, u32 drawCount, u32 stride) override { + auto* dxBuf = static_cast(buffer); + if (!dxBuf) return; + + auto* sig = m_ctx.drawSig; + if (!sig) return; + + u32 actualStride = (stride > 0) ? stride : 16; // sizeof(D3D12_DRAW_ARGUMENTS) + for (u32 i = 0; i < drawCount; ++i) { + m_ctx.cmdList->ExecuteIndirect(sig, 1, dxBuf->handle(), + offset + static_cast(i) * actualStride, nullptr, 0); + } + } + + void drawIndexedIndirect(Buffer* buffer, u64 offset, u32 drawCount, u32 stride) override { + auto* dxBuf = static_cast(buffer); + if (!dxBuf) return; + + auto* sig = m_ctx.drawIndexedSig; + if (!sig) return; + + u32 actualStride = (stride > 0) ? stride : 20; // sizeof(D3D12_DRAW_INDEXED_ARGUMENTS) + for (u32 i = 0; i < drawCount; ++i) { + m_ctx.cmdList->ExecuteIndirect(sig, 1, dxBuf->handle(), + offset + static_cast(i) * actualStride, nullptr, 0); + } + } + + // ---- Queries ---- + + void writeTimestamp(QuerySet* querySet, u32 index) override { + auto* qs = static_cast(querySet); + if (qs) m_ctx.cmdList->EndQuery(qs->handle(), D3D12_QUERY_TYPE_TIMESTAMP, index); + } + + void beginOcclusionQuery(QuerySet* querySet, u32 index) override { + auto* qs = static_cast(querySet); + if (qs) m_ctx.cmdList->BeginQuery(qs->handle(), D3D12_QUERY_TYPE_OCCLUSION, index); + } + + void endOcclusionQuery(QuerySet* querySet, u32 index) override { + auto* qs = static_cast(querySet); + if (qs) m_ctx.cmdList->EndQuery(qs->handle(), D3D12_QUERY_TYPE_OCCLUSION, index); + } + + // ---- MeshShaderPassExt ---- + + void setMeshPipeline(MeshPipeline* pipeline) override { + auto* dxPipeline = static_cast(pipeline); + if (!dxPipeline) return; + m_currentMeshPipeline = dxPipeline; + m_currentPipeline = nullptr; // clear regular pipeline + + auto* cmdList = m_ctx.cmdList; + cmdList->SetPipelineState(dxPipeline->handle()); + cmdList->SetGraphicsRootSignature(dxPipeline->pipelineLayout()->handle()); + } + + void drawMeshTasks(u32 groupCountX, u32 groupCountY, u32 groupCountZ) override { + // Need ID3D12GraphicsCommandList6 for DispatchMesh. + ID3D12GraphicsCommandList6* cmdList6 = nullptr; + HRESULT hr = m_ctx.cmdList->QueryInterface(IID_PPV_ARGS(&cmdList6)); + if (SUCCEEDED(hr) && cmdList6) { + cmdList6->DispatchMesh(groupCountX, groupCountY, groupCountZ); + cmdList6->Release(); + } + } + + void drawMeshTasksIndirect(Buffer* buffer, u64 offset, u32 drawCount, u32 stride) override { + auto* dxBuf = static_cast(buffer); + if (!dxBuf) return; + + auto* sig = m_ctx.dispatchMeshSig; + if (!sig) return; + + u32 actualStride = (stride > 0) ? stride : 12; // sizeof(D3D12_DISPATCH_MESH_ARGUMENTS): 3 x u32 + for (u32 i = 0; i < drawCount; ++i) { + m_ctx.cmdList->ExecuteIndirect(sig, 1, dxBuf->handle(), + offset + static_cast(i) * actualStride, nullptr, 0); + } + } + + void drawMeshTasksIndirectCount(Buffer* buffer, u64 offset, + Buffer* countBuffer, u64 countOffset, + u32 maxDrawCount, u32 /*stride*/) override { + auto* dxBuf = static_cast(buffer); + auto* dxCountBuf = static_cast(countBuffer); + if (!dxBuf || !dxCountBuf) return; + + auto* sig = m_ctx.dispatchMeshSig; + if (!sig) return; + + m_ctx.cmdList->ExecuteIndirect(sig, maxDrawCount, dxBuf->handle(), + offset, dxCountBuf->handle(), countOffset); + } + + // ---- End ---- + + void executeBundles(std::span bundles) override { + for (usize i = 0; i < bundles.size(); ++i) { + if (auto* b = static_cast(bundles[i])) { + // DX12 requires the parent command list to have the same root signature + // and PSO set before ExecuteBundle. + if (b->rootSig()) m_ctx.cmdList->SetGraphicsRootSignature(b->rootSig()); + if (b->pso()) m_ctx.cmdList->SetPipelineState(b->pso()); + m_ctx.cmdList->ExecuteBundle(b->handle()); + } + } + } + + void end() override { + // Timestamp at pass end. + if (m_desc.timestampQuerySet) { + auto* qs = static_cast(m_desc.timestampQuerySet); + if (qs) + m_ctx.cmdList->EndQuery(qs->handle(), D3D12_QUERY_TYPE_TIMESTAMP, m_desc.endTimestampIndex); + } + + // MSAA resolve: resolve multisampled color attachments to their resolve targets. + const auto& colorAtts = m_desc.colorAttachments; + for (usize i = 0; i < colorAtts.size(); ++i) { + const auto& ca = colorAtts[i]; + if (!ca.resolveTarget) continue; + + auto* srcView = static_cast(ca.view); + auto* dstView = static_cast(ca.resolveTarget); + if (!srcView || !dstView) continue; + + auto* srcTex = srcView->dxTexture(); + auto* dstTex = dstView->dxTexture(); + + TextureFormat format = srcView->format(); + if (format == TextureFormat::Undefined) + format = srcView->dxTexture()->desc.format; + DXGI_FORMAT dxgiFormat = toDxgiFormat(format); + + // Transition src to resolve source, dst to resolve dest. + D3D12_RESOURCE_BARRIER barriers[2]{}; + barriers[0].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barriers[0].Transition.pResource = srcTex->handle(); + barriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; + barriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_RESOLVE_SOURCE; + barriers[0].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + + barriers[1].Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barriers[1].Transition.pResource = dstTex->handle(); + barriers[1].Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; + barriers[1].Transition.StateAfter = D3D12_RESOURCE_STATE_RESOLVE_DEST; + barriers[1].Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + + m_ctx.cmdList->ResourceBarrier(2, barriers); + + m_ctx.cmdList->ResolveSubresource(dstTex->handle(), 0, srcTex->handle(), 0, dxgiFormat); + + // Transition back. + barriers[0].Transition.StateBefore = D3D12_RESOURCE_STATE_RESOLVE_SOURCE; + barriers[0].Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; + barriers[1].Transition.StateBefore = D3D12_RESOURCE_STATE_RESOLVE_DEST; + barriers[1].Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET; + + m_ctx.cmdList->ResourceBarrier(2, barriers); + } + + m_currentPipeline = nullptr; + m_currentMeshPipeline = nullptr; + } + + // Accessors for bundle recording: the last pipeline/root-sig set on this encoder. + [[nodiscard]] ID3D12PipelineState* currentPso() const { return m_currentPipeline ? m_currentPipeline->handle() : nullptr; } + [[nodiscard]] ID3D12RootSignature* currentRootSig() const { + auto* l = m_currentPipeline ? m_currentPipeline->pipelineLayout() : nullptr; + return l ? l->handle() : nullptr; + } + +private: + DxPipelineLayoutImpl* getCurrentLayout() { + if (m_currentPipeline) + return m_currentPipeline->pipelineLayout(); + if (m_currentMeshPipeline) + return m_currentMeshPipeline->pipelineLayout(); + return nullptr; + } + + DxRenderPassContext m_ctx; + RenderPassDesc m_desc{}; + DxRenderPipelineImpl* m_currentPipeline = nullptr; + DxMeshPipelineImpl* m_currentMeshPipeline = nullptr; + + // Cached vertex buffer views for re-application on pipeline change. + D3D12_VERTEX_BUFFER_VIEW m_cachedVbs[8]{}; + u32 m_cachedVbCount = 0; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxRenderPipeline.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxRenderPipeline.cppm new file mode 100644 index 00000000..92dc0cb2 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxRenderPipeline.cppm @@ -0,0 +1,167 @@ +/// DX12 implementation of RenderPipeline. +/// Wraps a D3D12 graphics pipeline state object. + +module; + +#include "DxIncludes.h" +#include +#include + +#include +#include + +export module rhi.dx12:render_pipeline; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :pipeline_layout; +import :shader_module; +import :pipeline_cache; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxRenderPipelineImpl : public RenderPipeline { +public: + Status init(ID3D12Device* device, const RenderPipelineDesc& d) { + m_layout = static_cast(d.layout); + if (!m_layout) return ErrorCode::Unknown; + + D3D12_GRAPHICS_PIPELINE_STATE_DESC pso{}; + pso.pRootSignature = m_layout->handle(); + + // Vertex shader. + auto* vsMod = static_cast(d.vertex.shader.module); + if (!vsMod) return ErrorCode::Unknown; + auto vsCode = vsMod->bytecode(); + pso.VS = { vsCode.data(), vsCode.size() }; + + // Fragment shader. + if (d.fragment.has_value()) { + auto* psMod = static_cast(d.fragment->shader.module); + if (psMod) { auto ps = psMod->bytecode(); pso.PS = { ps.data(), ps.size() }; } + } + + // Input layout. + std::vector elems; + auto bufs = d.vertex.buffers; + m_vtxBufCount = static_cast(std::min(bufs.size(), usize(8))); + for (usize i = 0; i < bufs.size(); ++i) { + const auto& buf = bufs[i]; + if (i < 8) m_vtxStrides[i] = buf.stride; + auto attrs = buf.attributes; + for (usize j = 0; j < attrs.size(); ++j) { + const auto& a = attrs[j]; + D3D12_INPUT_ELEMENT_DESC e{}; + e.SemanticName = "TEXCOORD"; + e.SemanticIndex = a.shaderLocation; + e.Format = toDxgiVertexFormat(a.format); + e.InputSlot = static_cast(i); + e.AlignedByteOffset = a.offset; + e.InputSlotClass = (buf.stepMode == VertexStepMode::Instance) + ? D3D12_INPUT_CLASSIFICATION_PER_INSTANCE_DATA + : D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA; + e.InstanceDataStepRate = (buf.stepMode == VertexStepMode::Instance) ? 1 : 0; + elems.push_back(e); + } + } + pso.InputLayout = { elems.data(), static_cast(elems.size()) }; + + // Topology. + pso.PrimitiveTopologyType = toPrimitiveTopologyType(d.primitive.topology); + m_topology = toPrimitiveTopology(d.primitive.topology); + + // Rasterizer. + pso.RasterizerState.FillMode = toFillMode(d.primitive.fillMode); + pso.RasterizerState.CullMode = toCullMode(d.primitive.cullMode); + pso.RasterizerState.FrontCounterClockwise = (d.primitive.frontFace == FrontFace::CCW) ? TRUE : FALSE; + pso.RasterizerState.DepthClipEnable = d.primitive.depthClipEnabled ? TRUE : FALSE; + pso.RasterizerState.MultisampleEnable = (d.multisample.count > 1) ? TRUE : FALSE; + + if (d.depthStencil.has_value()) { + pso.RasterizerState.DepthBias = d.depthStencil->depthBias; + pso.RasterizerState.DepthBiasClamp = d.depthStencil->depthBiasClamp; + pso.RasterizerState.SlopeScaledDepthBias = d.depthStencil->depthBiasSlopeScale; + } + + // Blend. + std::span targets = d.fragment.has_value() ? d.fragment->targets : std::span(); + pso.BlendState.AlphaToCoverageEnable = d.multisample.alphaToCoverageEnabled ? TRUE : FALSE; + pso.BlendState.IndependentBlendEnable = (targets.size() > 1) ? TRUE : FALSE; + for (usize i = 0; i < targets.size() && i < 8; ++i) { + const auto& t = targets[i]; + auto& rt = pso.BlendState.RenderTarget[i]; + rt.RenderTargetWriteMask = static_cast(t.writeMask); + if (t.blend.has_value()) { + rt.BlendEnable = TRUE; + rt.SrcBlend = toBlendFactor(t.blend->color.srcFactor); + rt.DestBlend = toBlendFactor(t.blend->color.dstFactor); + rt.BlendOp = toBlendOp(t.blend->color.operation); + rt.SrcBlendAlpha = toBlendFactor(t.blend->alpha.srcFactor); + rt.DestBlendAlpha= toBlendFactor(t.blend->alpha.dstFactor); + rt.BlendOpAlpha = toBlendOp(t.blend->alpha.operation); + } + } + + // Depth/stencil. + if (d.depthStencil.has_value()) { + const auto& ds = *d.depthStencil; + pso.DepthStencilState.DepthEnable = ds.depthTestEnabled ? TRUE : FALSE; + pso.DepthStencilState.DepthWriteMask = ds.depthWriteEnabled ? D3D12_DEPTH_WRITE_MASK_ALL : D3D12_DEPTH_WRITE_MASK_ZERO; + pso.DepthStencilState.DepthFunc = toComparisonFunc(ds.depthCompare); + pso.DepthStencilState.StencilEnable = ds.stencilEnabled ? TRUE : FALSE; + pso.DepthStencilState.StencilReadMask = ds.stencilReadMask; + pso.DepthStencilState.StencilWriteMask= ds.stencilWriteMask; + + auto& ff = pso.DepthStencilState.FrontFace; + ff.StencilFailOp = toStencilOp(ds.stencilFront.failOp); + ff.StencilDepthFailOp = toStencilOp(ds.stencilFront.depthFailOp); + ff.StencilPassOp = toStencilOp(ds.stencilFront.passOp); + ff.StencilFunc = toComparisonFunc(ds.stencilFront.compare); + + auto& bf = pso.DepthStencilState.BackFace; + bf.StencilFailOp = toStencilOp(ds.stencilBack.failOp); + bf.StencilDepthFailOp = toStencilOp(ds.stencilBack.depthFailOp); + bf.StencilPassOp = toStencilOp(ds.stencilBack.passOp); + bf.StencilFunc = toComparisonFunc(ds.stencilBack.compare); + + pso.DSVFormat = toDxgiFormat(ds.format); + } + + // Render targets. + pso.NumRenderTargets = static_cast(std::min(targets.size(), usize(8))); + for (usize i = 0; i < targets.size() && i < 8; ++i) + pso.RTVFormats[i] = toDxgiFormat(targets[i].format); + + // Multisample. + pso.SampleDesc.Count = std::max(d.multisample.count, 1u); + pso.SampleMask = (d.multisample.mask != 0) ? d.multisample.mask : ~0u; + + // Create PSO. + HRESULT hr = device->CreateGraphicsPipelineState(&pso, IID_PPV_ARGS(&m_pipelineState)); + if (FAILED(hr)) { + logErrorf("DxRenderPipeline: CreateGraphicsPipelineState failed (0x%08X)", static_cast(hr)); + return ErrorCode::Unknown; + } + return ErrorCode::Ok; + } + + void cleanup() { m_pipelineState.Reset(); } + + [[nodiscard]] ID3D12PipelineState* handle() const { return m_pipelineState.Get(); } + [[nodiscard]] D3D_PRIMITIVE_TOPOLOGY topology() const { return m_topology; } + [[nodiscard]] DxPipelineLayoutImpl* pipelineLayout() const { return m_layout; } + [[nodiscard]] u32 getVertexStride(u32 slot) const { return (slot < m_vtxBufCount) ? m_vtxStrides[slot] : 0; } + +private: + ComPtr m_pipelineState; + DxPipelineLayoutImpl* m_layout = nullptr; + D3D_PRIMITIVE_TOPOLOGY m_topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST; + u32 m_vtxStrides[8]{}; + u32 m_vtxBufCount = 0; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxRhi.test.cpp b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxRhi.test.cpp new file mode 100644 index 00000000..5f2e0e45 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxRhi.test.cpp @@ -0,0 +1,40 @@ +#include + +import core; +import rhi; +import rhi.dx12; + +using namespace draco; +using namespace draco::rhi; + +// Windows-only. Requires a working DXGI/D3D12 stack (a hardware adapter or the +// WARP software adapter). Where no device is available the backend still +// initializes; adapter-dependent checks are guarded. + +TEST_CASE("rhi.dx12: backend initializes and enumerates adapters") +{ + Backend* backend = nullptr; + dx12::DxBackendDesc desc{}; + REQUIRE(dx12::createDxBackend(desc, backend).isOk()); + REQUIRE(backend != nullptr); + CHECK(backend->isInitialized); + + auto adapters = backend->enumerateAdapters(); + INFO("adapter count: ", adapters.size()); + + for (Adapter* a : adapters) { + const AdapterInfo info = a->info(); + CHECK(!info.name.empty()); + } + + if (!adapters.empty()) { + // Adapters are ordered best-first; a device should be creatable from [0]. + Device* device = nullptr; + CHECK(adapters[0]->createDevice(DeviceDesc{}, device).isOk()); + CHECK(device != nullptr); + // Tear the device down before the backend releases the factory/adapters. + if (device) device->destroy(); + } + + backend->destroy(); +} diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxSampler.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxSampler.cppm new file mode 100644 index 00000000..448fc6d0 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxSampler.cppm @@ -0,0 +1,65 @@ +/// DX12 implementation of Sampler. + +module; + +#include "DxIncludes.h" + +export module rhi.dx12:sampler; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :descriptor_heap; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxSamplerImpl : public Sampler { +public: + Status init(ID3D12Device* device, const SamplerDesc& d, DxDescriptorHeapAllocator* samplerHeap) { + m_samplerHeap = samplerHeap; + + bool isComparison = d.compare.has_value(); + + D3D12_SAMPLER_DESC sd{}; + if (isComparison) + sd.Filter = toFilter(d.minFilter, d.magFilter, d.mipmapFilter, true); + else if (d.maxAnisotropy > 1) + sd.Filter = D3D12_FILTER_ANISOTROPIC; + else + sd.Filter = toFilter(d.minFilter, d.magFilter, d.mipmapFilter, false); + + sd.AddressU = toAddressMode(d.addressU); + sd.AddressV = toAddressMode(d.addressV); + sd.AddressW = toAddressMode(d.addressW); + sd.MipLODBias = d.mipLodBias; + sd.MaxAnisotropy = static_cast(d.maxAnisotropy); + sd.ComparisonFunc = isComparison ? toComparisonFunc(*d.compare) : D3D12_COMPARISON_FUNC_NEVER; + sd.MinLOD = d.minLod; + sd.MaxLOD = d.maxLod; + + switch (d.borderColor) { + case SamplerBorderColor::TransparentBlack: sd.BorderColor[0]=0; sd.BorderColor[1]=0; sd.BorderColor[2]=0; sd.BorderColor[3]=0; break; + case SamplerBorderColor::OpaqueBlack: sd.BorderColor[0]=0; sd.BorderColor[1]=0; sd.BorderColor[2]=0; sd.BorderColor[3]=1; break; + case SamplerBorderColor::OpaqueWhite: sd.BorderColor[0]=1; sd.BorderColor[1]=1; sd.BorderColor[2]=1; sd.BorderColor[3]=1; break; + } + + m_handle = samplerHeap->allocate(); + device->CreateSampler(&sd, m_handle); + return ErrorCode::Ok; + } + + void cleanup() { + if (m_samplerHeap) m_samplerHeap->free(m_handle); + } + + [[nodiscard]] D3D12_CPU_DESCRIPTOR_HANDLE handle() const { return m_handle; } + +private: + D3D12_CPU_DESCRIPTOR_HANDLE m_handle{}; + DxDescriptorHeapAllocator* m_samplerHeap = nullptr; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxShaderModule.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxShaderModule.cppm new file mode 100644 index 00000000..d2d47d00 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxShaderModule.cppm @@ -0,0 +1,36 @@ +/// DX12 implementation of ShaderModule. Stores DXIL bytecode. + +module; + +#include +#include + +#include + +export module rhi.dx12:shader_module; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxShaderModuleImpl : public ShaderModule { +public: + Status init(const ShaderModuleDesc& d) { + m_bytecode.resize(d.code.size()); + std::memcpy(m_bytecode.data(), d.code.data(), d.code.size()); + return ErrorCode::Ok; + } + + void cleanup() { m_bytecode.clear(); } + + [[nodiscard]] std::span bytecode() const { return { m_bytecode.data(), m_bytecode.size() }; } + +private: + std::vector m_bytecode; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxSurface.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxSurface.cppm new file mode 100644 index 00000000..bcd5c9c5 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxSurface.cppm @@ -0,0 +1,27 @@ +/// DX12 implementation of Surface. Simply stores the HWND. + +module; + +#include "DxIncludes.h" + +export module rhi.dx12:surface; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxSurfaceImpl : public Surface { +public: + explicit DxSurfaceImpl(HWND hwnd) : m_hwnd(hwnd) {} + + [[nodiscard]] HWND handle() const { return m_hwnd; } + +private: + HWND m_hwnd = nullptr; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxSwapChain.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxSwapChain.cppm new file mode 100644 index 00000000..2dca4e64 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxSwapChain.cppm @@ -0,0 +1,161 @@ +/// DX12 implementation of SwapChain. +/// Wraps IDXGISwapChain3 for presentation. + +module; + +#include "DxIncludes.h" +#include + +export module rhi.dx12:swap_chain; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :surface; +import :texture; +import :texture_view; +import :descriptor_heap; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxDeviceImpl; // forward +class DxQueueImpl; // forward + +class DxSwapChainImpl : public SwapChain { +public: + Status init(ID3D12Device* device, IDXGIFactory4* factory, ID3D12CommandQueue* gfxQueue, + DxSurfaceImpl* surface, const SwapChainDesc& d, + DxDescriptorHeapAllocator* srvHeap, DxDescriptorHeapAllocator* rtvHeap, + DxDescriptorHeapAllocator* dsvHeap) { + m_d3dDevice = device; + m_format = d.format; + m_width = d.width; + m_height = d.height; + m_bufferCount = d.bufferCount; + m_presentMode = d.presentMode; + m_srvHeap = srvHeap; + m_rtvHeap = rtvHeap; + m_dsvHeap = dsvHeap; + + DXGI_FORMAT swapFmt = stripSrgb(toDxgiFormat(d.format)); + + DXGI_SWAP_CHAIN_DESC1 sd{}; + sd.Width = d.width; + sd.Height = d.height; + sd.Format = swapFmt; + sd.SampleDesc = { 1, 0 }; + sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + sd.BufferCount = d.bufferCount; + sd.Scaling = DXGI_SCALING_STRETCH; + sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + sd.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED; + if (d.presentMode == PresentMode::Immediate) + sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + + ComPtr sc1; + HRESULT hr = factory->CreateSwapChainForHwnd( + gfxQueue, surface->handle(), &sd, nullptr, nullptr, &sc1); + if (FAILED(hr)) { + logErrorf("DxSwapChain: CreateSwapChainForHwnd failed (0x%08X)", static_cast(hr)); + return ErrorCode::Unknown; + } + + factory->MakeWindowAssociation(surface->handle(), DXGI_MWA_NO_ALT_ENTER); + + hr = sc1.As(&m_swapChain); + if (FAILED(hr)) return ErrorCode::Unknown; + + if (acquireBackBuffers() != ErrorCode::Ok) return ErrorCode::Unknown; + m_currentIndex = m_swapChain->GetCurrentBackBufferIndex(); + return ErrorCode::Ok; + } + + // ---- SwapChain interface ---- + TextureFormat format() const override { return m_format; } + u32 width() const override { return m_width; } + u32 height() const override { return m_height; } + u32 bufferCount() const override { return m_bufferCount; } + u32 currentImageIndex() const override { return m_currentIndex; } + Texture* currentTexture() override { return (m_currentIndex < m_textures.size()) ? m_textures[m_currentIndex] : nullptr; } + TextureView* currentTextureView()override { return (m_currentIndex < m_views.size()) ? m_views[m_currentIndex] : nullptr; } + + Status acquireNextImage() override { + m_currentIndex = m_swapChain->GetCurrentBackBufferIndex(); + return ErrorCode::Ok; + } + + Status present(Queue* /*queue*/) override { + UINT syncInterval = 1, flags = 0; + switch (m_presentMode) { + case PresentMode::Immediate: syncInterval = 0; flags = DXGI_PRESENT_ALLOW_TEARING; break; + case PresentMode::Mailbox: syncInterval = 0; break; + case PresentMode::Fifo: syncInterval = 1; break; + case PresentMode::FifoRelaxed: syncInterval = 1; break; + } + return SUCCEEDED(m_swapChain->Present(syncInterval, flags)) ? ErrorCode::Ok : ErrorCode::Unknown; + } + + Status resize(u32 w, u32 h) override { + if (w == 0 || h == 0) return ErrorCode::Ok; + m_width = w; m_height = h; + releaseBackBuffers(); + HRESULT hr = m_swapChain->ResizeBuffers(m_bufferCount, w, h, + stripSrgb(toDxgiFormat(m_format)), 0); + if (FAILED(hr)) return ErrorCode::Unknown; + if (acquireBackBuffers() != ErrorCode::Ok) return ErrorCode::Unknown; + m_currentIndex = m_swapChain->GetCurrentBackBufferIndex(); + return ErrorCode::Ok; + } + + void cleanup() { + releaseBackBuffers(); + m_swapChain.Reset(); + } + +private: + Status acquireBackBuffers() { + for (u32 i = 0; i < m_bufferCount; ++i) { + ID3D12Resource* resource = nullptr; + if (FAILED(m_swapChain->GetBuffer(i, IID_PPV_ARGS(&resource)))) return ErrorCode::Unknown; + + auto* tex = new DxTextureImpl(); + TextureDesc td{}; td.dimension = TextureDimension::Texture2D; td.format = m_format; + td.width = m_width; td.height = m_height; td.arrayLayerCount = 1; td.mipLevelCount = 1; + td.sampleCount = 1; td.usage = TextureUsage::RenderTarget; + tex->initFromExisting(resource, td); + resource->Release(); // initFromExisting AddRef'd + m_textures.push_back(tex); + + auto* view = new DxTextureViewImpl(); + TextureViewDesc vd{}; vd.format = m_format; vd.dimension = TextureViewDimension::Texture2D; + vd.mipLevelCount = 1; vd.arrayLayerCount = 1; + view->init(m_d3dDevice, tex, vd, m_srvHeap, m_rtvHeap, m_dsvHeap); + m_views.push_back(view); + } + return ErrorCode::Ok; + } + + void releaseBackBuffers() { + for (auto* v : m_views) { v->cleanup(); delete v; } + m_views.clear(); + for (auto* t : m_textures) { t->cleanup(); delete t; } + m_textures.clear(); + } + + ComPtr m_swapChain; + ID3D12Device* m_d3dDevice = nullptr; + TextureFormat m_format = TextureFormat::RGBA8UnormSrgb; + u32 m_width = 0, m_height = 0, m_bufferCount = 2; + u32 m_currentIndex = 0; + PresentMode m_presentMode = PresentMode::Fifo; + std::vector m_textures; + std::vector m_views; + DxDescriptorHeapAllocator* m_srvHeap = nullptr; + DxDescriptorHeapAllocator* m_rtvHeap = nullptr; + DxDescriptorHeapAllocator* m_dsvHeap = nullptr; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxTexture.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxTexture.cppm new file mode 100644 index 00000000..9220ae1b --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxTexture.cppm @@ -0,0 +1,136 @@ +/// DX12 implementation of Texture. + +module; + +#include "DxIncludes.h" +#include + +#include +#include + +export module rhi.dx12:texture; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxTextureImpl : public Texture { +public: + Status init(ID3D12Device* device, const TextureDesc& d) { + desc = d; + + DXGI_FORMAT format = isDepthFormat(d.format) + ? toTypelessDepthFormat(d.format) + : toDxgiFormat(d.format); + + D3D12_RESOURCE_DESC rd{}; + rd.Dimension = toResourceDimension(d.dimension); + rd.Width = static_cast(d.width); + rd.Height = d.height; + rd.DepthOrArraySize = static_cast((d.dimension == TextureDimension::Texture3D) ? d.depth : d.arrayLayerCount); + rd.MipLevels = static_cast(d.mipLevelCount); + rd.Format = format; + rd.SampleDesc = { d.sampleCount, 0 }; + rd.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; + rd.Flags = toTextureResourceFlags(d.usage); + + D3D12_HEAP_PROPERTIES heapProps{}; + heapProps.Type = D3D12_HEAP_TYPE_DEFAULT; + + m_state = D3D12_RESOURCE_STATE_COMMON; + + D3D12_CLEAR_VALUE clearVal{}; + D3D12_CLEAR_VALUE* pClearVal = nullptr; + + if (static_cast(d.usage & TextureUsage::DepthStencil)) { + clearVal.Format = toDxgiFormat(d.format); + clearVal.DepthStencil = { 1.0f, 0 }; + pClearVal = &clearVal; + m_state = D3D12_RESOURCE_STATE_DEPTH_WRITE; + } else if (static_cast(d.usage & TextureUsage::RenderTarget)) { + clearVal.Format = format; + clearVal.Color[0] = 0; clearVal.Color[1] = 0; clearVal.Color[2] = 0; clearVal.Color[3] = 1; + pClearVal = &clearVal; + m_state = D3D12_RESOURCE_STATE_RENDER_TARGET; + } + + HRESULT hr = device->CreateCommittedResource( + &heapProps, D3D12_HEAP_FLAG_NONE, + &rd, m_state, pClearVal, + IID_PPV_ARGS(&m_resource)); + if (FAILED(hr)) { + logErrorf("DxTexture: CreateCommittedResource failed (0x%08X)", static_cast(hr)); + return ErrorCode::Unknown; + } + m_ownsResource = true; + return ErrorCode::Ok; + } + + /// Initialize from an existing resource (e.g. swap chain buffer). Does not own. + void initFromExisting(ID3D12Resource* resource, const TextureDesc& d) { + m_resource.Attach(resource); + m_resource->AddRef(); // ComPtr will Release - balance it + desc = d; + m_ownsResource = false; + m_state = D3D12_RESOURCE_STATE_PRESENT; + } + + void cleanup() { + m_subresourceStates.clear(); + m_resource.Reset(); + } + + // ---- Internal ---- + [[nodiscard]] ID3D12Resource* handle() const { return m_resource.Get(); } + + [[nodiscard]] D3D12_RESOURCE_STATES currentState() const { return m_state; } + void setState(D3D12_RESOURCE_STATES s) { m_state = s; m_subresourceStates.clear(); } + + [[nodiscard]] D3D12_RESOURCE_STATES getSubresourceState(u32 mip, u32 layer) const { + if (m_subresourceStates.empty()) return m_state; + u32 idx = mip + layer * desc.mipLevelCount; + return (idx < m_subresourceStates.size()) ? m_subresourceStates[idx] : m_state; + } + + void setSubresourceState(u32 baseMip, u32 mipCount, u32 baseLayer, u32 layerCount, D3D12_RESOURCE_STATES s) { + u32 totalMips = desc.mipLevelCount; + u32 totalLayers = std::max((desc.dimension == TextureDimension::Texture3D) ? desc.depth : desc.arrayLayerCount, 1u); + u32 mipEnd = (mipCount == ~0u) ? totalMips : std::min(baseMip + mipCount, totalMips); + u32 layerEnd = (layerCount == ~0u) ? totalLayers : std::min(baseLayer + layerCount, totalLayers); + + // All subresources? Collapse to uniform. + if (baseMip == 0 && mipEnd >= totalMips && baseLayer == 0 && layerEnd >= totalLayers) { + m_state = s; + m_subresourceStates.clear(); + return; + } + // Promote to per-subresource. + if (m_subresourceStates.empty()) { + if (s == m_state) return; + m_subresourceStates.resize(totalMips * totalLayers, m_state); + } + for (u32 l = baseLayer; l < layerEnd; ++l) + for (u32 m = baseMip; m < mipEnd; ++m) + m_subresourceStates[m + l * totalMips] = s; + + // Try to collapse. + auto first = m_subresourceStates[0]; + for (usize i = 1; i < m_subresourceStates.size(); ++i) + if (m_subresourceStates[i] != first) return; + m_state = first; + m_subresourceStates.clear(); + } + +private: + ComPtr m_resource; + D3D12_RESOURCE_STATES m_state = D3D12_RESOURCE_STATE_COMMON; + std::vector m_subresourceStates; + bool m_ownsResource = true; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxTextureView.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxTextureView.cppm new file mode 100644 index 00000000..5d506e0d --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxTextureView.cppm @@ -0,0 +1,289 @@ +/// DX12 implementation of TextureView. +/// Lazily creates SRV/RTV/DSV/UAV CPU descriptor handles on demand. + +module; + +#include "DxIncludes.h" + +export module rhi.dx12:texture_view; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :texture; +import :descriptor_heap; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxTextureViewImpl : public TextureView { +public: + Status init(ID3D12Device* device, DxTextureImpl* tex, const TextureViewDesc& d, + DxDescriptorHeapAllocator* srvHeap, DxDescriptorHeapAllocator* rtvHeap, + DxDescriptorHeapAllocator* dsvHeap) { + m_device = device; + m_texture = tex; + m_viewDesc= d; + m_srvHeap = srvHeap; + m_rtvHeap = rtvHeap; + m_dsvHeap = dsvHeap; + return ErrorCode::Ok; + } + + // ---- Lazy SRV ---- + D3D12_CPU_DESCRIPTOR_HANDLE getSrv() { + if (m_hasSrv) return m_srv; + + auto fmt = (m_viewDesc.format == TextureFormat::Undefined) ? m_texture->desc.format : m_viewDesc.format; + DXGI_FORMAT srvFmt = isDepthFormat(fmt) ? toDepthSrvFormat(fmt) : toDxgiFormat(fmt); + + D3D12_SHADER_RESOURCE_VIEW_DESC sd{}; + sd.Format = srvFmt; + sd.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + + u32 mips = m_viewDesc.mipLevelCount; + if (mips == 0) mips = m_texture->desc.mipLevelCount - m_viewDesc.baseMipLevel; + + switch (m_viewDesc.dimension) { + case TextureViewDimension::Texture1D: + sd.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1D; + sd.Texture1D.MostDetailedMip = m_viewDesc.baseMipLevel; + sd.Texture1D.MipLevels = mips; + break; + case TextureViewDimension::Texture1DArray: + sd.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE1DARRAY; + sd.Texture1DArray.MostDetailedMip = m_viewDesc.baseMipLevel; + sd.Texture1DArray.MipLevels = mips; + sd.Texture1DArray.FirstArraySlice = m_viewDesc.baseArrayLayer; + sd.Texture1DArray.ArraySize = m_viewDesc.arrayLayerCount; + break; + case TextureViewDimension::Texture2D: + if (m_texture->desc.sampleCount > 1) { + sd.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMS; + } else { + sd.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D; + sd.Texture2D.MostDetailedMip = m_viewDesc.baseMipLevel; + sd.Texture2D.MipLevels = mips; + } + break; + case TextureViewDimension::Texture2DArray: + if (m_texture->desc.sampleCount > 1) { + sd.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DMSARRAY; + sd.Texture2DMSArray.FirstArraySlice = m_viewDesc.baseArrayLayer; + sd.Texture2DMSArray.ArraySize = m_viewDesc.arrayLayerCount; + } else { + sd.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2DARRAY; + sd.Texture2DArray.MostDetailedMip = m_viewDesc.baseMipLevel; + sd.Texture2DArray.MipLevels = mips; + sd.Texture2DArray.FirstArraySlice = m_viewDesc.baseArrayLayer; + sd.Texture2DArray.ArraySize = m_viewDesc.arrayLayerCount; + } + break; + case TextureViewDimension::TextureCube: + sd.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE; + sd.TextureCube.MostDetailedMip = m_viewDesc.baseMipLevel; + sd.TextureCube.MipLevels = mips; + break; + case TextureViewDimension::TextureCubeArray: + sd.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBEARRAY; + sd.TextureCubeArray.MostDetailedMip = m_viewDesc.baseMipLevel; + sd.TextureCubeArray.MipLevels = mips; + sd.TextureCubeArray.First2DArrayFace = m_viewDesc.baseArrayLayer; + sd.TextureCubeArray.NumCubes = m_viewDesc.arrayLayerCount / 6; + break; + case TextureViewDimension::Texture3D: + sd.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE3D; + sd.Texture3D.MostDetailedMip = m_viewDesc.baseMipLevel; + sd.Texture3D.MipLevels = mips; + break; + } + + m_srv = m_srvHeap->allocate(); + m_device->CreateShaderResourceView(m_texture->handle(), &sd, m_srv); + m_hasSrv = true; + return m_srv; + } + + // ---- Lazy RTV ---- + D3D12_CPU_DESCRIPTOR_HANDLE getRtv() { + if (m_hasRtv) return m_rtv; + + auto fmt = (m_viewDesc.format == TextureFormat::Undefined) ? m_texture->desc.format : m_viewDesc.format; + bool isArray = m_texture->desc.arrayLayerCount > 1; + + D3D12_RENDER_TARGET_VIEW_DESC rd{}; + rd.Format = toDxgiFormat(fmt); + + if (m_viewDesc.dimension == TextureViewDimension::Texture2D) { + if (isArray) { + if (m_texture->desc.sampleCount > 1) { + rd.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY; + rd.Texture2DMSArray.FirstArraySlice = m_viewDesc.baseArrayLayer; + rd.Texture2DMSArray.ArraySize = m_viewDesc.arrayLayerCount; + } else { + rd.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY; + rd.Texture2DArray.MipSlice = m_viewDesc.baseMipLevel; + rd.Texture2DArray.FirstArraySlice = m_viewDesc.baseArrayLayer; + rd.Texture2DArray.ArraySize = m_viewDesc.arrayLayerCount; + } + } else if (m_texture->desc.sampleCount > 1) { + rd.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMS; + } else { + rd.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D; + rd.Texture2D.MipSlice = m_viewDesc.baseMipLevel; + } + } else if (m_viewDesc.dimension == TextureViewDimension::Texture3D) { + rd.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE3D; + rd.Texture3D.MipSlice = m_viewDesc.baseMipLevel; + rd.Texture3D.WSize = m_viewDesc.arrayLayerCount; + } else { + // 2DArray, Cube, CubeArray + if (m_texture->desc.sampleCount > 1) { + rd.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DMSARRAY; + rd.Texture2DMSArray.FirstArraySlice = m_viewDesc.baseArrayLayer; + rd.Texture2DMSArray.ArraySize = m_viewDesc.arrayLayerCount; + } else { + rd.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY; + rd.Texture2DArray.MipSlice = m_viewDesc.baseMipLevel; + rd.Texture2DArray.FirstArraySlice = m_viewDesc.baseArrayLayer; + rd.Texture2DArray.ArraySize = m_viewDesc.arrayLayerCount; + } + } + + m_rtv = m_rtvHeap->allocate(); + m_device->CreateRenderTargetView(m_texture->handle(), &rd, m_rtv); + m_hasRtv = true; + return m_rtv; + } + + // ---- Lazy DSV ---- + D3D12_CPU_DESCRIPTOR_HANDLE getDsv() { + if (m_hasDsv) return m_dsv; + + auto fmt = (m_viewDesc.format == TextureFormat::Undefined) ? m_texture->desc.format : m_viewDesc.format; + bool isArray = m_texture->desc.arrayLayerCount > 1; + + D3D12_DEPTH_STENCIL_VIEW_DESC dd{}; + dd.Format = toDxgiFormat(fmt); + + if (m_viewDesc.dimension == TextureViewDimension::Texture2D) { + if (isArray) { + if (m_texture->desc.sampleCount > 1) { + dd.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY; + dd.Texture2DMSArray.FirstArraySlice = m_viewDesc.baseArrayLayer; + dd.Texture2DMSArray.ArraySize = m_viewDesc.arrayLayerCount; + } else { + dd.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DARRAY; + dd.Texture2DArray.MipSlice = m_viewDesc.baseMipLevel; + dd.Texture2DArray.FirstArraySlice = m_viewDesc.baseArrayLayer; + dd.Texture2DArray.ArraySize = m_viewDesc.arrayLayerCount; + } + } else if (m_texture->desc.sampleCount > 1) { + dd.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DMS; + } else { + dd.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D; + dd.Texture2D.MipSlice = m_viewDesc.baseMipLevel; + } + } else { + if (m_texture->desc.sampleCount > 1) { + dd.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DMSARRAY; + dd.Texture2DMSArray.FirstArraySlice = m_viewDesc.baseArrayLayer; + dd.Texture2DMSArray.ArraySize = m_viewDesc.arrayLayerCount; + } else { + dd.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2DARRAY; + dd.Texture2DArray.MipSlice = m_viewDesc.baseMipLevel; + dd.Texture2DArray.FirstArraySlice = m_viewDesc.baseArrayLayer; + dd.Texture2DArray.ArraySize = m_viewDesc.arrayLayerCount; + } + } + + m_dsv = m_dsvHeap->allocate(); + m_device->CreateDepthStencilView(m_texture->handle(), &dd, m_dsv); + m_hasDsv = true; + return m_dsv; + } + + // ---- Lazy UAV ---- + D3D12_CPU_DESCRIPTOR_HANDLE getUav() { + if (m_hasUav) return m_uav; + + auto fmt = (m_viewDesc.format == TextureFormat::Undefined) ? m_texture->desc.format : m_viewDesc.format; + + D3D12_UNORDERED_ACCESS_VIEW_DESC ud{}; + ud.Format = toDxgiFormat(fmt); + + switch (m_viewDesc.dimension) { + case TextureViewDimension::Texture1D: + ud.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1D; + ud.Texture1D.MipSlice = m_viewDesc.baseMipLevel; + break; + case TextureViewDimension::Texture1DArray: + ud.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE1DARRAY; + ud.Texture1DArray.MipSlice = m_viewDesc.baseMipLevel; + ud.Texture1DArray.FirstArraySlice = m_viewDesc.baseArrayLayer; + ud.Texture1DArray.ArraySize = m_viewDesc.arrayLayerCount; + break; + case TextureViewDimension::Texture2D: + ud.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; + ud.Texture2D.MipSlice = m_viewDesc.baseMipLevel; + break; + case TextureViewDimension::Texture2DArray: + case TextureViewDimension::TextureCube: + case TextureViewDimension::TextureCubeArray: + ud.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2DARRAY; + ud.Texture2DArray.MipSlice = m_viewDesc.baseMipLevel; + ud.Texture2DArray.FirstArraySlice = m_viewDesc.baseArrayLayer; + ud.Texture2DArray.ArraySize = m_viewDesc.arrayLayerCount; + break; + case TextureViewDimension::Texture3D: + ud.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE3D; + ud.Texture3D.MipSlice = m_viewDesc.baseMipLevel; + ud.Texture3D.FirstWSlice = m_viewDesc.baseArrayLayer; + ud.Texture3D.WSize = m_viewDesc.arrayLayerCount; + break; + default: + ud.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D; + ud.Texture2D.MipSlice = m_viewDesc.baseMipLevel; + break; + } + + m_uav = m_srvHeap->allocate(); + m_device->CreateUnorderedAccessView(m_texture->handle(), nullptr, &ud, m_uav); + m_hasUav = true; + return m_uav; + } + + void cleanup() { + if (m_hasSrv) { m_srvHeap->free(m_srv); m_hasSrv = false; } + if (m_hasRtv) { m_rtvHeap->free(m_rtv); m_hasRtv = false; } + if (m_hasDsv) { m_dsvHeap->free(m_dsv); m_hasDsv = false; } + if (m_hasUav) { m_srvHeap->free(m_uav); m_hasUav = false; } + } + + // ---- Internal ---- + [[nodiscard]] DxTextureImpl* dxTexture() const { return m_texture; } + [[nodiscard]] TextureViewDesc viewDesc() const { return m_viewDesc; } + [[nodiscard]] TextureFormat format() const { + return (m_viewDesc.format == TextureFormat::Undefined) ? m_texture->desc.format : m_viewDesc.format; + } + // Base-mip extent (a view onto mip N is half-sized per level). DX12 has no Vulkan-style renderArea + // validation so a stale base size here wouldn't fault, but callers (e.g. the render graph's + // viewport default) expect the view's true dimensions - keep it correct + consistent with Vulkan. + [[nodiscard]] u32 width() const { u32 w = m_texture->desc.width >> m_viewDesc.baseMipLevel; return w ? w : 1u; } + [[nodiscard]] u32 height() const { u32 h = m_texture->desc.height >> m_viewDesc.baseMipLevel; return h ? h : 1u; } + +private: + ID3D12Device* m_device = nullptr; + DxTextureImpl* m_texture = nullptr; + TextureViewDesc m_viewDesc{}; + DxDescriptorHeapAllocator* m_srvHeap = nullptr; + DxDescriptorHeapAllocator* m_rtvHeap = nullptr; + DxDescriptorHeapAllocator* m_dsvHeap = nullptr; + + D3D12_CPU_DESCRIPTOR_HANDLE m_srv{}, m_rtv{}, m_dsv{}, m_uav{}; + bool m_hasSrv = false, m_hasRtv = false, m_hasDsv = false, m_hasUav = false; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/DX12/DxTransferBatch.cppm b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxTransferBatch.cppm new file mode 100644 index 00000000..53ef4ec6 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/DX12/DxTransferBatch.cppm @@ -0,0 +1,278 @@ +/// DX12 implementation of TransferBatch. + +module; + +#include "DxIncludes.h" +#include + +#include +#include + +export module rhi.dx12:transfer_batch; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :buffer; +import :texture; +import :fence; + +using namespace draco; + +export namespace draco::rhi::dx12 { + +class DxQueueImpl; // forward + +class DxTransferBatchImpl : public TransferBatch { +public: + Status init(ID3D12Device* device, ID3D12CommandQueue* queue, QueueType queueType) { + m_device = device; + m_queue = queue; + + HRESULT hr = device->CreateCommandAllocator( + toCommandListType(queueType), + IID_PPV_ARGS(&m_allocator)); + if (FAILED(hr)) { + logErrorf("DxTransferBatch: CreateCommandAllocator failed (0x%08X)", static_cast(hr)); + return ErrorCode::Unknown; + } + + hr = device->CreateCommandList(0, + toCommandListType(queueType), + m_allocator.Get(), nullptr, + IID_PPV_ARGS(&m_cmdList)); + if (FAILED(hr)) { + logErrorf("DxTransferBatch: CreateCommandList failed (0x%08X)", static_cast(hr)); + return ErrorCode::Unknown; + } + + // Command list starts open; close it until we need it. + m_cmdList->Close(); + + // Create fence for synchronous submit. + hr = device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence)); + if (FAILED(hr)) { + logErrorf("DxTransferBatch: CreateFence failed (0x%08X)", static_cast(hr)); + return ErrorCode::Unknown; + } + + m_fenceEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + m_fenceValue = 0; + + return ErrorCode::Ok; + } + + // ---- TransferBatch interface ---- + + void writeBuffer(Buffer* dst, u64 dstOffset, std::span data) override { + auto* dxDst = static_cast(dst); + if (!dxDst || data.size() == 0) return; + + ensureRecording(); + + // Create upload-heap staging buffer. + u64 stagingSize = static_cast(data.size()); + ComPtr staging; + + D3D12_HEAP_PROPERTIES heapProps{}; + heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; + + D3D12_RESOURCE_DESC rd{}; + rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + rd.Width = stagingSize; + rd.Height = 1; + rd.DepthOrArraySize = 1; + rd.MipLevels = 1; + rd.Format = DXGI_FORMAT_UNKNOWN; + rd.SampleDesc = { 1, 0 }; + rd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + rd.Flags = D3D12_RESOURCE_FLAG_NONE; + + HRESULT hr = m_device->CreateCommittedResource( + &heapProps, D3D12_HEAP_FLAG_NONE, + &rd, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, + IID_PPV_ARGS(&staging)); + if (FAILED(hr)) return; + + // Map and copy data. + void* mapped = nullptr; + staging->Map(0, nullptr, &mapped); + std::memcpy(mapped, data.data(), data.size()); + staging->Unmap(0, nullptr); + + m_stagingBuffers.push_back(std::move(staging)); + + // Record copy command. + m_cmdList->CopyBufferRegion(dxDst->handle(), dstOffset, + m_stagingBuffers.back().Get(), 0, stagingSize); + } + + void writeTexture(Texture* dst, std::span data, + const TextureDataLayout& layout, Extent3D extent, + u32 mipLevel, u32 arrayLayer) override { + auto* dxTex = static_cast(dst); + if (!dxTex || data.size() == 0) return; + + ensureRecording(); + + // Calculate aligned row pitch (D3D12 requires 256-byte row alignment). + u32 alignedRowPitch = (layout.bytesPerRow + 255) & ~u32(255); + u32 rowsPerImage = (layout.rowsPerImage > 0) ? layout.rowsPerImage : extent.height; + u64 stagingSize = static_cast(alignedRowPitch) * rowsPerImage * extent.depth; + + ComPtr staging; + + D3D12_HEAP_PROPERTIES heapProps{}; + heapProps.Type = D3D12_HEAP_TYPE_UPLOAD; + + D3D12_RESOURCE_DESC rd{}; + rd.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + rd.Width = stagingSize; + rd.Height = 1; + rd.DepthOrArraySize = 1; + rd.MipLevels = 1; + rd.Format = DXGI_FORMAT_UNKNOWN; + rd.SampleDesc = { 1, 0 }; + rd.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + rd.Flags = D3D12_RESOURCE_FLAG_NONE; + + HRESULT hr = m_device->CreateCommittedResource( + &heapProps, D3D12_HEAP_FLAG_NONE, + &rd, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, + IID_PPV_ARGS(&staging)); + if (FAILED(hr)) return; + + // Map and copy data row by row (handles pitch alignment). + void* mapped = nullptr; + staging->Map(0, nullptr, &mapped); + + const u8* srcPtr = data.data() + layout.offset; + u8* dstPtr = static_cast(mapped); + for (u32 z = 0; z < extent.depth; ++z) { + for (u32 row = 0; row < rowsPerImage; ++row) { + std::memcpy( + dstPtr + (z * rowsPerImage + row) * alignedRowPitch, + srcPtr + (z * rowsPerImage + row) * layout.bytesPerRow, + layout.bytesPerRow); + } + } + + staging->Unmap(0, nullptr); + m_stagingBuffers.push_back(std::move(staging)); + + // Transition texture to copy dest. + D3D12_RESOURCE_BARRIER barrier{}; + barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + barrier.Transition.pResource = dxTex->handle(); + barrier.Transition.StateBefore = dxTex->currentState(); + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST; + barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; + if (dxTex->currentState() != D3D12_RESOURCE_STATE_COPY_DEST) + m_cmdList->ResourceBarrier(1, &barrier); + + u32 subresource = mipLevel + arrayLayer * dxTex->desc.mipLevelCount; + + D3D12_TEXTURE_COPY_LOCATION srcLoc{}; + srcLoc.pResource = m_stagingBuffers.back().Get(); + srcLoc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + srcLoc.PlacedFootprint.Offset = 0; + srcLoc.PlacedFootprint.Footprint.Format = toDxgiFormat(dxTex->desc.format); + srcLoc.PlacedFootprint.Footprint.Width = extent.width; + srcLoc.PlacedFootprint.Footprint.Height = extent.height; + srcLoc.PlacedFootprint.Footprint.Depth = extent.depth; + srcLoc.PlacedFootprint.Footprint.RowPitch = alignedRowPitch; + + D3D12_TEXTURE_COPY_LOCATION dstLoc{}; + dstLoc.pResource = dxTex->handle(); + dstLoc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + dstLoc.SubresourceIndex = subresource; + + m_cmdList->CopyTextureRegion(&dstLoc, 0, 0, 0, &srcLoc, nullptr); + + // Transition back to common. + barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST; + barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COMMON; + m_cmdList->ResourceBarrier(1, &barrier); + dxTex->setState(D3D12_RESOURCE_STATE_COMMON); + } + + Status submit() override { + if (!m_isRecording) return ErrorCode::Ok; + + m_cmdList->Close(); + m_isRecording = false; + + ID3D12CommandList* lists[] = { m_cmdList.Get() }; + m_queue->ExecuteCommandLists(1, lists); + + // Wait for completion. + ++m_fenceValue; + m_queue->Signal(m_fence.Get(), m_fenceValue); + if (m_fence->GetCompletedValue() < m_fenceValue) { + m_fence->SetEventOnCompletion(m_fenceValue, m_fenceEvent); + WaitForSingleObject(m_fenceEvent, INFINITE); + } + + releaseStagingBuffers(); + return ErrorCode::Ok; + } + + Status submitAsync(Fence* fence, u64 signalValue) override { + if (!m_isRecording) return ErrorCode::Ok; + + m_cmdList->Close(); + m_isRecording = false; + + ID3D12CommandList* lists[] = { m_cmdList.Get() }; + m_queue->ExecuteCommandLists(1, lists); + + if (auto* dxFence = static_cast(fence)) + m_queue->Signal(dxFence->handle(), signalValue); + + // Note: staging buffers can't be released until GPU is done. + // Caller must wait on the fence before calling reset(). + return ErrorCode::Ok; + } + + void reset() override { + releaseStagingBuffers(); + } + + void destroy() override { + releaseStagingBuffers(); + + if (m_fenceEvent) { CloseHandle(m_fenceEvent); m_fenceEvent = nullptr; } + m_fence.Reset(); + m_cmdList.Reset(); + m_allocator.Reset(); + } + +private: + void ensureRecording() { + if (!m_isRecording) { + m_allocator->Reset(); + m_cmdList->Reset(m_allocator.Get(), nullptr); + m_isRecording = true; + } + } + + void releaseStagingBuffers() { + m_stagingBuffers.clear(); + } + + ID3D12Device* m_device = nullptr; + ID3D12CommandQueue* m_queue = nullptr; + ComPtr m_allocator; + ComPtr m_cmdList; + bool m_isRecording = false; + + // ComPtr's operator overloads are incompatible with Array's placement-new. + std::vector> m_stagingBuffers; + + ComPtr m_fence; + u64 m_fenceValue = 0; + HANDLE m_fenceEvent = nullptr; +}; + +} // namespace draco::rhi::dx12 diff --git a/Engine/cpp/Runtime/Rendering/RHI/Descriptors.cppm b/Engine/cpp/Runtime/Rendering/RHI/Descriptors.cppm new file mode 100644 index 00000000..82606f7c --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Descriptors.cppm @@ -0,0 +1,421 @@ +/// Descriptor structs for creating RHI resources, pipelines, and render passes. + +module; + +#include +#include +#include +#include + +export module rhi:descriptors; + +import core.stdtypes; +import :enums; +import :texture_format; +import :types; +import :forward; + +using namespace draco; + +export namespace draco::rhi { + +// ---- Resources ---- + +struct BufferDesc { + u64 size = 0; + BufferUsage usage = BufferUsage::None; + MemoryLocation memory = MemoryLocation::GpuOnly; + std::u8string_view label; +}; + +struct TextureDesc { + TextureDimension dimension = TextureDimension::Texture2D; + TextureFormat format = TextureFormat::Undefined; + u32 width = 1, height = 1, depth = 1; + u32 arrayLayerCount = 1; + u32 mipLevelCount = 1; + u32 sampleCount = 1; + TextureUsage usage = TextureUsage::None; + std::u8string_view label; + + /// Convenience factory for a 2D render target. + static TextureDesc renderTarget(TextureFormat fmt, u32 w, u32 h, u32 samples = 1, std::u8string_view lbl = {}) { + TextureDesc d{}; + d.format = fmt; d.width = w; d.height = h; d.sampleCount = samples; + d.usage = TextureUsage::RenderTarget | TextureUsage::Sampled; + d.label = lbl; + return d; + } + + /// Convenience factory for a depth buffer. + static TextureDesc depthBuffer(TextureFormat fmt, u32 w, u32 h, u32 samples = 1, std::u8string_view lbl = {}) { + TextureDesc d{}; + d.format = fmt; d.width = w; d.height = h; d.sampleCount = samples; + d.usage = TextureUsage::DepthStencil; + d.label = lbl; + return d; + } +}; + +struct TextureViewDesc { + TextureFormat format = TextureFormat::Undefined; + TextureViewDimension dimension = TextureViewDimension::Texture2D; + u32 baseMipLevel = 0; + u32 mipLevelCount = 1; + u32 baseArrayLayer = 0; + u32 arrayLayerCount = 1; + TextureAspect aspect = TextureAspect::All; + std::u8string_view label; +}; + +struct SamplerDesc { + FilterMode minFilter = FilterMode::Linear; + FilterMode magFilter = FilterMode::Linear; + MipmapFilterMode mipmapFilter = MipmapFilterMode::Linear; + AddressMode addressU = AddressMode::Repeat; + AddressMode addressV = AddressMode::Repeat; + AddressMode addressW = AddressMode::Repeat; + f32 mipLodBias = 0.0f; + f32 minLod = 0.0f; + f32 maxLod = 1000.0f; + u16 maxAnisotropy = 1; + std::optional compare; + SamplerBorderColor borderColor = SamplerBorderColor::TransparentBlack; + std::u8string_view label; +}; + +struct ShaderModuleDesc { + std::span code; ///< SPIR-V or DXIL bytecode. + std::u8string_view label; +}; + +struct QuerySetDesc { + QueryType type = QueryType::Timestamp; + u32 count = 0; + std::u8string_view label; +}; + +struct SwapChainDesc { + u32 width = 0; + u32 height = 0; + TextureFormat format = TextureFormat::BGRA8UnormSrgb; + PresentMode presentMode = PresentMode::Fifo; + u32 bufferCount = 2; + std::u8string_view label; +}; + +// ---- Binding ---- + +/// Describes one entry in a bind group layout. +struct BindGroupLayoutEntry { + u32 binding = 0; + ShaderStage visibility = ShaderStage::None; + BindingType type = BindingType::UniformBuffer; + TextureViewDimension textureDimension = TextureViewDimension::Texture2D; + bool textureMultisampled = false; + TextureFormat storageTextureFormat = TextureFormat::Undefined; + bool hasDynamicOffset = false; + u32 storageBufferStride = 0; + u32 count = 1; + std::u8string_view label; + + /// Factory: uniform buffer binding. + static BindGroupLayoutEntry uniformBuffer(u32 binding, ShaderStage vis) { + BindGroupLayoutEntry e{}; e.binding = binding; e.visibility = vis; + e.type = BindingType::UniformBuffer; return e; + } + + /// Factory: sampled texture binding. + static BindGroupLayoutEntry sampledTexture(u32 binding, ShaderStage vis, + TextureViewDimension dim = TextureViewDimension::Texture2D) { + BindGroupLayoutEntry e{}; e.binding = binding; e.visibility = vis; + e.type = BindingType::SampledTexture; e.textureDimension = dim; return e; + } + + /// Factory: sampler binding. + static BindGroupLayoutEntry sampler(u32 binding, ShaderStage vis) { + BindGroupLayoutEntry e{}; e.binding = binding; e.visibility = vis; + e.type = BindingType::Sampler; return e; + } + + /// Factory: storage buffer (read-write) binding. + static BindGroupLayoutEntry storageBuffer(u32 binding, ShaderStage vis, bool readOnly = false) { + BindGroupLayoutEntry e{}; e.binding = binding; e.visibility = vis; + e.type = readOnly ? BindingType::StorageBufferReadOnly : BindingType::StorageBufferReadWrite; + return e; + } +}; + +struct BindGroupLayoutDesc { + std::span entries; + std::u8string_view label; +}; + +/// Describes one resource binding within a bind group. +struct BindGroupEntry { + Buffer* buffer = nullptr; + u64 bufferOffset = 0; + u64 bufferSize = 0; + TextureView* textureView = nullptr; + Sampler* sampler = nullptr; + AccelStruct* accelStruct = nullptr; + + static BindGroupEntry bufferEntry(Buffer* buf, u64 offset, u64 size) { + BindGroupEntry e{}; e.buffer = buf; e.bufferOffset = offset; e.bufferSize = size; return e; + } + static BindGroupEntry textureEntry(TextureView* view) { + BindGroupEntry e{}; e.textureView = view; return e; + } + static BindGroupEntry samplerEntry(Sampler* s) { + BindGroupEntry e{}; e.sampler = s; return e; + } + static BindGroupEntry accelStructEntry(AccelStruct* as) { + BindGroupEntry e{}; e.accelStruct = as; return e; + } +}; + +struct BindGroupDesc { + BindGroupLayout* layout = nullptr; + std::span entries; + std::u8string_view label; +}; + +/// For updating individual entries in a bindless bind group. +struct BindlessUpdateEntry { + u32 layoutIndex = 0; + u32 arrayIndex = 0; + Buffer* buffer = nullptr; + u64 bufferOffset = 0; + u64 bufferSize = 0; + TextureView* textureView = nullptr; + Sampler* sampler = nullptr; +}; + +// ---- Pipelines ---- + +struct PipelineLayoutDesc { + std::span bindGroupLayouts; + std::span pushConstantRanges; + std::u8string_view label; +}; + +struct PipelineCacheDesc { + std::span initialData; + std::u8string_view label; +}; + +struct VertexAttribute { + VertexFormat format = VertexFormat::Float32; + u32 offset = 0; + u32 shaderLocation = 0; +}; + +struct VertexBufferLayout { + u32 stride = 0; + VertexStepMode stepMode = VertexStepMode::Vertex; + std::span attributes; +}; + +struct BlendComponent { + BlendFactor srcFactor = BlendFactor::One; + BlendFactor dstFactor = BlendFactor::Zero; + BlendOperation operation = BlendOperation::Add; +}; + +struct BlendState { + BlendComponent color; + BlendComponent alpha; + + // Standard alpha blending: srcAlpha*src + (1-srcAlpha)*dst. + static constexpr BlendState alphaBlend() { + return { { BlendFactor::SrcAlpha, BlendFactor::OneMinusSrcAlpha, BlendOperation::Add }, + { BlendFactor::One, BlendFactor::OneMinusSrcAlpha, BlendOperation::Add } }; + } + // Premultiplied alpha: src + (1-srcAlpha)*dst. + static constexpr BlendState premultipliedAlpha() { + return { { BlendFactor::One, BlendFactor::OneMinusSrcAlpha, BlendOperation::Add }, + { BlendFactor::One, BlendFactor::OneMinusSrcAlpha, BlendOperation::Add } }; + } + // Additive: src + dst - accumulate, e.g. the bloom upsample chain. + static constexpr BlendState additive() { + return { { BlendFactor::One, BlendFactor::One, BlendOperation::Add }, + { BlendFactor::One, BlendFactor::One, BlendOperation::Add } }; + } + // Multiply: src * dst. + static constexpr BlendState multiply() { + return { { BlendFactor::Dst, BlendFactor::Zero, BlendOperation::Add }, + { BlendFactor::DstAlpha, BlendFactor::Zero, BlendOperation::Add } }; + } +}; + +struct ColorTargetState { + TextureFormat format = TextureFormat::Undefined; + std::optional blend; + ColorWriteMask writeMask = ColorWriteMask::All; +}; + +struct StencilFaceState { + CompareFunction compare = CompareFunction::Always; + StencilOperation failOp = StencilOperation::Keep; + StencilOperation depthFailOp = StencilOperation::Keep; + StencilOperation passOp = StencilOperation::Keep; +}; + +struct DepthStencilState { + TextureFormat format = TextureFormat::Undefined; + bool depthTestEnabled = true; + bool depthWriteEnabled = true; + CompareFunction depthCompare = CompareFunction::Less; + bool stencilEnabled = false; + u8 stencilReadMask = 0xFF; + u8 stencilWriteMask = 0xFF; + StencilFaceState stencilFront; + StencilFaceState stencilBack; + i32 depthBias = 0; + f32 depthBiasSlopeScale = 0.0f; + f32 depthBiasClamp = 0.0f; +}; + +struct PrimitiveState { + PrimitiveTopology topology = PrimitiveTopology::TriangleList; + FrontFace frontFace = FrontFace::CCW; + CullMode cullMode = CullMode::None; + FillMode fillMode = FillMode::Solid; + bool depthClipEnabled = true; +}; + +struct MultisampleState { + u32 count = 1; + u32 mask = 0xFFFFFFFF; + bool alphaToCoverageEnabled = false; +}; + +/// Programmable shader stage (vertex, fragment, compute, mesh, task, etc.). +struct ProgrammableStage { + ShaderModule* module = nullptr; + std::u8string_view entryPoint = u8"main"; + ShaderStage stage = ShaderStage::None; +}; + +struct VertexState { + ProgrammableStage shader; + std::span buffers; +}; + +struct FragmentState { + ProgrammableStage shader; + std::span targets; +}; + +/// Descriptor for creating a graphics (render) pipeline. +struct RenderPipelineDesc { + PipelineLayout* layout = nullptr; + VertexState vertex; + std::optional fragment; + PrimitiveState primitive; + std::optional depthStencil; + MultisampleState multisample; + PipelineCache* cache = nullptr; + std::u8string_view label; +}; + +/// Descriptor for creating a compute pipeline. +struct ComputePipelineDesc { + PipelineLayout* layout = nullptr; + ProgrammableStage compute; + PipelineCache* cache = nullptr; + std::u8string_view label; +}; + +// ---- Render pass ---- + +/// Color attachment for a render pass. +struct ColorAttachment { + TextureView* view = nullptr; + TextureView* resolveTarget = nullptr; + LoadOp loadOp = LoadOp::Clear; + StoreOp storeOp = StoreOp::Store; + ClearColor clearValue = ClearColor::black(); +}; + +/// Depth/stencil attachment for a render pass. +struct DepthStencilAttachment { + TextureView* view = nullptr; + LoadOp depthLoadOp = LoadOp::Clear; + StoreOp depthStoreOp = StoreOp::Store; + f32 depthClearValue = 1.0f; + bool depthReadOnly = false; + LoadOp stencilLoadOp = LoadOp::Clear; + StoreOp stencilStoreOp = StoreOp::Store; + u32 stencilClearValue = 0; + bool stencilReadOnly = false; +}; + +/// Color attachment list (up to maxColorAttachments). +using ColorAttachmentList = std::vector; + +/// Descriptor for beginning a render pass. +struct RenderPassDesc { + ColorAttachmentList colorAttachments; + std::optional depthStencilAttachment; + QuerySet* timestampQuerySet = nullptr; + u32 beginTimestampIndex = 0; + u32 endTimestampIndex = 0; + // How draws are supplied. Default Inline; set SecondaryCommandBuffers to execute render + // bundles into this pass (Vulkan needs to know at begin time; other backends ignore it). + RenderPassContents contents = RenderPassContents::Inline; + std::u8string_view label; +}; + +// Describes a render bundle's target signature so it can be validated against - and replayed +// into - compatible render passes: the attachment formats + sample count it records for. The +// render-area extent lets the Vulkan backend record a full-target viewport/scissor into the +// secondary command buffer (bundles carry no pass-level dynamic state; other backends inherit +// it from the pass and ignore the extent). +struct RenderBundleDesc { + TextureFormat colorFormats[maxColorAttachments] = { TextureFormat::Undefined }; + u32 colorFormatCount = 0; + TextureFormat depthStencilFormat = TextureFormat::Undefined; + u32 sampleCount = 1; + // The bundle's viewport/scissor (a sub-rect of the target for split-screen). x/y default to 0; + // width/height are the viewport extent. A Vulkan secondary / DX12 bundle records this up front + // since it can't inherit dynamic viewport state from the parent. + i32 viewportX = 0; + i32 viewportY = 0; + u32 width = 0; + u32 height = 0; + std::u8string_view label; +}; + +// ---- Barriers ---- + +struct BufferBarrier { + Buffer* buffer = nullptr; + ResourceState oldState = ResourceState::Undefined; + ResourceState newState = ResourceState::Undefined; + u64 offset = 0; + u64 size = ~0ull; +}; + +struct TextureBarrier { + Texture* texture = nullptr; + ResourceState oldState = ResourceState::Undefined; + ResourceState newState = ResourceState::Undefined; + u32 baseMipLevel = 0; + u32 mipLevelCount = ~0u; + u32 baseArrayLayer = 0; + u32 arrayLayerCount= ~0u; +}; + +struct MemoryBarrier { + ResourceState oldState = ResourceState::Undefined; + ResourceState newState = ResourceState::Undefined; +}; + +struct BarrierGroup { + std::span bufferBarriers; + std::span textureBarriers; + std::span memoryBarriers; +}; + +} // namespace draco::rhi diff --git a/Engine/cpp/Runtime/Rendering/RHI/Device.cppm b/Engine/cpp/Runtime/Rendering/RHI/Device.cppm new file mode 100644 index 00000000..0922d02d --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Device.cppm @@ -0,0 +1,200 @@ +/// Abstract Backend, Adapter, and Device interfaces. +/// +/// Backend is the entry point - it enumerates GPU adapters and creates +/// presentation surfaces. Adapter represents a physical GPU. Device is +/// the central factory for all GPU resources. +/// +/// Mesh shader and ray tracing creation methods are on Device directly +/// (virtual no-ops returning ErrorCode::NotSupported when not supported). +/// Callers check device->features.meshShaders / .rayTracing before use. + +module; + +#include +#include + +export module rhi:device; + +import core.stdtypes; +import core.status; +import :enums; +import :texture_format; +import :types; +import :descriptors; +import :ext_descriptors; +import :resources; +import :commands; +import :queue; +import :swapchain; + +using namespace draco; + +export namespace draco::rhi { + +// ---- Backend ---- + +/// RHI backend entry point (Vulkan, DX12, etc.). +class Backend { +public: + virtual ~Backend() = default; + + bool isInitialized = false; + + /// Returns all available GPU adapters in preference order, most + /// preferred first (discrete > integrated > unknown > CPU). Backends + /// guarantee this ordering via sortAdaptersByPreference(), so callers + /// that just want "the best available GPU" can take element [0]. + [[nodiscard]] virtual std::span enumerateAdapters() = 0; + + /// Creates a presentation surface from a native window handle. + /// On Win32: windowHandle = HWND, displayHandle = nullptr. + /// On X11: windowHandle = XID (as void*), displayHandle = Display*. + /// On Wayland: windowHandle = wl_surface*, displayHandle = wl_display*. + virtual Status createSurface(void* windowHandle, void* displayHandle, Surface*& out) = 0; + + Status createSurface(void* windowHandle, Surface*& out) { + return createSurface(windowHandle, nullptr, out); + } + + /// Destroy the backend and all objects it owns. + virtual void destroy() = 0; +}; + +// ---- Adapter ---- + +/// Represents a physical GPU. Query capabilities and create a logical device. +class Adapter { +public: + virtual ~Adapter() = default; + + /// Populate adapter info (name, vendor, features, limits). + virtual void getInfo(AdapterInfo& out) = 0; + + /// Convenience: returns a copy of the adapter info. + [[nodiscard]] AdapterInfo info() { AdapterInfo i; getInfo(i); return i; } + + /// Create a logical device from this adapter. + virtual Status createDevice(const DeviceDesc& desc, Device*& out) = 0; +}; + +/// Selection preference for an adapter type - lower is more preferred. +/// Defines the single source of truth for "best GPU first" ordering. +[[nodiscard]] inline int adapterPreferenceRank(AdapterType type) { + switch (type) { + case AdapterType::DiscreteGpu: return 0; + case AdapterType::IntegratedGpu: return 1; + case AdapterType::Unknown: return 2; + case AdapterType::Cpu: return 3; + } + return 4; +} + +/// Reorder adapters so the most preferred GPU is first (see +/// adapterPreferenceRank). Backends call this after enumeration so that +/// enumerateAdapters()[0] is the recommended default. The sort is stable, +/// preserving the driver's native order among adapters of equal type. +inline void sortAdaptersByPreference(std::vector& adapters) { + // Stable insertion sort by preference rank (adapter counts are tiny). + for (usize i = 1; i < adapters.size(); ++i) { + Adapter* key = adapters[i]; + const int keyRank = adapterPreferenceRank(key->info().type); + usize j = i; + while (j > 0 && adapterPreferenceRank(adapters[j - 1]->info().type) > keyRank) { + adapters[j] = adapters[j - 1]; + --j; + } + adapters[j] = key; + } +} + +// ---- Device ---- + +/// Central factory for GPU resources, pipelines, and command infrastructure. +class Device { +public: + virtual ~Device() = default; + + DeviceType type = DeviceType::Null; + DeviceFeatures features{}; + + // ---- Queries ---- + [[nodiscard]] virtual Queue* getQueue(QueueType type, u32 index = 0) = 0; + [[nodiscard]] virtual u32 getQueueCount(QueueType type) = 0; + /// Query hardware format support for a given texture format. + [[nodiscard]] virtual FormatSupport getFormatSupport(TextureFormat format) = 0; + + // ---- Resource creation ---- + virtual Status createBuffer(const BufferDesc& desc, Buffer*& out) = 0; + virtual Status createTexture(const TextureDesc& desc, Texture*& out) = 0; + virtual Status createTextureView(Texture* texture, const TextureViewDesc& desc, TextureView*& out) = 0; + virtual Status createSampler(const SamplerDesc& desc, Sampler*& out) = 0; + virtual Status createShaderModule(const ShaderModuleDesc& desc, ShaderModule*& out) = 0; + virtual Status createBindGroupLayout(const BindGroupLayoutDesc& desc, BindGroupLayout*& out) = 0; + virtual Status createBindGroup(const BindGroupDesc& desc, BindGroup*& out) = 0; + virtual Status createPipelineLayout(const PipelineLayoutDesc& desc, PipelineLayout*& out) = 0; + virtual Status createPipelineCache(const PipelineCacheDesc& desc, PipelineCache*& out) = 0; + virtual Status createRenderPipeline(const RenderPipelineDesc& desc, RenderPipeline*& out) = 0; + virtual Status createComputePipeline(const ComputePipelineDesc& desc, ComputePipeline*& out) = 0; + virtual Status createCommandPool(QueueType queueType, CommandPool*& out) = 0; + virtual Status createFence(u64 initialValue, Fence*& out) = 0; + virtual Status createQuerySet(const QuerySetDesc& desc, QuerySet*& out) = 0; + virtual Status createSwapChain(Surface* surface, const SwapChainDesc& desc, SwapChain*& out) = 0; + + // ---- Resource destruction ---- + virtual void destroyBuffer(Buffer*& buf) = 0; + virtual void destroyTexture(Texture*& tex) = 0; + virtual void destroyTextureView(TextureView*& view) = 0; + virtual void destroySampler(Sampler*& sampler) = 0; + virtual void destroyShaderModule(ShaderModule*& shaderModule) = 0; + virtual void destroyBindGroupLayout(BindGroupLayout*& layout) = 0; + virtual void destroyBindGroup(BindGroup*& group) = 0; + virtual void destroyPipelineLayout(PipelineLayout*& layout) = 0; + virtual void destroyPipelineCache(PipelineCache*& cache) = 0; + virtual void destroyRenderPipeline(RenderPipeline*& pipeline) = 0; + virtual void destroyComputePipeline(ComputePipeline*& pipeline) = 0; + virtual void destroyCommandPool(CommandPool*& pool) = 0; + virtual void destroyFence(Fence*& fence) = 0; + virtual void destroyQuerySet(QuerySet*& querySet) = 0; + virtual void destroySwapChain(SwapChain*& swapChain) = 0; + virtual void destroySurface(Surface*& surface) = 0; + + // ---- Mesh shader extension (folded into Device) ---- + /// Create a mesh shader pipeline. Returns Unsupported if mesh shaders + /// are not enabled on this device. + virtual Status createMeshPipeline(const MeshPipelineDesc& desc, MeshPipeline*& out) { + (void)desc; out = nullptr; return ErrorCode::NotSupported; + } + virtual void destroyMeshPipeline(MeshPipeline*& pipeline) { (void)pipeline; } + + // ---- Ray tracing extension (folded into Device) ---- + /// Shader binding table handle properties. Populated by the backend + /// during device creation when ray tracing is enabled. + u32 shaderGroupHandleSize = 0; + u32 shaderGroupHandleAlignment = 0; + u32 shaderGroupBaseAlignment = 0; + + /// Create an acceleration structure. Returns Unsupported if ray tracing + /// is not enabled on this device. + virtual Status createAccelStruct(const AccelStructDesc& desc, AccelStruct*& out) { + (void)desc; out = nullptr; return ErrorCode::NotSupported; + } + virtual void destroyAccelStruct(AccelStruct*& accelStruct) { (void)accelStruct; } + + virtual Status createRayTracingPipeline(const RayTracingPipelineDesc& desc, RayTracingPipeline*& out) { + (void)desc; out = nullptr; return ErrorCode::NotSupported; + } + virtual void destroyRayTracingPipeline(RayTracingPipeline*& pipeline) { (void)pipeline; } + + /// Retrieve shader group handles for building shader binding tables. + virtual Status getShaderGroupHandles(RayTracingPipeline* pipeline, u32 firstGroup, + u32 groupCount, std::span outData) { + (void)pipeline; (void)firstGroup; (void)groupCount; (void)outData; + return ErrorCode::NotSupported; + } + + // ---- Lifecycle ---- + virtual void waitIdle() = 0; + virtual void destroy() = 0; +}; + +} // namespace draco::rhi diff --git a/Engine/cpp/Runtime/Rendering/RHI/Enums.cppm b/Engine/cpp/Runtime/Rendering/RHI/Enums.cppm new file mode 100644 index 00000000..bed22d4e --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Enums.cppm @@ -0,0 +1,193 @@ +// All RHI enumerations and flag operators. + +export module rhi:enums; + +import core.stdtypes; + +using namespace draco; + +export namespace draco::rhi { + +enum class DeviceType : u32 { Vulkan, DX12, Null }; +enum class QueueType : u32 { Graphics, Compute, Transfer }; + +enum class BufferUsage : u32 { + None = 0, + CopySrc = 1 << 0, + CopyDst = 1 << 1, + Vertex = 1 << 2, + Index = 1 << 3, + Uniform = 1 << 4, + Storage = 1 << 5, + StorageRead = 1 << 6, + Indirect = 1 << 7, + AccelStructInput = 1 << 8, + ShaderBindingTable = 1 << 9, + AccelStructScratch = 1 << 10, +}; +inline constexpr BufferUsage operator|(BufferUsage a, BufferUsage b) { return static_cast(static_cast(a) | static_cast(b)); } +inline constexpr BufferUsage operator&(BufferUsage a, BufferUsage b) { return static_cast(static_cast(a) & static_cast(b)); } +inline constexpr bool hasFlag(BufferUsage v, BufferUsage f) { return (v & f) == f; } + +enum class TextureUsage : u32 { + None = 0, + CopySrc = 1 << 0, + CopyDst = 1 << 1, + Sampled = 1 << 2, + Storage = 1 << 3, + RenderTarget = 1 << 4, + DepthStencil = 1 << 5, + InputAttachment = 1 << 6, +}; +inline constexpr TextureUsage operator|(TextureUsage a, TextureUsage b) { return static_cast(static_cast(a) | static_cast(b)); } +inline constexpr TextureUsage operator&(TextureUsage a, TextureUsage b) { return static_cast(static_cast(a) & static_cast(b)); } + +enum class MemoryLocation : u32 { GpuOnly, CpuToGpu, GpuToCpu, Auto }; + +enum class ResourceState : u32 { + Undefined = 0, + VertexBuffer = 1 << 0, + IndexBuffer = 1 << 1, + UniformBuffer = 1 << 2, + ShaderRead = 1 << 3, + ShaderWrite = 1 << 4, + RenderTarget = 1 << 5, + DepthStencilWrite= 1 << 6, + DepthStencilRead = 1 << 7, + IndirectArgument = 1 << 8, + CopySrc = 1 << 9, + CopyDst = 1 << 10, + Present = 1 << 11, + InputAttachment = 1 << 12, + General = 1 << 13, + AccelStructRead = 1 << 14, + AccelStructWrite = 1 << 15, +}; +inline constexpr ResourceState operator|(ResourceState a, ResourceState b) { return static_cast(static_cast(a) | static_cast(b)); } +inline constexpr ResourceState operator&(ResourceState a, ResourceState b) { return static_cast(static_cast(a) & static_cast(b)); } + +enum class TextureDimension : u32 { Texture1D, Texture2D, Texture3D }; +enum class TextureViewDimension : u32 { Texture1D, Texture1DArray, Texture2D, Texture2DArray, TextureCube, TextureCubeArray, Texture3D }; + +enum class TextureAspect : u32 { All = 0, DepthOnly = 1, StencilOnly = 2 }; + +enum class ShaderStage : u32 { + None = 0, + Vertex = 1 << 0, + Fragment = 1 << 1, + Compute = 1 << 2, + Mesh = 1 << 3, + Task = 1 << 4, + RayGen = 1 << 5, + ClosestHit = 1 << 6, + Miss = 1 << 7, + AnyHit = 1 << 8, + Intersection = 1 << 9, + Callable = 1 << 10, + AllGraphics = Vertex | Fragment, + All = 0x7FF, +}; +inline constexpr ShaderStage operator|(ShaderStage a, ShaderStage b) { return static_cast(static_cast(a) | static_cast(b)); } +inline constexpr ShaderStage operator&(ShaderStage a, ShaderStage b) { return static_cast(static_cast(a) & static_cast(b)); } + +enum class BindingType : u32 { + UniformBuffer, StorageBufferReadOnly, StorageBufferReadWrite, + SampledTexture, StorageTextureReadOnly, StorageTextureReadWrite, + Sampler, ComparisonSampler, + BindlessTextures, BindlessSamplers, BindlessStorageBuffers, BindlessStorageTextures, + AccelerationStructure, +}; + +enum class FilterMode : u32 { Nearest, Linear }; +enum class MipmapFilterMode: u32 { Nearest, Linear }; +enum class AddressMode : u32 { Repeat, MirrorRepeat, ClampToEdge, ClampToBorder }; +enum class SamplerBorderColor : u32 { TransparentBlack, OpaqueBlack, OpaqueWhite }; + +enum class PrimitiveTopology : u32 { PointList, LineList, LineStrip, TriangleList, TriangleStrip }; +enum class FrontFace : u32 { CCW, CW }; +enum class CullMode : u32 { None, Front, Back }; +enum class FillMode : u32 { Solid, Wireframe }; + +enum class CompareFunction : u32 { Never, Less, Equal, LessEqual, Greater, NotEqual, GreaterEqual, Always }; +enum class StencilOperation: u32 { Keep, Zero, Replace, IncrementClamp, DecrementClamp, Invert, IncrementWrap, DecrementWrap }; + +enum class BlendFactor : u32 { + Zero, One, Src, OneMinusSrc, SrcAlpha, OneMinusSrcAlpha, + Dst, OneMinusDst, DstAlpha, OneMinusDstAlpha, + SrcAlphaSaturated, Constant, OneMinusConstant, +}; +enum class BlendOperation : u32 { Add, Subtract, ReverseSubtract, Min, Max }; + +enum class LoadOp : u32 { Load, Clear, DontCare }; +enum class StoreOp : u32 { Store, DontCare }; + +// How a render pass's draw commands are supplied. `Inline` records draws directly into the +// pass (the default). `SecondaryCommandBuffers` means the pass body is supplied by executed +// render bundles only (no inline draws) - Vulkan begins the rendering scope with the secondary- +// command-buffer contents flag; DX12 / WebGPU ignore it (they allow bundles in any pass). +enum class RenderPassContents : u32 { Inline, SecondaryCommandBuffers }; + +enum class IndexFormat : u32 { UInt16, UInt32 }; +enum class VertexStepMode: u32 { Vertex, Instance }; + +enum class VertexFormat : u32 { + Uint8x2, Uint8x4, Sint8x2, Sint8x4, + Unorm8x2, Unorm8x4, Snorm8x2, Snorm8x4, + Uint16x2, Uint16x4, Sint16x2, Sint16x4, + Unorm16x2, Unorm16x4, Snorm16x2, Snorm16x4, + Float16x2, Float16x4, + Float32, Float32x2, Float32x3, Float32x4, + Uint32, Uint32x2, Uint32x3, Uint32x4, + Sint32, Sint32x2, Sint32x3, Sint32x4, +}; + +enum class ColorWriteMask : u8 { + None = 0, + Red = 1 << 0, + Green = 1 << 1, + Blue = 1 << 2, + Alpha = 1 << 3, + All = Red | Green | Blue | Alpha, +}; +inline constexpr ColorWriteMask operator|(ColorWriteMask a, ColorWriteMask b) { return static_cast(static_cast(a) | static_cast(b)); } + +enum class FormatSupport : u32 { + Unsupported = 0, + Texture = 1 << 0, + StorageTexture = 1 << 1, + ColorAttachment = 1 << 2, + DepthStencil = 1 << 3, + Buffer = 1 << 4, + StorageBuffer = 1 << 5, + VertexBuffer = 1 << 6, + BlendableColor = 1 << 7, + LinearFilter = 1 << 8, +}; +inline constexpr FormatSupport operator|(FormatSupport a, FormatSupport b) { return static_cast(static_cast(a) | static_cast(b)); } +inline constexpr FormatSupport operator&(FormatSupport a, FormatSupport b) { return static_cast(static_cast(a) & static_cast(b)); } + +enum class PresentMode : u32 { Immediate, Mailbox, Fifo, FifoRelaxed }; +enum class AdapterType : u32 { DiscreteGpu, IntegratedGpu, Cpu, Unknown }; +enum class QueryType : u32 { Timestamp, Occlusion, PipelineStatistics }; + +// Ray tracing enums. +enum class AccelStructType : u32 { TopLevel, BottomLevel }; +enum class GeometryType : u32 { Triangles, AABBs }; + +enum class GeometryFlags : u32 { + None = 0, + Opaque = 1 << 0, + NoDuplicateAnyHitInvocation = 1 << 1, +}; +inline constexpr GeometryFlags operator|(GeometryFlags a, GeometryFlags b) { return static_cast(static_cast(a) | static_cast(b)); } + +enum class AccelStructBuildFlags : u32 { + None = 0, + AllowUpdate = 1 << 0, + AllowCompaction = 1 << 1, + PreferFastTrace = 1 << 2, + PreferFastBuild = 1 << 3, +}; +inline constexpr AccelStructBuildFlags operator|(AccelStructBuildFlags a, AccelStructBuildFlags b) { return static_cast(static_cast(a) | static_cast(b)); } + +} // namespace draco::rhi diff --git a/Engine/cpp/Runtime/Rendering/RHI/ExtDescriptors.cppm b/Engine/cpp/Runtime/Rendering/RHI/ExtDescriptors.cppm new file mode 100644 index 00000000..7f7f35fd --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/ExtDescriptors.cppm @@ -0,0 +1,95 @@ +/// Descriptor structs for mesh shader and ray tracing extensions. + +module; + +#include +#include +#include + +export module rhi:ext_descriptors; + +import core.stdtypes; +import :enums; +import :texture_format; +import :types; +import :forward; +import :descriptors; + +using namespace draco; + +export namespace draco::rhi { + +// ---- Mesh shader pipeline ---- + +/// Descriptor for creating a mesh shader pipeline. +struct MeshPipelineDesc { + PipelineLayout* layout = nullptr; + std::optional task; ///< Optional task (amplification) shader. + ProgrammableStage mesh; ///< Required mesh shader. + std::optional fragment; + std::span colorTargets; + PrimitiveState primitive; + std::optional depthStencil; + MultisampleState multisample; + PipelineCache* cache = nullptr; + std::u8string_view label; +}; + +// ---- Ray tracing ---- + +/// Descriptor for creating an acceleration structure. +struct AccelStructDesc { + AccelStructType type = AccelStructType::BottomLevel; + AccelStructBuildFlags flags = AccelStructBuildFlags::PreferFastTrace; + std::u8string_view label; +}; + +/// Triangle geometry for BLAS construction. +struct AccelStructGeometryTriangles { + Buffer* vertexBuffer = nullptr; + u64 vertexOffset = 0; + u32 vertexCount = 0; + u32 vertexStride = 0; + VertexFormat vertexFormat = VertexFormat::Float32x3; + Buffer* indexBuffer = nullptr; + u64 indexOffset = 0; + u32 indexCount = 0; + IndexFormat indexFormat = IndexFormat::UInt32; + Buffer* transformBuffer = nullptr; + u64 transformOffset = 0; + GeometryFlags flags = GeometryFlags::Opaque; +}; + +/// AABB geometry for procedural BLAS construction. +struct AccelStructGeometryAABBs { + Buffer* aabbBuffer = nullptr; + u64 offset = 0; + u32 count = 0; + u32 stride = 24; ///< sizeof(VkAabbPositionsKHR) + GeometryFlags flags = GeometryFlags::Opaque; +}; + +/// Shader group definition for a ray tracing pipeline. +struct RayTracingShaderGroup { + enum class Type : u32 { General, TrianglesHitGroup, ProceduralHitGroup }; + + Type type = Type::General; + u32 generalShaderIndex = ~0u; + u32 closestHitShaderIndex = ~0u; + u32 anyHitShaderIndex = ~0u; + u32 intersectionShaderIndex = ~0u; +}; + +/// Descriptor for creating a ray tracing pipeline. +struct RayTracingPipelineDesc { + PipelineLayout* layout = nullptr; + std::span stages; + std::span groups; + u32 maxRecursionDepth = 1; + u32 maxPayloadSize = 0; + u32 maxAttributeSize = 0; + PipelineCache* cache = nullptr; + std::u8string_view label; +}; + +} // namespace draco::rhi diff --git a/Engine/cpp/Runtime/Rendering/RHI/Extensions.cppm b/Engine/cpp/Runtime/Rendering/RHI/Extensions.cppm new file mode 100644 index 00000000..85b57a4f --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Extensions.cppm @@ -0,0 +1,76 @@ +/// Extension base classes for multiple inheritance. +/// +/// VK backend encoders inherit these alongside their core base classes: +/// class VkRenderPassEncoder : public RenderPassEncoder, public MeshShaderPassExt { ... }; +/// class VkCommandEncoder : public CommandEncoder, public RayTracingEncoderExt { ... }; +/// +/// Callers probe for support via the asMeshShaderExt() / asRayTracingExt() +/// cross-query methods (which return `this` on supporting encoders, else null). + +module; + +#include + +export module rhi:extensions; + +import core.stdtypes; +import :enums; +import :types; +import :descriptors; +import :ext_descriptors; +import :resources; + +using namespace draco; + +export namespace draco::rhi { + +/// Mesh shader render pass extension. Implemented by backend +/// RenderPassEncoder subclasses that support mesh shaders. +class MeshShaderPassExt { +public: + virtual ~MeshShaderPassExt() = default; + + /// Bind a mesh shader pipeline. + virtual void setMeshPipeline(MeshPipeline* pipeline) = 0; + /// Dispatch mesh shader work groups. + virtual void drawMeshTasks(u32 groupCountX, u32 groupCountY = 1, u32 groupCountZ = 1) = 0; + /// Dispatch mesh shader work groups via an indirect buffer. + virtual void drawMeshTasksIndirect(Buffer* buffer, u64 offset, u32 drawCount = 1, u32 stride = 0) = 0; + /// Dispatch mesh shader work groups with an indirect count buffer. + virtual void drawMeshTasksIndirectCount(Buffer* buffer, u64 offset, + Buffer* countBuffer, u64 countOffset, + u32 maxDrawCount, u32 stride) = 0; +}; + +/// Ray tracing command encoder extension. Implemented by backend +/// CommandEncoder subclasses that support ray tracing. +class RayTracingEncoderExt { +public: + virtual ~RayTracingEncoderExt() = default; + + /// Build a bottom-level acceleration structure from triangle and/or AABB geometry. + virtual void buildBottomLevelAccelStruct(AccelStruct* dst, Buffer* scratchBuffer, u64 scratchOffset, + std::span triangles, + std::span aabbs) = 0; + + /// Build a top-level acceleration structure from an instance buffer. + virtual void buildTopLevelAccelStruct(AccelStruct* dst, Buffer* scratchBuffer, u64 scratchOffset, + Buffer* instanceBuffer, u64 instanceOffset, u32 instanceCount) = 0; + + /// Bind a ray tracing pipeline. + virtual void setRayTracingPipeline(RayTracingPipeline* pipeline) = 0; + + /// Bind a resource group for the ray tracing pipeline. + virtual void setBindGroup(u32 index, BindGroup* group, std::span dynamicOffsets = {}) = 0; + + /// Upload push constants for the ray tracing pipeline. + virtual void setPushConstants(ShaderStage stages, u32 offset, u32 size, const void* data) = 0; + + /// Dispatch rays using shader binding tables. + virtual void traceRays(Buffer* raygenSBT, u64 raygenOffset, u64 raygenStride, + Buffer* missSBT, u64 missOffset, u64 missStride, + Buffer* hitSBT, u64 hitOffset, u64 hitStride, + u32 width, u32 height, u32 depth = 1) = 0; +}; + +} // namespace draco::rhi diff --git a/Engine/cpp/Runtime/Rendering/RHI/Forward.cppm b/Engine/cpp/Runtime/Rendering/RHI/Forward.cppm new file mode 100644 index 00000000..b99c76c2 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Forward.cppm @@ -0,0 +1,43 @@ +/// Forward declarations for all RHI types. + +export module rhi:forward; + +export namespace draco::rhi { + +class Backend; +class Adapter; +class Device; +class Queue; +class Surface; +class SwapChain; +class Buffer; +class Texture; +class TextureView; +class Sampler; +class ShaderModule; +class Fence; +class QuerySet; +class BindGroupLayout; +class BindGroup; +class PipelineLayout; +class PipelineCache; +class RenderPipeline; +class ComputePipeline; +class MeshPipeline; +class AccelStruct; +class RayTracingPipeline; +class CommandBuffer; +class CommandPool; +class CommandEncoder; +class RenderCommandEncoder; +class RenderPassEncoder; +class RenderBundleEncoder; +class RenderBundle; +class ComputePassEncoder; +class TransferBatch; + +// Extension base classes (for multiple inheritance). +class MeshShaderPassExt; +class RayTracingEncoderExt; + +} // namespace draco::rhi diff --git a/Engine/cpp/Runtime/Rendering/RHI/Log.cppm b/Engine/cpp/Runtime/Rendering/RHI/Log.cppm new file mode 100644 index 00000000..a2a069a0 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Log.cppm @@ -0,0 +1,41 @@ +// RHI logging shim. The ported backends use printf-style char* logging +// (logError / logWarning / logErrorf / logWarningf); this routes those to +// stdout/stderr. A thin layer so the large backend bodies port unchanged. + +module; +#include +#include + +export module rhi:log; + +export namespace draco::rhi +{ + inline void logWrite(bool error, const char* utf8) + { + std::FILE* out = error ? stderr : stdout; + std::fputs(utf8, out); + std::fputc('\n', out); + } + + inline void logError(const char* message) { logWrite(true, message); } + inline void logWarning(const char* message) { logWrite(false, message); } + inline void logInfo(const char* message) { logWrite(false, message); } + + inline void logErrorf(const char* fmt, ...) + { + char buf[1024]; + va_list ap; va_start(ap, fmt); + std::vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + logWrite(true, buf); + } + + inline void logWarningf(const char* fmt, ...) + { + char buf[1024]; + va_list ap; va_start(ap, fmt); + std::vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + logWrite(false, buf); + } +} diff --git a/Engine/cpp/Runtime/Rendering/RHI/Null/NullModule.cppm b/Engine/cpp/Runtime/Rendering/RHI/Null/NullModule.cppm new file mode 100644 index 00000000..bc8fa838 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Null/NullModule.cppm @@ -0,0 +1,334 @@ +/// Null RHI backend - stub implementations for all interfaces. +/// Useful for headless testing, CI, or when no GPU is available. + +module; + +#include +#include +#include + +export module rhi.null; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::null { + +// ---- Stub resource classes ---- + +class NullBuffer : public Buffer { +public: + void* map() override { return m_mapped; } + void unmap() override {} + void allocate(u64 size) { m_data.resize(static_cast(size)); m_mapped = m_data.data(); } +private: + std::vector m_data; + void* m_mapped = nullptr; +}; + +class NullTexture : public Texture {}; +class NullTextureView : public TextureView {}; +class NullSampler : public Sampler {}; +class NullShaderModule : public ShaderModule {}; +class NullSurface : public Surface {}; +class NullCommandBuffer : public CommandBuffer {}; + +class NullFence : public Fence { +public: + u64 completedValue() override { return m_value; } + bool wait(u64 value, u64) override { m_value = value; return true; } + void signal(u64 v) { m_value = v; } +private: + u64 m_value = 0; +}; + +class NullQuerySet : public QuerySet {}; + +class NullBindGroupLayout : public BindGroupLayout { +public: + std::span entries() const override { return {}; } +}; + +class NullBindGroup : public BindGroup { +public: + BindGroupLayout* layout() override { return nullptr; } + void updateBindless(std::span) override {} +}; + +class NullPipelineLayout : public PipelineLayout {}; + +class NullPipelineCache : public PipelineCache { +public: + u32 getDataSize() override { return 0; } + Status getData(std::span) override { return ErrorCode::Ok; } +}; + +class NullRenderPipeline : public RenderPipeline {}; +class NullComputePipeline : public ComputePipeline {}; +class NullMeshPipeline : public MeshPipeline {}; + +class NullAccelStruct : public AccelStruct { +public: + AccelStructType type() const override { return AccelStructType::BottomLevel; } + u64 deviceAddress() const override { return 0; } +}; + +class NullRayTracingPipeline : public RayTracingPipeline {}; + +// ---- Stub encoders ---- + +class NullRenderPassEncoder : public RenderPassEncoder, public MeshShaderPassExt { +public: + MeshShaderPassExt* asMeshShaderExt() noexcept override { return this; } + void setPipeline(RenderPipeline*) override {} + void setBindGroup(u32, BindGroup*, std::span) override {} + void setPushConstants(ShaderStage, u32, u32, const void*) override {} + void setVertexBuffer(u32, Buffer*, u64) override {} + void setIndexBuffer(Buffer*, IndexFormat, u64) override {} + void setViewport(f32, f32, f32, f32, f32, f32) override {} + void setScissor(i32, i32, u32, u32) override {} + void setBlendConstant(f32, f32, f32, f32) override {} + void setStencilReference(u32) override {} + void draw(u32, u32, u32, u32) override {} + void drawIndexed(u32, u32, u32, i32, u32) override {} + void drawIndirect(Buffer*, u64, u32, u32) override {} + void drawIndexedIndirect(Buffer*, u64, u32, u32) override {} + void executeBundles(std::span) override {} + void writeTimestamp(QuerySet*, u32) override {} + void beginOcclusionQuery(QuerySet*, u32) override {} + void endOcclusionQuery(QuerySet*, u32) override {} + void end() override {} + void setMeshPipeline(MeshPipeline*) override {} + void drawMeshTasks(u32, u32, u32) override {} + void drawMeshTasksIndirect(Buffer*, u64, u32, u32) override {} + void drawMeshTasksIndirectCount(Buffer*, u64, Buffer*, u64, u32, u32) override {} +}; + +class NullRenderBundle : public RenderBundle {}; + +class NullRenderBundleEncoder : public RenderBundleEncoder { +public: + void setPipeline(RenderPipeline*) override {} + void setBindGroup(u32, BindGroup*, std::span) override {} + void setPushConstants(ShaderStage, u32, u32, const void*) override {} + void setVertexBuffer(u32, Buffer*, u64) override {} + void setIndexBuffer(Buffer*, IndexFormat, u64) override {} + void draw(u32, u32, u32, u32) override {} + void drawIndexed(u32, u32, u32, i32, u32) override {} + void drawIndirect(Buffer*, u64, u32, u32) override {} + void drawIndexedIndirect(Buffer*, u64, u32, u32) override {} + RenderBundle* finish() override { return &bundle; } + NullRenderBundle bundle; +}; + +class NullComputePassEncoder : public ComputePassEncoder { +public: + void setPipeline(ComputePipeline*) override {} + void setBindGroup(u32, BindGroup*, std::span) override {} + void setPushConstants(ShaderStage, u32, u32, const void*) override {} + void dispatch(u32, u32, u32) override {} + void dispatchIndirect(Buffer*, u64) override {} + void computeBarrier() override {} + void writeTimestamp(QuerySet*, u32) override {} + void end() override {} +}; + +class NullCommandEncoder : public CommandEncoder, public RayTracingEncoderExt { +public: + RayTracingEncoderExt* asRayTracingExt() noexcept override { return this; } + NullRenderPassEncoder rpe; + NullComputePassEncoder cpe; + NullRenderBundleEncoder rbe; + NullCommandBuffer cb; + + RenderPassEncoder* beginRenderPass(const RenderPassDesc&) override { return &rpe; } + ComputePassEncoder* beginComputePass(std::u8string_view) override { return &cpe; } + RenderBundleEncoder* createRenderBundleEncoder(const RenderBundleDesc&) override { return &rbe; } + void barrier(const BarrierGroup&) override {} + void copyBufferToBuffer(Buffer*, u64, Buffer*, u64, u64) override {} + void copyBufferToTexture(Buffer*, Texture*, const BufferTextureCopyRegion&) override {} + void copyTextureToBuffer(Texture*, Buffer*, const BufferTextureCopyRegion&) override {} + void copyTextureToTexture(Texture*, Texture*, const TextureCopyRegion&) override {} + void blit(Texture*, Texture*) override {} + void generateMipmaps(Texture*) override {} + void resolveTexture(Texture*, Texture*) override {} + void resetQuerySet(QuerySet*, u32, u32) override {} + void writeTimestamp(QuerySet*, u32) override {} + void resolveQuerySet(QuerySet*, u32, u32, Buffer*, u64) override {} + void beginDebugLabel(std::u8string_view, f32, f32, f32, f32) override {} + void endDebugLabel() override {} + void insertDebugLabel(std::u8string_view, f32, f32, f32, f32) override {} + CommandBuffer* finish() override { return &cb; } + + // RayTracingEncoderExt + void buildBottomLevelAccelStruct(AccelStruct*, Buffer*, u64, std::span, std::span) override {} + void buildTopLevelAccelStruct(AccelStruct*, Buffer*, u64, Buffer*, u64, u32) override {} + void setRayTracingPipeline(RayTracingPipeline*) override {} + void setBindGroup(u32, BindGroup*, std::span) override {} + void setPushConstants(ShaderStage, u32, u32, const void*) override {} + void traceRays(Buffer*, u64, u64, Buffer*, u64, u64, Buffer*, u64, u64, u32, u32, u32) override {} +}; + +class NullCommandPool : public CommandPool { +public: + NullCommandEncoder enc; + Status createEncoder(CommandEncoder*& out) override { out = &enc; return ErrorCode::Ok; } + void destroyEncoder(CommandEncoder*&) override {} + void reset() override {} +}; + +class NullTransferBatch : public TransferBatch { +public: + void writeBuffer(Buffer*, u64, std::span) override {} + void writeTexture(Texture*, std::span, const TextureDataLayout&, Extent3D, u32, u32) override {} + Status submit() override { return ErrorCode::Ok; } + Status submitAsync(Fence*, u64) override { return ErrorCode::Ok; } + void reset() override {} + void destroy() override {} +}; + +class NullSwapChain : public SwapChain { +public: + NullTexture tex; + NullTextureView view; + TextureFormat m_format = TextureFormat::BGRA8UnormSrgb; + u32 m_width = 0; + u32 m_height = 0; + u32 m_count = 2; + u32 m_imgIdx = 0; + + TextureFormat format() const override { return m_format; } + u32 width() const override { return m_width; } + u32 height() const override { return m_height; } + u32 bufferCount() const override { return m_count; } + u32 currentImageIndex() const override { return m_imgIdx; } + Status acquireNextImage() override { m_imgIdx = (m_imgIdx + 1) % m_count; return ErrorCode::Ok; } + Texture* currentTexture() override { return &tex; } + TextureView* currentTextureView() override { return &view; } + Status present(Queue*) override { return ErrorCode::Ok; } + Status resize(u32 w, u32 h) override { m_width = w; m_height = h; return ErrorCode::Ok; } +}; + +class NullQueue : public Queue { +public: + NullTransferBatch tb; + void submit(std::span) override {} + void submit(std::span, Fence* f, u64 v) override { if (auto* nf = static_cast(f)) nf->signal(v); } + void submit(std::span, std::span, std::span, Fence* f, u64 v) override { if (auto* nf = static_cast(f)) nf->signal(v); } + void waitIdle() override {} + Status createTransferBatch(TransferBatch*& out) override { out = &tb; return ErrorCode::Ok; } + void destroyTransferBatch(TransferBatch*&) override {} + f32 timestampPeriod() const override { return 1.0f; } +}; + +// ---- Null Device ---- + +class NullDevice : public Device { +public: + NullQueue gfxQueue, compQueue, xferQueue; + + NullDevice() { + type = DeviceType::Null; + gfxQueue.queueType = QueueType::Graphics; + compQueue.queueType = QueueType::Compute; + xferQueue.queueType = QueueType::Transfer; + } + + Queue* getQueue(QueueType t, u32) override { + switch (t) { + case QueueType::Graphics: return &gfxQueue; + case QueueType::Compute: return &compQueue; + case QueueType::Transfer: return &xferQueue; + } return nullptr; + } + u32 getQueueCount(QueueType) override { return 1; } + FormatSupport getFormatSupport(TextureFormat) override { return FormatSupport::Texture | FormatSupport::ColorAttachment | FormatSupport::DepthStencil; } + + Status createBuffer(const BufferDesc& d, Buffer*& out) override { + auto* b = new NullBuffer(); b->desc = d; b->allocate(d.size); out = b; return ErrorCode::Ok; + } + Status createTexture(const TextureDesc& d, Texture*& out) override { auto* t = new NullTexture(); t->desc = d; out = t; return ErrorCode::Ok; } + Status createTextureView(Texture* tex, const TextureViewDesc& d, TextureView*& out) override { auto* v = new NullTextureView(); v->desc = d; v->texture = tex; out = v; return ErrorCode::Ok; } + Status createSampler(const SamplerDesc& d, Sampler*& out) override { auto* s = new NullSampler(); s->desc = d; out = s; return ErrorCode::Ok; } + Status createShaderModule(const ShaderModuleDesc&, ShaderModule*& out) override { out = new NullShaderModule(); return ErrorCode::Ok; } + Status createBindGroupLayout(const BindGroupLayoutDesc&, BindGroupLayout*& out) override { out = new NullBindGroupLayout(); return ErrorCode::Ok; } + Status createBindGroup(const BindGroupDesc&, BindGroup*& out) override { out = new NullBindGroup(); return ErrorCode::Ok; } + Status createPipelineLayout(const PipelineLayoutDesc&, PipelineLayout*& out) override { out = new NullPipelineLayout(); return ErrorCode::Ok; } + Status createPipelineCache(const PipelineCacheDesc&, PipelineCache*& out) override { out = new NullPipelineCache(); return ErrorCode::Ok; } + Status createRenderPipeline(const RenderPipelineDesc&, RenderPipeline*& out) override { out = new NullRenderPipeline(); return ErrorCode::Ok; } + Status createComputePipeline(const ComputePipelineDesc&, ComputePipeline*& out) override { out = new NullComputePipeline(); return ErrorCode::Ok; } + Status createCommandPool(QueueType, CommandPool*& out) override { out = new NullCommandPool(); return ErrorCode::Ok; } + Status createFence(u64, Fence*& out) override { out = new NullFence(); return ErrorCode::Ok; } + Status createQuerySet(const QuerySetDesc& d, QuerySet*& out) override { auto* q = new NullQuerySet(); q->type = d.type; q->count = d.count; out = q; return ErrorCode::Ok; } + Status createSwapChain(Surface*, const SwapChainDesc& d, SwapChain*& out) override { + auto* sc = new NullSwapChain(); sc->m_format = d.format; sc->m_width = d.width; sc->m_height = d.height; sc->m_count = d.bufferCount; out = sc; return ErrorCode::Ok; + } + + void destroyBuffer(Buffer*& x) override { delete x; x = nullptr; } + void destroyTexture(Texture*& x) override { delete x; x = nullptr; } + void destroyTextureView(TextureView*& x) override { delete x; x = nullptr; } + void destroySampler(Sampler*& x) override { delete x; x = nullptr; } + void destroyShaderModule(ShaderModule*& x)override { delete x; x = nullptr; } + void destroyBindGroupLayout(BindGroupLayout*& x) override { delete x; x = nullptr; } + void destroyBindGroup(BindGroup*& x) override { delete x; x = nullptr; } + void destroyPipelineLayout(PipelineLayout*& x) override { delete x; x = nullptr; } + void destroyPipelineCache(PipelineCache*& x) override { delete x; x = nullptr; } + void destroyRenderPipeline(RenderPipeline*& x) override { delete x; x = nullptr; } + void destroyComputePipeline(ComputePipeline*& x) override { delete x; x = nullptr; } + void destroyCommandPool(CommandPool*& x) override { delete x; x = nullptr; } + void destroyFence(Fence*& x) override { delete x; x = nullptr; } + void destroyQuerySet(QuerySet*& x) override { delete x; x = nullptr; } + void destroySwapChain(SwapChain*& x) override { delete x; x = nullptr; } + void destroySurface(Surface*& x) override { delete x; x = nullptr; } + + void waitIdle() override {} + void destroy() override { delete this; } +}; + +// ---- Null Adapter ---- + +class NullAdapter : public Adapter { +public: + void getInfo(AdapterInfo& out) override { + out.name = u8"Null Device"; + out.vendorId = 0; + out.deviceId = 0; + out.type = AdapterType::Cpu; + } + Status createDevice(const DeviceDesc&, Device*& out) override { + out = new NullDevice(); + return ErrorCode::Ok; + } +}; + +// ---- Null Backend ---- + +class NullBackend : public Backend { +public: + NullAdapter adapter; + Adapter* adapterPtr = &adapter; + + std::span enumerateAdapters() override { + return std::span(&adapterPtr, 1); + } + + Status createSurface(void*, void*, Surface*& out) override { + out = new NullSurface(); + return ErrorCode::Ok; + } + + void destroy() override { delete this; } +}; + +/// Creates a null backend for headless / GPU-less testing. +Status createNullBackend(Backend*& out) { + auto* b = new NullBackend(); + b->isInitialized = true; + out = b; + return ErrorCode::Ok; +} + +} // namespace draco::rhi::null diff --git a/Engine/cpp/Runtime/Rendering/RHI/Null/NullRhi.test.cpp b/Engine/cpp/Runtime/Rendering/RHI/Null/NullRhi.test.cpp new file mode 100644 index 00000000..85cd0f28 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Null/NullRhi.test.cpp @@ -0,0 +1,55 @@ +#include + +import core; +import rhi; +import rhi.null; + +using namespace draco; +using namespace draco::rhi; + +TEST_CASE("rhi.null: backend enumerates an adapter and creates a device") +{ + Backend* backend = nullptr; + REQUIRE(null::createNullBackend(backend).isOk()); + REQUIRE(backend != nullptr); + CHECK(backend->isInitialized); + + auto adapters = backend->enumerateAdapters(); + REQUIRE(adapters.size() == 1u); + + const AdapterInfo info = adapters[0]->info(); + CHECK(info.name == u8"Null Device"); + CHECK(info.type == AdapterType::Cpu); + + Device* device = nullptr; + REQUIRE(adapters[0]->createDevice(DeviceDesc{}, device).isOk()); + REQUIRE(device != nullptr); + + backend->destroy(); +} + +TEST_CASE("rhi.null: device creates resources and a mappable buffer") +{ + Backend* backend = nullptr; + REQUIRE(null::createNullBackend(backend).isOk()); + Device* device = nullptr; + REQUIRE(backend->enumerateAdapters()[0]->createDevice(DeviceDesc{}, device).isOk()); + + BufferDesc bufferDesc{}; + bufferDesc.size = 256; + Buffer* buffer = nullptr; + REQUIRE(device->createBuffer(bufferDesc, buffer).isOk()); + REQUIRE(buffer != nullptr); + CHECK(buffer->map() != nullptr); // null backend backs it with host memory + buffer->unmap(); + + Texture* texture = nullptr; + CHECK(device->createTexture(TextureDesc{}, texture).isOk()); + CHECK(texture != nullptr); + + // Unsupported extensions degrade, they don't crash. + MeshPipeline* mesh = nullptr; + CHECK(device->createMeshPipeline(MeshPipelineDesc{}, mesh).code() == ErrorCode::NotSupported); + + backend->destroy(); +} diff --git a/Engine/cpp/Runtime/Rendering/RHI/Pipelines.cpp b/Engine/cpp/Runtime/Rendering/RHI/Pipelines.cpp deleted file mode 100644 index ef711c23..00000000 --- a/Engine/cpp/Runtime/Rendering/RHI/Pipelines.cpp +++ /dev/null @@ -1,70 +0,0 @@ -module; - -#include -#include -#include "macros.h" - -module rendering.rhi; - -import core.stdtypes; -import core.math.constants; - -namespace draco::rendering::rhi -{ - PipelineHandle createPipeline(const PipelineDesc& desc) - { - RHI_ASSERT(desc.vs != InvalidShader, "Pipeline missing vertex shader"); - RHI_ASSERT(desc.fs != InvalidShader, "Pipeline missing fragment shader"); - - bgfx::ProgramHandle prog = bgfx::createProgram(resolve(desc.vs), resolve(desc.fs), true); - - u64 state = mapState(desc.state, desc.blend, desc.depth, desc.cull, desc.depthWrite); - - return g_pipelines.create({ prog, state }); - } - - LayoutHandle createVertexLayout(const VertexLayoutDesc& desc) - { - bgfx::VertexLayout layout; - layout.begin(); - - for (const auto& e : desc.elements) - { - layout.add(map_attrib(e.attrib), e.count, map_attrib_type(e.type), e.normalized); - } - - layout.end(); - - return g_layouts.create({ layout }); - } - - ShaderHandle createShader(const void* data, u32 size) - { - RHI_ASSERT(data && size > 0, "Invalid shader data"); - - bgfx::ShaderHandle sh = bgfx::createShader(bgfx::copy(data, size)); - - return g_shaders.create(sh); - } - - bgfx::ShaderHandle resolve(ShaderHandle h) - { - auto* sh = g_shaders.get(h); - return sh ? *sh : bgfx::ShaderHandle{ bgfx::kInvalidHandle }; - } - - // For debugging/tooling - bgfx::ShaderHandle* getShaderNative(ShaderHandle h) - { - return getChecked(g_shaders, h, "Shader"); - } - - void destroyShader(ShaderHandle h) - { - if (auto* sh = getChecked(g_shaders, h, "Shader")) - { - destroyLater(*sh); - g_shaders.destroy(h); - } - } -} diff --git a/Engine/cpp/Runtime/Rendering/RHI/Queue.cppm b/Engine/cpp/Runtime/Rendering/RHI/Queue.cppm new file mode 100644 index 00000000..7993da1e --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Queue.cppm @@ -0,0 +1,52 @@ +/// Abstract GPU command queue. Handles command submission, fence +/// synchronization, and transfer batch creation. + +module; + +#include + +export module rhi:queue; + +import core.stdtypes; +import core.status; +import :enums; +import :resources; + +using namespace draco; + +export namespace draco::rhi { + +class TransferBatch; + +class Queue { +public: + virtual ~Queue() = default; + + /// The type of work this queue supports. + QueueType queueType = QueueType::Graphics; + + /// Submit command buffers for execution. + virtual void submit(std::span commandBuffers) = 0; + + /// Submit with fence signaling. + virtual void submit(std::span commandBuffers, + Fence* signalFence, u64 signalValue) = 0; + + /// Submit with full synchronization: wait on fences, then signal. + virtual void submit(std::span commandBuffers, + std::span waitFences, std::span waitValues, + Fence* signalFence, u64 signalValue) = 0; + + /// Block until all submitted work on this queue completes. + virtual void waitIdle() = 0; + + /// Create a transfer batch for batching CPU->GPU uploads. + virtual Status createTransferBatch(TransferBatch*& out) = 0; + /// Destroy a transfer batch. + virtual void destroyTransferBatch(TransferBatch*& batch) = 0; + + /// Timestamp period in nanoseconds per tick. + [[nodiscard]] virtual f32 timestampPeriod() const = 0; +}; + +} // namespace draco::rhi diff --git a/Engine/cpp/Runtime/Rendering/RHI/RHI.cppm b/Engine/cpp/Runtime/Rendering/RHI/RHI.cppm deleted file mode 100644 index 491e8c95..00000000 --- a/Engine/cpp/Runtime/Rendering/RHI/RHI.cppm +++ /dev/null @@ -1,340 +0,0 @@ -module; - -#include -#include -#include -#include -#include - -#include - -#include "macros.h" - -export module rendering.rhi; - -import core.stdtypes; -import core.math.constants; -import core.memory; -import rendering.rhi.vertex; - -export namespace draco::rendering::rhi -{ - // Which native windowing system the handles passed to init() belong to. RHI - // only needs this to tell bgfx when a surface is Wayland (a wl_surface* must - // be flagged as such); everything else uses bgfx's default handling. The - // caller maps its own window abstraction onto this. - enum class NativeWindowType - { - Default, - Win32, - X11, - Wayland, - Cocoa, - }; - - struct BufferTag {}; - struct PipelineTag {}; - struct ShaderTag {}; - struct UniformTag {}; - struct TextureTag {}; - struct FramebufferTag {}; - struct LayoutTag {}; - - using BufferHandle = core::memory::Handle; - using PipelineHandle = core::memory::Handle; - using ShaderHandle = core::memory::Handle; - using UniformHandle = core::memory::Handle; - using TextureHandle = core::memory::Handle; - using FramebufferHandle = core::memory::Handle; - using LayoutHandle = core::memory::Handle; - - using ViewID = u16; // bgfx native - using SamplerHandle = u64; // bgfx sampler flags - - inline constexpr BufferHandle InvalidBuffer{}; - inline constexpr PipelineHandle InvalidPipeline{}; - inline constexpr ShaderHandle InvalidShader{}; - inline constexpr UniformHandle InvalidUniform{}; - inline constexpr TextureHandle InvalidTexture{}; - inline constexpr FramebufferHandle InvalidFramebuffer{}; - inline constexpr LayoutHandle InvalidLayout{}; - inline constexpr SamplerHandle InvalidSampler = 0; - inline constexpr ViewID InvalidView = math::UINT16_MAX_VAL; - - enum class PipelineState : u64 { - Default = 0, - WriteRGB = 1ULL << 0, - WriteAlpha = 1ULL << 1, - MSAA = 1ULL << 2, - PrimitiveTriStrip = 1ULL << 3, - }; - - enum class ClearFlags : u32 { - Color = BGFX_CLEAR_COLOR, - Depth = BGFX_CLEAR_DEPTH, - Stencil = BGFX_CLEAR_STENCIL - }; - - struct ViewDesc { - FramebufferHandle fb = InvalidFramebuffer; - u16 x = 0, y = 0, w = 0, h = 0; - u32 clearFlags = 0; - u32 clearColor = 0; - }; - - enum class UniformType - { - Sampler, - Vec4, - Mat3, - Mat4, - }; - - struct UniformBind { - UniformHandle handle; - const void* data; - u16 num; - }; - - enum class TextureFormat { - RGBA8, - BGRA8, - D16, - D24, - D24S8, - D32 - }; - - enum class BlendMode { - None, - Alpha, - Additive, - Multiply - }; - - enum class DepthTest { - None, - Less, - Equal, - Always - }; - - enum class CullMode { - None, - CW, - CCW - }; - - struct Buffer - { - bgfx::VertexBufferHandle vbh = BGFX_INVALID_HANDLE; - bgfx::DynamicVertexBufferHandle dvbh = BGFX_INVALID_HANDLE; - bgfx::IndexBufferHandle ibh = BGFX_INVALID_HANDLE; - bgfx::DynamicIndexBufferHandle dibh; - bool isDynamic = false; - bool isIndex = false; - }; - - struct FramebufferResource { - bgfx::FrameBufferHandle fbh; - TextureHandle texture; - }; - - struct VertexLayoutResource - { - bgfx::VertexLayout layout; - }; - - struct ScissorRect { - u16 x, y, w, h; - bool enabled = true; - }; - - struct DeletionReq { - u64 frame; - std::function cleanup; - }; - - struct PipelineDesc - { - ShaderHandle vs; - ShaderHandle fs; - PipelineState state = PipelineState::Default; - - BlendMode blend = BlendMode::None; - DepthTest depth = DepthTest::Less; - CullMode cull = CullMode::CCW; - - bool depthWrite = true; - }; - - struct RenderPacket - { - u64 sortKey = 0; - - BufferHandle vertexBuffer = InvalidBuffer; - BufferHandle indexBuffer = InvalidBuffer; - PipelineHandle pipeline = InvalidPipeline; - - u32 vertexCount = math::UINT32_MAX_VAL; - u32 indexCount = math::UINT32_MAX_VAL; - - UniformHandle samplerUniform = InvalidUniform; - SamplerHandle samplerFlags = InvalidSampler; - TextureHandle textureHandle = InvalidTexture; - - f32 color[4] = {1,1,1,1}; - - std::vector uniforms; - u8 textureUnit = 0; - - f32 model[16] = { - 1,0,0,0, - 0,1,0,0, - 0,0,1,0, - 0,0,0,1 - }; - - u32 drawTags = 0; - }; - - struct Pipeline { - bgfx::ProgramHandle program; - u64 state; - }; - - bool init(void* display_type, void* window_handle, NativeWindowType window_type, u16 width, u16 height); - void resize(u16 width, u16 height); - void shutdown(); - - PipelineHandle createPipeline(const PipelineDesc&); - - BufferHandle createVertexBuffer(const void* data, u32 size, LayoutHandle layout_h); - BufferHandle createIndexBuffer(const void* data, u32 size); - void destroyBuffer(BufferHandle handle); - - UniformHandle createUniform(const char* name, UniformType type, u16 num = 1); - void destroyUniform(UniformHandle handle); - void setUniform(UniformHandle handle, const void* value, u16 num = 1); - - TextureHandle createTexture(const void* data, u32 width, u32 height, u32 flags = 0); - void destroyTexture(TextureHandle handle); - - FramebufferHandle createFramebuffer(u32 width, u32 height, TextureFormat format); - void destroyFramebuffer(FramebufferHandle handle); - TextureHandle getFramebufferTexture(FramebufferHandle handle); - - BufferHandle createDynamicVertexBuffer(u32 size, LayoutHandle layout); - void updateDynamicVertexBuffer(BufferHandle handle, u32 start_vertex, const void* data, u32 size); - - BufferHandle createDynamicIndexBuffer(u32 size, u16 flags = BGFX_BUFFER_NONE); - void updateDynamicIndexBuffer(BufferHandle handle, u32 start_index, const void* data, u32 size); - - LayoutHandle createVertexLayout(const VertexLayoutDesc& desc); - - SamplerHandle createSampler(bool linear, bool clamp); - - // Expects bgfx compiled shader binary (shaderc output) - ShaderHandle createShader(const void* data, u32 size); - bgfx::ShaderHandle resolve(ShaderHandle h); - // For debugging/tooling - bgfx::ShaderHandle* getShaderNative(ShaderHandle h); - void destroyShader(ShaderHandle h); - - void perspective(f32* out, f32 fov, f32 aspect, f32 nearp, f32 farp); - void lookAt(f32* out, const f32* eye, const f32* at, const f32* up); - - // Note: Internal use only, use apply_view() instead - void setViewRect(ViewID view, u16 x, u16 y, u16 w, u16 h); - void setViewFramebuffer(ViewID view, FramebufferHandle handle); - - void setViewProjection(ViewID view, const f32* view_mtx, const f32* proj_mtx); - void setScissor(const ScissorRect& r); - void setStencil(u32 f_stencil, u32 b_stencil); - - void applyView(ViewID view, const ViewDesc& desc); - - void identityMatrix(f32* _mtx); - - u64 mapState(PipelineState s, BlendMode, DepthTest, CullMode, bool depth_write); - bgfx::UniformType::Enum map_uniform_type(UniformType t); - bgfx::Attrib::Enum map_attrib(Attrib a); - bgfx::AttribType::Enum map_attrib_type(AttribType t); - - void submit(const RenderPacket&, ViewID); - - void beginFrame(); - void endFrame(); - - template - void destroyResource(T handle); - - void processDeletions(); - - inline u64 makeSortKey(u8 layer, u8 pass, u16 pipeline, u16 texture, u16 depth = 0) - { - return (u64(layer) << 56) | (u64(pass) << 48) | (u64(pipeline) << 32) | (u64(texture) << 16) | u64(depth); - } - - constexpr PipelineState operator|(PipelineState a, PipelineState b) { - return static_cast(static_cast(a) | static_cast(b)); - } - - constexpr PipelineState operator&(PipelineState a, PipelineState b) { - return static_cast(static_cast(a) & static_cast(b)); - } -} - -// These are the things that we don't export but are visible to all implementation files -namespace draco::rendering::rhi -{ - using namespace draco::core::memory; - - extern HandleRegistry g_buffers; - extern HandleRegistry g_pipelines; - extern HandleRegistry g_uniforms; - extern HandleRegistry g_textures; - extern HandleRegistry g_framebuffers; - extern HandleRegistry g_shaders; - extern HandleRegistry g_layouts; - - // Deferred destruction queue (GPU-safe deletion) - extern std::vector g_deletion_queue; - - extern u16 g_width; - extern u16 g_height; - - // Ensures a handle is valid before use - // TODO: Replace with something better - template - auto* getChecked(Registry& reg, HandleT h, const char* name) - { - if (!reg.valid(h)) - { - RHI_WARN(false, "{} handle invalid or stale!", name); - return (decltype(reg.get(h)))nullptr; - } - - return reg.get(h); - } -} - -// Re-export the namespace since the things it uses aren't declared before -// This is a bit hacky but it works -// The problem is that if we move the unexported namespace above the other exported namespace, stuff isn't declared yet & it gives errors -// The same goes for the exported namespace -// In a nutshell, they both rely on each other which is why we use this hack (AR-DEV-1) -export namespace draco::rendering::rhi -{ - void queueDestruction(std::function cb); - - // Explicit overloads for each bgfx resource type - void destroyLater(bgfx::ShaderHandle handle); - void destroyLater(bgfx::UniformHandle handle); - void destroyLater(bgfx::VertexBufferHandle handle); - void destroyLater(bgfx::IndexBufferHandle handle); - void destroyLater(bgfx::DynamicVertexBufferHandle handle); - void destroyLater(bgfx::DynamicIndexBufferHandle handle); - void destroyLater(bgfx::TextureHandle handle); - void destroyLater(bgfx::FrameBufferHandle handle); -} diff --git a/Engine/cpp/Runtime/Rendering/RHI/Resources.cppm b/Engine/cpp/Runtime/Rendering/RHI/Resources.cppm new file mode 100644 index 00000000..4e143822 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Resources.cppm @@ -0,0 +1,174 @@ +/// Abstract resource classes. Each represents a GPU-allocated object +/// owned and destroyed by the Device. + +module; + +#include + +export module rhi:resources; + +import core.stdtypes; +import core.status; +import :enums; +import :texture_format; +import :types; +import :descriptors; +import :ext_descriptors; + +using namespace draco; + +export namespace draco::rhi { + +/// GPU buffer (vertex, index, uniform, storage, etc.). +class Buffer { +public: + virtual ~Buffer() = default; + + BufferDesc desc{}; + + [[nodiscard]] u64 getSize() const { return desc.size; } + [[nodiscard]] BufferUsage usage() const { return desc.usage; } + + /// Map the buffer for CPU access. Returns nullptr if not mappable. + [[nodiscard]] virtual void* map() = 0; + virtual void unmap() = 0; +}; + +/// GPU texture (1D/2D/3D, with mip levels and array layers). +class Texture { +public: + virtual ~Texture() = default; + + TextureDesc desc{}; + ResourceState initialState = ResourceState::Undefined; +}; + +/// A view into a subset of a texture's mip levels and array layers. +class TextureView { +public: + virtual ~TextureView() = default; + + TextureViewDesc desc{}; + Texture* texture = nullptr; +}; + +/// Texture sampler (filtering, addressing, comparison). +class Sampler { +public: + virtual ~Sampler() = default; + SamplerDesc desc{}; +}; + +/// Compiled shader module (SPIR-V or DXIL bytecode). +class ShaderModule { +public: + virtual ~ShaderModule() = default; +}; + +/// Presentation surface created from a native window handle. +class Surface { +public: + virtual ~Surface() = default; +}; + +/// Immutable recorded command buffer, ready for queue submission. +class CommandBuffer { +public: + virtual ~CommandBuffer() = default; +}; + +/// Timeline fence for CPU/GPU synchronization. +class Fence { +public: + virtual ~Fence() = default; + + /// Returns the most recently completed (signaled) value. + [[nodiscard]] virtual u64 completedValue() = 0; + + /// Blocks the CPU until the fence reaches `value`, or until timeout. + /// Returns true on success, false on timeout. + virtual bool wait(u64 value, u64 timeoutNs = ~0ull) = 0; +}; + +/// GPU query set (timestamp, occlusion, pipeline statistics). +class QuerySet { +public: + virtual ~QuerySet() = default; + + QueryType type = QueryType::Timestamp; + u32 count = 0; +}; + +/// Defines the layout of resource bindings for a bind group. +class BindGroupLayout { +public: + virtual ~BindGroupLayout() = default; + + [[nodiscard]] virtual std::span entries() const = 0; +}; + +/// A set of resource bindings that can be bound to a pipeline. +class BindGroup { +public: + virtual ~BindGroup() = default; + + [[nodiscard]] virtual BindGroupLayout* layout() = 0; + + /// Update individual entries in a bindless bind group. + virtual void updateBindless(std::span entries) = 0; +}; + +/// Describes the resource binding layout for a pipeline (bind group +/// layouts + push constant ranges). +class PipelineLayout { +public: + virtual ~PipelineLayout() = default; +}; + +/// Caches compiled pipeline state for faster subsequent creation. +class PipelineCache { +public: + virtual ~PipelineCache() = default; + + [[nodiscard]] virtual u32 getDataSize() = 0; + virtual Status getData(std::span outData) = 0; +}; + +/// Compiled graphics (rasterization) pipeline. +class RenderPipeline { +public: + virtual ~RenderPipeline() = default; + PipelineLayout* layout = nullptr; +}; + +/// Compiled compute pipeline. +class ComputePipeline { +public: + virtual ~ComputePipeline() = default; + PipelineLayout* layout = nullptr; +}; + +/// Compiled mesh shader pipeline. +class MeshPipeline { +public: + virtual ~MeshPipeline() = default; + PipelineLayout* layout = nullptr; +}; + +/// Bottom-level or top-level acceleration structure for ray tracing. +class AccelStruct { +public: + virtual ~AccelStruct() = default; + + [[nodiscard]] virtual AccelStructType type() const = 0; + [[nodiscard]] virtual u64 deviceAddress() const = 0; +}; + +/// Compiled ray tracing pipeline. +class RayTracingPipeline { +public: + virtual ~RayTracingPipeline() = default; + PipelineLayout* layout = nullptr; +}; + +} // namespace draco::rhi diff --git a/Engine/cpp/Runtime/Rendering/RHI/RhiModule.cppm b/Engine/cpp/Runtime/Rendering/RHI/RhiModule.cppm new file mode 100644 index 00000000..b3a39714 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/RhiModule.cppm @@ -0,0 +1,15 @@ +export module rhi; + +export import :log; +export import :enums; +export import :texture_format; +export import :types; +export import :forward; +export import :descriptors; +export import :ext_descriptors; +export import :resources; +export import :commands; +export import :extensions; +export import :queue; +export import :swapchain; +export import :device; diff --git a/Engine/cpp/Runtime/Rendering/RHI/SwapChain.cppm b/Engine/cpp/Runtime/Rendering/RHI/SwapChain.cppm new file mode 100644 index 00000000..c6205c94 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/SwapChain.cppm @@ -0,0 +1,46 @@ +/// Abstract swap chain for double/triple-buffered presentation. + +export module rhi:swapchain; + +import core.stdtypes; +import core.status; +import :enums; +import :texture_format; +import :resources; +import :queue; + +using namespace draco; + +export namespace draco::rhi { + +class SwapChain { +public: + virtual ~SwapChain() = default; + + /// Format of the swap chain back buffers. + [[nodiscard]] virtual TextureFormat format() const = 0; + /// Width in pixels. + [[nodiscard]] virtual u32 width() const = 0; + /// Height in pixels. + [[nodiscard]] virtual u32 height() const = 0; + /// Number of back buffers. + [[nodiscard]] virtual u32 bufferCount() const = 0; + /// Index of the currently acquired back buffer. + [[nodiscard]] virtual u32 currentImageIndex() const = 0; + + /// Acquire the next back buffer for rendering. Must be called + /// before accessing currentTexture / currentTextureView. + virtual Status acquireNextImage() = 0; + + /// The texture of the currently acquired back buffer. + [[nodiscard]] virtual Texture* currentTexture() = 0; + /// A view of the currently acquired back buffer. + [[nodiscard]] virtual TextureView* currentTextureView() = 0; + + /// Present the current back buffer to the screen. + virtual Status present(Queue* queue) = 0; + /// Resize the swap chain (e.g. after a window resize). + virtual Status resize(u32 width, u32 height) = 0; +}; + +} // namespace draco::rhi diff --git a/Engine/cpp/Runtime/Rendering/RHI/Texture.cpp b/Engine/cpp/Runtime/Rendering/RHI/Texture.cpp deleted file mode 100644 index 4a9193ce..00000000 --- a/Engine/cpp/Runtime/Rendering/RHI/Texture.cpp +++ /dev/null @@ -1,126 +0,0 @@ -module; - -#include -#include "macros.h" - -module rendering.rhi; - -import core.stdtypes; -import core.math.constants; - -namespace draco::rendering::rhi -{ - UniformHandle createUniform(const char* name, UniformType type, u16 num) - { - RHI_ASSERT(name != nullptr, "Uniform name is null"); - - auto u = bgfx::createUniform(name, map_uniform_type(type), num); - return g_uniforms.create(u); - } - - void setUniform(UniformHandle h, const void* data, u16 num) - { - auto* u = getChecked(g_uniforms, h, "Uniform"); - if (!u) return; - - RHI_ASSERT(data != nullptr, "Uniform data is null"); - - bgfx::setUniform(*u, data, num); - } - - void destroyUniform(UniformHandle h) - { - auto* u = getChecked(g_uniforms, h, "Uniform"); - if (!u) return; - - destroyLater(*u); - g_uniforms.destroy(h); - } - - TextureHandle createTexture(const void* data, u32 w, u32 h, u32 flags) - { - RHI_ASSERT(data != nullptr, "Texture data is null"); - RHI_ASSERT(w > 0 && h > 0, "Invalid texture dimensions"); - - auto tex = bgfx::createTexture2D( - w, h, false, 1, - bgfx::TextureFormat::RGBA8, - flags == 0 ? (BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP) : flags, - bgfx::copy(data, w * h * 4) - ); - - return g_textures.create(tex); - } - - void destroyTexture(TextureHandle h) - { - auto* tex = getChecked(g_textures, h, "Texture"); - if (!tex) return; - - destroyLater(*tex); - g_textures.destroy(h); - } - - FramebufferHandle createFramebuffer(u32 width, u32 height, TextureFormat format) - { - // We set render target flags so it can be attached to a framebuffer object - u64 flags = BGFX_TEXTURE_RT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP; - - bgfx::TextureFormat::Enum bgfx_format = bgfx::TextureFormat::RGBA8; - - bgfx::TextureHandle th = bgfx::createTexture2D(static_cast(width), static_cast(height), false, 1, bgfx_format, flags); - - RHI_ASSERT(bgfx::isValid(th), "Failed to allocate backing texture for Framebuffer"); - - TextureHandle color_tex_h = g_textures.create(th); - - bgfx::FrameBufferHandle fbh = bgfx::createFrameBuffer(1, &th, false); - - if (!bgfx::isValid(fbh)) - { - RHI_WARN(false, "Failed to construct native bgfx Framebuffer target!"); - // Roll back the allocated texture if the framebuffer generation bricks - destroyTexture(color_tex_h); - return InvalidFramebuffer; - } - - FramebufferResource res{}; - res.fbh = fbh; - res.texture = color_tex_h; - - return g_framebuffers.create(res); - } - - void destroyFramebuffer(FramebufferHandle handle) - { - if (auto* fb = getChecked(g_framebuffers, handle, "Framebuffer")) - { - // Safely queue the native hardware framebuffer destruction 2 frames out - destroyLater(fb->fbh); - - // Clean up the associated internal texture resource using existing pipelines - if (fb->texture != InvalidTexture) - { - if (auto* th = g_textures.get(fb->texture)) - { - destroyLater(*th); - } - g_textures.destroy(fb->texture); - } - - // Evict our registry tracking slot - g_framebuffers.destroy(handle); - } - } - - TextureHandle getFramebufferTexture(FramebufferHandle handle) - { - auto* fb = getChecked(g_framebuffers, handle, "Framebuffer"); - if (!fb) - { - return InvalidTexture; - } - - return fb->texture; - } -} diff --git a/Engine/cpp/Runtime/Rendering/RHI/TextureFormat.cppm b/Engine/cpp/Runtime/Rendering/RHI/TextureFormat.cppm new file mode 100644 index 00000000..aa7c78f5 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/TextureFormat.cppm @@ -0,0 +1,157 @@ +/// Texture format enum and query helpers. + +export module rhi:texture_format; + +import core.stdtypes; + +using namespace draco; + +export namespace draco::rhi { + +/// Pixel formats for textures and render targets. Naming follows +/// WebGPU / Vulkan conventions: component layout + bit-depth + type. +enum class TextureFormat : u32 { + Undefined = 0, + + // 8-bit per channel. + R8Unorm, R8Snorm, R8Uint, R8Sint, + + // 16-bit per channel. + R16Uint, R16Sint, R16Float, + RG8Unorm, RG8Snorm, RG8Uint, RG8Sint, + + // 32-bit per channel. + R32Uint, R32Sint, R32Float, + RG16Uint, RG16Sint, RG16Float, + RGBA8Unorm, RGBA8UnormSrgb, RGBA8Snorm, RGBA8Uint, RGBA8Sint, + BGRA8Unorm, BGRA8UnormSrgb, + RGB10A2Unorm, RGB10A2Uint, + RG11B10Float, RGB9E5Float, + + // 64-bit per channel. + RG32Uint, RG32Sint, RG32Float, + RGBA16Uint, RGBA16Sint, RGBA16Float, RGBA16Unorm, RGBA16Snorm, + + // 128-bit per channel. + RGBA32Uint, RGBA32Sint, RGBA32Float, + + // Depth / stencil. + Depth16Unorm, Depth24Plus, Depth24PlusStencil8, + Depth32Float, Depth32FloatStencil8, Stencil8, + + // BC compressed. + BC1RGBAUnorm, BC1RGBAUnormSrgb, + BC2RGBAUnorm, BC2RGBAUnormSrgb, + BC3RGBAUnorm, BC3RGBAUnormSrgb, + BC4RUnorm, BC4RSnorm, + BC5RGUnorm, BC5RGSnorm, + BC6HRGBUfloat, BC6HRGBFloat, + BC7RGBAUnorm, BC7RGBAUnormSrgb, + + // ASTC compressed. + ASTC4x4Unorm, ASTC4x4UnormSrgb, + ASTC5x5Unorm, ASTC5x5UnormSrgb, + ASTC6x6Unorm, ASTC6x6UnormSrgb, + ASTC8x8Unorm, ASTC8x8UnormSrgb, +}; + +/// True for Depth16Unorm, Depth24Plus, Depth32Float and their stencil variants. +[[nodiscard]] constexpr bool isDepthFormat(TextureFormat f) { + return f >= TextureFormat::Depth16Unorm && f <= TextureFormat::Depth32FloatStencil8; +} + +/// True for any depth or stencil format (includes Stencil8). +[[nodiscard]] constexpr bool isDepthStencil(TextureFormat f) { + return f >= TextureFormat::Depth16Unorm && f <= TextureFormat::Stencil8; +} + +/// True if the format has a depth component. +[[nodiscard]] constexpr bool hasDepth(TextureFormat f) { + switch (f) { + case TextureFormat::Depth16Unorm: + case TextureFormat::Depth24Plus: + case TextureFormat::Depth24PlusStencil8: + case TextureFormat::Depth32Float: + case TextureFormat::Depth32FloatStencil8: + return true; + default: return false; + } +} + +/// True if the format has a stencil component. +[[nodiscard]] constexpr bool hasStencil(TextureFormat f) { + switch (f) { + case TextureFormat::Depth24PlusStencil8: + case TextureFormat::Depth32FloatStencil8: + case TextureFormat::Stencil8: + return true; + default: return false; + } +} + +/// True for BC or ASTC compressed formats. +[[nodiscard]] constexpr bool isCompressed(TextureFormat f) { + return f >= TextureFormat::BC1RGBAUnorm && f <= TextureFormat::ASTC8x8UnormSrgb; +} + +/// True for sRGB variants. +[[nodiscard]] constexpr bool isSrgb(TextureFormat f) { + switch (f) { + case TextureFormat::RGBA8UnormSrgb: + case TextureFormat::BGRA8UnormSrgb: + case TextureFormat::BC1RGBAUnormSrgb: + case TextureFormat::BC2RGBAUnormSrgb: + case TextureFormat::BC3RGBAUnormSrgb: + case TextureFormat::BC7RGBAUnormSrgb: + case TextureFormat::ASTC4x4UnormSrgb: + case TextureFormat::ASTC5x5UnormSrgb: + case TextureFormat::ASTC6x6UnormSrgb: + case TextureFormat::ASTC8x8UnormSrgb: + return true; + default: return false; + } +} + +/// Returns bytes per pixel for uncompressed formats; 0 for compressed or unknown. +[[nodiscard]] constexpr u32 bytesPerPixel(TextureFormat f) { + switch (f) { + case TextureFormat::R8Unorm: + case TextureFormat::Stencil8: + return 1; + case TextureFormat::R16Uint: + case TextureFormat::R16Sint: + case TextureFormat::R16Float: + case TextureFormat::RG8Unorm: + case TextureFormat::Depth16Unorm: + return 2; + case TextureFormat::RGBA8Unorm: + case TextureFormat::RGBA8UnormSrgb: + case TextureFormat::BGRA8Unorm: + case TextureFormat::BGRA8UnormSrgb: + case TextureFormat::RG16Float: + case TextureFormat::R32Float: + case TextureFormat::R32Uint: + case TextureFormat::R32Sint: + case TextureFormat::RGB10A2Unorm: + case TextureFormat::RG11B10Float: + case TextureFormat::Depth24Plus: + case TextureFormat::Depth24PlusStencil8: + case TextureFormat::Depth32Float: + return 4; + case TextureFormat::Depth32FloatStencil8: + return 8; + case TextureFormat::RG32Float: + case TextureFormat::RG32Uint: + case TextureFormat::RGBA16Float: + case TextureFormat::RGBA16Uint: + case TextureFormat::RGBA16Sint: + return 8; + case TextureFormat::RGBA32Float: + case TextureFormat::RGBA32Uint: + case TextureFormat::RGBA32Sint: + return 16; + default: return 0; // Compressed or unknown. + } +} + +} // namespace draco::rhi diff --git a/Engine/cpp/Runtime/Rendering/RHI/Types.cppm b/Engine/cpp/Runtime/Rendering/RHI/Types.cppm new file mode 100644 index 00000000..9ed60c8d --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Types.cppm @@ -0,0 +1,142 @@ +/// Core RHI types: geometry primitives, adapter info, device features, +/// device descriptor, copy regions, and push constant ranges. + +module; + +#include +#include + +export module rhi:types; + +import core.stdtypes; +import :enums; +import :texture_format; + +using namespace draco; + +export namespace draco::rhi { + +/// Maximum number of simultaneous color attachments. +constexpr i32 maxColorAttachments = 8; + +// ---- Geometry primitives ---- + +struct Extent3D { + u32 width = 0; + u32 height = 1; + u32 depth = 1; +}; + +struct Origin3D { + u32 x = 0; + u32 y = 0; + u32 z = 0; +}; + +/// RGBA clear color in linear space. +struct ClearColor { + f32 r = 0.0f, g = 0.0f, b = 0.0f, a = 1.0f; + + static constexpr ClearColor black() { return { 0, 0, 0, 1 }; } + static constexpr ClearColor white() { return { 1, 1, 1, 1 }; } + static constexpr ClearColor cornflowerBlue() { return { 0.392f, 0.584f, 0.929f, 1.0f }; } +}; + +// ---- Adapter / Device info ---- + +/// Information about a physical GPU adapter. +struct AdapterInfo { + std::u8string name; + u32 vendorId = 0; + u32 deviceId = 0; + AdapterType type = AdapterType::Unknown; + + /// Features and limits the adapter supports. Populate via the backend's + /// adapter enumeration; the caller inspects these before creating a device. + struct Features { + // Feature flags. + bool bindlessDescriptors = false; + bool timestampQueries = false; + bool pipelineStatisticsQueries= false; + bool multiDrawIndirect = false; + bool depthClamp = false; + bool fillModeWireframe = false; + bool textureCompressionBC = false; + bool textureCompressionASTC = false; + bool independentBlend = false; + bool multiViewport = false; + bool meshShaders = false; + bool rayTracing = false; + + // Limits. + u32 maxBindGroups = 4; + u32 maxBindingsPerGroup = 16; + u32 maxPushConstantSize = 128; + u32 maxTextureDimension2D = 8192; + u32 maxTextureArrayLayers = 256; + u32 maxComputeWorkgroupSizeX = 256; + u32 maxComputeWorkgroupSizeY = 256; + u32 maxComputeWorkgroupSizeZ = 64; + u32 maxComputeWorkgroupsPerDimension = 65535; + u32 minUniformBufferOffsetAlignment = 256; + u32 minStorageBufferOffsetAlignment = 256; + u32 timestampPeriodNs = 1; + u64 maxBufferSize = 256ull * 1024 * 1024; + + // Mesh shader limits. + u32 maxMeshOutputVertices = 0; + u32 maxMeshOutputPrimitives = 0; + u32 maxMeshWorkgroupSize = 0; + u32 maxTaskWorkgroupSize = 0; + } supportedFeatures; +}; + +/// Features and limits requested when creating a device. +using DeviceFeatures = AdapterInfo::Features; + +/// Descriptor for logical device creation. +struct DeviceDesc { + DeviceFeatures requiredFeatures{}; + u32 graphicsQueueCount = 1; + u32 computeQueueCount = 0; + u32 transferQueueCount = 0; + std::u8string_view label; +}; + +// ---- Copy regions ---- + +/// Layout of a buffer holding texture data (for CPU->GPU uploads). +struct TextureDataLayout { + u64 offset = 0; + u32 bytesPerRow = 0; + u32 rowsPerImage = 0; +}; + +/// Region for texture-to-texture copies. +struct TextureCopyRegion { + u32 srcMipLevel = 0; + u32 srcArrayLayer = 0; + u32 dstMipLevel = 0; + u32 dstArrayLayer = 0; + Extent3D extent; +}; + +/// Region for buffer<->texture copies. +struct BufferTextureCopyRegion { + u64 bufferOffset = 0; + u32 bytesPerRow = 0; + u32 rowsPerImage = 0; + u32 textureMipLevel = 0; + u32 textureArrayLayer = 0; + Origin3D textureOrigin; + Extent3D textureExtent; +}; + +/// Push constant range within a pipeline layout. +struct PushConstantRange { + ShaderStage stages = ShaderStage::None; + u32 offset = 0; + u32 size = 0; +}; + +} // namespace draco::rhi diff --git a/Engine/cpp/Runtime/Rendering/RHI/UniformRegistry.cppm b/Engine/cpp/Runtime/Rendering/RHI/UniformRegistry.cppm deleted file mode 100644 index 2bf48381..00000000 --- a/Engine/cpp/Runtime/Rendering/RHI/UniformRegistry.cppm +++ /dev/null @@ -1,48 +0,0 @@ -module; - -#include -#include -#include -#include - -export module rendering.rhi.uniform_registry; - -import rendering.rhi; - -export namespace draco::rendering::rhi -{ - inline std::unordered_map g_uniform_map; - - inline uint32_t hashUniform(const std::string& name) - { - return static_cast(std::hash{}(name)); - } - - inline void registerUniform(uint32_t hash, UniformHandle h) - { - g_uniform_map[hash] = h; - } - - inline void unregisterUniform(uint32_t hash, UniformHandle h) - { - auto it = g_uniform_map.find(hash); - - if (it != g_uniform_map.end() && it->second == h) - g_uniform_map.erase(it); - } - - inline void clearUniformRegistry() - { - g_uniform_map.clear(); - } - - inline UniformHandle getUniform(uint32_t hash) - { - auto it = g_uniform_map.find(hash); - - if (it == g_uniform_map.end()) - return InvalidUniform; - - return it->second; - } -} diff --git a/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedAdapter.cppm b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedAdapter.cppm new file mode 100644 index 00000000..d841926f --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedAdapter.cppm @@ -0,0 +1,28 @@ +/// Validation wrapper for Adapter. + +export module rhi.validation:validated_adapter; + +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::validation { + +class ValidatedDevice; + +class ValidatedAdapter : public Adapter { +public: + explicit ValidatedAdapter(Adapter* inner) : m_inner(inner) {} + + void getInfo(AdapterInfo& out) override { m_inner->getInfo(out); } + + Status createDevice(const DeviceDesc& desc, Device*& out) override; + + Adapter* inner() const { return m_inner; } + +private: + Adapter* m_inner; +}; + +} // namespace draco::rhi::validation diff --git a/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedBackend.cppm b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedBackend.cppm new file mode 100644 index 00000000..d22c56cc --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedBackend.cppm @@ -0,0 +1,76 @@ +/// Validation wrapper for Backend. + +module; + +#include +#include + +export module rhi.validation:validated_backend; + +import core.stdtypes; +import core.status; +import rhi; +import :validated_adapter; + +using namespace draco; + +export namespace draco::rhi::validation { + +class ValidatedBackend : public Backend { +public: + explicit ValidatedBackend(Backend* inner) : m_inner(inner) { + isInitialized = inner->isInitialized; + } + + std::span enumerateAdapters() override { + if (!m_inner->isInitialized) { + logError("[Validation] enumerateAdapters: backend not initialized"); + return {}; + } + + if (m_adapterWrappers.empty()) { + auto innerAdapters = m_inner->enumerateAdapters(); + m_adapterWrappers.reserve(innerAdapters.size()); + m_adapterPtrs.reserve(innerAdapters.size()); + for (usize i = 0; i < innerAdapters.size(); ++i) { + auto* w = createValidatedAdapter(innerAdapters[i]); + m_adapterWrappers.push_back(w); + m_adapterPtrs.push_back(w); + } + } + return std::span(m_adapterPtrs.data(), m_adapterPtrs.size()); + } + + Status createSurface(void* windowHandle, void* displayHandle, Surface*& out) override { + if (!windowHandle) { + logError("[Validation] createSurface: windowHandle is null"); + out = nullptr; + return ErrorCode::InvalidArgument; + } + return m_inner->createSurface(windowHandle, displayHandle, out); + } + + void destroy() override { + for (auto* w : m_adapterWrappers) delete w; + m_adapterWrappers.clear(); + m_adapterPtrs.clear(); + m_inner->destroy(); + delete this; + } + + Backend* inner() const { return m_inner; } + +private: + static ValidatedAdapter* createValidatedAdapter(Adapter* inner); + + Backend* m_inner; + std::vector m_adapterWrappers; + std::vector m_adapterPtrs; +}; + +Backend* createValidatedBackend(Backend* inner) { + if (!inner) { logError("[Validation] createValidatedBackend: inner is null"); return nullptr; } + return new ValidatedBackend(inner); +} + +} // namespace draco::rhi::validation diff --git a/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedCommandEncoder.cppm b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedCommandEncoder.cppm new file mode 100644 index 00000000..6267eaaf --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedCommandEncoder.cppm @@ -0,0 +1,271 @@ +/// Validation wrapper for CommandEncoder + RayTracingEncoderExt. + +module; + +#include +#include +#include + +export module rhi.validation:validated_command_encoder; + +import core.stdtypes; +import rhi; +import :validated_render_pass_encoder; +import :validated_compute_pass_encoder; +import :validated_render_bundle_encoder; + +using namespace draco; + +export namespace draco::rhi::validation { + +enum class EncoderState { Recording, InRenderPass, InComputePass, Finished }; + +class ValidatedCommandEncoder : public CommandEncoder, public RayTracingEncoderExt { +public: + RayTracingEncoderExt* asRayTracingExt() noexcept override { return this; } + explicit ValidatedCommandEncoder(CommandEncoder* inner) + : m_inner(inner) {} + + ~ValidatedCommandEncoder() override { + // Bundle encoders (and the bundles they own) live until the command encoder is + // destroyed - by then its submission has completed, so the inner bundles are done. + for (auto* e : m_bundleEncoders) delete e; + } + + // Called by sub-encoders when their end() fires. + void onPassEnded() { m_state = EncoderState::Recording; } + + // ---- CommandEncoder ---- + + RenderPassEncoder* beginRenderPass(const RenderPassDesc& desc) override { + if (!checkState("beginRenderPass", EncoderState::Recording)) return &m_rpe; + if (desc.colorAttachments.empty() && + (!desc.depthStencilAttachment.has_value() || !desc.depthStencilAttachment->view)) + logWarning("[Validation] beginRenderPass: no color or depth attachment"); + for (usize i = 0; i < desc.colorAttachments.size(); ++i) + if (!desc.colorAttachments[i].view) + logErrorf("[Validation] beginRenderPass: color attachment %d view is null", static_cast(i)); + + m_state = EncoderState::InRenderPass; + auto* innerRpe = m_inner->beginRenderPass(desc); + m_rpe.begin(innerRpe, this); + return &m_rpe; + } + + ComputePassEncoder* beginComputePass(std::u8string_view label) override { + if (!checkState("beginComputePass", EncoderState::Recording)) return &m_cpe; + m_state = EncoderState::InComputePass; + auto* innerCpe = m_inner->beginComputePass(label); + m_cpe.begin(innerCpe, this); + return &m_cpe; + } + + RenderBundleEncoder* createRenderBundleEncoder(const RenderBundleDesc& desc) override { + if (!checkState("createRenderBundleEncoder", EncoderState::Recording)) return nullptr; + auto* inner = m_inner->createRenderBundleEncoder(desc); + if (!inner) return nullptr; // backend does not support bundles + auto* wrapped = new ValidatedRenderBundleEncoder(inner); + m_bundleEncoders.push_back(wrapped); // owned: freed in this encoder's destructor + return wrapped; + } + + void barrier(const BarrierGroup& group) override { + if (!checkState("barrier", EncoderState::Recording)) return; + m_inner->barrier(group); + } + + void copyBufferToBuffer(Buffer* src, u64 srcOff, Buffer* dst, u64 dstOff, u64 size) override { + if (!checkState("copyBufferToBuffer", EncoderState::Recording)) return; + if (!src) { logError("[Validation] copyBufferToBuffer: src is null"); return; } + if (!dst) { logError("[Validation] copyBufferToBuffer: dst is null"); return; } + if (size == 0) logWarning("[Validation] copyBufferToBuffer: size is 0"); + m_inner->copyBufferToBuffer(src, srcOff, dst, dstOff, size); + } + + void copyBufferToTexture(Buffer* src, Texture* dst, const BufferTextureCopyRegion& r) override { + if (!checkState("copyBufferToTexture", EncoderState::Recording)) return; + if (!src) { logError("[Validation] copyBufferToTexture: src is null"); return; } + if (!dst) { logError("[Validation] copyBufferToTexture: dst is null"); return; } + m_inner->copyBufferToTexture(src, dst, r); + } + + void copyTextureToBuffer(Texture* src, Buffer* dst, const BufferTextureCopyRegion& r) override { + if (!checkState("copyTextureToBuffer", EncoderState::Recording)) return; + if (!src) { logError("[Validation] copyTextureToBuffer: src is null"); return; } + if (!dst) { logError("[Validation] copyTextureToBuffer: dst is null"); return; } + m_inner->copyTextureToBuffer(src, dst, r); + } + + void copyTextureToTexture(Texture* src, Texture* dst, const TextureCopyRegion& r) override { + if (!checkState("copyTextureToTexture", EncoderState::Recording)) return; + if (!src) { logError("[Validation] copyTextureToTexture: src is null"); return; } + if (!dst) { logError("[Validation] copyTextureToTexture: dst is null"); return; } + m_inner->copyTextureToTexture(src, dst, r); + } + + void blit(Texture* src, Texture* dst) override { + if (!checkState("blit", EncoderState::Recording)) return; + if (!src || !dst) { logError("[Validation] blit: src or dst is null"); return; } + m_inner->blit(src, dst); + } + + void generateMipmaps(Texture* tex) override { + if (!checkState("generateMipmaps", EncoderState::Recording)) return; + if (!tex) { logError("[Validation] generateMipmaps: texture is null"); return; } + m_inner->generateMipmaps(tex); + } + + void resolveTexture(Texture* src, Texture* dst) override { + if (!checkState("resolveTexture", EncoderState::Recording)) return; + if (!src || !dst) { logError("[Validation] resolveTexture: src or dst is null"); return; } + m_inner->resolveTexture(src, dst); + } + + void resetQuerySet(QuerySet* qs, u32 first, u32 count) override { + if (!checkState("resetQuerySet", EncoderState::Recording)) return; + if (!qs) { logError("[Validation] resetQuerySet: querySet is null"); return; } + m_inner->resetQuerySet(qs, first, count); + } + + void writeTimestamp(QuerySet* qs, u32 index) override { + if (!checkState("writeTimestamp", EncoderState::Recording)) return; + if (!qs) { logError("[Validation] writeTimestamp: querySet is null"); return; } + m_inner->writeTimestamp(qs, index); + } + + void resolveQuerySet(QuerySet* qs, u32 first, u32 count, Buffer* dst, u64 dstOff) override { + if (!checkState("resolveQuerySet", EncoderState::Recording)) return; + if (!qs) { logError("[Validation] resolveQuerySet: querySet is null"); return; } + if (!dst) { logError("[Validation] resolveQuerySet: dst is null"); return; } + m_inner->resolveQuerySet(qs, first, count, dst, dstOff); + } + + void beginDebugLabel(std::u8string_view label, f32 r, f32 g, f32 b, f32 a) override { + if (m_state == EncoderState::Finished) { logError("[Validation] beginDebugLabel: encoder finished"); return; } + m_debugLabelDepth++; + m_inner->beginDebugLabel(label, r, g, b, a); + } + + void endDebugLabel() override { + if (m_state == EncoderState::Finished) { logError("[Validation] endDebugLabel: encoder finished"); return; } + if (m_debugLabelDepth <= 0) { logError("[Validation] endDebugLabel: no matching begin"); return; } + m_debugLabelDepth--; + m_inner->endDebugLabel(); + } + + void insertDebugLabel(std::u8string_view label, f32 r, f32 g, f32 b, f32 a) override { + if (m_state == EncoderState::Finished) return; + m_inner->insertDebugLabel(label, r, g, b, a); + } + + CommandBuffer* finish() override { + if (m_state == EncoderState::Finished) { logError("[Validation] finish: encoder already finished"); return nullptr; } + if (m_state == EncoderState::InRenderPass) logError("[Validation] finish: render pass still open"); + if (m_state == EncoderState::InComputePass) logError("[Validation] finish: compute pass still open"); + if (m_debugLabelDepth > 0) logWarningf("[Validation] finish: %d debug label(s) not closed", m_debugLabelDepth); + m_state = EncoderState::Finished; + return m_inner->finish(); + } + + // ---- RayTracingEncoderExt ---- + + void buildBottomLevelAccelStruct(AccelStruct* dst, Buffer* scratch, u64 scratchOff, + std::span tris, + std::span aabbs) override { + if (!checkState("buildBLAS", EncoderState::Recording)) return; + if (!dst) { logError("[Validation] buildBLAS: dst is null"); return; } + if (!scratch) { logError("[Validation] buildBLAS: scratch is null"); return; } + auto* rt = m_inner->asRayTracingExt(); + if (rt) rt->buildBottomLevelAccelStruct(dst, scratch, scratchOff, tris, aabbs); + else logError("[Validation] buildBLAS: inner encoder does not support ray tracing"); + } + + void buildTopLevelAccelStruct(AccelStruct* dst, Buffer* scratch, u64 scratchOff, + Buffer* instanceBuf, u64 instanceOff, u32 instanceCount) override { + if (!checkState("buildTLAS", EncoderState::Recording)) return; + if (!dst || !scratch || !instanceBuf) { logError("[Validation] buildTLAS: null argument"); return; } + auto* rt = m_inner->asRayTracingExt(); + if (rt) rt->buildTopLevelAccelStruct(dst, scratch, scratchOff, instanceBuf, instanceOff, instanceCount); + else logError("[Validation] buildTLAS: inner encoder does not support ray tracing"); + } + + void setRayTracingPipeline(RayTracingPipeline* pipeline) override { + if (!checkState("setRayTracingPipeline", EncoderState::Recording)) return; + if (!pipeline) { logError("[Validation] setRayTracingPipeline: pipeline is null"); return; } + m_rtPipelineBound = true; + auto* rt = m_inner->asRayTracingExt(); + if (rt) rt->setRayTracingPipeline(pipeline); + } + + void setBindGroup(u32 index, BindGroup* group, std::span dynOffsets) override { + if (!checkState("RT setBindGroup", EncoderState::Recording)) return; + if (!group) { logError("[Validation] RT setBindGroup: group is null"); return; } + if (!m_rtPipelineBound) logWarning("[Validation] RT setBindGroup: no RT pipeline bound"); + auto* rt = m_inner->asRayTracingExt(); + if (rt) rt->setBindGroup(index, group, dynOffsets); + } + + void setPushConstants(ShaderStage stages, u32 offset, u32 size, const void* data) override { + if (!checkState("RT setPushConstants", EncoderState::Recording)) return; + if (!m_rtPipelineBound) logWarning("[Validation] RT setPushConstants: no RT pipeline bound"); + if (!data && size > 0) { logError("[Validation] RT setPushConstants: data is null"); return; } + auto* rt = m_inner->asRayTracingExt(); + if (rt) rt->setPushConstants(stages, offset, size, data); + } + + void traceRays(Buffer* raygenSBT, u64 raygenOff, u64 raygenStride, + Buffer* missSBT, u64 missOff, u64 missStride, + Buffer* hitSBT, u64 hitOff, u64 hitStride, + u32 width, u32 height, u32 depth) override { + if (!checkState("traceRays", EncoderState::Recording)) return; + if (!m_rtPipelineBound) { logError("[Validation] traceRays: no RT pipeline bound"); return; } + if (!raygenSBT) { logError("[Validation] traceRays: raygenSBT is null"); return; } + auto* rt = m_inner->asRayTracingExt(); + if (rt) rt->traceRays(raygenSBT, raygenOff, raygenStride, missSBT, missOff, missStride, + hitSBT, hitOff, hitStride, width, height, depth); + } + + CommandEncoder* inner() const { return m_inner; } + +private: + bool checkState(const char* method, EncoderState expected) { + if (m_state == EncoderState::Finished) { + logErrorf("[Validation] %s: encoder already finished", method); + return false; + } + if (m_state != expected) { + logErrorf("[Validation] %s: wrong state (expected Recording, got %s)", method, + m_state == EncoderState::InRenderPass ? "InRenderPass" : + m_state == EncoderState::InComputePass ? "InComputePass" : "?"); + return false; + } + return true; + } + + CommandEncoder* m_inner; + EncoderState m_state = EncoderState::Recording; + i32 m_debugLabelDepth = 0; + bool m_rtPipelineBound = false; + + ValidatedRenderPassEncoder m_rpe; + ValidatedComputePassEncoder m_cpe; + std::vector m_bundleEncoders; // owned wrappers (freed in dtor) +}; + +// ---- Deferred end() implementations ---- + +void ValidatedRenderPassEncoder::end() { + if (m_ended) { logError("[Validation] RenderPassEncoder::end: already ended"); return; } + m_ended = true; + m_inner->end(); + if (m_parent) m_parent->onPassEnded(); +} + +void ValidatedComputePassEncoder::end() { + if (m_ended) { logError("[Validation] ComputePassEncoder::end: already ended"); return; } + m_ended = true; + m_inner->end(); + if (m_parent) m_parent->onPassEnded(); +} + +} // namespace draco::rhi::validation diff --git a/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedCommandPool.cppm b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedCommandPool.cppm new file mode 100644 index 00000000..192c1384 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedCommandPool.cppm @@ -0,0 +1,47 @@ +/// Validation wrapper for CommandPool. + +export module rhi.validation:validated_command_pool; + +import core.stdtypes; +import core.status; +import rhi; +import :validated_command_encoder; + +using namespace draco; + +export namespace draco::rhi::validation { + +class ValidatedCommandPool : public CommandPool { +public: + explicit ValidatedCommandPool(CommandPool* inner) : m_inner(inner) {} + + Status createEncoder(CommandEncoder*& out) override { + CommandEncoder* innerEnc = nullptr; + Status r = m_inner->createEncoder(innerEnc); + if (r != ErrorCode::Ok || !innerEnc) { out = nullptr; return r; } + out = new ValidatedCommandEncoder(innerEnc); + return ErrorCode::Ok; + } + + void destroyEncoder(CommandEncoder*& encoder) override { + if (!encoder) return; + auto* ve = static_cast(encoder); + if (ve) { + CommandEncoder* innerEnc = ve->inner(); + m_inner->destroyEncoder(innerEnc); + delete ve; + } else { + m_inner->destroyEncoder(encoder); + } + encoder = nullptr; + } + + void reset() override { m_inner->reset(); } + + CommandPool* inner() const { return m_inner; } + +private: + CommandPool* m_inner; +}; + +} // namespace draco::rhi::validation diff --git a/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedComputePassEncoder.cppm b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedComputePassEncoder.cppm new file mode 100644 index 00000000..2991dcd7 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedComputePassEncoder.cppm @@ -0,0 +1,82 @@ +/// Validation wrapper for ComputePassEncoder. + +module; + +#include + +export module rhi.validation:validated_compute_pass_encoder; + +import core.stdtypes; +import rhi; + +using namespace draco; + +export namespace draco::rhi::validation { + +class ValidatedCommandEncoder; // forward + +class ValidatedComputePassEncoder : public ComputePassEncoder { +public: + void begin(ComputePassEncoder* inner, ValidatedCommandEncoder* parent) { + m_inner = inner; m_parent = parent; + m_pipelineBound = false; m_ended = false; + } + + void setPipeline(ComputePipeline* pipeline) override { + if (m_ended) { logError("[Validation] compute setPipeline: pass ended"); return; } + if (!pipeline) { logError("[Validation] compute setPipeline: pipeline is null"); return; } + m_pipelineBound = true; + m_inner->setPipeline(pipeline); + } + + void setBindGroup(u32 index, BindGroup* group, std::span dynOffsets) override { + if (m_ended) return; + if (!group) { logError("[Validation] compute setBindGroup: group is null"); return; } + if (!m_pipelineBound) logWarning("[Validation] compute setBindGroup: no pipeline bound"); + m_inner->setBindGroup(index, group, dynOffsets); + } + + void setPushConstants(ShaderStage stages, u32 offset, u32 size, const void* data) override { + if (m_ended) return; + if (!m_pipelineBound) logWarning("[Validation] compute setPushConstants: no pipeline bound"); + if (!data && size > 0) { logError("[Validation] compute setPushConstants: data is null"); return; } + if (offset % 4 != 0) logError("[Validation] compute setPushConstants: offset not 4-byte aligned"); + if (size % 4 != 0) logError("[Validation] compute setPushConstants: size not 4-byte aligned"); + m_inner->setPushConstants(stages, offset, size, data); + } + + void dispatch(u32 x, u32 y, u32 z) override { + if (m_ended) { logError("[Validation] dispatch: pass ended"); return; } + if (!m_pipelineBound) { logError("[Validation] dispatch: no pipeline bound"); return; } + if (x == 0 || y == 0 || z == 0) logWarning("[Validation] dispatch: zero dimension"); + m_inner->dispatch(x, y, z); + } + + void dispatchIndirect(Buffer* buffer, u64 offset) override { + if (m_ended) { logError("[Validation] dispatchIndirect: pass ended"); return; } + if (!m_pipelineBound) { logError("[Validation] dispatchIndirect: no pipeline bound"); return; } + if (!buffer) { logError("[Validation] dispatchIndirect: buffer is null"); return; } + m_inner->dispatchIndirect(buffer, offset); + } + + void computeBarrier() override { + if (m_ended) return; + m_inner->computeBarrier(); + } + + void writeTimestamp(QuerySet* qs, u32 index) override { + if (m_ended) return; + if (!qs) { logError("[Validation] compute writeTimestamp: querySet is null"); return; } + m_inner->writeTimestamp(qs, index); + } + + void end() override; + +private: + ComputePassEncoder* m_inner = nullptr; + ValidatedCommandEncoder* m_parent = nullptr; + bool m_pipelineBound = false; + bool m_ended = false; +}; + +} // namespace draco::rhi::validation diff --git a/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedDevice.cppm b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedDevice.cppm new file mode 100644 index 00000000..1f25f90a --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedDevice.cppm @@ -0,0 +1,260 @@ +/// Validation wrapper for Device. Tracks all live resources for leak +/// detection, validates create/destroy parameters. + +module; + +#include +#include + +export module rhi.validation:validated_device; + +import core.stdtypes; +import core.status; +import rhi; +import :validated_fence; +import :validated_swap_chain; +import :validated_command_pool; +import :validated_queue; + +using namespace draco; + +export namespace draco::rhi::validation { + +class ValidatedDevice : public Device { +public: + explicit ValidatedDevice(Device* inner) : m_inner(inner) { + type = inner->type; + features = inner->features; + shaderGroupHandleSize = inner->shaderGroupHandleSize; + shaderGroupHandleAlignment = inner->shaderGroupHandleAlignment; + shaderGroupBaseAlignment = inner->shaderGroupBaseAlignment; + } + + // ---- Queues ---- + Queue* getQueue(QueueType t, u32 index) override { + // Wrap on first access. + Queue* raw = m_inner->getQueue(t, index); + if (!raw) return nullptr; + for (auto& w : m_queueWrappers) if (w.raw == raw) return w.validated; + auto* vq = new ValidatedQueue(raw); + m_queueWrappers.push_back({ raw, vq }); + return vq; + } + u32 getQueueCount(QueueType t) override { return m_inner->getQueueCount(t); } + FormatSupport getFormatSupport(TextureFormat f) override { return m_inner->getFormatSupport(f); } + + // ---- Create methods (with validation + tracking) ---- +#define V_CREATE(Type, method, desc_t) \ + Status method(const desc_t& d, Type*& out) override { \ + if (m_destroyed) { logError("[Validation] " #method ": device destroyed"); out = nullptr; return ErrorCode::Unknown; } \ + Status r = m_inner->method(d, out); \ + if (r == ErrorCode::Ok && out) m_live##Type##s.push_back(out); \ + return r; \ + } + + V_CREATE(Buffer, createBuffer, BufferDesc) + V_CREATE(Texture, createTexture, TextureDesc) + V_CREATE(Sampler, createSampler, SamplerDesc) + V_CREATE(ShaderModule, createShaderModule, ShaderModuleDesc) + V_CREATE(BindGroupLayout, createBindGroupLayout, BindGroupLayoutDesc) + V_CREATE(BindGroup, createBindGroup, BindGroupDesc) + V_CREATE(PipelineLayout, createPipelineLayout, PipelineLayoutDesc) + V_CREATE(PipelineCache, createPipelineCache, PipelineCacheDesc) + V_CREATE(RenderPipeline, createRenderPipeline, RenderPipelineDesc) + V_CREATE(ComputePipeline, createComputePipeline, ComputePipelineDesc) + V_CREATE(QuerySet, createQuerySet, QuerySetDesc) +#undef V_CREATE + + Status createTextureView(Texture* tex, const TextureViewDesc& d, TextureView*& out) override { + if (m_destroyed) { logError("[Validation] createTextureView: device destroyed"); out = nullptr; return ErrorCode::Unknown; } + if (!tex) { logError("[Validation] createTextureView: texture is null"); out = nullptr; return ErrorCode::Unknown; } + Status r = m_inner->createTextureView(tex, d, out); + if (r == ErrorCode::Ok && out) m_liveTextureViews.push_back(out); + return r; + } + + Status createCommandPool(QueueType qt, CommandPool*& out) override { + if (m_destroyed) { logError("[Validation] createCommandPool: device destroyed"); out = nullptr; return ErrorCode::Unknown; } + CommandPool* innerPool = nullptr; + Status r = m_inner->createCommandPool(qt, innerPool); + if (r != ErrorCode::Ok || !innerPool) { out = nullptr; return r; } + out = new ValidatedCommandPool(innerPool); + m_liveCommandPools.push_back(out); + return ErrorCode::Ok; + } + + Status createFence(u64 initialValue, Fence*& out) override { + if (m_destroyed) { logError("[Validation] createFence: device destroyed"); out = nullptr; return ErrorCode::Unknown; } + Fence* innerFence = nullptr; + Status r = m_inner->createFence(initialValue, innerFence); + if (r != ErrorCode::Ok || !innerFence) { out = nullptr; return r; } + out = new ValidatedFence(innerFence); + m_liveFences.push_back(out); + return ErrorCode::Ok; + } + + Status createSwapChain(Surface* surface, const SwapChainDesc& d, SwapChain*& out) override { + if (m_destroyed) { logError("[Validation] createSwapChain: device destroyed"); out = nullptr; return ErrorCode::Unknown; } + SwapChain* innerSc = nullptr; + Status r = m_inner->createSwapChain(surface, d, innerSc); + if (r != ErrorCode::Ok || !innerSc) { out = nullptr; return r; } + out = new ValidatedSwapChain(innerSc); + m_liveSwapChains.push_back(out); + return ErrorCode::Ok; + } + + // ---- Mesh/RT (forwarded, validated for destroyed state) ---- + Status createMeshPipeline(const MeshPipelineDesc& d, MeshPipeline*& out) override { + if (m_destroyed) { out = nullptr; return ErrorCode::Unknown; } + Status r = m_inner->createMeshPipeline(d, out); + if (r == ErrorCode::Ok && out) m_liveMeshPipelines.push_back(out); + return r; + } + void destroyMeshPipeline(MeshPipeline*& p) override { removeAndDestroy(m_liveMeshPipelines, p, [&](auto*& x){ m_inner->destroyMeshPipeline(x); }); } + + Status createAccelStruct(const AccelStructDesc& d, AccelStruct*& out) override { + if (m_destroyed) { out = nullptr; return ErrorCode::Unknown; } + Status r = m_inner->createAccelStruct(d, out); + if (r == ErrorCode::Ok && out) m_liveAccelStructs.push_back(out); + return r; + } + void destroyAccelStruct(AccelStruct*& a) override { removeAndDestroy(m_liveAccelStructs, a, [&](auto*& x){ m_inner->destroyAccelStruct(x); }); } + + Status createRayTracingPipeline(const RayTracingPipelineDesc& d, RayTracingPipeline*& out) override { + if (m_destroyed) { out = nullptr; return ErrorCode::Unknown; } + Status r = m_inner->createRayTracingPipeline(d, out); + if (r == ErrorCode::Ok && out) m_liveRtPipelines.push_back(out); + return r; + } + void destroyRayTracingPipeline(RayTracingPipeline*& p) override { removeAndDestroy(m_liveRtPipelines, p, [&](auto*& x){ m_inner->destroyRayTracingPipeline(x); }); } + + Status getShaderGroupHandles(RayTracingPipeline* p, u32 first, u32 count, std::span out) override { + return m_inner->getShaderGroupHandles(p, first, count, out); + } + + // ---- Destroy methods (with tracking removal) ---- +#define V_DESTROY(Type, method, list) \ + void method(Type*& x) override { removeAndDestroy(list, x, [&](auto*& p){ m_inner->method(p); }); } + + V_DESTROY(Buffer, destroyBuffer, m_liveBuffers) + V_DESTROY(Texture, destroyTexture, m_liveTextures) + V_DESTROY(TextureView, destroyTextureView, m_liveTextureViews) + V_DESTROY(Sampler, destroySampler, m_liveSamplers) + V_DESTROY(ShaderModule, destroyShaderModule, m_liveShaderModules) + V_DESTROY(BindGroupLayout, destroyBindGroupLayout, m_liveBindGroupLayouts) + V_DESTROY(BindGroup, destroyBindGroup, m_liveBindGroups) + V_DESTROY(PipelineLayout, destroyPipelineLayout, m_livePipelineLayouts) + V_DESTROY(PipelineCache, destroyPipelineCache, m_livePipelineCaches) + V_DESTROY(RenderPipeline, destroyRenderPipeline, m_liveRenderPipelines) + V_DESTROY(ComputePipeline, destroyComputePipeline, m_liveComputePipelines) + V_DESTROY(QuerySet, destroyQuerySet, m_liveQuerySets) +#undef V_DESTROY + + void destroyCommandPool(CommandPool*& pool) override { + if (!pool) return; + removeFromList(m_liveCommandPools, pool); + auto* vp = static_cast(pool); + if (vp) { CommandPool* innerPool = vp->inner(); m_inner->destroyCommandPool(innerPool); delete vp; } + else m_inner->destroyCommandPool(pool); + pool = nullptr; + } + + void destroyFence(Fence*& fence) override { + if (!fence) return; + removeFromList(m_liveFences, fence); + auto* vf = static_cast(fence); + if (vf) { Fence* innerFence = vf->inner(); m_inner->destroyFence(innerFence); delete vf; } + else m_inner->destroyFence(fence); + fence = nullptr; + } + + void destroySwapChain(SwapChain*& sc) override { + if (!sc) return; + removeFromList(m_liveSwapChains, sc); + auto* vs = static_cast(sc); + if (vs) { SwapChain* innerSc = vs->inner(); m_inner->destroySwapChain(innerSc); delete vs; } + else m_inner->destroySwapChain(sc); + sc = nullptr; + } + + void destroySurface(Surface*& s) override { m_inner->destroySurface(s); } + + void waitIdle() override { m_inner->waitIdle(); } + + void destroy() override { + if (m_destroyed) { logError("[Validation] Device::destroy: already destroyed"); return; } + m_destroyed = true; + reportLeaks(); + for (auto& w : m_queueWrappers) delete w.validated; + m_queueWrappers.clear(); + m_inner->destroy(); + delete this; + } + +private: + template + void removeFromList(std::vector& list, T* item) { + for (usize i = 0; i < list.size(); ++i) { + if (list[i] == item) { list.erase(list.begin() + i); return; } + } + } + + template + void removeAndDestroy(std::vector& list, T*& item, Fn destroyFn) { + if (!item) return; + removeFromList(list, item); + destroyFn(item); + item = nullptr; + } + + void reportLeaks() { + auto report = [](const char* name, usize count) { + if (count > 0) logWarningf("[Validation] Device destroyed with %zu live %s(s)", count, name); + }; + report("Buffer", m_liveBuffers.size()); + report("Texture", m_liveTextures.size()); + report("TextureView", m_liveTextureViews.size()); + report("Sampler", m_liveSamplers.size()); + report("ShaderModule", m_liveShaderModules.size()); + report("BindGroupLayout", m_liveBindGroupLayouts.size()); + report("BindGroup", m_liveBindGroups.size()); + report("PipelineLayout", m_livePipelineLayouts.size()); + report("PipelineCache", m_livePipelineCaches.size()); + report("RenderPipeline", m_liveRenderPipelines.size()); + report("ComputePipeline", m_liveComputePipelines.size()); + report("MeshPipeline", m_liveMeshPipelines.size()); + report("AccelStruct", m_liveAccelStructs.size()); + report("RayTracingPipeline", m_liveRtPipelines.size()); + report("CommandPool", m_liveCommandPools.size()); + report("Fence", m_liveFences.size()); + report("SwapChain", m_liveSwapChains.size()); + report("QuerySet", m_liveQuerySets.size()); + } + + Device* m_inner; + bool m_destroyed = false; + + struct QueueWrap { Queue* raw; ValidatedQueue* validated; }; + std::vector m_queueWrappers; + + std::vector m_liveBuffers; + std::vector m_liveTextures; + std::vector m_liveTextureViews; + std::vector m_liveSamplers; + std::vector m_liveShaderModules; + std::vector m_liveBindGroupLayouts; + std::vector m_liveBindGroups; + std::vector m_livePipelineLayouts; + std::vector m_livePipelineCaches; + std::vector m_liveRenderPipelines; + std::vector m_liveComputePipelines; + std::vector m_liveMeshPipelines; + std::vector m_liveAccelStructs; + std::vectorm_liveRtPipelines; + std::vector m_liveCommandPools; + std::vector m_liveFences; + std::vector m_liveSwapChains; + std::vector m_liveQuerySets; +}; + +} // namespace draco::rhi::validation diff --git a/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedFence.cppm b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedFence.cppm new file mode 100644 index 00000000..08cdd789 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedFence.cppm @@ -0,0 +1,41 @@ +/// Validation wrapper for Fence. + +export module rhi.validation:validated_fence; + +import core.stdtypes; +import rhi; + +using namespace draco; + +export namespace draco::rhi::validation { + +class ValidatedFence : public Fence { +public: + explicit ValidatedFence(Fence* inner) : m_inner(inner) {} + + u64 completedValue() override { return m_inner->completedValue(); } + + bool wait(u64 value, u64 timeoutNs) override { + if (value > m_lastSignaled && m_lastSignaled > 0) { + logWarningf("[Validation] Fence::wait: waiting for value %llu but highest signaled is %llu", + static_cast(value), static_cast(m_lastSignaled)); + } + return m_inner->wait(value, timeoutNs); + } + + void trackSignal(u64 value) { + if (value <= m_lastSignaled && m_lastSignaled > 0) { + logWarningf("[Validation] Fence signal value %llu is not monotonically increasing (last=%llu)", + static_cast(value), static_cast(m_lastSignaled)); + } + m_lastSignaled = value; + } + + Fence* inner() const { return m_inner; } + +private: + Fence* m_inner; + u64 m_lastSignaled = 0; +}; + +} // namespace draco::rhi::validation diff --git a/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedQueue.cppm b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedQueue.cppm new file mode 100644 index 00000000..d8cd71fe --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedQueue.cppm @@ -0,0 +1,86 @@ +/// Validation wrapper for Queue. + +module; + +#include +#include + +export module rhi.validation:validated_queue; + +import core.stdtypes; +import core.status; +import rhi; +import :validated_fence; +import :validated_transfer_batch; + +using namespace draco; + +export namespace draco::rhi::validation { + +class ValidatedQueue : public Queue { +public: + explicit ValidatedQueue(Queue* inner) : m_inner(inner) { queueType = inner->queueType; } + + void submit(std::span cmdBufs) override { + for (usize i = 0; i < cmdBufs.size(); ++i) + if (!cmdBufs[i]) logErrorf("[Validation] Queue::submit: commandBuffer[%zu] is null", i); + m_inner->submit(cmdBufs); + } + + void submit(std::span cmdBufs, Fence* signalFence, u64 signalValue) override { + if (!signalFence) { logError("[Validation] Queue::submit: signalFence is null"); return; } + auto* vf = static_cast(signalFence); + Fence* innerFence = vf ? vf->inner() : signalFence; + if (vf) vf->trackSignal(signalValue); + m_inner->submit(cmdBufs, innerFence, signalValue); + } + + void submit(std::span cmdBufs, + std::span waitFences, std::span waitValues, + Fence* signalFence, u64 signalValue) override { + if (waitFences.size() != waitValues.size()) + logError("[Validation] Queue::submit: waitFences and waitValues count mismatch"); + // Unwrap validated fences for both wait and signal. + std::vector innerWait(waitFences.size()); + for (usize i = 0; i < waitFences.size(); ++i) { + auto* vw = static_cast(waitFences[i]); + innerWait[i] = vw ? vw->inner() : waitFences[i]; + } + auto* vf = static_cast(signalFence); + Fence* innerSignal = vf ? vf->inner() : signalFence; + if (vf) vf->trackSignal(signalValue); + m_inner->submit(cmdBufs, std::span(innerWait.data(), innerWait.size()), waitValues, innerSignal, signalValue); + } + + void waitIdle() override { m_inner->waitIdle(); } + + Status createTransferBatch(TransferBatch*& out) override { + TransferBatch* innerBatch = nullptr; + Status r = m_inner->createTransferBatch(innerBatch); + if (r != ErrorCode::Ok || !innerBatch) { out = nullptr; return r; } + out = new ValidatedTransferBatch(innerBatch); + return ErrorCode::Ok; + } + + void destroyTransferBatch(TransferBatch*& batch) override { + if (!batch) return; + auto* vt = static_cast(batch); + if (vt) { + TransferBatch* innerBatch = vt->inner(); + m_inner->destroyTransferBatch(innerBatch); + delete vt; + } else { + m_inner->destroyTransferBatch(batch); + } + batch = nullptr; + } + + f32 timestampPeriod() const override { return m_inner->timestampPeriod(); } + + Queue* inner() const { return m_inner; } + +private: + Queue* m_inner; +}; + +} // namespace draco::rhi::validation diff --git a/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedRenderBundleEncoder.cppm b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedRenderBundleEncoder.cppm new file mode 100644 index 00000000..e5116f7b --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedRenderBundleEncoder.cppm @@ -0,0 +1,112 @@ +/// Validation wrappers for RenderBundleEncoder + RenderBundle. +/// Mirrors ValidatedRenderPassEncoder's draw-recording checks for the bundle subset. + +module; + +#include + +export module rhi.validation:validated_render_bundle_encoder; + +import core.stdtypes; +import rhi; + +using namespace draco; + +export namespace draco::rhi::validation { + +// Wraps an inner bundle so the validation layer can unwrap it at executeBundles time. +class ValidatedRenderBundle : public RenderBundle { +public: + explicit ValidatedRenderBundle(RenderBundle* inner) : m_inner(inner) {} + [[nodiscard]] RenderBundle* inner() const noexcept { return m_inner; } +private: + RenderBundle* m_inner; +}; + +// Validates the draw-recording subset (pipeline bound before draws, non-null resources) and +// forwards to the inner bundle encoder. Owns the ValidatedRenderBundle it produces at finish. +class ValidatedRenderBundleEncoder : public RenderBundleEncoder { +public: + explicit ValidatedRenderBundleEncoder(RenderBundleEncoder* inner) : m_inner(inner) {} + ~ValidatedRenderBundleEncoder() override { delete m_bundle; } + + void setPipeline(RenderPipeline* pipeline) override { + if (m_finished) { logError("[Validation] bundle setPipeline: bundle already finished"); return; } + if (!pipeline) { logError("[Validation] bundle setPipeline: pipeline is null"); return; } + m_pipelineBound = true; + m_inner->setPipeline(pipeline); + } + + void setBindGroup(u32 index, BindGroup* group, std::span dynOffsets) override { + if (m_finished) { logError("[Validation] bundle setBindGroup: bundle already finished"); return; } + if (!group) { logError("[Validation] bundle setBindGroup: group is null"); return; } + m_inner->setBindGroup(index, group, dynOffsets); + } + + void setPushConstants(ShaderStage stages, u32 offset, u32 size, const void* data) override { + if (m_finished) { logError("[Validation] bundle setPushConstants: bundle already finished"); return; } + if (!data && size > 0) { logError("[Validation] bundle setPushConstants: data is null but size > 0"); return; } + if (offset % 4 != 0) logError("[Validation] bundle setPushConstants: offset must be 4-byte aligned"); + if (size % 4 != 0) logError("[Validation] bundle setPushConstants: size must be 4-byte aligned"); + m_inner->setPushConstants(stages, offset, size, data); + } + + void setVertexBuffer(u32 slot, Buffer* buffer, u64 offset) override { + if (m_finished) { logError("[Validation] bundle setVertexBuffer: bundle already finished"); return; } + if (!buffer) { logError("[Validation] bundle setVertexBuffer: buffer is null"); return; } + m_inner->setVertexBuffer(slot, buffer, offset); + } + + void setIndexBuffer(Buffer* buffer, IndexFormat format, u64 offset) override { + if (m_finished) { logError("[Validation] bundle setIndexBuffer: bundle already finished"); return; } + if (!buffer) { logError("[Validation] bundle setIndexBuffer: buffer is null"); return; } + m_inner->setIndexBuffer(buffer, format, offset); + } + + void draw(u32 vertexCount, u32 instanceCount, u32 firstVertex, u32 firstInstance) override { + if (!checkDrawReady("bundle draw")) return; + if (vertexCount == 0) logWarning("[Validation] bundle draw: vertexCount is 0"); + m_inner->draw(vertexCount, instanceCount, firstVertex, firstInstance); + } + + void drawIndexed(u32 indexCount, u32 instanceCount, u32 firstIndex, i32 baseVertex, u32 firstInstance) override { + if (!checkDrawReady("bundle drawIndexed")) return; + if (indexCount == 0) logWarning("[Validation] bundle drawIndexed: indexCount is 0"); + m_inner->drawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance); + } + + void drawIndirect(Buffer* buffer, u64 offset, u32 drawCount, u32 stride) override { + if (!checkDrawReady("bundle drawIndirect")) return; + if (!buffer) { logError("[Validation] bundle drawIndirect: buffer is null"); return; } + m_inner->drawIndirect(buffer, offset, drawCount, stride); + } + + void drawIndexedIndirect(Buffer* buffer, u64 offset, u32 drawCount, u32 stride) override { + if (!checkDrawReady("bundle drawIndexedIndirect")) return; + if (!buffer) { logError("[Validation] bundle drawIndexedIndirect: buffer is null"); return; } + m_inner->drawIndexedIndirect(buffer, offset, drawCount, stride); + } + + RenderBundle* finish() override { + if (m_finished) { logError("[Validation] bundle finish: already finished"); return m_bundle; } + m_finished = true; + RenderBundle* innerBundle = m_inner->finish(); + if (innerBundle == nullptr) { logError("[Validation] bundle finish: inner returned null"); return nullptr; } + m_bundle = new ValidatedRenderBundle(innerBundle); // freed by this encoder's destructor + return m_bundle; + } + +private: + bool checkDrawReady(const char* method) { + if (m_finished) { logErrorf("[Validation] %s: bundle already finished", method); return false; } + if (!m_pipelineBound) { logErrorf("[Validation] %s: no pipeline bound", method); return false; } + return true; + } + + RenderBundleEncoder* m_inner; + ValidatedRenderBundle* m_bundle = nullptr; + bool m_pipelineBound = false; + bool m_finished = false; +}; + +} // namespace draco::rhi::validation diff --git a/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedRenderPassEncoder.cppm b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedRenderPassEncoder.cppm new file mode 100644 index 00000000..cbf5e304 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedRenderPassEncoder.cppm @@ -0,0 +1,194 @@ +/// Validation wrapper for RenderPassEncoder + MeshShaderPassExt. + +module; + +#include +#include + +export module rhi.validation:validated_render_pass_encoder; + +import core.stdtypes; +import rhi; +import :validated_render_bundle_encoder; + +using namespace draco; + +export namespace draco::rhi::validation { + +class ValidatedCommandEncoder; // forward + +class ValidatedRenderPassEncoder : public RenderPassEncoder, public MeshShaderPassExt { +public: + MeshShaderPassExt* asMeshShaderExt() noexcept override { return this; } + void begin(RenderPassEncoder* inner, ValidatedCommandEncoder* parent) { + m_inner = inner; m_parent = parent; + m_pipelineBound = false; m_viewportSet = false; m_scissorSet = false; m_ended = false; + m_meshPipelineBound = false; + } + + // ---- RenderPassEncoder ---- + + void setPipeline(RenderPipeline* pipeline) override { + if (m_ended) { logError("[Validation] setPipeline: render pass ended"); return; } + if (!pipeline) { logError("[Validation] setPipeline: pipeline is null"); return; } + m_pipelineBound = true; m_meshPipelineBound = false; + m_inner->setPipeline(pipeline); + } + + void setBindGroup(u32 index, BindGroup* group, std::span dynOffsets) override { + if (m_ended) { logError("[Validation] setBindGroup: render pass ended"); return; } + if (!group) { logError("[Validation] setBindGroup: group is null"); return; } + m_inner->setBindGroup(index, group, dynOffsets); + } + + void setPushConstants(ShaderStage stages, u32 offset, u32 size, const void* data) override { + if (m_ended) { logError("[Validation] setPushConstants: render pass ended"); return; } + if (!m_pipelineBound && !m_meshPipelineBound) logWarning("[Validation] setPushConstants: no pipeline bound"); + if (!data && size > 0) { logError("[Validation] setPushConstants: data is null but size > 0"); return; } + if (size == 0) { logWarning("[Validation] setPushConstants: size is 0"); return; } + if (offset % 4 != 0) logError("[Validation] setPushConstants: offset must be 4-byte aligned"); + if (size % 4 != 0) logError("[Validation] setPushConstants: size must be 4-byte aligned"); + m_inner->setPushConstants(stages, offset, size, data); + } + + void setVertexBuffer(u32 slot, Buffer* buffer, u64 offset) override { + if (m_ended) { logError("[Validation] setVertexBuffer: render pass ended"); return; } + if (!buffer) { logError("[Validation] setVertexBuffer: buffer is null"); return; } + m_inner->setVertexBuffer(slot, buffer, offset); + } + + void setIndexBuffer(Buffer* buffer, IndexFormat format, u64 offset) override { + if (m_ended) { logError("[Validation] setIndexBuffer: render pass ended"); return; } + if (!buffer) { logError("[Validation] setIndexBuffer: buffer is null"); return; } + m_inner->setIndexBuffer(buffer, format, offset); + } + + void setViewport(f32 x, f32 y, f32 w, f32 h, f32 minD, f32 maxD) override { + if (m_ended) { logError("[Validation] setViewport: render pass ended"); return; } + m_viewportSet = true; + m_inner->setViewport(x, y, w, h, minD, maxD); + } + + void setScissor(i32 x, i32 y, u32 w, u32 h) override { + if (m_ended) { logError("[Validation] setScissor: render pass ended"); return; } + m_scissorSet = true; + m_inner->setScissor(x, y, w, h); + } + + void setBlendConstant(f32 r, f32 g, f32 b, f32 a) override { + if (m_ended) return; + m_inner->setBlendConstant(r, g, b, a); + } + + void setStencilReference(u32 ref) override { + if (m_ended) return; + m_inner->setStencilReference(ref); + } + + void draw(u32 vertexCount, u32 instanceCount, u32 firstVertex, u32 firstInstance) override { + if (!checkDrawReady("draw")) return; + if (vertexCount == 0) logWarning("[Validation] draw: vertexCount is 0"); + m_inner->draw(vertexCount, instanceCount, firstVertex, firstInstance); + } + + void drawIndexed(u32 indexCount, u32 instanceCount, u32 firstIndex, i32 baseVertex, u32 firstInstance) override { + if (!checkDrawReady("drawIndexed")) return; + if (indexCount == 0) logWarning("[Validation] drawIndexed: indexCount is 0"); + m_inner->drawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance); + } + + void drawIndirect(Buffer* buffer, u64 offset, u32 drawCount, u32 stride) override { + if (!checkDrawReady("drawIndirect")) return; + if (!buffer) { logError("[Validation] drawIndirect: buffer is null"); return; } + m_inner->drawIndirect(buffer, offset, drawCount, stride); + } + + void drawIndexedIndirect(Buffer* buffer, u64 offset, u32 drawCount, u32 stride) override { + if (!checkDrawReady("drawIndexedIndirect")) return; + if (!buffer) { logError("[Validation] drawIndexedIndirect: buffer is null"); return; } + m_inner->drawIndexedIndirect(buffer, offset, drawCount, stride); + } + + void executeBundles(std::span bundles) override { + if (m_ended) { logError("[Validation] executeBundles: render pass ended"); return; } + if (!m_viewportSet) logWarning("[Validation] executeBundles: viewport not set - bundles inherit viewport from the parent pass"); + if (!m_scissorSet) logWarning("[Validation] executeBundles: scissor not set - bundles inherit scissor from the parent pass"); + // Unwrap each ValidatedRenderBundle to its inner bundle before forwarding. + std::vector inner(bundles.size()); + for (usize i = 0; i < bundles.size(); ++i) { + auto* vb = static_cast(bundles[i]); + if (!vb) { logErrorf("[Validation] executeBundles: bundle %d is null", static_cast(i)); return; } + inner[i] = vb->inner(); + } + m_inner->executeBundles(std::span{ inner.data(), inner.size() }); + } + + void writeTimestamp(QuerySet* qs, u32 index) override { + if (m_ended) return; + if (!qs) { logError("[Validation] writeTimestamp: querySet is null"); return; } + m_inner->writeTimestamp(qs, index); + } + + void beginOcclusionQuery(QuerySet* qs, u32 index) override { + if (m_ended) return; + if (!qs) { logError("[Validation] beginOcclusionQuery: querySet is null"); return; } + m_inner->beginOcclusionQuery(qs, index); + } + + void endOcclusionQuery(QuerySet* qs, u32 index) override { + if (m_ended) return; + m_inner->endOcclusionQuery(qs, index); + } + + void end() override; + + // ---- MeshShaderPassExt ---- + + void setMeshPipeline(MeshPipeline* pipeline) override { + if (m_ended) { logError("[Validation] setMeshPipeline: render pass ended"); return; } + if (!pipeline) { logError("[Validation] setMeshPipeline: pipeline is null"); return; } + m_meshPipelineBound = true; m_pipelineBound = false; + auto* mp = m_inner->asMeshShaderExt(); + if (mp) mp->setMeshPipeline(pipeline); + else logError("[Validation] setMeshPipeline: inner encoder does not support mesh shaders"); + } + + void drawMeshTasks(u32 gx, u32 gy, u32 gz) override { + if (!checkDrawReady("drawMeshTasks")) return; + auto* mp = m_inner->asMeshShaderExt(); + if (mp) mp->drawMeshTasks(gx, gy, gz); + } + + void drawMeshTasksIndirect(Buffer* buf, u64 offset, u32 drawCount, u32 stride) override { + if (!checkDrawReady("drawMeshTasksIndirect")) return; + if (!buf) { logError("[Validation] drawMeshTasksIndirect: buffer is null"); return; } + auto* mp = m_inner->asMeshShaderExt(); + if (mp) mp->drawMeshTasksIndirect(buf, offset, drawCount, stride); + } + + void drawMeshTasksIndirectCount(Buffer* buf, u64 offset, Buffer* countBuf, u64 countOffset, u32 maxDrawCount, u32 stride) override { + if (!checkDrawReady("drawMeshTasksIndirectCount")) return; + if (!buf || !countBuf) { logError("[Validation] drawMeshTasksIndirectCount: buffer is null"); return; } + auto* mp = m_inner->asMeshShaderExt(); + if (mp) mp->drawMeshTasksIndirectCount(buf, offset, countBuf, countOffset, maxDrawCount, stride); + } + +private: + bool checkDrawReady(const char* method) { + if (m_ended) { logErrorf("[Validation] %s: render pass ended", method); return false; } + if (!m_pipelineBound && !m_meshPipelineBound) { logErrorf("[Validation] %s: no pipeline bound", method); return false; } + if (!m_viewportSet) { logErrorf("[Validation] %s: viewport not set", method); return false; } + if (!m_scissorSet) { logErrorf("[Validation] %s: scissor not set", method); return false; } + return true; + } + + RenderPassEncoder* m_inner = nullptr; + ValidatedCommandEncoder* m_parent = nullptr; + bool m_pipelineBound = false; + bool m_meshPipelineBound = false; + bool m_viewportSet = false; + bool m_scissorSet = false; + bool m_ended = false; +}; + +} // namespace draco::rhi::validation diff --git a/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedSwapChain.cppm b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedSwapChain.cppm new file mode 100644 index 00000000..9f56245d --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedSwapChain.cppm @@ -0,0 +1,54 @@ +/// Validation wrapper for SwapChain. + +export module rhi.validation:validated_swap_chain; + +import core.stdtypes; +import core.status; +import rhi; +import :validated_queue; + +using namespace draco; + +export namespace draco::rhi::validation { + +class ValidatedSwapChain : public SwapChain { +public: + explicit ValidatedSwapChain(SwapChain* inner) : m_inner(inner) {} + + TextureFormat format() const override { return m_inner->format(); } + u32 width() const override { return m_inner->width(); } + u32 height() const override { return m_inner->height(); } + u32 bufferCount() const override { return m_inner->bufferCount(); } + u32 currentImageIndex() const override { return m_inner->currentImageIndex(); } + Texture* currentTexture() override { return m_inner->currentTexture(); } + TextureView* currentTextureView() override { return m_inner->currentTextureView(); } + + Status acquireNextImage() override { + if (m_imageAcquired) logWarning("[Validation] SwapChain::acquireNextImage: image already acquired"); + Status r = m_inner->acquireNextImage(); + if (r == ErrorCode::Ok) m_imageAcquired = true; + return r; + } + + Status present(Queue* queue) override { + if (!m_imageAcquired) logWarning("[Validation] SwapChain::present: no image acquired"); + m_imageAcquired = false; + // Unwrap validated queue so the inner swap chain gets the raw queue. + auto* vq = static_cast(queue); + return m_inner->present(vq ? vq->inner() : queue); + } + + Status resize(u32 w, u32 h) override { + if (m_imageAcquired) logError("[Validation] SwapChain::resize: cannot resize while image is acquired"); + if (w == 0 || h == 0) logError("[Validation] SwapChain::resize: dimensions are zero"); + return m_inner->resize(w, h); + } + + SwapChain* inner() const { return m_inner; } + +private: + SwapChain* m_inner; + bool m_imageAcquired = false; +}; + +} // namespace draco::rhi::validation diff --git a/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedTransferBatch.cppm b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedTransferBatch.cppm new file mode 100644 index 00000000..56b39827 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedTransferBatch.cppm @@ -0,0 +1,74 @@ +/// Validation wrapper for TransferBatch. + +module; + +#include + +export module rhi.validation:validated_transfer_batch; + +import core.stdtypes; +import core.status; +import rhi; +import :validated_fence; + +using namespace draco; + +export namespace draco::rhi::validation { + +class ValidatedTransferBatch : public TransferBatch { +public: + explicit ValidatedTransferBatch(TransferBatch* inner) : m_inner(inner) {} + + void writeBuffer(Buffer* dst, u64 dstOffset, std::span data) override { + if (m_destroyed) { logError("[Validation] TransferBatch::writeBuffer: batch already destroyed"); return; } + if (!dst) { logError("[Validation] TransferBatch::writeBuffer: dst is null"); return; } + if (data.size() == 0) { logWarning("[Validation] TransferBatch::writeBuffer: data is empty"); return; } + m_pendingWrites++; + m_inner->writeBuffer(dst, dstOffset, data); + } + + void writeTexture(Texture* dst, std::span data, const TextureDataLayout& layout, + Extent3D extent, u32 mipLevel, u32 arrayLayer) override { + if (m_destroyed) { logError("[Validation] TransferBatch::writeTexture: batch already destroyed"); return; } + if (!dst) { logError("[Validation] TransferBatch::writeTexture: dst is null"); return; } + if (data.size() == 0) { logWarning("[Validation] TransferBatch::writeTexture: data is empty"); return; } + if (extent.width == 0 || extent.height == 0) { logError("[Validation] TransferBatch::writeTexture: extent is zero"); return; } + m_pendingWrites++; + m_inner->writeTexture(dst, data, layout, extent, mipLevel, arrayLayer); + } + + Status submit() override { + if (m_destroyed) { logError("[Validation] TransferBatch::submit: batch already destroyed"); return ErrorCode::Unknown; } + if (m_pendingWrites == 0) logWarning("[Validation] TransferBatch::submit: no pending writes"); + m_pendingWrites = 0; + return m_inner->submit(); + } + + Status submitAsync(Fence* fence, u64 signalValue) override { + if (m_destroyed) { logError("[Validation] TransferBatch::submitAsync: batch already destroyed"); return ErrorCode::Unknown; } + if (!fence) { logError("[Validation] TransferBatch::submitAsync: fence is null"); return ErrorCode::Unknown; } + if (m_pendingWrites == 0) logWarning("[Validation] TransferBatch::submitAsync: no pending writes"); + m_pendingWrites = 0; + auto* vf = static_cast(fence); + Fence* innerFence = vf ? vf->inner() : fence; + if (vf) vf->trackSignal(signalValue); + return m_inner->submitAsync(innerFence, signalValue); + } + + void reset() override { m_pendingWrites = 0; m_inner->reset(); } + + void destroy() override { + if (m_destroyed) { logWarning("[Validation] TransferBatch::destroy: already destroyed"); return; } + m_destroyed = true; + m_inner->destroy(); + } + + TransferBatch* inner() const { return m_inner; } + +private: + TransferBatch* m_inner; + bool m_destroyed = false; + i32 m_pendingWrites = 0; +}; + +} // namespace draco::rhi::validation diff --git a/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedWiring.cppm b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedWiring.cppm new file mode 100644 index 00000000..33377069 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidatedWiring.cppm @@ -0,0 +1,28 @@ +/// Deferred implementations that break circular dependencies between +/// ValidatedBackend, ValidatedAdapter, and ValidatedDevice. + +export module rhi.validation:wiring; + +import core.status; +import rhi; +import :validated_backend; +import :validated_adapter; +import :validated_device; + +using namespace draco; + +namespace draco::rhi::validation { + +ValidatedAdapter* ValidatedBackend::createValidatedAdapter(Adapter* inner) { + return new ValidatedAdapter(inner); +} + +Status ValidatedAdapter::createDevice(const DeviceDesc& desc, Device*& out) { + Device* innerDevice = nullptr; + Status r = m_inner->createDevice(desc, innerDevice); + if (r != ErrorCode::Ok || !innerDevice) { out = nullptr; return r; } + out = new ValidatedDevice(innerDevice); + return ErrorCode::Ok; +} + +} // namespace draco::rhi::validation diff --git a/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidationModule.cppm b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidationModule.cppm new file mode 100644 index 00000000..274250e5 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidationModule.cppm @@ -0,0 +1,17 @@ +/// Validation layer module surface. +/// Provides createValidatedBackend() as the entry point. + +export module rhi.validation; + +export import :validated_backend; +export import :validated_adapter; +export import :validated_device; +export import :validated_queue; +export import :validated_command_pool; +export import :validated_command_encoder; +export import :validated_render_pass_encoder; +export import :validated_render_bundle_encoder; +export import :validated_compute_pass_encoder; +export import :validated_swap_chain; +export import :validated_fence; +export import :validated_transfer_batch; diff --git a/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidationRhi.test.cpp b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidationRhi.test.cpp new file mode 100644 index 00000000..7c2027da --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Validation/ValidationRhi.test.cpp @@ -0,0 +1,45 @@ +#include + +import core; +import rhi; +import rhi.null; +import rhi.validation; + +using namespace draco; +using namespace draco::rhi; + +TEST_CASE("rhi.validation: wraps a backend and forwards valid calls") +{ + Backend* inner = nullptr; + REQUIRE(null::createNullBackend(inner).isOk()); + + validation::ValidatedBackend vb(inner); + auto adapters = vb.enumerateAdapters(); + REQUIRE(adapters.size() >= 1u); + + Device* device = nullptr; + REQUIRE(adapters[0]->createDevice(DeviceDesc{}, device).isOk()); + REQUIRE(device != nullptr); + + BufferDesc bufferDesc{}; + bufferDesc.size = 128; + Buffer* buffer = nullptr; + CHECK(device->createBuffer(bufferDesc, buffer).isOk()); + CHECK(buffer != nullptr); +} + +TEST_CASE("rhi.validation: catches invalid usage (null texture)") +{ + Backend* inner = nullptr; + REQUIRE(null::createNullBackend(inner).isOk()); + + validation::ValidatedBackend vb(inner); + Device* device = nullptr; + REQUIRE(vb.enumerateAdapters()[0]->createDevice(DeviceDesc{}, device).isOk()); + + // The validation layer rejects a null texture (and logs a diagnostic) instead + // of forwarding it to the backend. + TextureView* view = nullptr; + CHECK_FALSE(device->createTextureView(nullptr, TextureViewDesc{}, view).isOk()); + CHECK(view == nullptr); +} diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vertex.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vertex.cppm deleted file mode 100644 index fa2356e5..00000000 --- a/Engine/cpp/Runtime/Rendering/RHI/Vertex.cppm +++ /dev/null @@ -1,51 +0,0 @@ -module; - -#include -#include - -export module rendering.rhi.vertex; -import core.stdtypes; - -export namespace draco::rendering::rhi { - enum class Attrib { - Position, - Color0, - TexCoord0, - Normal, - Tangent - }; - - enum class AttribType { - Float, - Uint8 - }; - - struct VertexElement { - Attrib attrib; - u16 count; - AttribType type; - bool normalized = false; - }; - - struct VertexLayoutDesc { - std::vector elements; - }; - - struct alignas(u32) TexturedVertex { - float x, y, z; - float u, v; - u32 color; - }; - static_assert(sizeof(TexturedVertex) == 24); - - // Helper to get the standard layout for the current vertex struct - inline VertexLayoutDesc getTexturedVertexLayout() { - return { - .elements = { - { Attrib::Position, 3, AttribType::Float }, - { Attrib::TexCoord0, 2, AttribType::Float }, - { Attrib::Color0, 4, AttribType::Uint8, true } - } - }; - } -} diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkAccelStruct.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkAccelStruct.cppm new file mode 100644 index 00000000..9c4c8457 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkAccelStruct.cppm @@ -0,0 +1,100 @@ +/// Vulkan implementation of AccelStruct (acceleration structure). + +module; + +#include "VkIncludes.h" + +export module rhi.vk:accel_struct; + +import core.stdtypes; +import core.status; +import rhi; +import :adapter; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkAccelStructImpl : public AccelStruct { +public: + Status init(VkDevice device, VkAdapterImpl* adapter, const AccelStructDesc& desc, u64 size) { + m_type = desc.type; + m_device = device; + + // Create buffer for the acceleration structure. + VkBufferCreateInfo bufCi{}; + bufCi.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufCi.size = size; + bufCi.usage = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + if (vkCreateBuffer(device, &bufCi, nullptr, &m_buffer) != VK_SUCCESS) return ErrorCode::Unknown; + + VkMemoryRequirements memReqs{}; + vkGetBufferMemoryRequirements(device, m_buffer, &memReqs); + + i32 memType = adapter->findMemoryType(static_cast(memReqs.memoryTypeBits), VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (memType < 0) { vkDestroyBuffer(device, m_buffer, nullptr); m_buffer = VK_NULL_HANDLE; return ErrorCode::Unknown; } + + VkMemoryAllocateFlagsInfo allocFlags{}; + allocFlags.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO; + allocFlags.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT; + + VkMemoryAllocateInfo ai{}; + ai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + ai.pNext = &allocFlags; + ai.allocationSize = memReqs.size; + ai.memoryTypeIndex = static_cast(memType); + if (vkAllocateMemory(device, &ai, nullptr, &m_memory) != VK_SUCCESS) { + vkDestroyBuffer(device, m_buffer, nullptr); m_buffer = VK_NULL_HANDLE; return ErrorCode::Unknown; + } + vkBindBufferMemory(device, m_buffer, m_memory, 0); + + // Create acceleration structure. + VkAccelerationStructureCreateInfoKHR asCi{}; + asCi.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR; + asCi.buffer = m_buffer; + asCi.size = size; + asCi.type = desc.type == AccelStructType::TopLevel + ? VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR + : VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; + + auto pfnCreate = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkCreateAccelerationStructureKHR")); + if (!pfnCreate || pfnCreate(device, &asCi, nullptr, &m_accel) != VK_SUCCESS) return ErrorCode::Unknown; + + // Get device address. + VkAccelerationStructureDeviceAddressInfoKHR addrInfo{}; + addrInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR; + addrInfo.accelerationStructure = m_accel; + auto pfnAddr = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkGetAccelerationStructureDeviceAddressKHR")); + if (pfnAddr) m_deviceAddress = pfnAddr(device, &addrInfo); + + return ErrorCode::Ok; + } + + void cleanup(VkDevice device) { + if (m_accel != VK_NULL_HANDLE) { + auto pfn = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkDestroyAccelerationStructureKHR")); + if (pfn) pfn(device, m_accel, nullptr); + m_accel = VK_NULL_HANDLE; + } + if (m_memory != VK_NULL_HANDLE) { vkFreeMemory(device, m_memory, nullptr); m_memory = VK_NULL_HANDLE; } + if (m_buffer != VK_NULL_HANDLE) { vkDestroyBuffer(device, m_buffer, nullptr); m_buffer = VK_NULL_HANDLE; } + } + + AccelStructType type() const override { return m_type; } + u64 deviceAddress() const override { return m_deviceAddress; } + + [[nodiscard]] VkAccelerationStructureKHR handle() const { return m_accel; } + +private: + VkAccelerationStructureKHR m_accel = VK_NULL_HANDLE; + VkBuffer m_buffer = VK_NULL_HANDLE; + VkDeviceMemory m_memory = VK_NULL_HANDLE; + VkDevice m_device = VK_NULL_HANDLE; + AccelStructType m_type = AccelStructType::BottomLevel; + u64 m_deviceAddress = 0; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkAdapter.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkAdapter.cppm new file mode 100644 index 00000000..c4913a50 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkAdapter.cppm @@ -0,0 +1,224 @@ +/// Vulkan implementation of Adapter. +/// Queries physical device properties, features, and queue families. +/// Provides feature detection and queue family selection. + +module; + +#include "VkIncludes.h" +#include +#include +#include + +#include + +export module rhi.vk:adapter; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkDeviceImpl; // forward + +class VkAdapterImpl : public Adapter { +public: + VkAdapterImpl(VkPhysicalDevice physicalDevice, VkInstance instance) + : m_physicalDevice(physicalDevice), m_instance(instance) + { + vkGetPhysicalDeviceProperties(m_physicalDevice, &m_properties); + vkGetPhysicalDeviceFeatures(m_physicalDevice, &m_features10); + vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &m_memoryProperties); + + u32 qfCount = 0; + vkGetPhysicalDeviceQueueFamilyProperties(m_physicalDevice, &qfCount, nullptr); + m_queueFamilies.resize(qfCount); + vkGetPhysicalDeviceQueueFamilyProperties(m_physicalDevice, &qfCount, m_queueFamilies.data()); + + queryExtensionSupport(); + } + + // ---- Adapter interface ---- + + void getInfo(AdapterInfo& out) override { + out.name = std::u8string(std::u8string_view(reinterpret_cast(m_properties.deviceName))); + out.vendorId = m_properties.vendorID; + out.deviceId = m_properties.deviceID; + + switch (m_properties.deviceType) { + case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: out.type = AdapterType::DiscreteGpu; break; + case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: out.type = AdapterType::IntegratedGpu; break; + case VK_PHYSICAL_DEVICE_TYPE_CPU: out.type = AdapterType::Cpu; break; + default: out.type = AdapterType::Unknown; break; + } + + out.supportedFeatures = buildFeatures(); + } + + Status createDevice(const DeviceDesc& desc, Device*& out) override; + + // ---- Feature building ---- + + DeviceFeatures buildFeatures() const { + DeviceFeatures f{}; + + f.bindlessDescriptors = m_supportsDescriptorIndexing; + f.timestampQueries = m_properties.limits.timestampComputeAndGraphics; + f.multiDrawIndirect = m_features10.multiDrawIndirect; + f.depthClamp = m_features10.depthClamp; + f.fillModeWireframe = m_features10.fillModeNonSolid; + f.textureCompressionBC = m_features10.textureCompressionBC; + f.textureCompressionASTC = m_features10.textureCompressionASTC_LDR; + f.independentBlend = m_features10.independentBlend; + f.multiViewport = m_features10.multiViewport; + f.meshShaders = m_supportsMeshShader; + f.rayTracing = m_supportsRayTracing; + f.pipelineStatisticsQueries = m_features10.pipelineStatisticsQuery; + + // Mesh shader limits. + if (m_supportsMeshShader) { + VkPhysicalDeviceMeshShaderPropertiesEXT meshProps{}; + meshProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_PROPERTIES_EXT; + VkPhysicalDeviceProperties2 p2{}; + p2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + p2.pNext = &meshProps; + vkGetPhysicalDeviceProperties2(m_physicalDevice, &p2); + f.maxMeshOutputVertices = meshProps.maxMeshOutputVertices; + f.maxMeshOutputPrimitives = meshProps.maxMeshOutputPrimitives; + f.maxMeshWorkgroupSize = meshProps.maxMeshWorkGroupInvocations; + f.maxTaskWorkgroupSize = meshProps.maxTaskWorkGroupInvocations; + } + + // Limits. + f.maxBindGroups = m_properties.limits.maxBoundDescriptorSets; + f.maxBindingsPerGroup = m_properties.limits.maxDescriptorSetUniformBuffers; + f.maxPushConstantSize = m_properties.limits.maxPushConstantsSize; + f.maxTextureDimension2D = m_properties.limits.maxImageDimension2D; + f.maxTextureArrayLayers = m_properties.limits.maxImageArrayLayers; + f.maxComputeWorkgroupSizeX = m_properties.limits.maxComputeWorkGroupSize[0]; + f.maxComputeWorkgroupSizeY = m_properties.limits.maxComputeWorkGroupSize[1]; + f.maxComputeWorkgroupSizeZ = m_properties.limits.maxComputeWorkGroupSize[2]; + f.maxComputeWorkgroupsPerDimension = m_properties.limits.maxComputeWorkGroupCount[0]; + f.maxBufferSize = static_cast(m_properties.limits.maxStorageBufferRange); + f.minUniformBufferOffsetAlignment = static_cast(m_properties.limits.minUniformBufferOffsetAlignment); + f.minStorageBufferOffsetAlignment = static_cast(m_properties.limits.minStorageBufferOffsetAlignment); + f.timestampPeriodNs = static_cast(m_properties.limits.timestampPeriod); + + return f; + } + + // ---- Queue family selection ---- + + /// Finds the best queue family index for the given type. + /// Prefers dedicated families for Compute and Transfer. + i32 findQueueFamily(QueueType type) const { + const auto count = static_cast(m_queueFamilies.size()); + switch (type) { + case QueueType::Graphics: + for (i32 i = 0; i < count; ++i) + if (m_queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) return i; + break; + case QueueType::Compute: + // Prefer dedicated (no graphics). + for (i32 i = 0; i < count; ++i) { + auto f = m_queueFamilies[i].queueFlags; + if ((f & VK_QUEUE_COMPUTE_BIT) && !(f & VK_QUEUE_GRAPHICS_BIT)) return i; + } + for (i32 i = 0; i < count; ++i) + if (m_queueFamilies[i].queueFlags & VK_QUEUE_COMPUTE_BIT) return i; + break; + case QueueType::Transfer: + // Prefer dedicated (no graphics or compute). + for (i32 i = 0; i < count; ++i) { + auto f = m_queueFamilies[i].queueFlags; + if ((f & VK_QUEUE_TRANSFER_BIT) && !(f & VK_QUEUE_GRAPHICS_BIT) && !(f & VK_QUEUE_COMPUTE_BIT)) + return i; + } + for (i32 i = 0; i < count; ++i) + if (m_queueFamilies[i].queueFlags & VK_QUEUE_TRANSFER_BIT) return i; + break; + } + return -1; + } + + /// Finds a memory type index matching the filter and property requirements. + i32 findMemoryType(u32 typeFilter, VkMemoryPropertyFlags properties) const { + for (u32 i = 0; i < m_memoryProperties.memoryTypeCount; ++i) { + if ((typeFilter & (1 << i)) && + (m_memoryProperties.memoryTypes[i].propertyFlags & properties) == properties) + return static_cast(i); + } + return -1; + } + + /// Maps a MemoryLocation to VkMemoryPropertyFlags. + static VkMemoryPropertyFlags getMemoryFlags(MemoryLocation loc) { + switch (loc) { + case MemoryLocation::GpuOnly: return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + case MemoryLocation::CpuToGpu: return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + case MemoryLocation::GpuToCpu: return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT; + case MemoryLocation::Auto: return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + } + return 0; + } + + // ---- Internal accessors ---- + [[nodiscard]] VkPhysicalDevice physicalDevice() const { return m_physicalDevice; } + [[nodiscard]] const VkPhysicalDeviceProperties& properties() const { return m_properties; } + [[nodiscard]] const VkPhysicalDeviceFeatures& features10() const { return m_features10; } + [[nodiscard]] const VkPhysicalDeviceMemoryProperties& memoryProperties() const { return m_memoryProperties; } + [[nodiscard]] const std::vector& queueFamilies() const { return m_queueFamilies; } + + [[nodiscard]] bool supportsDescriptorIndexing() const { return m_supportsDescriptorIndexing; } + [[nodiscard]] bool supportsMeshShader() const { return m_supportsMeshShader; } + [[nodiscard]] bool supportsRayTracing() const { return m_supportsRayTracing; } + +private: + void queryExtensionSupport() { + u32 extCount = 0; + vkEnumerateDeviceExtensionProperties(m_physicalDevice, nullptr, &extCount, nullptr); + std::vector exts(extCount); + vkEnumerateDeviceExtensionProperties(m_physicalDevice, nullptr, &extCount, exts.data()); + + for (const auto& ext : exts) { + const char* name = ext.extensionName; + if (std::strcmp(name, "VK_KHR_dynamic_rendering") == 0) m_supportsDynamicRendering = true; + if (std::strcmp(name, "VK_KHR_timeline_semaphore") == 0) m_supportsTimelineSemaphore = true; + if (std::strcmp(name, "VK_KHR_synchronization2") == 0) m_supportsSynchronization2 = true; + if (std::strcmp(name, "VK_EXT_descriptor_indexing") == 0) m_supportsDescriptorIndexing = true; + if (std::strcmp(name, "VK_EXT_mesh_shader") == 0) m_supportsMeshShader = true; + if (std::strcmp(name, "VK_KHR_ray_tracing_pipeline") == 0) m_supportsRayTracing = true; + } + + // Vulkan 1.3+: these are core. + u32 major = VK_API_VERSION_MAJOR(m_properties.apiVersion); + u32 minor = VK_API_VERSION_MINOR(m_properties.apiVersion); + if (major > 1 || (major == 1 && minor >= 3)) { + m_supportsDynamicRendering = true; + m_supportsTimelineSemaphore = true; + m_supportsSynchronization2 = true; + m_supportsDescriptorIndexing = true; + } else if (major == 1 && minor >= 2) { + m_supportsTimelineSemaphore = true; + m_supportsDescriptorIndexing = true; + } + } + + VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; + VkInstance m_instance = VK_NULL_HANDLE; + VkPhysicalDeviceProperties m_properties{}; + VkPhysicalDeviceFeatures m_features10{}; + VkPhysicalDeviceMemoryProperties m_memoryProperties{}; + std::vector m_queueFamilies; + + bool m_supportsDynamicRendering = false; + bool m_supportsTimelineSemaphore = false; + bool m_supportsSynchronization2 = false; + bool m_supportsDescriptorIndexing = false; + bool m_supportsMeshShader = false; + bool m_supportsRayTracing = false; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBackend.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBackend.cppm new file mode 100644 index 00000000..de0fde99 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBackend.cppm @@ -0,0 +1,351 @@ +/// Vulkan implementation of Backend. +/// Creates VkInstance, enumerates physical devices, creates surfaces. + +module; + +#include "VkIncludes.h" +#include +#include + +#include +#include +#include +#include + +export module rhi.vk:backend; + +import core.stdtypes; +import core.status; +import rhi; +import :adapter; +import :surface; + +using namespace draco; + +// Linux surface types - forward-declared to avoid header pollution. In the module +// purview (not the GMF): GCC requires the GMF to contain only #includes. +#if defined(__linux__) +extern "C" { typedef struct _XDisplay Display; } +typedef unsigned long XID; + +struct VkXlibSurfaceCreateInfoKHR { + VkStructureType sType; + const void* pNext; + VkFlags flags; + Display* dpy; + XID window; +}; +#define VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR static_cast(1000004000) +#define VK_KHR_XLIB_SURFACE_EXTENSION_NAME "VK_KHR_xlib_surface" +using PFN_vkCreateXlibSurfaceKHR = VkResult(VKAPI_PTR*)( + VkInstance, const VkXlibSurfaceCreateInfoKHR*, const VkAllocationCallbacks*, VkSurfaceKHR*); + +extern "C" { struct wl_display; struct wl_surface; } + +struct VkWaylandSurfaceCreateInfoKHR { + VkStructureType sType; + const void* pNext; + VkFlags flags; + wl_display* display; + wl_surface* surface; +}; +#define VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR static_cast(1000006000) +#define VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME "VK_KHR_wayland_surface" +using PFN_vkCreateWaylandSurfaceKHR = VkResult(VKAPI_PTR*)( + VkInstance, const VkWaylandSurfaceCreateInfoKHR*, const VkAllocationCallbacks*, VkSurfaceKHR*); +#endif // __linux__ + +export namespace draco::rhi::vk { + +/// Configuration for VK backend creation. +struct VkBackendDesc { + bool enableValidation = false; +}; + +/// Vulkan implementation of Backend. +class VkBackendImpl : public Backend { +public: + ~VkBackendImpl() override { destroyImpl(); } + + // ---- Backend interface ---- + + std::span enumerateAdapters() override { + return std::span(m_adapterPtrs.data(), m_adapterPtrs.size()); + } + + Status createSurface(void* windowHandle, void* +#if defined(__linux__) + displayHandle +#else + /*displayHandle*/ +#endif + , Surface*& out) override { + out = nullptr; + if (!windowHandle) { + logError("VkBackend: window handle is null"); + return ErrorCode::InvalidArgument; + } + + VkSurfaceKHR vkSurface = VK_NULL_HANDLE; + +#ifdef _WIN32 + VkWin32SurfaceCreateInfoKHR ci{}; + ci.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; + ci.hinstance = ::GetModuleHandleW(nullptr); + ci.hwnd = reinterpret_cast(windowHandle); + + VkResult vr = vkCreateWin32SurfaceKHR(m_instance, &ci, nullptr, &vkSurface); + if (vr != VK_SUCCESS) { + logErrorf("VkBackend: vkCreateWin32SurfaceKHR failed (%d)", static_cast(vr)); + return ErrorCode::Unknown; + } +#elif defined(__linux__) + // Detect surface type from the window handles SDL3 actually provides. + // SDL3 may choose Wayland even when DISPLAY is set (e.g. on Arch). + // displayHandle == X11 Display* for X11, or wl_display* for Wayland. + // We try Wayland first (if displayHandle looks like wl_display and we have the ext), + // then fall back to X11. + VkResult vr = VK_ERROR_INITIALIZATION_FAILED; + bool triedWayland = false, triedX11 = false; + + // If we have Wayland ext and the display handle could be wl_display, + // try Wayland. The SDL3 window's displayHandle returns wl_display* + // when SDL chose Wayland, or X11 Display* when SDL chose X11. + // We can't easily distinguish the pointer types, so we try Wayland first + // if the extension is available and WAYLAND_DISPLAY is set, then X11. + if (m_hasWayland && std::getenv("WAYLAND_DISPLAY")) { + auto fn = reinterpret_cast( + vkGetInstanceProcAddr(m_instance, "vkCreateWaylandSurfaceKHR")); + if (fn) { + VkWaylandSurfaceCreateInfoKHR ci{}; + ci.sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR; + ci.display = reinterpret_cast(displayHandle); + ci.surface = reinterpret_cast(windowHandle); + vr = fn(m_instance, &ci, nullptr, &vkSurface); + triedWayland = true; + } + } + + // Fall back to X11 if Wayland failed or wasn't tried. + if (vr != VK_SUCCESS && m_hasXlib) { + auto fn = reinterpret_cast( + vkGetInstanceProcAddr(m_instance, "vkCreateXlibSurfaceKHR")); + if (fn) { + VkXlibSurfaceCreateInfoKHR ci{}; + ci.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR; + ci.dpy = reinterpret_cast(displayHandle); + ci.window = static_cast(reinterpret_cast(windowHandle)); + vr = fn(m_instance, &ci, nullptr, &vkSurface); + triedX11 = true; + } + } + + if (vr != VK_SUCCESS) { + logErrorf("VkBackend: surface creation failed (tried wayland=%d, x11=%d)", triedWayland, triedX11); + return ErrorCode::Unknown; + } + if (vr != VK_SUCCESS) { + logErrorf("VkBackend: surface creation failed (%d)", static_cast(vr)); + return ErrorCode::Unknown; + } +#else + (void)displayHandle; + logError("VkBackend: surface creation not supported on this platform"); + return ErrorCode::NotSupported; +#endif + + out = new VkSurfaceImpl(vkSurface, m_instance); + return ErrorCode::Ok; + } + + void destroy() override { + destroyImpl(); + delete this; + } + + // ---- Internal ---- + + [[nodiscard]] VkInstance instance() const { return m_instance; } + [[nodiscard]] bool validationEnabled() const { return m_validationEnabled; } + +private: + friend Status createBackend(const VkBackendDesc& desc, Backend*& out); + + Status init(bool enableValidation) { + m_validationEnabled = enableValidation; + // Announce validation state so a perf run can confirm the layers are OFF (they add real overhead). + std::fprintf(stderr, "[Vulkan] validation layers: %s\n", enableValidation ? "ENABLED" : "DISABLED"); + + // ---- Application info ---- + VkApplicationInfo appInfo{}; + appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + appInfo.pApplicationName = "Draconic"; + appInfo.applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0); + appInfo.pEngineName = "Draconic"; + appInfo.engineVersion = VK_MAKE_API_VERSION(0, 1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_3; + + // ---- Extensions ---- + std::vector extensions; + extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + +#ifdef _WIN32 + extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); +#elif defined(__linux__) + { + u32 availCount = 0; + vkEnumerateInstanceExtensionProperties(nullptr, &availCount, nullptr); + std::vector avail(availCount); + vkEnumerateInstanceExtensionProperties(nullptr, &availCount, avail.data()); + auto hasExt = [&](const char* name) { + for (const auto& e : avail) + if (std::strcmp(e.extensionName, name) == 0) return true; + return false; + }; + + bool hasXlib = hasExt(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); + bool hasWayland = hasExt(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); + + // Enable both surface extensions if available. The actual surface + // type is detected at createSurface time from the window handles, + // because SDL3 may choose Wayland even when DISPLAY is set. + if (hasXlib) extensions.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); + if (hasWayland) extensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); + m_hasXlib = hasXlib; m_hasWayland = hasWayland; + if (!hasXlib && !hasWayland) { + logError("VkBackend: no surface extension available (need xlib or wayland)"); + return ErrorCode::Unknown; + } + } +#endif + + if (enableValidation) extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + + // ---- Layers ---- + std::vector layers; + if (enableValidation) layers.push_back("VK_LAYER_KHRONOS_validation"); + + // ---- Create instance ---- + VkInstanceCreateInfo ci{}; + ci.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + ci.pApplicationInfo = &appInfo; + ci.enabledExtensionCount = static_cast(extensions.size()); + ci.ppEnabledExtensionNames = extensions.data(); + ci.enabledLayerCount = static_cast(layers.size()); + ci.ppEnabledLayerNames = layers.data(); + + VkResult vr = vkCreateInstance(&ci, nullptr, &m_instance); + if (vr != VK_SUCCESS) { + logErrorf("VkBackend: vkCreateInstance failed (%d)", static_cast(vr)); + return ErrorCode::Unknown; + } + + if (enableValidation) setupDebugMessenger(); + + enumeratePhysicalDevices(); + + isInitialized = true; + return ErrorCode::Ok; + } + + void setupDebugMessenger() { + VkDebugUtilsMessengerCreateInfoEXT ci{}; + ci.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + ci.messageSeverity = + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + ci.messageType = + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + ci.pfnUserCallback = &debugCallback; + + auto pfn = reinterpret_cast( + vkGetInstanceProcAddr(m_instance, "vkCreateDebugUtilsMessengerEXT")); + if (pfn) pfn(m_instance, &ci, nullptr, &m_debugMessenger); + } + + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT /*types*/, + const VkDebugUtilsMessengerCallbackDataEXT* data, + void* /*userData*/) + { + // Print straight to stderr (not only LogErrorf, whose sink may be swallowed) so validation + // is actually visible in dev builds - the point of running with it on. + if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { + std::fprintf(stderr, "[Vulkan ERROR] %s\n", data->pMessage); + logErrorf("[Vulkan ERROR] %s", data->pMessage); + } else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + std::fprintf(stderr, "[Vulkan WARN] %s\n", data->pMessage); + logWarningf("[Vulkan WARN] %s", data->pMessage); + } + return VK_FALSE; + } + + void enumeratePhysicalDevices() { + u32 count = 0; + vkEnumeratePhysicalDevices(m_instance, &count, nullptr); + if (count == 0) return; + + std::vector devices(count); + vkEnumeratePhysicalDevices(m_instance, &count, devices.data()); + + m_adapters.reserve(count); + m_adapterPtrs.reserve(count); + for (VkPhysicalDevice pd : devices) { + auto* a = new VkAdapterImpl(pd, m_instance); + m_adapters.push_back(a); + m_adapterPtrs.push_back(a); + } + + // Expose adapters best-GPU-first; callers take [0]. See Backend::enumerateAdapters. + sortAdaptersByPreference(m_adapterPtrs); + } + + void destroyImpl() { + for (auto* a : m_adapters) delete a; + m_adapters.clear(); + m_adapterPtrs.clear(); + + if (m_debugMessenger != VK_NULL_HANDLE) { + auto pfn = reinterpret_cast( + vkGetInstanceProcAddr(m_instance, "vkDestroyDebugUtilsMessengerEXT")); + if (pfn) pfn(m_instance, m_debugMessenger, nullptr); + m_debugMessenger = VK_NULL_HANDLE; + } + + if (m_instance != VK_NULL_HANDLE) { + vkDestroyInstance(m_instance, nullptr); + m_instance = VK_NULL_HANDLE; + } + + isInitialized = false; + } + + VkInstance m_instance = VK_NULL_HANDLE; + VkDebugUtilsMessengerEXT m_debugMessenger = VK_NULL_HANDLE; + bool m_validationEnabled = false; + std::vector m_adapters; + std::vector m_adapterPtrs; + +#if defined(__linux__) + bool m_hasXlib = false; + bool m_hasWayland = false; +#endif +}; + +/// Creates a Vulkan backend. Caller owns the returned pointer - dispose via destroy(). +[[nodiscard]] Status createBackend(const VkBackendDesc& desc, Backend*& out) { + out = nullptr; + auto* b = new VkBackendImpl(); + Status r = b->init(desc.enableValidation); + if (r != ErrorCode::Ok) { + delete b; + return r; + } + out = b; + return ErrorCode::Ok; +} + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBarrierHelper.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBarrierHelper.cppm new file mode 100644 index 00000000..c4c2351a --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBarrierHelper.cppm @@ -0,0 +1,69 @@ +/// Converts ResourceState to Vulkan synchronization2 stage/access/layout. + +module; + +#include "VkIncludes.h" + +export module rhi.vk:barrier_helper; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::vk { + +struct StageAccess { + u64 stageMask = 0; + u64 accessMask = 0; +}; + +inline StageAccess getStageAccess(ResourceState state) { + StageAccess sa{}; + auto s = static_cast(state); + auto has = [&](ResourceState f) { return (s & static_cast(f)) != 0; }; + + if (has(ResourceState::VertexBuffer)) { sa.stageMask |= VK_PIPELINE_STAGE_2_VERTEX_INPUT_BIT; sa.accessMask |= VK_ACCESS_2_VERTEX_ATTRIBUTE_READ_BIT; } + if (has(ResourceState::IndexBuffer)) { sa.stageMask |= VK_PIPELINE_STAGE_2_INDEX_INPUT_BIT; sa.accessMask |= VK_ACCESS_2_INDEX_READ_BIT; } + if (has(ResourceState::UniformBuffer)) { sa.stageMask |= VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; sa.accessMask |= VK_ACCESS_2_UNIFORM_READ_BIT; } + if (has(ResourceState::ShaderRead)) { sa.stageMask |= VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; sa.accessMask |= VK_ACCESS_2_SHADER_READ_BIT; } + if (has(ResourceState::ShaderWrite)) { sa.stageMask |= VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; sa.accessMask |= VK_ACCESS_2_SHADER_WRITE_BIT; } + if (has(ResourceState::RenderTarget)) { sa.stageMask |= VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT; sa.accessMask |= VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT; } + if (has(ResourceState::DepthStencilWrite)){ sa.stageMask |= VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; sa.accessMask |= VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT; } + if (has(ResourceState::DepthStencilRead)) { sa.stageMask |= VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT; sa.accessMask |= VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT; } + if (has(ResourceState::IndirectArgument)) { sa.stageMask |= VK_PIPELINE_STAGE_2_DRAW_INDIRECT_BIT; sa.accessMask |= VK_ACCESS_2_INDIRECT_COMMAND_READ_BIT; } + if (has(ResourceState::CopySrc)) { sa.stageMask |= VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT; sa.accessMask |= VK_ACCESS_2_TRANSFER_READ_BIT; } + if (has(ResourceState::CopyDst)) { sa.stageMask |= VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT; sa.accessMask |= VK_ACCESS_2_TRANSFER_WRITE_BIT; } + if (has(ResourceState::Present)) { sa.stageMask |= VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; } + if (has(ResourceState::AccelStructRead)) { sa.stageMask |= VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; sa.accessMask |= VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR; } + if (has(ResourceState::AccelStructWrite)) { sa.stageMask |= VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR; sa.accessMask |= VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR; } + if (has(ResourceState::General)) { sa.stageMask |= VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; sa.accessMask |= VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT; } + + if (sa.stageMask == 0) sa.stageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + return sa; +} + +inline VkImageLayout getImageLayout(ResourceState state, TextureFormat format = TextureFormat::Undefined) { + auto s = static_cast(state); + auto has = [&](ResourceState f) { return (s & static_cast(f)) != 0; }; + + if (has(ResourceState::Present)) return VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + if (has(ResourceState::RenderTarget)) return VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + if (has(ResourceState::DepthStencilWrite))return VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + if (has(ResourceState::DepthStencilRead)) return VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; + if (has(ResourceState::ShaderRead)) { + // Depth/stencil textures must use DEPTH_STENCIL_READ_ONLY when sampled, + // not SHADER_READ_ONLY - the latter is only for color textures. + if (isDepthFormat(format)) + return VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; + return VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + } + if (has(ResourceState::ShaderWrite)) return VK_IMAGE_LAYOUT_GENERAL; + if (has(ResourceState::CopySrc)) return VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + if (has(ResourceState::CopyDst)) return VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + if (has(ResourceState::General)) return VK_IMAGE_LAYOUT_GENERAL; + return VK_IMAGE_LAYOUT_UNDEFINED; +} + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBindGroup.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBindGroup.cppm new file mode 100644 index 00000000..b6fc8bf2 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBindGroup.cppm @@ -0,0 +1,210 @@ +/// Vulkan implementation of BindGroup. + +module; + +#include "VkIncludes.h" +#include +#include + + +export module rhi.vk:bind_group; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :binding_shifts; +import :bind_group_layout; +import :buffer; +import :texture; +import :texture_view; +import :sampler; +import :accel_struct; +import :descriptor_pool_manager; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkBindGroupImpl : public BindGroup { +public: + Status init(VkDevice device, VkDescriptorPoolManager* poolMgr, const BindGroupDesc& desc, + const BindingShifts& shifts = {}) { + m_device = device; + m_shifts = shifts; + m_layout = static_cast(desc.layout); + if (!m_layout) return ErrorCode::Unknown; + + VkDescriptorPool pool; + if (poolMgr->allocate(m_layout->handle(), pool, m_layout->hasBindless(), m_layout->bindlessCount()) != ErrorCode::Ok) + return ErrorCode::Unknown; + m_pool = pool; + m_set = poolMgr->lastAllocatedSet(); + + writeDescriptors(device, desc); + return ErrorCode::Ok; + } + + void cleanup(VkDevice device, VkDescriptorPoolManager* poolMgr) { + if (m_set != VK_NULL_HANDLE && m_pool != VK_NULL_HANDLE) { + poolMgr->free(m_pool, m_set); + m_set = VK_NULL_HANDLE; + m_pool = VK_NULL_HANDLE; + } + (void)device; + } + + BindGroupLayout* layout() override { return m_layout; } + + void updateBindless(std::span entries) override { + if (entries.size() == 0) return; + + std::vector writes; + std::vector bufInfos; + std::vector imgInfos; + bufInfos.reserve(entries.size()); + imgInfos.reserve(entries.size()); + + auto layoutEntries = m_layout->entries(); + + for (usize i = 0; i < entries.size(); ++i) { + const auto& e = entries[i]; + if (e.layoutIndex >= static_cast(layoutEntries.size())) continue; + const auto& le = layoutEntries[e.layoutIndex]; + + VkWriteDescriptorSet w{}; + w.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + w.dstSet = m_set; + w.dstBinding = m_shifts.apply(le.type, le.binding); + w.dstArrayElement = e.arrayIndex; + w.descriptorCount = 1; + w.descriptorType = toVkDescriptorType(le); + + switch (le.type) { + case BindingType::BindlessStorageBuffers: + if (auto* vkBuf = static_cast(e.buffer)) { + VkDescriptorBufferInfo bi{}; bi.buffer = vkBuf->handle(); bi.offset = e.bufferOffset; + bi.range = e.bufferSize > 0 ? e.bufferSize : VK_WHOLE_SIZE; + bufInfos.push_back(bi); w.pBufferInfo = &bufInfos.back(); + } else continue; + break; + case BindingType::BindlessTextures: case BindingType::BindlessStorageTextures: + if (auto* vkView = static_cast(e.textureView)) { + VkDescriptorImageInfo ii{}; ii.imageView = vkView->handle(); + ii.imageLayout = le.type == BindingType::BindlessTextures + ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_GENERAL; + imgInfos.push_back(ii); w.pImageInfo = &imgInfos.back(); + } else continue; + break; + case BindingType::BindlessSamplers: + if (auto* vkSamp = static_cast(e.sampler)) { + VkDescriptorImageInfo ii{}; ii.sampler = vkSamp->handle(); + imgInfos.push_back(ii); w.pImageInfo = &imgInfos.back(); + } else continue; + break; + default: continue; + } + writes.push_back(w); + } + + if (!writes.empty()) + vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); + } + + [[nodiscard]] VkDescriptorSet handle() const { return m_set; } + +private: + void writeDescriptors(VkDevice device, const BindGroupDesc& desc) { + if (desc.entries.size() == 0) return; + + auto layoutEntries = m_layout->entries(); + + std::vector writes; + std::vector bufInfos; + std::vector imgInfos; + std::vector asWriteInfos; + std::vector asHandles; + bufInfos.reserve(desc.entries.size()); + imgInfos.reserve(desc.entries.size()); + asWriteInfos.reserve(desc.entries.size()); + asHandles.reserve(desc.entries.size()); + + usize entryIdx = 0; + for (usize i = 0; i < layoutEntries.size(); ++i) { + const auto& le = layoutEntries[i]; + // Skip bindless entries. + switch (le.type) { + case BindingType::BindlessTextures: case BindingType::BindlessSamplers: + case BindingType::BindlessStorageBuffers: case BindingType::BindlessStorageTextures: + continue; + default: break; + } + if (entryIdx >= desc.entries.size()) break; + const auto& e = desc.entries[entryIdx++]; + + VkWriteDescriptorSet w{}; + w.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + w.dstSet = m_set; + w.dstBinding = m_shifts.apply(le.type, le.binding); + w.dstArrayElement = 0; + w.descriptorCount = 1; + w.descriptorType = toVkDescriptorType(le); + + switch (le.type) { + case BindingType::UniformBuffer: case BindingType::StorageBufferReadOnly: case BindingType::StorageBufferReadWrite: + if (auto* vkBuf = static_cast(e.buffer)) { + VkDescriptorBufferInfo bi{}; bi.buffer = vkBuf->handle(); bi.offset = e.bufferOffset; + bi.range = e.bufferSize > 0 ? e.bufferSize : VK_WHOLE_SIZE; + bufInfos.push_back(bi); w.pBufferInfo = &bufInfos.back(); + } else continue; + break; + case BindingType::SampledTexture: case BindingType::StorageTextureReadOnly: case BindingType::StorageTextureReadWrite: + if (auto* vkView = static_cast(e.textureView)) { + VkDescriptorImageInfo ii{}; ii.imageView = vkView->handle(); + if (le.type == BindingType::SampledTexture) { + // Depth/stencil textures are always sampled in DEPTH_STENCIL_READ_ONLY layout. + auto* vkTex = vkView->texture; + if (vkTex && isDepthFormat(vkTex->desc.format)) + ii.imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; + else + ii.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + } else { + ii.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + } + imgInfos.push_back(ii); w.pImageInfo = &imgInfos.back(); + } else continue; + break; + case BindingType::Sampler: case BindingType::ComparisonSampler: + if (auto* vkSamp = static_cast(e.sampler)) { + VkDescriptorImageInfo ii{}; ii.sampler = vkSamp->handle(); + imgInfos.push_back(ii); w.pImageInfo = &imgInfos.back(); + } else continue; + break; + case BindingType::AccelerationStructure: + if (auto* vkAs = static_cast(e.accelStruct)) { + asHandles.push_back(vkAs->handle()); + VkWriteDescriptorSetAccelerationStructureKHR asInfo{}; + asInfo.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR; + asInfo.accelerationStructureCount = 1; + asInfo.pAccelerationStructures = &asHandles.back(); + asWriteInfos.push_back(asInfo); + w.pNext = &asWriteInfos.back(); + } else continue; + break; + default: continue; + } + writes.push_back(w); + } + + if (!writes.empty()) + vkUpdateDescriptorSets(device, static_cast(writes.size()), writes.data(), 0, nullptr); + } + + VkDevice m_device = VK_NULL_HANDLE; + VkDescriptorSet m_set = VK_NULL_HANDLE; + VkDescriptorPool m_pool = VK_NULL_HANDLE; + VkBindGroupLayoutImpl* m_layout = nullptr; + BindingShifts m_shifts{}; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBindGroupLayout.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBindGroupLayout.cppm new file mode 100644 index 00000000..05fdc082 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBindGroupLayout.cppm @@ -0,0 +1,110 @@ +/// Vulkan implementation of BindGroupLayout. + +module; + +#include "VkIncludes.h" +#include +#include + + +export module rhi.vk:bind_group_layout; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :binding_shifts; + +using namespace draco; + +export namespace draco::rhi::vk { + +inline VkDescriptorType toVkDescriptorType(const BindGroupLayoutEntry& e) { + switch (e.type) { + case BindingType::UniformBuffer: + return e.hasDynamicOffset ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC : VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + case BindingType::StorageBufferReadOnly: + case BindingType::StorageBufferReadWrite: + return e.hasDynamicOffset ? VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + case BindingType::SampledTexture: return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + case BindingType::StorageTextureReadOnly: + case BindingType::StorageTextureReadWrite: return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + case BindingType::Sampler: + case BindingType::ComparisonSampler: return VK_DESCRIPTOR_TYPE_SAMPLER; + case BindingType::BindlessTextures: return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + case BindingType::BindlessSamplers: return VK_DESCRIPTOR_TYPE_SAMPLER; + case BindingType::BindlessStorageBuffers: return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + case BindingType::BindlessStorageTextures: return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + case BindingType::AccelerationStructure: return VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR; + } + return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +} + +class VkBindGroupLayoutImpl : public BindGroupLayout { +public: + Status init(VkDevice device, const BindGroupLayoutDesc& desc, const BindingShifts& shifts = {}) { + m_entries.clear(); + for (usize i = 0; i < desc.entries.size(); ++i) { m_entries.push_back(desc.entries[i]); } + + std::vector bindings(desc.entries.size()); + std::vector flags(desc.entries.size()); + + for (usize i = 0; i < desc.entries.size(); ++i) { + const auto& e = desc.entries[i]; + auto& b = bindings[i]; + b = {}; + b.binding = shifts.apply(e.type, e.binding); + b.descriptorType = toVkDescriptorType(e); + b.descriptorCount = e.count; + b.stageFlags = toVkShaderStageFlags(e.visibility); + + flags[i] = 0; + if (e.count == ~0u) { + b.descriptorCount = 1024 * 16; + m_hasBindless = true; + m_bindlessCount = b.descriptorCount; + flags[i] = VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT + | VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT + | VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT; + } + } + + VkDescriptorSetLayoutBindingFlagsCreateInfo flagsInfo{}; + flagsInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO; + flagsInfo.bindingCount = static_cast(desc.entries.size()); + flagsInfo.pBindingFlags = flags.data(); + + VkDescriptorSetLayoutCreateInfo ci{}; + ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + ci.bindingCount = static_cast(desc.entries.size()); + ci.pBindings = bindings.data(); + if (m_hasBindless) { + ci.flags |= VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT; + ci.pNext = &flagsInfo; + } + + if (vkCreateDescriptorSetLayout(device, &ci, nullptr, &m_layout) != VK_SUCCESS) + return ErrorCode::Unknown; + return ErrorCode::Ok; + } + + void cleanup(VkDevice device) { + if (m_layout != VK_NULL_HANDLE) { vkDestroyDescriptorSetLayout(device, m_layout, nullptr); m_layout = VK_NULL_HANDLE; } + } + + std::span entries() const override { + return std::span(m_entries.data(), m_entries.size()); + } + + [[nodiscard]] VkDescriptorSetLayout handle() const { return m_layout; } + [[nodiscard]] bool hasBindless() const { return m_hasBindless; } + [[nodiscard]] u32 bindlessCount() const { return m_bindlessCount; } + +private: + VkDescriptorSetLayout m_layout = VK_NULL_HANDLE; + std::vector m_entries; + bool m_hasBindless = false; + u32 m_bindlessCount = 0; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBindingShifts.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBindingShifts.cppm new file mode 100644 index 00000000..660b443b --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBindingShifts.cppm @@ -0,0 +1,54 @@ +/// HLSL register binding shift configuration. + +export module rhi.vk:binding_shifts; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::vk { + +/// Maps HLSL register spaces to Vulkan descriptor bindings. +/// DXC's -fvk-*-shift flags use these values when compiling HLSL to SPIR-V. +struct BindingShifts { + u32 cbvShift = 0; ///< Constant buffer (b) register shift. + u32 srvShift = 0; ///< Shader resource view (t) register shift. + u32 uavShift = 0; ///< Unordered access view (u) register shift. + u32 samplerShift = 0; ///< Sampler (s) register shift. + + /// Standard layout: CBV=0, SRV=1000, UAV=2000, Sampler=3000. + static constexpr BindingShifts standard() { + return { 0, 1000, 2000, 3000 }; + } + + /// Applies the appropriate shift for a binding type. + u32 apply(BindingType type, u32 binding) const { + switch (type) { + case BindingType::UniformBuffer: + return binding + cbvShift; + case BindingType::SampledTexture: + case BindingType::BindlessTextures: + case BindingType::AccelerationStructure: + // A read-only StructuredBuffer is an SRV in HLSL (a `t` register), so it takes the SRV + // shift - unlike RWStructuredBuffer (a `u` register / UAV). DXC shifts the SPIR-V + // binding accordingly, so the layout must use the same class to match. + case BindingType::StorageBufferReadOnly: + return binding + srvShift; + case BindingType::StorageTextureReadOnly: + case BindingType::StorageTextureReadWrite: + case BindingType::BindlessStorageTextures: + case BindingType::StorageBufferReadWrite: + case BindingType::BindlessStorageBuffers: + return binding + uavShift; + case BindingType::Sampler: + case BindingType::ComparisonSampler: + case BindingType::BindlessSamplers: + return binding + samplerShift; + } + return binding; + } +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBuffer.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBuffer.cppm new file mode 100644 index 00000000..b77a0cf2 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkBuffer.cppm @@ -0,0 +1,100 @@ +/// Vulkan implementation of Buffer. + +module; + +#include "VkIncludes.h" + +export module rhi.vk:buffer; + +import core.stdtypes; +import core.status; +import rhi; +import :adapter; +import :conversions; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkDeviceImpl; // forward + +class VkBufferImpl : public Buffer { +public: + Status init(VkDevice device, VkAdapterImpl* adapter, const BufferDesc& d) { + desc = d; + + VkBufferCreateInfo ci{}; + ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + ci.size = d.size; + ci.usage = toVkBufferUsage(d.usage); + ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + if (vkCreateBuffer(device, &ci, nullptr, &m_buffer) != VK_SUCCESS) return ErrorCode::Unknown; + + VkMemoryRequirements memReqs{}; + vkGetBufferMemoryRequirements(device, m_buffer, &memReqs); + + auto memFlags = VkAdapterImpl::getMemoryFlags(d.memory); + i32 memType = adapter->findMemoryType(static_cast(memReqs.memoryTypeBits), memFlags); + + if (memType < 0 && d.memory == MemoryLocation::Auto) + memType = adapter->findMemoryType(static_cast(memReqs.memoryTypeBits), + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + if (memType < 0) { + vkDestroyBuffer(device, m_buffer, nullptr); + m_buffer = VK_NULL_HANDLE; + return ErrorCode::Unknown; + } + + bool needsDeviceAddress = hasFlag(d.usage, BufferUsage::AccelStructInput) + || hasFlag(d.usage, BufferUsage::ShaderBindingTable) + || hasFlag(d.usage, BufferUsage::AccelStructScratch); + + VkMemoryAllocateFlagsInfo allocFlags{}; + allocFlags.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO; + if (needsDeviceAddress) + allocFlags.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT; + + VkMemoryAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + if (needsDeviceAddress) allocInfo.pNext = &allocFlags; + allocInfo.allocationSize = memReqs.size; + allocInfo.memoryTypeIndex = static_cast(memType); + + if (vkAllocateMemory(device, &allocInfo, nullptr, &m_memory) != VK_SUCCESS) { + vkDestroyBuffer(device, m_buffer, nullptr); + m_buffer = VK_NULL_HANDLE; + return ErrorCode::Unknown; + } + + vkBindBufferMemory(device, m_buffer, m_memory, 0); + + // Persistently map host-visible buffers. + if (d.memory == MemoryLocation::CpuToGpu || d.memory == MemoryLocation::GpuToCpu) + vkMapMemory(device, m_memory, 0, d.size, 0, &m_mappedPtr); + + return ErrorCode::Ok; + } + + void cleanup(VkDevice device) { + if (m_mappedPtr) { vkUnmapMemory(device, m_memory); m_mappedPtr = nullptr; } + if (m_memory != VK_NULL_HANDLE) { vkFreeMemory(device, m_memory, nullptr); m_memory = VK_NULL_HANDLE; } + if (m_buffer != VK_NULL_HANDLE) { vkDestroyBuffer(device, m_buffer, nullptr); m_buffer = VK_NULL_HANDLE; } + } + + // ---- Buffer interface ---- + void* map() override { return m_mappedPtr; } + void unmap() override { /* persistently mapped */ } + + // ---- Internal ---- + [[nodiscard]] VkBuffer handle() const { return m_buffer; } + [[nodiscard]] VkDeviceMemory memory() const { return m_memory; } + +private: + VkBuffer m_buffer = VK_NULL_HANDLE; + VkDeviceMemory m_memory = VK_NULL_HANDLE; + void* m_mappedPtr = nullptr; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkCommandBuffer.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkCommandBuffer.cppm new file mode 100644 index 00000000..f60b08bc --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkCommandBuffer.cppm @@ -0,0 +1,27 @@ +/// Vulkan implementation of CommandBuffer. + +module; + +#include "VkIncludes.h" + +export module rhi.vk:command_buffer; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkCommandBufferImpl : public CommandBuffer { +public: + explicit VkCommandBufferImpl(VkCommandBuffer cmdBuf) : m_cmdBuf(cmdBuf) {} + + [[nodiscard]] VkCommandBuffer handle() const { return m_cmdBuf; } + +private: + VkCommandBuffer m_cmdBuf = VK_NULL_HANDLE; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkCommandEncoder.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkCommandEncoder.cppm new file mode 100644 index 00000000..027d6860 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkCommandEncoder.cppm @@ -0,0 +1,688 @@ +/// Vulkan implementation of CommandEncoder + RayTracingEncoderExt. + +module; + +#include "VkIncludes.h" +#include +#include +#include +#include + +#include + +export module rhi.vk:command_encoder; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :barrier_helper; +import :buffer; +import :texture; +import :texture_view; +import :bind_group; +import :pipeline_layout; +import :query_set; +import :command_buffer; +import :command_pool; +import :render_pass_encoder; +import :render_bundle_encoder; +import :compute_pass_encoder; +import :accel_struct; +import :ray_tracing_pipeline; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkCommandEncoderImpl : public CommandEncoder, public RayTracingEncoderExt { +public: + RayTracingEncoderExt* asRayTracingExt() noexcept override { return this; } + VkCommandEncoderImpl(VkCommandBuffer cmdBuf, VkDevice device, VkCommandPoolImpl* pool) + : m_cmdBuf(cmdBuf), m_device(device), m_pool(pool), + m_rpe(cmdBuf, device), m_cpe(cmdBuf) {} + + // ---- CommandEncoder ---- + + RenderPassEncoder* beginRenderPass(const RenderPassDesc& desc) override { + const auto& colorAtts = desc.colorAttachments; + std::vector vkColor(colorAtts.size()); + + for (usize i = 0; i < colorAtts.size(); ++i) { + const auto& att = colorAtts[i]; + auto* view = static_cast(att.view); + VkRenderingAttachmentInfo info{}; + info.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + info.imageView = view ? view->handle() : VK_NULL_HANDLE; + info.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + // Update texture layout tracking - dynamic rendering implicitly + // transitions attachments to the specified layout. + if (view) { + if (auto* vkTex = static_cast(view->texture)) + vkTex->setSubresourceLayout(view->desc.baseMipLevel, 1, view->desc.baseArrayLayer, 1, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + } + info.loadOp = toVkLoadOp(att.loadOp); + info.storeOp = toVkStoreOp(att.storeOp); + info.clearValue.color.float32[0] = att.clearValue.r; + info.clearValue.color.float32[1] = att.clearValue.g; + info.clearValue.color.float32[2] = att.clearValue.b; + info.clearValue.color.float32[3] = att.clearValue.a; + if (auto* rv = static_cast(att.resolveTarget)) { + info.resolveMode = VK_RESOLVE_MODE_AVERAGE_BIT; + info.resolveImageView = rv->handle(); + info.resolveImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + } + vkColor[i] = info; + } + + VkRect2D renderArea{}; + if (colorAtts.size() > 0) { + if (auto* v = static_cast(colorAtts[0].view)) + renderArea.extent = { v->width(), v->height() }; + } else if (desc.depthStencilAttachment.has_value()) { + if (auto* v = static_cast(desc.depthStencilAttachment->view)) + renderArea.extent = { v->width(), v->height() }; + } + + VkRenderingInfo ri{}; + ri.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + ri.renderArea = renderArea; + ri.layerCount = 1; + ri.colorAttachmentCount = static_cast(vkColor.size()); + ri.pColorAttachments = vkColor.data(); + + VkRenderingAttachmentInfo depthAtt{}, stencilAtt{}; + if (desc.depthStencilAttachment.has_value()) { + const auto& ds = *desc.depthStencilAttachment; + if (auto* dv = static_cast(ds.view)) { + depthAtt.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + depthAtt.imageView = dv->handle(); + depthAtt.imageLayout = ds.depthReadOnly + ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL + : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + // Update texture layout tracking. + if (auto* vkTex = static_cast(dv->texture)) + vkTex->setSubresourceLayout(dv->desc.baseMipLevel, 1, dv->desc.baseArrayLayer, 1, depthAtt.imageLayout); + depthAtt.loadOp = toVkLoadOp(ds.depthLoadOp); + depthAtt.storeOp = toVkStoreOp(ds.depthStoreOp); + depthAtt.clearValue.depthStencil = { ds.depthClearValue, ds.stencilClearValue }; + ri.pDepthAttachment = &depthAtt; + if (hasStencil(dv->format())) { + stencilAtt = depthAtt; + stencilAtt.loadOp = toVkLoadOp(ds.stencilLoadOp); + stencilAtt.storeOp = toVkStoreOp(ds.stencilStoreOp); + ri.pStencilAttachment = &stencilAtt; + } + } + } + + if (desc.contents == RenderPassContents::SecondaryCommandBuffers) + ri.flags |= VK_RENDERING_CONTENTS_SECONDARY_COMMAND_BUFFERS_BIT; + + vkCmdBeginRendering(m_cmdBuf, &ri); + return &m_rpe; + } + + ComputePassEncoder* beginComputePass(std::u8string_view) override { return &m_cpe; } + + RenderBundleEncoder* createRenderBundleEncoder(const RenderBundleDesc& desc) override { + VkCommandBuffer sec = m_pool->acquireSecondary(); + if (sec == VK_NULL_HANDLE) return nullptr; + + VkFormat colorFmts[maxColorAttachments] = {}; + for (u32 i = 0; i < desc.colorFormatCount && i < maxColorAttachments; ++i) + colorFmts[i] = toVkFormat(desc.colorFormats[i]); + const bool hasDepth = desc.depthStencilFormat != TextureFormat::Undefined; + + // Dynamic-rendering inheritance: the attachment signature this bundle is compatible with. + VkCommandBufferInheritanceRenderingInfo inh{}; + inh.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_RENDERING_INFO; + inh.colorAttachmentCount = desc.colorFormatCount; + inh.pColorAttachmentFormats = colorFmts; + inh.depthAttachmentFormat = hasDepth ? toVkFormat(desc.depthStencilFormat) : VK_FORMAT_UNDEFINED; + inh.stencilAttachmentFormat = (hasDepth && hasStencil(desc.depthStencilFormat)) + ? toVkFormat(desc.depthStencilFormat) : VK_FORMAT_UNDEFINED; + inh.rasterizationSamples = static_cast(desc.sampleCount ? desc.sampleCount : 1u); + + VkCommandBufferInheritanceInfo ii{}; + ii.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO; + ii.pNext = &inh; + + VkCommandBufferBeginInfo bi{}; + bi.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + bi.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT | VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + bi.pInheritanceInfo = ⅈ + vkBeginCommandBuffer(sec, &bi); + + // Bundles carry no pass-level dynamic state + Vulkan secondaries don't inherit it, so record + // the bundle's viewport + scissor up front (Y-flipped, like the pass encoder). The viewport + // is the desc's sub-rect (split-screen), not necessarily the full target. + if (desc.width > 0 && desc.height > 0) { + VkViewport vp{}; vp.x = static_cast(desc.viewportX); + vp.y = static_cast(desc.viewportY) + static_cast(desc.height); + vp.width = static_cast(desc.width); vp.height = -static_cast(desc.height); + vp.minDepth = 0.0f; vp.maxDepth = 1.0f; + vkCmdSetViewport(sec, 0, 1, &vp); + VkRect2D scs{}; scs.offset = { desc.viewportX, desc.viewportY }; scs.extent = { desc.width, desc.height }; + vkCmdSetScissor(sec, 0, 1, &scs); + } + + auto* enc = new VkRenderBundleEncoderImpl(sec); + m_pool->trackBundleEncoder(enc); + return enc; + } + + void barrier(const BarrierGroup& group) override { + std::vector memBs(group.memoryBarriers.size()); + std::vector bufBs(group.bufferBarriers.size()); + std::vector imgBs(group.textureBarriers.size()); + + for (usize i = 0; i < group.memoryBarriers.size(); ++i) { + auto src = getStageAccess(group.memoryBarriers[i].oldState); + auto dst = getStageAccess(group.memoryBarriers[i].newState); + memBs[i] = {}; memBs[i].sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; + memBs[i].srcStageMask = src.stageMask; memBs[i].srcAccessMask = src.accessMask; + memBs[i].dstStageMask = dst.stageMask; memBs[i].dstAccessMask = dst.accessMask; + } + + for (usize i = 0; i < group.bufferBarriers.size(); ++i) { + const auto& bb = group.bufferBarriers[i]; + auto src = getStageAccess(bb.oldState); auto dst = getStageAccess(bb.newState); + bufBs[i] = {}; bufBs[i].sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2; + bufBs[i].srcStageMask = src.stageMask; bufBs[i].srcAccessMask = src.accessMask; + bufBs[i].dstStageMask = dst.stageMask; bufBs[i].dstAccessMask = dst.accessMask; + bufBs[i].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + bufBs[i].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + if (auto* vb = static_cast(bb.buffer)) bufBs[i].buffer = vb->handle(); + bufBs[i].offset = bb.offset; + bufBs[i].size = (bb.size == ~0ull) ? VK_WHOLE_SIZE : bb.size; + } + + for (usize i = 0; i < group.textureBarriers.size(); ++i) { + const auto& tb = group.textureBarriers[i]; + auto src = getStageAccess(tb.oldState); auto dst = getStageAccess(tb.newState); + + auto* vkTex = static_cast(tb.texture); + TextureFormat format = vkTex ? vkTex->desc.format : TextureFormat::Undefined; + + auto newLayout = getImageLayout(tb.newState, format); + + // Resolve old layout from per-subresource tracking. + // Tracking is kept in sync via beginRenderPass layout updates. + VkImageLayout oldLayout; + if (vkTex) { + bool isWholeResource = (tb.mipLevelCount == ~0u) && (tb.arrayLayerCount == ~0u); + if (isWholeResource) + oldLayout = vkTex->currentLayout; + else + oldLayout = vkTex->getSubresourceLayout(tb.baseMipLevel, tb.baseArrayLayer); + // Update tracking. + vkTex->setSubresourceLayout(tb.baseMipLevel, tb.mipLevelCount, + tb.baseArrayLayer, tb.arrayLayerCount, newLayout); + } else { + oldLayout = getImageLayout(tb.oldState, format); + } + + imgBs[i] = {}; imgBs[i].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED) { + imgBs[i].srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; imgBs[i].srcAccessMask = 0; + } else { + imgBs[i].srcStageMask = src.stageMask; imgBs[i].srcAccessMask = src.accessMask; + } + imgBs[i].dstStageMask = dst.stageMask; imgBs[i].dstAccessMask = dst.accessMask; + imgBs[i].oldLayout = oldLayout; imgBs[i].newLayout = newLayout; + imgBs[i].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + imgBs[i].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + if (vkTex) imgBs[i].image = vkTex->handle(); + TextureFormat fmt = vkTex ? vkTex->desc.format : TextureFormat::Undefined; + imgBs[i].subresourceRange.aspectMask = getAspectMask(fmt); + imgBs[i].subresourceRange.baseMipLevel = tb.baseMipLevel; + imgBs[i].subresourceRange.levelCount = tb.mipLevelCount == ~0u ? VK_REMAINING_MIP_LEVELS : tb.mipLevelCount; + imgBs[i].subresourceRange.baseArrayLayer = tb.baseArrayLayer; + imgBs[i].subresourceRange.layerCount = tb.arrayLayerCount== ~0u ? VK_REMAINING_ARRAY_LAYERS : tb.arrayLayerCount; + } + + VkDependencyInfo di{}; di.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + di.memoryBarrierCount = static_cast(memBs.size()); di.pMemoryBarriers = memBs.data(); + di.bufferMemoryBarrierCount = static_cast(bufBs.size()); di.pBufferMemoryBarriers = bufBs.data(); + di.imageMemoryBarrierCount = static_cast(imgBs.size()); di.pImageMemoryBarriers = imgBs.data(); + vkCmdPipelineBarrier2(m_cmdBuf, &di); + } + + void copyBufferToBuffer(Buffer* src, u64 srcOff, Buffer* dst, u64 dstOff, u64 size) override { + auto* s = static_cast(src); auto* d = static_cast(dst); + if (!s || !d) return; + VkBufferCopy r{}; r.srcOffset = srcOff; r.dstOffset = dstOff; r.size = size; + vkCmdCopyBuffer(m_cmdBuf, s->handle(), d->handle(), 1, &r); + } + + void copyBufferToTexture(Buffer* src, Texture* dst, const BufferTextureCopyRegion& region) override { + auto* s = static_cast(src); auto* d = static_cast(dst); + if (!s || !d) return; + VkBufferImageCopy c{}; + c.bufferOffset = region.bufferOffset; + u32 bpp2 = bytesPerPixel(d->desc.format); + c.bufferRowLength = (bpp2 > 0 && region.bytesPerRow > 0) ? region.bytesPerRow / bpp2 : 0; + c.bufferImageHeight = region.rowsPerImage; + c.imageSubresource.aspectMask = getAspectMask(d->desc.format); + c.imageSubresource.mipLevel = region.textureMipLevel; + c.imageSubresource.baseArrayLayer = region.textureArrayLayer; + c.imageSubresource.layerCount = 1; + c.imageOffset = { static_cast(region.textureOrigin.x), static_cast(region.textureOrigin.y), static_cast(region.textureOrigin.z) }; + c.imageExtent = { region.textureExtent.width, region.textureExtent.height, region.textureExtent.depth }; + vkCmdCopyBufferToImage(m_cmdBuf, s->handle(), d->handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &c); + } + + void copyTextureToBuffer(Texture* src, Buffer* dst, const BufferTextureCopyRegion& region) override { + auto* s = static_cast(src); auto* d = static_cast(dst); + if (!s || !d) return; + VkBufferImageCopy c{}; + c.bufferOffset = region.bufferOffset; + u32 bpp = bytesPerPixel(s->desc.format); + c.bufferRowLength = (bpp > 0 && region.bytesPerRow > 0) ? region.bytesPerRow / bpp : 0; + c.bufferImageHeight = region.rowsPerImage; + c.imageSubresource.aspectMask = getAspectMask(s->desc.format); + c.imageSubresource.mipLevel = region.textureMipLevel; + c.imageSubresource.baseArrayLayer = region.textureArrayLayer; + c.imageSubresource.layerCount = 1; + c.imageOffset = { static_cast(region.textureOrigin.x), static_cast(region.textureOrigin.y), static_cast(region.textureOrigin.z) }; + c.imageExtent = { region.textureExtent.width, region.textureExtent.height, region.textureExtent.depth }; + vkCmdCopyImageToBuffer(m_cmdBuf, s->handle(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, d->handle(), 1, &c); + } + + void copyTextureToTexture(Texture* src, Texture* dst, const TextureCopyRegion& region) override { + auto* s = static_cast(src); auto* d = static_cast(dst); + if (!s || !d) return; + VkImageCopy c{}; + c.srcSubresource = { getAspectMask(s->desc.format), region.srcMipLevel, region.srcArrayLayer, 1 }; + c.dstSubresource = { getAspectMask(d->desc.format), region.dstMipLevel, region.dstArrayLayer, 1 }; + c.extent = { region.extent.width, region.extent.height, region.extent.depth }; + vkCmdCopyImage(m_cmdBuf, s->handle(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + d->handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &c); + } + + void blit(Texture* src, Texture* dst) override { + auto* s = static_cast(src); auto* d = static_cast(dst); + if (!s || !d) return; + VkImageBlit b{}; + b.srcSubresource = { getAspectMask(s->desc.format), 0, 0, 1 }; + b.srcOffsets[1] = { static_cast(s->desc.width), static_cast(s->desc.height), 1 }; + b.dstSubresource = { getAspectMask(d->desc.format), 0, 0, 1 }; + b.dstOffsets[1] = { static_cast(d->desc.width), static_cast(d->desc.height), 1 }; + vkCmdBlitImage(m_cmdBuf, s->handle(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + d->handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &b, VK_FILTER_LINEAR); + } + + void generateMipmaps(Texture* texture) override { + auto* vkTex = static_cast(texture); + if (!vkTex || vkTex->desc.mipLevelCount <= 1) return; + // Simplified: caller transitions all mips to TRANSFER_DST before calling. + i32 mipW = static_cast(vkTex->desc.width); + i32 mipH = static_cast(vkTex->desc.height); + auto aspect = getAspectMask(vkTex->desc.format); + u32 layers = vkTex->desc.arrayLayerCount; + + for (u32 i = 1; i < vkTex->desc.mipLevelCount; ++i) { + // Transition mip i-1 to TRANSFER_SRC for blit source. + // For mip 0 (i==1), use UNDEFINED as old layout because the caller's + // actual layout is unknown (TransferBatch leaves SHADER_READ_ONLY, etc.). + VkImageMemoryBarrier2 srcBarrier{}; srcBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + srcBarrier.srcStageMask = VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT; + srcBarrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + srcBarrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT; + srcBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; + srcBarrier.oldLayout = (i == 1) ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + srcBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + srcBarrier.image = vkTex->handle(); + srcBarrier.subresourceRange = { aspect, i - 1, 1, 0, layers }; + + // Transition mip i to TRANSFER_DST for blit destination. + VkImageMemoryBarrier2 dstBarrier{}; dstBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + dstBarrier.srcStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT; + dstBarrier.srcAccessMask = 0; + dstBarrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT; + dstBarrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + dstBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + dstBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + dstBarrier.image = vkTex->handle(); + dstBarrier.subresourceRange = { aspect, i, 1, 0, layers }; + + VkImageMemoryBarrier2 barriers[2] = { srcBarrier, dstBarrier }; + VkDependencyInfo dep{}; dep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + dep.imageMemoryBarrierCount = 2; dep.pImageMemoryBarriers = barriers; + vkCmdPipelineBarrier2(m_cmdBuf, &dep); + + i32 nw = std::max(1, mipW / 2), nh = std::max(1, mipH / 2); + VkImageBlit bl{}; + bl.srcSubresource = { aspect, i - 1, 0, layers }; bl.srcOffsets[1] = { mipW, mipH, 1 }; + bl.dstSubresource = { aspect, i, 0, layers }; bl.dstOffsets[1] = { nw, nh, 1 }; + vkCmdBlitImage(m_cmdBuf, vkTex->handle(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + vkTex->handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bl, VK_FILTER_LINEAR); + mipW = nw; mipH = nh; + } + // Transition last mip to SRC. + VkImageMemoryBarrier2 lb{}; lb.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; + lb.srcStageMask = VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT; lb.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; + lb.dstStageMask = VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT; lb.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; + lb.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; lb.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + lb.image = vkTex->handle(); + lb.subresourceRange = { aspect, vkTex->desc.mipLevelCount - 1, 1, 0, layers }; + VkDependencyInfo ldep{}; ldep.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + ldep.imageMemoryBarrierCount = 1; ldep.pImageMemoryBarriers = &lb; + vkCmdPipelineBarrier2(m_cmdBuf, &ldep); + + // Update tracked layout - all mips now in TRANSFER_SRC. + vkTex->currentLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + } + + void resolveTexture(Texture* src, Texture* dst) override { + auto* s = static_cast(src); auto* d = static_cast(dst); + if (!s || !d) return; + auto aspect = getAspectMask(s->desc.format); + VkImageResolve r{}; r.srcSubresource = { aspect, 0, 0, 1 }; r.dstSubresource = { aspect, 0, 0, 1 }; + r.extent = { s->desc.width, s->desc.height, 1 }; + vkCmdResolveImage(m_cmdBuf, s->handle(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + d->handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &r); + } + + void resetQuerySet(QuerySet* qs, u32 first, u32 count) override { + if (auto* q = static_cast(qs)) vkCmdResetQueryPool(m_cmdBuf, q->handle(), first, count); + } + + void writeTimestamp(QuerySet* qs, u32 index) override { + if (auto* q = static_cast(qs)) + vkCmdWriteTimestamp(m_cmdBuf, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, q->handle(), index); + } + + void resolveQuerySet(QuerySet* qs, u32 first, u32 count, Buffer* dst, u64 dstOffset) override { + auto* q = static_cast(qs); auto* b = static_cast(dst); + if (q && b) vkCmdCopyQueryPoolResults(m_cmdBuf, q->handle(), first, count, b->handle(), dstOffset, 8, + VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); + } + + void beginDebugLabel(std::u8string_view label, f32 r, f32 g, f32 b, f32 a) override { + char buf[256]{}; auto len = std::min(label.size(), static_cast(255)); + std::memcpy(buf, label.data(), len); + VkDebugUtilsLabelEXT li{}; li.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; + li.pLabelName = buf; li.color[0] = r; li.color[1] = g; li.color[2] = b; li.color[3] = a; + auto pfn = reinterpret_cast(vkGetDeviceProcAddr(m_device, "vkCmdBeginDebugUtilsLabelEXT")); + if (pfn) pfn(m_cmdBuf, &li); + } + + void endDebugLabel() override { + auto pfn = reinterpret_cast(vkGetDeviceProcAddr(m_device, "vkCmdEndDebugUtilsLabelEXT")); + if (pfn) pfn(m_cmdBuf); + } + + void insertDebugLabel(std::u8string_view label, f32 r, f32 g, f32 b, f32 a) override { + char buf[256]{}; auto len = std::min(label.size(), static_cast(255)); + std::memcpy(buf, label.data(), len); + VkDebugUtilsLabelEXT li{}; li.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; + li.pLabelName = buf; li.color[0] = r; li.color[1] = g; li.color[2] = b; li.color[3] = a; + auto pfn = reinterpret_cast(vkGetDeviceProcAddr(m_device, "vkCmdInsertDebugUtilsLabelEXT")); + if (pfn) pfn(m_cmdBuf, &li); + } + + CommandBuffer* finish() override { + vkEndCommandBuffer(m_cmdBuf); + auto* cb = new VkCommandBufferImpl(m_cmdBuf); + m_pool->trackCommandBuffer(cb); + return cb; + } + + // ---- RayTracingEncoderExt ---- + + void buildBottomLevelAccelStruct(AccelStruct* dst, Buffer* scratch, u64 scratchOffset, + std::span tris, std::span aabbs) override; + void buildTopLevelAccelStruct(AccelStruct* dst, Buffer* scratch, u64 scratchOffset, + Buffer* instanceBuf, u64 instanceOffset, u32 instanceCount) override; + void setRayTracingPipeline(RayTracingPipeline* pipeline) override; + void setBindGroup(u32 index, BindGroup* group, std::span dynOffsets) override; + void setPushConstants(ShaderStage stages, u32 offset, u32 size, const void* data) override; + void traceRays(Buffer* raygenSBT, u64 raygenOff, u64 raygenStride, + Buffer* missSBT, u64 missOff, u64 missStride, + Buffer* hitSBT, u64 hitOff, u64 hitStride, + u32 width, u32 height, u32 depth) override; + +private: + u64 getBufferDeviceAddress(VkBufferImpl* buf) { + VkBufferDeviceAddressInfo info{}; info.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + info.buffer = buf->handle(); + if (!m_pfnGetBufAddr) m_pfnGetBufAddr = reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkGetBufferDeviceAddress")); + return m_pfnGetBufAddr ? m_pfnGetBufAddr(m_device, &info) : 0; + } + + VkCommandBuffer m_cmdBuf = VK_NULL_HANDLE; + VkDevice m_device = VK_NULL_HANDLE; + VkCommandPoolImpl* m_pool = nullptr; + VkRenderPassEncoderImpl m_rpe; + VkComputePassEncoderImpl m_cpe; + + // RT state. + VkRayTracingPipelineImpl* m_currentRtPipeline = nullptr; + PFN_vkGetBufferDeviceAddress m_pfnGetBufAddr = nullptr; + PFN_vkCmdBuildAccelerationStructuresKHR m_pfnBuild = nullptr; + PFN_vkCmdTraceRaysKHR m_pfnTrace = nullptr; +}; + +// ---- Deferred RT method implementations ---- + +inline void VkCommandEncoderImpl::setRayTracingPipeline(RayTracingPipeline* pipeline) { + m_currentRtPipeline = static_cast(pipeline); + if (m_currentRtPipeline) + vkCmdBindPipeline(m_cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_currentRtPipeline->handle()); +} + +inline void VkCommandEncoderImpl::setBindGroup(u32 index, BindGroup* group, std::span dynOffsets) { + auto* bg = static_cast(group); + if (!bg || !m_currentRtPipeline || !m_currentRtPipeline->vkLayout()) return; + VkDescriptorSet set = bg->handle(); + vkCmdBindDescriptorSets(m_cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, + m_currentRtPipeline->vkLayout()->handle(), index, 1, &set, + static_cast(dynOffsets.size()), dynOffsets.data()); +} + +inline void VkCommandEncoderImpl::setPushConstants(ShaderStage stages, u32 offset, u32 size, const void* data) { + if (!m_currentRtPipeline || !m_currentRtPipeline->vkLayout()) return; + vkCmdPushConstants(m_cmdBuf, m_currentRtPipeline->vkLayout()->handle(), + toVkShaderStageFlags(stages), offset, size, data); +} + +inline void VkCommandEncoderImpl::traceRays( + Buffer* raygenSBT, u64 raygenOff, u64 raygenStride, + Buffer* missSBT, u64 missOff, u64 missStride, + Buffer* hitSBT, u64 hitOff, u64 hitStride, u32 w, u32 h, u32 d) { + auto* rg = static_cast(raygenSBT); + if (!rg) return; + VkStridedDeviceAddressRegionKHR rgn{}, msn{}, htn{}, cal{}; + rgn.deviceAddress = getBufferDeviceAddress(rg) + raygenOff; rgn.stride = raygenStride; rgn.size = raygenStride; + if (auto* ms = static_cast(missSBT)) { + msn.deviceAddress = getBufferDeviceAddress(ms) + missOff; msn.stride = missStride; msn.size = missStride; + } + if (auto* ht = static_cast(hitSBT)) { + htn.deviceAddress = getBufferDeviceAddress(ht) + hitOff; htn.stride = hitStride; htn.size = hitStride; + } + if (!m_pfnTrace) m_pfnTrace = reinterpret_cast(vkGetDeviceProcAddr(m_device, "vkCmdTraceRaysKHR")); + if (m_pfnTrace) m_pfnTrace(m_cmdBuf, &rgn, &msn, &htn, &cal, w, h, d); +} + +inline void VkCommandEncoderImpl::buildBottomLevelAccelStruct( + AccelStruct* dst, Buffer* scratch, u64 scratchOffset, + std::span tris, std::span aabbs) { + auto* as = static_cast(dst); + auto* sc = static_cast(scratch); + if (!as || !sc) return; + + usize total = tris.size() + aabbs.size(); + std::vector geoms(total); + std::vector ranges(total); + usize idx = 0; + + for (usize i = 0; i < tris.size(); ++i) { + const auto& t = tris[i]; + auto* vb = static_cast(t.vertexBuffer); if (!vb) continue; + VkAccelerationStructureGeometryTrianglesDataKHR td{}; + td.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR; + td.vertexFormat = toVkVertexFormat(t.vertexFormat); + td.vertexData.deviceAddress = getBufferDeviceAddress(vb) + t.vertexOffset; + td.vertexStride = t.vertexStride; td.maxVertex = t.vertexCount - 1; + if (auto* ib = static_cast(t.indexBuffer)) { + td.indexType = toVkIndexType(t.indexFormat); + td.indexData.deviceAddress = getBufferDeviceAddress(ib) + t.indexOffset; + } else td.indexType = VK_INDEX_TYPE_NONE_KHR; + if (auto* tb = static_cast(t.transformBuffer)) + td.transformData.deviceAddress = getBufferDeviceAddress(tb) + t.transformOffset; + geoms[idx] = {}; geoms[idx].sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR; + geoms[idx].geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR; geoms[idx].geometry.triangles = td; + { VkGeometryFlagsKHR gf = 0; + if (static_cast(t.flags) & static_cast(GeometryFlags::Opaque)) gf |= VK_GEOMETRY_OPAQUE_BIT_KHR; + if (static_cast(t.flags) & static_cast(GeometryFlags::NoDuplicateAnyHitInvocation)) gf |= VK_GEOMETRY_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_KHR; + geoms[idx].flags = gf; } + ranges[idx] = {}; ranges[idx].primitiveCount = t.indexBuffer ? t.indexCount / 3 : t.vertexCount / 3; + ++idx; + } + for (usize i = 0; i < aabbs.size(); ++i) { + const auto& a = aabbs[i]; + auto* ab = static_cast(a.aabbBuffer); if (!ab) continue; + VkAccelerationStructureGeometryAabbsDataKHR ad{}; + ad.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_AABBS_DATA_KHR; + ad.data.deviceAddress = getBufferDeviceAddress(ab) + a.offset; ad.stride = a.stride; + geoms[idx] = {}; geoms[idx].sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR; + geoms[idx].geometryType = VK_GEOMETRY_TYPE_AABBS_KHR; geoms[idx].geometry.aabbs = ad; + { VkGeometryFlagsKHR gf = 0; + if (static_cast(a.flags) & static_cast(GeometryFlags::Opaque)) gf |= VK_GEOMETRY_OPAQUE_BIT_KHR; + if (static_cast(a.flags) & static_cast(GeometryFlags::NoDuplicateAnyHitInvocation)) gf |= VK_GEOMETRY_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_KHR; + geoms[idx].flags = gf; } + ranges[idx] = {}; ranges[idx].primitiveCount = a.count; + ++idx; + } + + VkAccelerationStructureBuildGeometryInfoKHR bi{}; + bi.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR; + bi.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; + bi.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; + bi.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR; + bi.dstAccelerationStructure = as->handle(); + bi.geometryCount = static_cast(idx); bi.pGeometries = geoms.data(); + bi.scratchData.deviceAddress = getBufferDeviceAddress(sc) + scratchOffset; + + if (!m_pfnBuild) m_pfnBuild = reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkCmdBuildAccelerationStructuresKHR")); + if (!m_pfnBuild) return; + const VkAccelerationStructureBuildRangeInfoKHR* pRange = ranges.data(); + m_pfnBuild(m_cmdBuf, 1, &bi, &pRange); +} + +inline void VkCommandEncoderImpl::buildTopLevelAccelStruct( + AccelStruct* dst, Buffer* scratch, u64 scratchOffset, + Buffer* instanceBuf, u64 instanceOffset, u32 instanceCount) { + auto* as = static_cast(dst); + auto* sc = static_cast(scratch); + auto* ib = static_cast(instanceBuf); + if (!as || !sc || !ib) return; + + VkAccelerationStructureGeometryInstancesDataKHR id{}; + id.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR; + id.data.deviceAddress = getBufferDeviceAddress(ib) + instanceOffset; + VkAccelerationStructureGeometryKHR geom{}; geom.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR; + geom.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR; geom.geometry.instances = id; + + VkAccelerationStructureBuildGeometryInfoKHR bi{}; + bi.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR; + bi.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; + bi.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; + bi.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR; + bi.dstAccelerationStructure = as->handle(); + bi.geometryCount = 1; bi.pGeometries = &geom; + bi.scratchData.deviceAddress = getBufferDeviceAddress(sc) + scratchOffset; + + VkAccelerationStructureBuildRangeInfoKHR range{}; range.primitiveCount = instanceCount; + if (!m_pfnBuild) m_pfnBuild = reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkCmdBuildAccelerationStructuresKHR")); + if (!m_pfnBuild) return; + const VkAccelerationStructureBuildRangeInfoKHR* pRange = ⦥ + m_pfnBuild(m_cmdBuf, 1, &bi, &pRange); +} + +// ---- Deferred mesh shader method implementations on RenderPassEncoder ---- + +VkPipelineLayoutImpl* VkRenderPassEncoderImpl::getCurrentLayout() { + if (m_currentPipeline) return m_currentPipeline->vkLayout(); + if (m_currentMeshPipeline) return m_currentMeshPipeline->vkLayout(); + return nullptr; +} + +void VkRenderPassEncoderImpl::setMeshPipeline(MeshPipeline* pipeline) { + m_currentMeshPipeline = static_cast(pipeline); + m_currentPipeline = nullptr; + if (m_currentMeshPipeline) + vkCmdBindPipeline(m_cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_currentMeshPipeline->handle()); +} + +void VkRenderPassEncoderImpl::drawMeshTasks(u32 gx, u32 gy, u32 gz) { + if (!m_pfnDrawMesh) m_pfnDrawMesh = reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkCmdDrawMeshTasksEXT")); + if (m_pfnDrawMesh) m_pfnDrawMesh(m_cmdBuf, gx, gy, gz); +} + +void VkRenderPassEncoderImpl::drawMeshTasksIndirect(Buffer* buf, u64 offset, u32 drawCount, u32 stride) { + auto* vkBuf = static_cast(buf); if (!vkBuf) return; + if (!m_pfnDrawMeshIndirect) m_pfnDrawMeshIndirect = reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkCmdDrawMeshTasksIndirectEXT")); + if (m_pfnDrawMeshIndirect) m_pfnDrawMeshIndirect(m_cmdBuf, vkBuf->handle(), offset, drawCount, stride > 0 ? stride : 12); +} + +void VkRenderPassEncoderImpl::drawMeshTasksIndirectCount( + Buffer* buf, u64 offset, Buffer* countBuf, u64 countOffset, u32 maxDrawCount, u32 stride) { + auto* vkBuf = static_cast(buf); + auto* vkCb = static_cast(countBuf); + if (!vkBuf || !vkCb) return; + if (!m_pfnDrawMeshIndCount) m_pfnDrawMeshIndCount = reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkCmdDrawMeshTasksIndirectCountEXT")); + if (m_pfnDrawMeshIndCount) m_pfnDrawMeshIndCount(m_cmdBuf, vkBuf->handle(), offset, + vkCb->handle(), countOffset, maxDrawCount, stride > 0 ? stride : 12); +} + +// ---- CommandPool deferred implementations ---- + +Status VkCommandPoolImpl::createEncoder(CommandEncoder*& out) { + out = nullptr; + VkCommandBuffer cmdBuf = VK_NULL_HANDLE; + + if (!m_freeHandles.empty()) { + cmdBuf = m_freeHandles.back(); m_freeHandles.pop_back(); + } else { + VkCommandBufferAllocateInfo ai{}; + ai.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + ai.commandPool = m_pool; + ai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + ai.commandBufferCount = 1; + if (vkAllocateCommandBuffers(m_device, &ai, &cmdBuf) != VK_SUCCESS) return ErrorCode::Unknown; + } + + VkCommandBufferBeginInfo bi{}; + bi.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + vkBeginCommandBuffer(cmdBuf, &bi); + + out = new VkCommandEncoderImpl(cmdBuf, m_device, this); + return ErrorCode::Ok; +} + +void VkCommandPoolImpl::destroyEncoder(CommandEncoder*& encoder) { + if (encoder) { delete encoder; encoder = nullptr; } +} + +void VkCommandPoolImpl::reset() { + for (auto* cb : m_trackedBuffers) { m_freeHandles.push_back(cb->handle()); delete cb; } + m_trackedBuffers.clear(); + for (auto* e : m_trackedBundleEncoders) delete e; // each frees its produced bundle + m_trackedBundleEncoders.clear(); + for (auto h : m_liveSecondaries) m_freeSecondaries.push_back(h); // recycle (pool reset below) + m_liveSecondaries.clear(); + vkResetCommandPool(m_device, m_pool, 0); +} + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkCommandPool.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkCommandPool.cppm new file mode 100644 index 00000000..29f77b76 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkCommandPool.cppm @@ -0,0 +1,100 @@ +/// Vulkan implementation of CommandPool. + +module; + +#include "VkIncludes.h" +#include + + +export module rhi.vk:command_pool; + +import core.stdtypes; +import core.status; +import rhi; +import :adapter; +import :command_buffer; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkDeviceImpl; // forward +class VkCommandEncoderImpl; // forward + +class VkCommandPoolImpl : public CommandPool { +public: + Status init(VkDevice device, VkAdapterImpl* adapter, QueueType queueType) { + m_device = device; + + i32 familyIndex = adapter->findQueueFamily(queueType); + if (familyIndex < 0) return ErrorCode::Unknown; + m_familyIndex = static_cast(familyIndex); + + VkCommandPoolCreateInfo ci{}; + ci.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + ci.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; + ci.queueFamilyIndex = m_familyIndex; + + if (vkCreateCommandPool(device, &ci, nullptr, &m_pool) != VK_SUCCESS) return ErrorCode::Unknown; + return ErrorCode::Ok; + } + + // ---- CommandPool interface ---- + Status createEncoder(CommandEncoder*& out) override; + void destroyEncoder(CommandEncoder*& encoder) override; + void reset() override; + + void cleanup() { + for (auto* cb : m_trackedBuffers) delete cb; + m_trackedBuffers.clear(); + m_freeHandles.clear(); + for (auto* e : m_trackedBundleEncoders) delete e; // each frees its produced bundle + m_trackedBundleEncoders.clear(); + m_liveSecondaries.clear(); + m_freeSecondaries.clear(); + + if (m_pool != VK_NULL_HANDLE) { vkDestroyCommandPool(m_device, m_pool, nullptr); m_pool = VK_NULL_HANDLE; } + } + + // Called by encoder's finish() to register the command buffer. + void trackCommandBuffer(VkCommandBufferImpl* cb) { m_trackedBuffers.push_back(cb); } + + // ---- render bundles (secondary command buffers) ---- + + // Allocate (or recycle) a SECONDARY command buffer for a render bundle encoder. + [[nodiscard]] VkCommandBuffer acquireSecondary() { + VkCommandBuffer cb = VK_NULL_HANDLE; + if (!m_freeSecondaries.empty()) { cb = m_freeSecondaries.back(); m_freeSecondaries.pop_back(); } + else { + VkCommandBufferAllocateInfo ai{}; + ai.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + ai.commandPool = m_pool; + ai.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY; + ai.commandBufferCount = 1; + if (vkAllocateCommandBuffers(m_device, &ai, &cb) != VK_SUCCESS) return VK_NULL_HANDLE; + } + m_liveSecondaries.push_back(cb); // recycled on Reset + return cb; + } + + // Track a bundle-encoder wrapper so it (and the bundle it owns) is freed on Reset. + void trackBundleEncoder(RenderBundleEncoder* e) { m_trackedBundleEncoders.push_back(e); } + + [[nodiscard]] VkCommandPool handle() const { return m_pool; } + [[nodiscard]] VkDevice vkDevice() const { return m_device; } + + // Stored so the encoder can access it. + VkDeviceImpl* ownerDevice = nullptr; + +private: + VkDevice m_device = VK_NULL_HANDLE; + VkCommandPool m_pool = VK_NULL_HANDLE; + u32 m_familyIndex = 0; + std::vector m_freeHandles; + std::vector m_trackedBuffers; + std::vector m_freeSecondaries; // recyclable secondary handles + std::vector m_liveSecondaries; // handed out this cycle + std::vector m_trackedBundleEncoders; // wrappers freed on reset +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkComputePassEncoder.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkComputePassEncoder.cppm new file mode 100644 index 00000000..dee634dc --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkComputePassEncoder.cppm @@ -0,0 +1,80 @@ +/// Vulkan implementation of ComputePassEncoder. + +module; + +#include "VkIncludes.h" +#include + +export module rhi.vk:compute_pass_encoder; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :buffer; +import :bind_group; +import :compute_pipeline; +import :pipeline_layout; +import :query_set; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkComputePassEncoderImpl : public ComputePassEncoder { +public: + VkComputePassEncoderImpl(VkCommandBuffer cmdBuf) : m_cmdBuf(cmdBuf) {} + + void setPipeline(ComputePipeline* pipeline) override { + m_current = static_cast(pipeline); + if (m_current) vkCmdBindPipeline(m_cmdBuf, VK_PIPELINE_BIND_POINT_COMPUTE, m_current->handle()); + } + + void setBindGroup(u32 index, BindGroup* group, std::span dynOffsets) override { + auto* bg = static_cast(group); + if (!bg || !m_current || !m_current->vkLayout()) return; + VkDescriptorSet set = bg->handle(); + vkCmdBindDescriptorSets(m_cmdBuf, VK_PIPELINE_BIND_POINT_COMPUTE, + m_current->vkLayout()->handle(), index, 1, &set, + static_cast(dynOffsets.size()), dynOffsets.data()); + } + + void setPushConstants(ShaderStage stages, u32 offset, u32 size, const void* data) override { + if (!m_current || !m_current->vkLayout()) return; + vkCmdPushConstants(m_cmdBuf, m_current->vkLayout()->handle(), + toVkShaderStageFlags(stages), offset, size, data); + } + + void dispatch(u32 x, u32 y, u32 z) override { vkCmdDispatch(m_cmdBuf, x, y, z); } + + void dispatchIndirect(Buffer* buffer, u64 offset) override { + auto* vkBuf = static_cast(buffer); + if (vkBuf) vkCmdDispatchIndirect(m_cmdBuf, vkBuf->handle(), offset); + } + + void computeBarrier() override { + VkMemoryBarrier2 mb{}; + mb.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2; + mb.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + mb.srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT; + mb.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; + mb.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT; + VkDependencyInfo di{}; + di.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO; + di.memoryBarrierCount = 1; di.pMemoryBarriers = &mb; + vkCmdPipelineBarrier2(m_cmdBuf, &di); + } + + void writeTimestamp(QuerySet* qs, u32 index) override { + auto* q = static_cast(qs); + if (q) vkCmdWriteTimestamp(m_cmdBuf, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, q->handle(), index); + } + + void end() override { m_current = nullptr; } + +private: + VkCommandBuffer m_cmdBuf = VK_NULL_HANDLE; + VkComputePipelineImpl* m_current = nullptr; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkComputePipeline.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkComputePipeline.cppm new file mode 100644 index 00000000..659f499d --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkComputePipeline.cppm @@ -0,0 +1,66 @@ +/// Vulkan implementation of ComputePipeline. + +module; + +#include "VkIncludes.h" +#include + + +export module rhi.vk:compute_pipeline; + +import core.stdtypes; +import core.status; +import rhi; +import :shader_module; +import :pipeline_layout; +import :pipeline_cache; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkComputePipelineImpl : public ComputePipeline { +public: + Status init(VkDevice device, const ComputePipelineDesc& desc) { + auto* vkLayout = static_cast(desc.layout); + if (!vkLayout) return ErrorCode::Unknown; + layout = desc.layout; + m_layout = vkLayout; + + auto* vkMod = static_cast(desc.compute.module); + if (!vkMod) return ErrorCode::Unknown; + + std::u8string entry = std::u8string(desc.compute.entryPoint); + + VkPipelineShaderStageCreateInfo stage{}; + stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; + stage.module = vkMod->handle(); + stage.pName = reinterpret_cast(entry.c_str()); + + VkComputePipelineCreateInfo ci{}; + ci.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; + ci.stage = stage; + ci.layout = vkLayout->handle(); + + VkPipelineCache cacheHandle = VK_NULL_HANDLE; + if (desc.cache) cacheHandle = static_cast(desc.cache)->handle(); + + if (vkCreateComputePipelines(device, cacheHandle, 1, &ci, nullptr, &m_pipeline) != VK_SUCCESS) + return ErrorCode::Unknown; + return ErrorCode::Ok; + } + + void cleanup(VkDevice device) { + if (m_pipeline != VK_NULL_HANDLE) { vkDestroyPipeline(device, m_pipeline, nullptr); m_pipeline = VK_NULL_HANDLE; } + } + + [[nodiscard]] VkPipeline handle() const { return m_pipeline; } + [[nodiscard]] VkPipelineLayoutImpl* vkLayout() const { return m_layout; } + +private: + VkPipeline m_pipeline = VK_NULL_HANDLE; + VkPipelineLayoutImpl* m_layout = nullptr; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkConversions.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkConversions.cppm new file mode 100644 index 00000000..a287d29c --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkConversions.cppm @@ -0,0 +1,341 @@ +/// Conversion utilities between draco::rhi enums and Vulkan enums. + +module; + +#include "VkIncludes.h" + +export module rhi.vk:conversions; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::vk { + +/// Depth format support flags - set once at VkDevice init via setDepthFormatSupport(). +/// Defaults to supported; probing overrides if hardware lacks D24. +namespace detail { + inline bool g_depth24S8Supported = true; + inline bool g_depth24Supported = true; +} + +/// Called by VkDevice::init after probing physical device format properties. +inline void setDepthFormatSupport(bool depth24S8, bool depth24) { + detail::g_depth24S8Supported = depth24S8; + detail::g_depth24Supported = depth24; +} + +inline VkFormat toVkFormat(TextureFormat f) { + switch (f) { + case TextureFormat::Undefined: return VK_FORMAT_UNDEFINED; + case TextureFormat::R8Unorm: return VK_FORMAT_R8_UNORM; + case TextureFormat::R8Snorm: return VK_FORMAT_R8_SNORM; + case TextureFormat::R8Uint: return VK_FORMAT_R8_UINT; + case TextureFormat::R8Sint: return VK_FORMAT_R8_SINT; + case TextureFormat::R16Uint: return VK_FORMAT_R16_UINT; + case TextureFormat::R16Sint: return VK_FORMAT_R16_SINT; + case TextureFormat::R16Float: return VK_FORMAT_R16_SFLOAT; + case TextureFormat::RG8Unorm: return VK_FORMAT_R8G8_UNORM; + case TextureFormat::RG8Snorm: return VK_FORMAT_R8G8_SNORM; + case TextureFormat::RG8Uint: return VK_FORMAT_R8G8_UINT; + case TextureFormat::RG8Sint: return VK_FORMAT_R8G8_SINT; + case TextureFormat::R32Uint: return VK_FORMAT_R32_UINT; + case TextureFormat::R32Sint: return VK_FORMAT_R32_SINT; + case TextureFormat::R32Float: return VK_FORMAT_R32_SFLOAT; + case TextureFormat::RG16Uint: return VK_FORMAT_R16G16_UINT; + case TextureFormat::RG16Sint: return VK_FORMAT_R16G16_SINT; + case TextureFormat::RG16Float: return VK_FORMAT_R16G16_SFLOAT; + case TextureFormat::RGBA8Unorm: return VK_FORMAT_R8G8B8A8_UNORM; + case TextureFormat::RGBA8UnormSrgb: return VK_FORMAT_R8G8B8A8_SRGB; + case TextureFormat::RGBA8Snorm: return VK_FORMAT_R8G8B8A8_SNORM; + case TextureFormat::RGBA8Uint: return VK_FORMAT_R8G8B8A8_UINT; + case TextureFormat::RGBA8Sint: return VK_FORMAT_R8G8B8A8_SINT; + case TextureFormat::BGRA8Unorm: return VK_FORMAT_B8G8R8A8_UNORM; + case TextureFormat::BGRA8UnormSrgb: return VK_FORMAT_B8G8R8A8_SRGB; + case TextureFormat::RGB10A2Unorm: return VK_FORMAT_A2B10G10R10_UNORM_PACK32; + case TextureFormat::RGB10A2Uint: return VK_FORMAT_A2B10G10R10_UINT_PACK32; + case TextureFormat::RG11B10Float: return VK_FORMAT_B10G11R11_UFLOAT_PACK32; + case TextureFormat::RGB9E5Float: return VK_FORMAT_E5B9G9R9_UFLOAT_PACK32; + case TextureFormat::RG32Uint: return VK_FORMAT_R32G32_UINT; + case TextureFormat::RG32Sint: return VK_FORMAT_R32G32_SINT; + case TextureFormat::RG32Float: return VK_FORMAT_R32G32_SFLOAT; + case TextureFormat::RGBA16Uint: return VK_FORMAT_R16G16B16A16_UINT; + case TextureFormat::RGBA16Sint: return VK_FORMAT_R16G16B16A16_SINT; + case TextureFormat::RGBA16Float: return VK_FORMAT_R16G16B16A16_SFLOAT; + case TextureFormat::RGBA16Unorm: return VK_FORMAT_R16G16B16A16_UNORM; + case TextureFormat::RGBA16Snorm: return VK_FORMAT_R16G16B16A16_SNORM; + case TextureFormat::RGBA32Uint: return VK_FORMAT_R32G32B32A32_UINT; + case TextureFormat::RGBA32Sint: return VK_FORMAT_R32G32B32A32_SINT; + case TextureFormat::RGBA32Float: return VK_FORMAT_R32G32B32A32_SFLOAT; + case TextureFormat::Depth16Unorm: return VK_FORMAT_D16_UNORM; + case TextureFormat::Depth24Plus: return detail::g_depth24Supported ? VK_FORMAT_X8_D24_UNORM_PACK32 : VK_FORMAT_D32_SFLOAT; + case TextureFormat::Depth24PlusStencil8:return detail::g_depth24S8Supported ? VK_FORMAT_D24_UNORM_S8_UINT : VK_FORMAT_D32_SFLOAT_S8_UINT; + case TextureFormat::Depth32Float: return VK_FORMAT_D32_SFLOAT; + case TextureFormat::Depth32FloatStencil8:return VK_FORMAT_D32_SFLOAT_S8_UINT; + case TextureFormat::Stencil8: return VK_FORMAT_S8_UINT; + case TextureFormat::BC1RGBAUnorm: return VK_FORMAT_BC1_RGBA_UNORM_BLOCK; + case TextureFormat::BC1RGBAUnormSrgb: return VK_FORMAT_BC1_RGBA_SRGB_BLOCK; + case TextureFormat::BC2RGBAUnorm: return VK_FORMAT_BC2_UNORM_BLOCK; + case TextureFormat::BC2RGBAUnormSrgb: return VK_FORMAT_BC2_SRGB_BLOCK; + case TextureFormat::BC3RGBAUnorm: return VK_FORMAT_BC3_UNORM_BLOCK; + case TextureFormat::BC3RGBAUnormSrgb: return VK_FORMAT_BC3_SRGB_BLOCK; + case TextureFormat::BC4RUnorm: return VK_FORMAT_BC4_UNORM_BLOCK; + case TextureFormat::BC4RSnorm: return VK_FORMAT_BC4_SNORM_BLOCK; + case TextureFormat::BC5RGUnorm: return VK_FORMAT_BC5_UNORM_BLOCK; + case TextureFormat::BC5RGSnorm: return VK_FORMAT_BC5_SNORM_BLOCK; + case TextureFormat::BC6HRGBUfloat: return VK_FORMAT_BC6H_UFLOAT_BLOCK; + case TextureFormat::BC6HRGBFloat: return VK_FORMAT_BC6H_SFLOAT_BLOCK; + case TextureFormat::BC7RGBAUnorm: return VK_FORMAT_BC7_UNORM_BLOCK; + case TextureFormat::BC7RGBAUnormSrgb: return VK_FORMAT_BC7_SRGB_BLOCK; + case TextureFormat::ASTC4x4Unorm: return VK_FORMAT_ASTC_4x4_UNORM_BLOCK; + case TextureFormat::ASTC4x4UnormSrgb: return VK_FORMAT_ASTC_4x4_SRGB_BLOCK; + case TextureFormat::ASTC5x5Unorm: return VK_FORMAT_ASTC_5x5_UNORM_BLOCK; + case TextureFormat::ASTC5x5UnormSrgb: return VK_FORMAT_ASTC_5x5_SRGB_BLOCK; + case TextureFormat::ASTC6x6Unorm: return VK_FORMAT_ASTC_6x6_UNORM_BLOCK; + case TextureFormat::ASTC6x6UnormSrgb: return VK_FORMAT_ASTC_6x6_SRGB_BLOCK; + case TextureFormat::ASTC8x8Unorm: return VK_FORMAT_ASTC_8x8_UNORM_BLOCK; + case TextureFormat::ASTC8x8UnormSrgb: return VK_FORMAT_ASTC_8x8_SRGB_BLOCK; + default: return VK_FORMAT_UNDEFINED; + } +} + +inline VkFormat toVkVertexFormat(VertexFormat f) { + switch (f) { + case VertexFormat::Uint8x2: return VK_FORMAT_R8G8_UINT; + case VertexFormat::Uint8x4: return VK_FORMAT_R8G8B8A8_UINT; + case VertexFormat::Sint8x2: return VK_FORMAT_R8G8_SINT; + case VertexFormat::Sint8x4: return VK_FORMAT_R8G8B8A8_SINT; + case VertexFormat::Unorm8x2: return VK_FORMAT_R8G8_UNORM; + case VertexFormat::Unorm8x4: return VK_FORMAT_R8G8B8A8_UNORM; + case VertexFormat::Snorm8x2: return VK_FORMAT_R8G8_SNORM; + case VertexFormat::Snorm8x4: return VK_FORMAT_R8G8B8A8_SNORM; + case VertexFormat::Uint16x2: return VK_FORMAT_R16G16_UINT; + case VertexFormat::Uint16x4: return VK_FORMAT_R16G16B16A16_UINT; + case VertexFormat::Sint16x2: return VK_FORMAT_R16G16_SINT; + case VertexFormat::Sint16x4: return VK_FORMAT_R16G16B16A16_SINT; + case VertexFormat::Unorm16x2: return VK_FORMAT_R16G16_UNORM; + case VertexFormat::Unorm16x4: return VK_FORMAT_R16G16B16A16_UNORM; + case VertexFormat::Snorm16x2: return VK_FORMAT_R16G16_SNORM; + case VertexFormat::Snorm16x4: return VK_FORMAT_R16G16B16A16_SNORM; + case VertexFormat::Float16x2: return VK_FORMAT_R16G16_SFLOAT; + case VertexFormat::Float16x4: return VK_FORMAT_R16G16B16A16_SFLOAT; + case VertexFormat::Float32: return VK_FORMAT_R32_SFLOAT; + case VertexFormat::Float32x2: return VK_FORMAT_R32G32_SFLOAT; + case VertexFormat::Float32x3: return VK_FORMAT_R32G32B32_SFLOAT; + case VertexFormat::Float32x4: return VK_FORMAT_R32G32B32A32_SFLOAT; + case VertexFormat::Uint32: return VK_FORMAT_R32_UINT; + case VertexFormat::Uint32x2: return VK_FORMAT_R32G32_UINT; + case VertexFormat::Uint32x3: return VK_FORMAT_R32G32B32_UINT; + case VertexFormat::Uint32x4: return VK_FORMAT_R32G32B32A32_UINT; + case VertexFormat::Sint32: return VK_FORMAT_R32_SINT; + case VertexFormat::Sint32x2: return VK_FORMAT_R32G32_SINT; + case VertexFormat::Sint32x3: return VK_FORMAT_R32G32B32_SINT; + case VertexFormat::Sint32x4: return VK_FORMAT_R32G32B32A32_SINT; + default: return VK_FORMAT_UNDEFINED; + } +} + +inline VkBufferUsageFlags toVkBufferUsage(BufferUsage u) { + VkBufferUsageFlags f = 0; + if (hasFlag(u, BufferUsage::CopySrc)) f |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + if (hasFlag(u, BufferUsage::CopyDst)) f |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; + if (hasFlag(u, BufferUsage::Vertex)) f |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + if (hasFlag(u, BufferUsage::Index)) f |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT; + if (hasFlag(u, BufferUsage::Uniform)) f |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; + if (hasFlag(u, BufferUsage::Storage)) f |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + if (hasFlag(u, BufferUsage::StorageRead))f|= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + if (hasFlag(u, BufferUsage::Indirect)) f |= VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT; + if (hasFlag(u, BufferUsage::AccelStructInput)) + f |= VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + if (hasFlag(u, BufferUsage::ShaderBindingTable)) + f |= VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + if (hasFlag(u, BufferUsage::AccelStructScratch)) + f |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + return f; +} + +inline VkImageUsageFlags toVkImageUsage(TextureUsage u) { + VkImageUsageFlags f = 0; + if ((static_cast(u) & static_cast(TextureUsage::CopySrc)) != 0) f |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + if ((static_cast(u) & static_cast(TextureUsage::CopyDst)) != 0) f |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + if ((static_cast(u) & static_cast(TextureUsage::Sampled)) != 0) f |= VK_IMAGE_USAGE_SAMPLED_BIT; + if ((static_cast(u) & static_cast(TextureUsage::Storage)) != 0) f |= VK_IMAGE_USAGE_STORAGE_BIT; + if ((static_cast(u) & static_cast(TextureUsage::RenderTarget)) != 0) f |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + if ((static_cast(u) & static_cast(TextureUsage::DepthStencil)) != 0) f |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + if ((static_cast(u) & static_cast(TextureUsage::InputAttachment))!= 0) f |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT; + return f; +} + +inline VkImageType toVkImageType(TextureDimension d) { + switch (d) { + case TextureDimension::Texture1D: return VK_IMAGE_TYPE_1D; + case TextureDimension::Texture2D: return VK_IMAGE_TYPE_2D; + case TextureDimension::Texture3D: return VK_IMAGE_TYPE_3D; + } return VK_IMAGE_TYPE_2D; +} + +inline VkImageViewType toVkImageViewType(TextureViewDimension d) { + switch (d) { + case TextureViewDimension::Texture1D: return VK_IMAGE_VIEW_TYPE_1D; + case TextureViewDimension::Texture1DArray: return VK_IMAGE_VIEW_TYPE_1D_ARRAY; + case TextureViewDimension::Texture2D: return VK_IMAGE_VIEW_TYPE_2D; + case TextureViewDimension::Texture2DArray: return VK_IMAGE_VIEW_TYPE_2D_ARRAY; + case TextureViewDimension::TextureCube: return VK_IMAGE_VIEW_TYPE_CUBE; + case TextureViewDimension::TextureCubeArray: return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; + case TextureViewDimension::Texture3D: return VK_IMAGE_VIEW_TYPE_3D; + } return VK_IMAGE_VIEW_TYPE_2D; +} + +inline VkFilter toVkFilter(FilterMode m) { return m == FilterMode::Nearest ? VK_FILTER_NEAREST : VK_FILTER_LINEAR; } +inline VkSamplerMipmapMode toVkMipmapMode(MipmapFilterMode m) { return m == MipmapFilterMode::Nearest ? VK_SAMPLER_MIPMAP_MODE_NEAREST : VK_SAMPLER_MIPMAP_MODE_LINEAR; } + +inline VkSamplerAddressMode toVkAddressMode(AddressMode m) { + switch (m) { + case AddressMode::Repeat: return VK_SAMPLER_ADDRESS_MODE_REPEAT; + case AddressMode::MirrorRepeat: return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; + case AddressMode::ClampToEdge: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + case AddressMode::ClampToBorder:return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; + } return VK_SAMPLER_ADDRESS_MODE_REPEAT; +} + +inline VkBorderColor toVkBorderColor(SamplerBorderColor c) { + switch (c) { + case SamplerBorderColor::TransparentBlack: return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; + case SamplerBorderColor::OpaqueBlack: return VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK; + case SamplerBorderColor::OpaqueWhite: return VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + } return VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; +} + +inline VkCompareOp toVkCompareOp(CompareFunction f) { + switch (f) { + case CompareFunction::Never: return VK_COMPARE_OP_NEVER; + case CompareFunction::Less: return VK_COMPARE_OP_LESS; + case CompareFunction::Equal: return VK_COMPARE_OP_EQUAL; + case CompareFunction::LessEqual: return VK_COMPARE_OP_LESS_OR_EQUAL; + case CompareFunction::Greater: return VK_COMPARE_OP_GREATER; + case CompareFunction::NotEqual: return VK_COMPARE_OP_NOT_EQUAL; + case CompareFunction::GreaterEqual: return VK_COMPARE_OP_GREATER_OR_EQUAL; + case CompareFunction::Always: return VK_COMPARE_OP_ALWAYS; + } return VK_COMPARE_OP_NEVER; +} + +inline VkPrimitiveTopology toVkTopology(PrimitiveTopology t) { + switch (t) { + case PrimitiveTopology::PointList: return VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + case PrimitiveTopology::LineList: return VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + case PrimitiveTopology::LineStrip: return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; + case PrimitiveTopology::TriangleList: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + case PrimitiveTopology::TriangleStrip: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; + } return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; +} + +inline VkFrontFace toVkFrontFace(FrontFace f) { return f == FrontFace::CCW ? VK_FRONT_FACE_COUNTER_CLOCKWISE : VK_FRONT_FACE_CLOCKWISE; } +inline VkCullModeFlags toVkCullMode(CullMode m) { switch (m) { case CullMode::None: return VK_CULL_MODE_NONE; case CullMode::Front: return VK_CULL_MODE_FRONT_BIT; case CullMode::Back: return VK_CULL_MODE_BACK_BIT; } return VK_CULL_MODE_NONE; } +inline VkPolygonMode toVkPolygonMode(FillMode m) { return m == FillMode::Solid ? VK_POLYGON_MODE_FILL : VK_POLYGON_MODE_LINE; } + +inline VkBlendFactor toVkBlendFactor(BlendFactor f) { + switch (f) { + case BlendFactor::Zero: return VK_BLEND_FACTOR_ZERO; + case BlendFactor::One: return VK_BLEND_FACTOR_ONE; + case BlendFactor::Src: return VK_BLEND_FACTOR_SRC_COLOR; + case BlendFactor::OneMinusSrc: return VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR; + case BlendFactor::SrcAlpha: return VK_BLEND_FACTOR_SRC_ALPHA; + case BlendFactor::OneMinusSrcAlpha: return VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + case BlendFactor::Dst: return VK_BLEND_FACTOR_DST_COLOR; + case BlendFactor::OneMinusDst: return VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR; + case BlendFactor::DstAlpha: return VK_BLEND_FACTOR_DST_ALPHA; + case BlendFactor::OneMinusDstAlpha: return VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA; + case BlendFactor::SrcAlphaSaturated:return VK_BLEND_FACTOR_SRC_ALPHA_SATURATE; + case BlendFactor::Constant: return VK_BLEND_FACTOR_CONSTANT_COLOR; + case BlendFactor::OneMinusConstant: return VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR; + } return VK_BLEND_FACTOR_ZERO; +} + +inline VkBlendOp toVkBlendOp(BlendOperation o) { + switch (o) { + case BlendOperation::Add: return VK_BLEND_OP_ADD; + case BlendOperation::Subtract: return VK_BLEND_OP_SUBTRACT; + case BlendOperation::ReverseSubtract: return VK_BLEND_OP_REVERSE_SUBTRACT; + case BlendOperation::Min: return VK_BLEND_OP_MIN; + case BlendOperation::Max: return VK_BLEND_OP_MAX; + } return VK_BLEND_OP_ADD; +} + +inline VkStencilOp toVkStencilOp(StencilOperation o) { + switch (o) { + case StencilOperation::Keep: return VK_STENCIL_OP_KEEP; + case StencilOperation::Zero: return VK_STENCIL_OP_ZERO; + case StencilOperation::Replace: return VK_STENCIL_OP_REPLACE; + case StencilOperation::IncrementClamp: return VK_STENCIL_OP_INCREMENT_AND_CLAMP; + case StencilOperation::DecrementClamp: return VK_STENCIL_OP_DECREMENT_AND_CLAMP; + case StencilOperation::Invert: return VK_STENCIL_OP_INVERT; + case StencilOperation::IncrementWrap: return VK_STENCIL_OP_INCREMENT_AND_WRAP; + case StencilOperation::DecrementWrap: return VK_STENCIL_OP_DECREMENT_AND_WRAP; + } return VK_STENCIL_OP_KEEP; +} + +inline VkAttachmentLoadOp toVkLoadOp(LoadOp o) { switch (o) { case LoadOp::Load: return VK_ATTACHMENT_LOAD_OP_LOAD; case LoadOp::Clear: return VK_ATTACHMENT_LOAD_OP_CLEAR; case LoadOp::DontCare: return VK_ATTACHMENT_LOAD_OP_DONT_CARE; } return VK_ATTACHMENT_LOAD_OP_CLEAR; } +inline VkAttachmentStoreOp toVkStoreOp(StoreOp o) { return o == StoreOp::Store ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE; } +inline VkIndexType toVkIndexType(IndexFormat f) { return f == IndexFormat::UInt16 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32; } + +inline VkPresentModeKHR toVkPresentMode(PresentMode m) { + switch (m) { + case PresentMode::Immediate: return VK_PRESENT_MODE_IMMEDIATE_KHR; + case PresentMode::Mailbox: return VK_PRESENT_MODE_MAILBOX_KHR; + case PresentMode::Fifo: return VK_PRESENT_MODE_FIFO_KHR; + case PresentMode::FifoRelaxed: return VK_PRESENT_MODE_FIFO_RELAXED_KHR; + } return VK_PRESENT_MODE_FIFO_KHR; +} + +inline VkImageAspectFlags getAspectMask(TextureFormat f) { + if (isDepthStencil(f)) { + VkImageAspectFlags a = 0; + if (hasDepth(f)) a |= VK_IMAGE_ASPECT_DEPTH_BIT; + if (hasStencil(f)) a |= VK_IMAGE_ASPECT_STENCIL_BIT; + return a; + } + return VK_IMAGE_ASPECT_COLOR_BIT; +} + +inline VkSampleCountFlagBits toVkSampleCount(u32 c) { + switch (c) { + case 1: return VK_SAMPLE_COUNT_1_BIT; + case 2: return VK_SAMPLE_COUNT_2_BIT; + case 4: return VK_SAMPLE_COUNT_4_BIT; + case 8: return VK_SAMPLE_COUNT_8_BIT; + case 16: return VK_SAMPLE_COUNT_16_BIT; + case 32: return VK_SAMPLE_COUNT_32_BIT; + case 64: return VK_SAMPLE_COUNT_64_BIT; + default: return VK_SAMPLE_COUNT_1_BIT; + } +} + +inline VkColorComponentFlags toVkColorWriteMask(ColorWriteMask m) { + VkColorComponentFlags f = 0; + if (static_cast(m) & static_cast(ColorWriteMask::Red)) f |= VK_COLOR_COMPONENT_R_BIT; + if (static_cast(m) & static_cast(ColorWriteMask::Green)) f |= VK_COLOR_COMPONENT_G_BIT; + if (static_cast(m) & static_cast(ColorWriteMask::Blue)) f |= VK_COLOR_COMPONENT_B_BIT; + if (static_cast(m) & static_cast(ColorWriteMask::Alpha)) f |= VK_COLOR_COMPONENT_A_BIT; + return f; +} + +inline VkShaderStageFlags toVkShaderStageFlags(ShaderStage s) { + VkShaderStageFlags f = 0; + auto v = static_cast(s); + if (v & static_cast(ShaderStage::Vertex)) f |= VK_SHADER_STAGE_VERTEX_BIT; + if (v & static_cast(ShaderStage::Fragment)) f |= VK_SHADER_STAGE_FRAGMENT_BIT; + if (v & static_cast(ShaderStage::Compute)) f |= VK_SHADER_STAGE_COMPUTE_BIT; + if (v & static_cast(ShaderStage::Mesh)) f |= VK_SHADER_STAGE_MESH_BIT_EXT; + if (v & static_cast(ShaderStage::Task)) f |= VK_SHADER_STAGE_TASK_BIT_EXT; + if (v & static_cast(ShaderStage::RayGen)) f |= VK_SHADER_STAGE_RAYGEN_BIT_KHR; + if (v & static_cast(ShaderStage::ClosestHit)) f |= VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; + if (v & static_cast(ShaderStage::Miss)) f |= VK_SHADER_STAGE_MISS_BIT_KHR; + if (v & static_cast(ShaderStage::AnyHit)) f |= VK_SHADER_STAGE_ANY_HIT_BIT_KHR; + if (v & static_cast(ShaderStage::Intersection)) f |= VK_SHADER_STAGE_INTERSECTION_BIT_KHR; + if (v & static_cast(ShaderStage::Callable)) f |= VK_SHADER_STAGE_CALLABLE_BIT_KHR; + return f; +} + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkDescriptorPoolManager.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkDescriptorPoolManager.cppm new file mode 100644 index 00000000..4856943e --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkDescriptorPoolManager.cppm @@ -0,0 +1,121 @@ +/// Manages VkDescriptorPool allocation with auto-grow. + +module; + +#include "VkIncludes.h" +#include + + +export module rhi.vk:descriptor_pool_manager; + +import core.stdtypes; +import core.status; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkDescriptorPoolManager { +public: + VkDescriptorPoolManager(VkDevice device, u32 maxSetsPerPool = 256, bool accelStructEnabled = false) + : m_device(device), m_maxSetsPerPool(maxSetsPerPool), m_accelStructEnabled(accelStructEnabled) {} + + Status allocate(VkDescriptorSetLayout layout, VkDescriptorPool& outPool, + bool updateAfterBind = false, u32 variableCount = 0) { + outPool = VK_NULL_HANDLE; + + VkDescriptorSetVariableDescriptorCountAllocateInfo varCountInfo{}; + varCountInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO; + if (variableCount > 0) { + varCountInfo.descriptorSetCount = 1; + varCountInfo.pDescriptorCounts = &variableCount; + } + + // Try existing pools. + for (auto pool : m_pools) { + VkDescriptorSet set = VK_NULL_HANDLE; + VkDescriptorSetAllocateInfo ai{}; + ai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + ai.descriptorPool = pool; + ai.descriptorSetCount = 1; + ai.pSetLayouts = &layout; + if (variableCount > 0) ai.pNext = &varCountInfo; + + if (vkAllocateDescriptorSets(m_device, &ai, &set) == VK_SUCCESS) { + outPool = pool; + m_lastAllocatedSet = set; + return ErrorCode::Ok; + } + } + + // Create new pool. + if (createPool(updateAfterBind) != ErrorCode::Ok) return ErrorCode::Unknown; + + auto pool = m_pools.back(); + VkDescriptorSet set = VK_NULL_HANDLE; + VkDescriptorSetAllocateInfo ai{}; + ai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + ai.descriptorPool = pool; + ai.descriptorSetCount = 1; + ai.pSetLayouts = &layout; + if (variableCount > 0) ai.pNext = &varCountInfo; + + if (vkAllocateDescriptorSets(m_device, &ai, &set) != VK_SUCCESS) return ErrorCode::Unknown; + + outPool = pool; + m_lastAllocatedSet = set; + return ErrorCode::Ok; + } + + [[nodiscard]] VkDescriptorSet lastAllocatedSet() const { return m_lastAllocatedSet; } + + void free(VkDescriptorPool pool, VkDescriptorSet set) { + vkFreeDescriptorSets(m_device, pool, 1, &set); + } + + void destroy() { + for (auto pool : m_pools) vkDestroyDescriptorPool(m_device, pool, nullptr); + m_pools.clear(); + } + +private: + Status createPool(bool updateAfterBind) { + u32 mult = updateAfterBind ? 64 : 1; + VkDescriptorPoolSize sizes[] = { + { VK_DESCRIPTOR_TYPE_SAMPLER, m_maxSetsPerPool * mult }, + { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, m_maxSetsPerPool * 4 * mult }, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, m_maxSetsPerPool * mult }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, m_maxSetsPerPool * 2 }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, m_maxSetsPerPool * 2 * mult }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, m_maxSetsPerPool }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, m_maxSetsPerPool }, + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, m_maxSetsPerPool * 4 * mult }, + { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, m_maxSetsPerPool }, + { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, m_maxSetsPerPool }, + { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, m_maxSetsPerPool }, + { VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, m_maxSetsPerPool }, + }; + u32 sizeCount = m_accelStructEnabled ? 12 : 11; + + VkDescriptorPoolCreateInfo ci{}; + ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + ci.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; + if (updateAfterBind) ci.flags |= VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT; + ci.maxSets = m_maxSetsPerPool; + ci.poolSizeCount = sizeCount; + ci.pPoolSizes = sizes; + + VkDescriptorPool pool = VK_NULL_HANDLE; + if (vkCreateDescriptorPool(m_device, &ci, nullptr, &pool) != VK_SUCCESS) return ErrorCode::Unknown; + m_pools.push_back(pool); + return ErrorCode::Ok; + } + + VkDevice m_device; + std::vector m_pools; + u32 m_maxSetsPerPool; + bool m_accelStructEnabled; + VkDescriptorSet m_lastAllocatedSet = VK_NULL_HANDLE; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkDevice.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkDevice.cppm new file mode 100644 index 00000000..5c024ffa --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkDevice.cppm @@ -0,0 +1,553 @@ +/// Vulkan implementation of Device. + +module; + +#include "VkIncludes.h" +#include +#include +#include +#include + +#include +#include + +export module rhi.vk:device; + +import core.stdtypes; +import core.status; +import rhi; +import :adapter; +import :surface; +import :buffer; +import :texture; +import :texture_view; +import :sampler; +import :shader_module; +import :fence; +import :query_set; +import :binding_shifts; +import :bind_group_layout; +import :bind_group; +import :pipeline_layout; +import :pipeline_cache; +import :render_pipeline; +import :compute_pipeline; +import :mesh_pipeline; +import :accel_struct; +import :ray_tracing_pipeline; +import :command_pool; +import :command_buffer; +import :queue; +import :swap_chain; +import :descriptor_pool_manager; +import :conversions; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkDeviceImpl : public Device { +public: + Status init(VkAdapterImpl* adapter, const DeviceDesc& desc) { + m_adapter = adapter; + features = adapter->buildFeatures(); + + bool bindlessEnabled = desc.requiredFeatures.bindlessDescriptors && adapter->supportsDescriptorIndexing(); + bool meshEnabled = desc.requiredFeatures.meshShaders && adapter->supportsMeshShader(); + bool rtEnabled = desc.requiredFeatures.rayTracing && adapter->supportsRayTracing(); + m_meshEnabled = meshEnabled; + m_rtEnabled = rtEnabled; + m_bindlessEnabled = bindlessEnabled; + // Validation flag: set externally by Adapter.createDevice after init. + + // Find queue families. + i32 gfxFamily = adapter->findQueueFamily(QueueType::Graphics); + i32 compFamily = adapter->findQueueFamily(QueueType::Compute); + i32 xferFamily = adapter->findQueueFamily(QueueType::Transfer); + if (gfxFamily < 0) return ErrorCode::Unknown; + + // Build queue create infos. + struct FamilyRequest { u32 family; u32 count; }; + std::vector familyReqs; + auto addFamily = [&](i32 f, u32 requested) { + if (f < 0 || requested == 0) return; + u32 avail = adapter->queueFamilies()[f].queueCount; + for (auto& fr : familyReqs) { if (fr.family == static_cast(f)) { fr.count = std::min(fr.count + requested, avail); return; } } + familyReqs.push_back({ static_cast(f), std::min(requested, avail) }); + }; + addFamily(gfxFamily, desc.graphicsQueueCount); + addFamily(compFamily, desc.computeQueueCount); + addFamily(xferFamily, desc.transferQueueCount); + + std::vector queueCis; + std::vector> priorities; + for (auto& fr : familyReqs) { + priorities.emplace_back(fr.count, 1.0f); + VkDeviceQueueCreateInfo qci{}; qci.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + qci.queueFamilyIndex = fr.family; qci.queueCount = fr.count; + qci.pQueuePriorities = priorities.back().data(); + queueCis.push_back(qci); + } + + // Extensions. + std::vector exts; + exts.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + if (meshEnabled) exts.push_back("VK_EXT_mesh_shader"); + if (rtEnabled) { + exts.push_back("VK_KHR_ray_tracing_pipeline"); + exts.push_back("VK_KHR_acceleration_structure"); + exts.push_back("VK_KHR_deferred_host_operations"); + exts.push_back("VK_KHR_ray_query"); + } + + // Feature chain. + VkPhysicalDeviceVulkan13Features features13{}; features13.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES; + features13.dynamicRendering = VK_TRUE; features13.synchronization2 = VK_TRUE; + features13.shaderDemoteToHelperInvocation = VK_TRUE; + + VkPhysicalDeviceVulkan12Features features12{}; features12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES; + features12.pNext = &features13; + features12.timelineSemaphore = VK_TRUE; + if (bindlessEnabled) { + features12.descriptorIndexing = VK_TRUE; + features12.descriptorBindingPartiallyBound = VK_TRUE; + features12.descriptorBindingVariableDescriptorCount = VK_TRUE; + features12.descriptorBindingSampledImageUpdateAfterBind = VK_TRUE; + features12.descriptorBindingStorageBufferUpdateAfterBind = VK_TRUE; + features12.runtimeDescriptorArray = VK_TRUE; + features12.shaderSampledImageArrayNonUniformIndexing = VK_TRUE; + features12.shaderStorageBufferArrayNonUniformIndexing = VK_TRUE; + } + if (rtEnabled) features12.bufferDeviceAddress = VK_TRUE; + + void* featureChainTail = &features12; + + VkPhysicalDeviceMeshShaderFeaturesEXT meshFeatures{}; + meshFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MESH_SHADER_FEATURES_EXT; + if (meshEnabled) { meshFeatures.taskShader = VK_TRUE; meshFeatures.meshShader = VK_TRUE; + meshFeatures.pNext = featureChainTail; featureChainTail = &meshFeatures; } + + VkPhysicalDeviceRayTracingPipelineFeaturesKHR rtPipeFeatures{}; + rtPipeFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR; + VkPhysicalDeviceAccelerationStructureFeaturesKHR asFeatures{}; + asFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR; + VkPhysicalDeviceRayQueryFeaturesKHR rqFeatures{}; + rqFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR; + if (rtEnabled) { + rtPipeFeatures.rayTracingPipeline = VK_TRUE; + asFeatures.accelerationStructure = VK_TRUE; + rqFeatures.rayQuery = VK_TRUE; + rtPipeFeatures.pNext = featureChainTail; featureChainTail = &rtPipeFeatures; + asFeatures.pNext = featureChainTail; featureChainTail = &asFeatures; + rqFeatures.pNext = featureChainTail; featureChainTail = &rqFeatures; + } + + VkPhysicalDeviceFeatures2 features2{}; features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features2.pNext = featureChainTail; + features2.features = adapter->features10(); + + VkDeviceCreateInfo dci{}; dci.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + dci.pNext = &features2; + dci.queueCreateInfoCount = static_cast(queueCis.size()); + dci.pQueueCreateInfos = queueCis.data(); + dci.enabledExtensionCount = static_cast(exts.size()); + dci.ppEnabledExtensionNames = exts.data(); + + if (vkCreateDevice(adapter->physicalDevice(), &dci, nullptr, &m_device) != VK_SUCCESS) + return ErrorCode::Unknown; + + type = DeviceType::Vulkan; + + // Retrieve queues. + auto retrieveQueues = [&](i32 family, u32 count, QueueType qt, u32 offset) { + if (family < 0 || count == 0) return; + f32 tsPeriod = adapter->properties().limits.timestampPeriod; + for (u32 i = 0; i < count; ++i) { + VkQueue q = VK_NULL_HANDLE; + vkGetDeviceQueue(m_device, static_cast(family), offset + i, &q); + auto* vkQ = new VkQueueImpl(q, qt, static_cast(family), tsPeriod, this, m_device, adapter->physicalDevice()); + m_allQueues.push_back(vkQ); + switch (qt) { + case QueueType::Graphics: m_gfxQueues.push_back(vkQ); break; + case QueueType::Compute: m_compQueues.push_back(vkQ); break; + case QueueType::Transfer: m_xferQueues.push_back(vkQ); break; + } + } + }; + // Track how many queues have been claimed from each family to avoid overrun. + auto familyAvail = [&](i32 family) -> u32 { + u32 total = adapter->queueFamilies()[family].queueCount; + for (auto& fr : familyReqs) if (fr.family == static_cast(family)) return fr.count; + return total; + }; + + u32 gfxCount = std::min(desc.graphicsQueueCount, familyAvail(gfxFamily)); + retrieveQueues(gfxFamily, gfxCount, QueueType::Graphics, 0); + + if (compFamily >= 0) { + u32 offset = (compFamily == gfxFamily) ? gfxCount : 0; + u32 avail = adapter->queueFamilies()[compFamily].queueCount - offset; + u32 compCount = std::min(desc.computeQueueCount, avail); + retrieveQueues(compFamily, compCount, QueueType::Compute, offset); + } + if (xferFamily >= 0) { + u32 offset = 0; + if (xferFamily == gfxFamily) offset = gfxCount + static_cast(m_compQueues.size()); + else if (xferFamily == compFamily) offset = static_cast(m_compQueues.size()); + u32 avail = adapter->queueFamilies()[xferFamily].queueCount - offset; + u32 xferCount = std::min(desc.transferQueueCount, avail); + retrieveQueues(xferFamily, xferCount, QueueType::Transfer, offset); + } + + // Descriptor pool manager. + m_poolManager = new VkDescriptorPoolManager(m_device, 256, rtEnabled); + + // Probe depth-stencil format support. D24_S8 is optional (unsupported on AMD/RADV). + // Configures toVkFormat() to substitute D32F/D32F_S8 when D24 variants aren't supported. + { + VkFormatProperties fp{}; + vkGetPhysicalDeviceFormatProperties(adapter->physicalDevice(), VK_FORMAT_D24_UNORM_S8_UINT, &fp); + bool d24s8 = (fp.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0; + vkGetPhysicalDeviceFormatProperties(adapter->physicalDevice(), VK_FORMAT_X8_D24_UNORM_PACK32, &fp); + bool d24 = (fp.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0; + setDepthFormatSupport(d24s8, d24); + } + + // RT properties. + if (rtEnabled) { + VkPhysicalDeviceRayTracingPipelinePropertiesKHR rtProps{}; + rtProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR; + VkPhysicalDeviceProperties2 p2{}; p2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; p2.pNext = &rtProps; + vkGetPhysicalDeviceProperties2(adapter->physicalDevice(), &p2); + shaderGroupHandleSize = rtProps.shaderGroupHandleSize; + shaderGroupHandleAlignment = rtProps.shaderGroupHandleAlignment; + shaderGroupBaseAlignment = rtProps.shaderGroupBaseAlignment; + } + + return ErrorCode::Ok; + } + + // ---- Device interface ---- + + Queue* getQueue(QueueType t, u32 index) override { + switch (t) { + case QueueType::Graphics: return index < m_gfxQueues.size() ? m_gfxQueues[index] : nullptr; + case QueueType::Compute: return index < m_compQueues.size() ? m_compQueues[index] : nullptr; + case QueueType::Transfer: return index < m_xferQueues.size() ? m_xferQueues[index] : nullptr; + } return nullptr; + } + u32 getQueueCount(QueueType t) override { + switch (t) { + case QueueType::Graphics: return static_cast(m_gfxQueues.size()); + case QueueType::Compute: return static_cast(m_compQueues.size()); + case QueueType::Transfer: return static_cast(m_xferQueues.size()); + } return 0; + } + + FormatSupport getFormatSupport(TextureFormat format) override { + VkFormatProperties fp{}; + vkGetPhysicalDeviceFormatProperties(m_adapter->physicalDevice(), toVkFormat(format), &fp); + auto opt = fp.optimalTilingFeatures; + auto buf = fp.bufferFeatures; + FormatSupport s = FormatSupport::Unsupported; + if (opt & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) s = s | FormatSupport::Texture; + if (opt & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) s = s | FormatSupport::StorageTexture; + if (opt & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) s = s | FormatSupport::ColorAttachment; + if (opt & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) s = s | FormatSupport::DepthStencil; + if (opt & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT) s = s | FormatSupport::BlendableColor; + if (opt & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)s = s | FormatSupport::LinearFilter; + if (buf & VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT) s = s | FormatSupport::Buffer; + if (buf & VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT) s = s | FormatSupport::StorageBuffer; + if (buf & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT) s = s | FormatSupport::VertexBuffer; + return s; + } + + // ---- Resource creation ---- + Status createBuffer(const BufferDesc& d, Buffer*& out) override { + auto* b = new VkBufferImpl(); if (b->init(m_device, m_adapter, d) != ErrorCode::Ok) { delete b; out = nullptr; return ErrorCode::Unknown; } + out = b; return ErrorCode::Ok; + } + Status createTexture(const TextureDesc& d, Texture*& out) override { + auto* t = new VkTextureImpl(); if (t->init(m_device, m_adapter, d) != ErrorCode::Ok) { delete t; out = nullptr; return ErrorCode::Unknown; } + out = t; return ErrorCode::Ok; + } + Status createTextureView(Texture* tex, const TextureViewDesc& d, TextureView*& out) override { + auto* v = new VkTextureViewImpl(); if (v->init(m_device, static_cast(tex), d) != ErrorCode::Ok) { delete v; out = nullptr; return ErrorCode::Unknown; } + out = v; return ErrorCode::Ok; + } + Status createSampler(const SamplerDesc& d, Sampler*& out) override { + auto* s = new VkSamplerImpl(); if (s->init(m_device, d) != ErrorCode::Ok) { delete s; out = nullptr; return ErrorCode::Unknown; } + out = s; return ErrorCode::Ok; + } + Status createShaderModule(const ShaderModuleDesc& d, ShaderModule*& out) override { + auto* m = new VkShaderModuleImpl(); if (m->init(m_device, d) != ErrorCode::Ok) { delete m; out = nullptr; return ErrorCode::Unknown; } + out = m; return ErrorCode::Ok; + } + Status createBindGroupLayout(const BindGroupLayoutDesc& d, BindGroupLayout*& out) override { + auto* l = new VkBindGroupLayoutImpl(); if (l->init(m_device, d, m_bindingShifts) != ErrorCode::Ok) { delete l; out = nullptr; return ErrorCode::Unknown; } + out = l; return ErrorCode::Ok; + } + Status createBindGroup(const BindGroupDesc& d, BindGroup*& out) override { + auto* g = new VkBindGroupImpl(); if (g->init(m_device, m_poolManager, d, m_bindingShifts) != ErrorCode::Ok) { delete g; out = nullptr; return ErrorCode::Unknown; } + out = g; return ErrorCode::Ok; + } + Status createPipelineLayout(const PipelineLayoutDesc& d, PipelineLayout*& out) override { + auto* l = new VkPipelineLayoutImpl(); if (l->init(m_device, d) != ErrorCode::Ok) { delete l; out = nullptr; return ErrorCode::Unknown; } + out = l; return ErrorCode::Ok; + } + Status createPipelineCache(const PipelineCacheDesc& d, PipelineCache*& out) override { + auto* c = new VkPipelineCacheImpl(); if (c->init(m_device, d) != ErrorCode::Ok) { delete c; out = nullptr; return ErrorCode::Unknown; } + out = c; return ErrorCode::Ok; + } + Status createRenderPipeline(const RenderPipelineDesc& d, RenderPipeline*& out) override { + auto* p = new VkRenderPipelineImpl(); if (p->init(m_device, d) != ErrorCode::Ok) { delete p; out = nullptr; return ErrorCode::Unknown; } + out = p; return ErrorCode::Ok; + } + Status createComputePipeline(const ComputePipelineDesc& d, ComputePipeline*& out) override { + auto* p = new VkComputePipelineImpl(); if (p->init(m_device, d) != ErrorCode::Ok) { delete p; out = nullptr; return ErrorCode::Unknown; } + out = p; return ErrorCode::Ok; + } + Status createCommandPool(QueueType qt, CommandPool*& out) override { + auto* p = new VkCommandPoolImpl(); p->ownerDevice = this; + if (p->init(m_device, m_adapter, qt) != ErrorCode::Ok) { delete p; out = nullptr; return ErrorCode::Unknown; } + out = p; return ErrorCode::Ok; + } + Status createFence(u64 initialValue, Fence*& out) override { + auto* f = new VkFenceImpl(); if (f->init(m_device, initialValue) != ErrorCode::Ok) { delete f; out = nullptr; return ErrorCode::Unknown; } + out = f; return ErrorCode::Ok; + } + Status createQuerySet(const QuerySetDesc& d, QuerySet*& out) override { + auto* q = new VkQuerySetImpl(); if (q->init(m_device, d) != ErrorCode::Ok) { delete q; out = nullptr; return ErrorCode::Unknown; } + out = q; return ErrorCode::Ok; + } + Status createSwapChain(Surface* surface, const SwapChainDesc& d, SwapChain*& out) override { + auto* sc = new VkSwapChainImpl(); + auto* vkSurf = static_cast(surface); + if (sc->init(m_device, m_adapter->physicalDevice(), vkSurf->handle(), d, this) != ErrorCode::Ok) + { delete sc; out = nullptr; return ErrorCode::Unknown; } + out = sc; return ErrorCode::Ok; + } + + // ---- Mesh shader (folded in) ---- + Status createMeshPipeline(const MeshPipelineDesc& d, MeshPipeline*& out) override { + if (!m_meshEnabled) { out = nullptr; return ErrorCode::NotSupported; } + auto* p = new VkMeshPipelineImpl(); if (p->init(m_device, d) != ErrorCode::Ok) { delete p; out = nullptr; return ErrorCode::Unknown; } + out = p; return ErrorCode::Ok; + } + void destroyMeshPipeline(MeshPipeline*& p) override { + if (p) { static_cast(p)->cleanup(m_device); delete p; p = nullptr; } + } + + // ---- Ray tracing (folded in) ---- + Status createAccelStruct(const AccelStructDesc& d, AccelStruct*& out) override { + if (!m_rtEnabled) { out = nullptr; return ErrorCode::NotSupported; } + auto* a = new VkAccelStructImpl(); if (a->init(m_device, m_adapter, d, 256 * 1024) != ErrorCode::Ok) { delete a; out = nullptr; return ErrorCode::Unknown; } + out = a; return ErrorCode::Ok; + } + void destroyAccelStruct(AccelStruct*& a) override { + if (a) { static_cast(a)->cleanup(m_device); delete a; a = nullptr; } + } + Status createRayTracingPipeline(const RayTracingPipelineDesc& d, RayTracingPipeline*& out) override { + if (!m_rtEnabled) { out = nullptr; return ErrorCode::NotSupported; } + auto* p = new VkRayTracingPipelineImpl(); if (p->init(m_device, d) != ErrorCode::Ok) { delete p; out = nullptr; return ErrorCode::Unknown; } + out = p; return ErrorCode::Ok; + } + void destroyRayTracingPipeline(RayTracingPipeline*& p) override { + if (p) { static_cast(p)->cleanup(m_device); delete p; p = nullptr; } + } + Status getShaderGroupHandles(RayTracingPipeline* pipeline, u32 firstGroup, u32 groupCount, std::span outData) override { + if (!m_rtEnabled) return ErrorCode::NotSupported; + auto* p = static_cast(pipeline); + auto pfn = reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkGetRayTracingShaderGroupHandlesKHR")); + if (!pfn) return ErrorCode::Unknown; + return pfn(m_device, p->handle(), firstGroup, groupCount, outData.size(), outData.data()) == VK_SUCCESS + ? ErrorCode::Ok : ErrorCode::Unknown; + } + + // ---- Resource destruction ---- + void destroyBuffer(Buffer*& b) override { if (b) { static_cast(b)->cleanup(m_device); delete b; b = nullptr; } } + void destroyTexture(Texture*& t) override { if (t) { static_cast(t)->cleanup(m_device); delete t; t = nullptr; } } + void destroyTextureView(TextureView*& v) override { if (v) { static_cast(v)->cleanup(m_device); delete v; v = nullptr; } } + void destroySampler(Sampler*& s) override { if (s) { static_cast(s)->cleanup(m_device); delete s; s = nullptr; } } + void destroyShaderModule(ShaderModule*& m) override { if (m) { static_cast(m)->cleanup(m_device); delete m; m = nullptr; } } + void destroyBindGroupLayout(BindGroupLayout*& l) override { if (l) { static_cast(l)->cleanup(m_device); delete l; l = nullptr; } } + void destroyBindGroup(BindGroup*& g) override { if (g) { static_cast(g)->cleanup(m_device, m_poolManager); delete g; g = nullptr; } } + void destroyPipelineLayout(PipelineLayout*& l) override { if (l) { static_cast(l)->cleanup(m_device); delete l; l = nullptr; } } + void destroyPipelineCache(PipelineCache*& c) override { if (c) { static_cast(c)->cleanup(m_device); delete c; c = nullptr; } } + void destroyRenderPipeline(RenderPipeline*& p) override { if (p) { static_cast(p)->cleanup(m_device); delete p; p = nullptr; } } + void destroyComputePipeline(ComputePipeline*& p) override { if (p) { static_cast(p)->cleanup(m_device); delete p; p = nullptr; } } + void destroyCommandPool(CommandPool*& p) override { if (p) { static_cast(p)->cleanup(); delete p; p = nullptr; } } + void destroyFence(Fence*& f) override { if (f) { static_cast(f)->cleanup(m_device); delete f; f = nullptr; } } + void destroyQuerySet(QuerySet*& q) override { if (q) { static_cast(q)->cleanup(m_device); delete q; q = nullptr; } } + void destroySwapChain(SwapChain*& sc) override { if (sc) { static_cast(sc)->cleanup(); delete sc; sc = nullptr; } } + void destroySurface(Surface*& s) override { if (s) { static_cast(s)->destroy(); delete s; s = nullptr; } } + + void waitIdle() override { vkDeviceWaitIdle(m_device); } + void destroy() override { + waitIdle(); + if (m_poolManager) { m_poolManager->destroy(); delete m_poolManager; m_poolManager = nullptr; } + for (auto* q : m_allQueues) delete q; + m_allQueues.clear(); m_gfxQueues.clear(); m_compQueues.clear(); m_xferQueues.clear(); + if (m_device != VK_NULL_HANDLE) { vkDestroyDevice(m_device, nullptr); m_device = VK_NULL_HANDLE; } + delete this; + } + + // ---- Swap chain sync ---- + void setPendingSwapChainSync(VkSemaphore acquire, VkSemaphore present) { + m_pendingAcquire = acquire; m_pendingPresent = present; m_hasPendingSync = true; + } + bool consumePendingSwapChainSync(VkSemaphore& acquire, VkSemaphore& present) { + if (!m_hasPendingSync) return false; + acquire = m_pendingAcquire; present = m_pendingPresent; m_hasPendingSync = false; return true; + } + + [[nodiscard]] VkDevice handle() const { return m_device; } + [[nodiscard]] VkAdapterImpl* adapter() const { return m_adapter; } + [[nodiscard]] bool validationEnabled() const { return m_validationEnabled; } + + [[nodiscard]] const BindingShifts& bindingShifts() const { return m_bindingShifts; } + void setBindingShifts(const BindingShifts& s) { m_bindingShifts = s; } + + /// Set a Vulkan debug name on an object (only when validation is enabled). + void setDebugName(VkObjectType objectType, u64 objectHandle, std::u8string_view name) { + if (!m_validationEnabled || name.empty()) return; + auto pfn = reinterpret_cast( + vkGetDeviceProcAddr(m_device, "vkSetDebugUtilsObjectNameEXT")); + if (!pfn) return; + char buf[256]{}; + auto len = std::min(name.size(), static_cast(255)); + std::memcpy(buf, name.data(), len); + VkDebugUtilsObjectNameInfoEXT ni{}; + ni.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; + ni.objectType = objectType; + ni.objectHandle = objectHandle; + ni.pObjectName = buf; + pfn(m_device, &ni); + } + +private: + VkDevice m_device = VK_NULL_HANDLE; + VkAdapterImpl* m_adapter = nullptr; + bool m_meshEnabled = false; + bool m_rtEnabled = false; + bool m_bindlessEnabled = false; + bool m_validationEnabled= false; + BindingShifts m_bindingShifts = BindingShifts::standard(); + VkDescriptorPoolManager* m_poolManager = nullptr; + + std::vector m_allQueues, m_gfxQueues, m_compQueues, m_xferQueues; + + VkSemaphore m_pendingAcquire = VK_NULL_HANDLE; + VkSemaphore m_pendingPresent = VK_NULL_HANDLE; + bool m_hasPendingSync = false; +}; + +// ---- Adapter::CreateDevice implementation ---- + +Status VkAdapterImpl::createDevice(const DeviceDesc& desc, Device*& out) { + auto* dev = new VkDeviceImpl(); + if (dev->init(this, desc) != ErrorCode::Ok) { delete dev; out = nullptr; return ErrorCode::Unknown; } + out = dev; return ErrorCode::Ok; +} + +// ---- SwapChain acquire/present implementations ---- + +Status VkSwapChainImpl::acquireNextImage() { + VkSemaphore acquireSem = m_acquireSems[m_frameIndex]; + u32 imgIdx = 0; + VkResult vr = vkAcquireNextImageKHR(m_device, m_swapchain, ~0ull, acquireSem, VK_NULL_HANDLE, &imgIdx); + m_currentImageIndex = imgIdx; + if (vr == VK_ERROR_OUT_OF_DATE_KHR) return ErrorCode::Unknown; + if (vr != VK_SUCCESS && vr != VK_SUBOPTIMAL_KHR) return ErrorCode::Unknown; + VkSemaphore presentSem = m_presentSems[m_currentImageIndex]; + m_owner->setPendingSwapChainSync(acquireSem, presentSem); + return ErrorCode::Ok; +} + +Status VkSwapChainImpl::present(Queue* queue) { + auto* vkQ = static_cast(queue); + VkSemaphore waitSem = m_presentSems[m_currentImageIndex]; + VkPresentInfoKHR pi{}; pi.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + pi.waitSemaphoreCount = 1; pi.pWaitSemaphores = &waitSem; + pi.swapchainCount = 1; pi.pSwapchains = &m_swapchain; pi.pImageIndices = &m_currentImageIndex; + VkResult vr = vkQueuePresentKHR(vkQ->handle(), &pi); + m_frameIndex = (m_frameIndex + 1) % m_bufferCount; + if (vr == VK_ERROR_OUT_OF_DATE_KHR || vr == VK_SUBOPTIMAL_KHR) return ErrorCode::Unknown; + return vr == VK_SUCCESS ? ErrorCode::Ok : ErrorCode::Unknown; +} + +// ---- Queue submit-with-fence (needs Device for swap chain sync) ---- + +void VkQueueImpl::submit(std::span cmdBufs, Fence* signalFence, u64 signalValue) { + if (cmdBufs.size() == 0) return; + std::vector bufs(cmdBufs.size()); + for (usize i = 0; i < cmdBufs.size(); ++i) + bufs[i] = static_cast(cmdBufs[i])->handle(); + + auto* vkFence = static_cast(signalFence); + if (!vkFence) return; + + VkSemaphore acquireSem = VK_NULL_HANDLE, presentSem = VK_NULL_HANDLE; + bool hasSync = m_device->consumePendingSwapChainSync(acquireSem, presentSem); + + VkSemaphore waitSems[1] = { acquireSem }; + VkPipelineStageFlags waitStages[1] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; + u64 waitValues[1] = { 0 }; + + auto timelineSem = vkFence->handle(); + VkSemaphore signalSems[2] = { timelineSem, presentSem }; + u64 signalValues[2] = { signalValue, 0 }; + + VkTimelineSemaphoreSubmitInfo tsi{}; tsi.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO; + tsi.signalSemaphoreValueCount = hasSync ? 2u : 1u; tsi.pSignalSemaphoreValues = signalValues; + tsi.waitSemaphoreValueCount = hasSync ? 1u : 0u; tsi.pWaitSemaphoreValues = waitValues; + + VkSubmitInfo si{}; si.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; si.pNext = &tsi; + si.commandBufferCount = static_cast(bufs.size()); si.pCommandBuffers = bufs.data(); + si.signalSemaphoreCount = hasSync ? 2u : 1u; si.pSignalSemaphores = signalSems; + if (hasSync) { si.waitSemaphoreCount = 1; si.pWaitSemaphores = waitSems; si.pWaitDstStageMask = waitStages; } + + vkQueueSubmit(m_queue, 1, &si, VK_NULL_HANDLE); +} + +void VkQueueImpl::submit(std::span cmdBufs, + std::span waitFences, std::span waitVals, + Fence* signalFence, u64 signalValue) { + if (cmdBufs.size() == 0) return; + std::vector bufs(cmdBufs.size()); + for (usize i = 0; i < cmdBufs.size(); ++i) + bufs[i] = static_cast(cmdBufs[i])->handle(); + + // 5-arg submit does NOT consume swap chain sync - matching Sedulous. + // The 2-arg submit (used by the first queue submit each frame) handles it. + std::vector waitSems(waitFences.size()); + std::vector waitStages(waitFences.size()); + for (usize i = 0; i < waitFences.size(); ++i) { + waitSems[i] = static_cast(waitFences[i])->handle(); + waitStages[i] = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + } + + VkTimelineSemaphoreSubmitInfo tsi{}; + tsi.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO; + tsi.waitSemaphoreValueCount = static_cast(waitVals.size()); + tsi.pWaitSemaphoreValues = waitVals.data(); + + VkSubmitInfo si{}; si.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; si.pNext = &tsi; + si.commandBufferCount = static_cast(bufs.size()); si.pCommandBuffers = bufs.data(); + si.waitSemaphoreCount = static_cast(waitSems.size()); si.pWaitSemaphores = waitSems.data(); + si.pWaitDstStageMask = waitStages.data(); + + VkSemaphore signalSem = VK_NULL_HANDLE; + if (signalFence) { + signalSem = static_cast(signalFence)->handle(); + tsi.signalSemaphoreValueCount = 1; + tsi.pSignalSemaphoreValues = &signalValue; + si.signalSemaphoreCount = 1; + si.pSignalSemaphores = &signalSem; + } + + vkQueueSubmit(m_queue, 1, &si, VK_NULL_HANDLE); +} + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkFence.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkFence.cppm new file mode 100644 index 00000000..9ed2d920 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkFence.cppm @@ -0,0 +1,65 @@ +/// Vulkan implementation of Fence (timeline semaphore). + +module; + +#include "VkIncludes.h" + +export module rhi.vk:fence; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkFenceImpl : public Fence { +public: + Status init(VkDevice device, u64 initialValue) { + m_device = device; + + VkSemaphoreTypeCreateInfo typeInfo{}; + typeInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO; + typeInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; + typeInfo.initialValue = initialValue; + + VkSemaphoreCreateInfo ci{}; + ci.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + ci.pNext = &typeInfo; + + if (vkCreateSemaphore(device, &ci, nullptr, &m_semaphore) != VK_SUCCESS) return ErrorCode::Unknown; + return ErrorCode::Ok; + } + + void cleanup(VkDevice device) { + if (m_semaphore != VK_NULL_HANDLE) { + vkDestroySemaphore(device, m_semaphore, nullptr); + m_semaphore = VK_NULL_HANDLE; + } + } + + // ---- Fence interface ---- + u64 completedValue() override { + u64 value = 0; + vkGetSemaphoreCounterValue(m_device, m_semaphore, &value); + return value; + } + + bool wait(u64 value, u64 timeoutNs) override { + VkSemaphoreWaitInfo wi{}; + wi.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO; + wi.semaphoreCount = 1; + wi.pSemaphores = &m_semaphore; + wi.pValues = &value; + return vkWaitSemaphores(m_device, &wi, timeoutNs) == VK_SUCCESS; + } + + [[nodiscard]] VkSemaphore handle() const { return m_semaphore; } + +private: + VkSemaphore m_semaphore = VK_NULL_HANDLE; + VkDevice m_device = VK_NULL_HANDLE; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkIncludes.h b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkIncludes.h new file mode 100644 index 00000000..e018778e --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkIncludes.h @@ -0,0 +1,19 @@ +#ifndef DRACO_RHI_VK_INCLUDES_H_ +#define DRACO_RHI_VK_INCLUDES_H_ + +#ifdef _WIN32 +# ifndef VK_USE_PLATFORM_WIN32_KHR +# define VK_USE_PLATFORM_WIN32_KHR +# endif +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include +#endif + +#include + +#endif // DRACO_RHI_VK_INCLUDES_H_ diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkMeshPipeline.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkMeshPipeline.cppm new file mode 100644 index 00000000..39be6721 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkMeshPipeline.cppm @@ -0,0 +1,166 @@ +/// Vulkan implementation of MeshPipeline. + +module; + +#include "VkIncludes.h" +#include +#include + + +export module rhi.vk:mesh_pipeline; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :shader_module; +import :pipeline_layout; +import :pipeline_cache; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkMeshPipelineImpl : public MeshPipeline { +public: + Status init(VkDevice device, const MeshPipelineDesc& desc) { + auto* vkLayout = static_cast(desc.layout); + if (!vkLayout) return ErrorCode::Unknown; + layout = desc.layout; + m_layout = vkLayout; + + std::vector stages; + std::u8string meshEntry = std::u8string(desc.mesh.entryPoint); + std::u8string taskEntry, fsEntry; + + // Task shader (optional). + if (desc.task.has_value()) { + taskEntry = std::u8string(desc.task->entryPoint); + if (auto* mod = static_cast(desc.task->module)) { + VkPipelineShaderStageCreateInfo s{}; s.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + s.stage = VK_SHADER_STAGE_TASK_BIT_EXT; s.module = mod->handle(); s.pName = reinterpret_cast(taskEntry.c_str()); + stages.push_back(s); + } + } + + // Mesh shader (required). + auto* meshMod = static_cast(desc.mesh.module); + if (!meshMod) return ErrorCode::Unknown; + { VkPipelineShaderStageCreateInfo s{}; s.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + s.stage = VK_SHADER_STAGE_MESH_BIT_EXT; s.module = meshMod->handle(); s.pName = reinterpret_cast(meshEntry.c_str()); + stages.push_back(s); } + + // Fragment shader (optional). + if (desc.fragment.has_value()) { + fsEntry = std::u8string(desc.fragment->shader.entryPoint); + if (auto* mod = static_cast(desc.fragment->shader.module)) { + VkPipelineShaderStageCreateInfo s{}; s.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + s.stage = VK_SHADER_STAGE_FRAGMENT_BIT; s.module = mod->handle(); s.pName = reinterpret_cast(fsEntry.c_str()); + stages.push_back(s); + } + } + + // Viewport (dynamic). + VkPipelineViewportStateCreateInfo vp{}; vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + vp.viewportCount = 1; vp.scissorCount = 1; + + // Rasterization. + VkPipelineRasterizationStateCreateInfo rs{}; rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rs.depthClampEnable = desc.primitive.depthClipEnabled ? VK_FALSE : VK_TRUE; + rs.polygonMode = toVkPolygonMode(desc.primitive.fillMode); + rs.cullMode = toVkCullMode(desc.primitive.cullMode); + rs.frontFace = toVkFrontFace(desc.primitive.frontFace); + rs.lineWidth = 1.0f; + if (desc.depthStencil.has_value()) { + rs.depthBiasEnable = (desc.depthStencil->depthBias != 0 || desc.depthStencil->depthBiasSlopeScale != 0) ? VK_TRUE : VK_FALSE; + rs.depthBiasConstantFactor = static_cast(desc.depthStencil->depthBias); + rs.depthBiasSlopeFactor = desc.depthStencil->depthBiasSlopeScale; + rs.depthBiasClamp = desc.depthStencil->depthBiasClamp; + } + + // Multisample. + VkPipelineMultisampleStateCreateInfo ms{}; ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + ms.rasterizationSamples = toVkSampleCount(desc.multisample.count); + ms.alphaToCoverageEnable = desc.multisample.alphaToCoverageEnabled ? VK_TRUE : VK_FALSE; + u32 sampleMask = desc.multisample.mask; ms.pSampleMask = &sampleMask; + + // Color blend. + std::vector blendAtts(desc.colorTargets.size()); + for (usize i = 0; i < desc.colorTargets.size(); ++i) { + auto& ba = blendAtts[i]; ba = {}; + ba.colorWriteMask = toVkColorWriteMask(desc.colorTargets[i].writeMask); + if (desc.colorTargets[i].blend.has_value()) { + ba.blendEnable = VK_TRUE; + ba.srcColorBlendFactor = toVkBlendFactor(desc.colorTargets[i].blend->color.srcFactor); + ba.dstColorBlendFactor = toVkBlendFactor(desc.colorTargets[i].blend->color.dstFactor); + ba.colorBlendOp = toVkBlendOp(desc.colorTargets[i].blend->color.operation); + ba.srcAlphaBlendFactor = toVkBlendFactor(desc.colorTargets[i].blend->alpha.srcFactor); + ba.dstAlphaBlendFactor = toVkBlendFactor(desc.colorTargets[i].blend->alpha.dstFactor); + ba.alphaBlendOp = toVkBlendOp(desc.colorTargets[i].blend->alpha.operation); + } + } + VkPipelineColorBlendStateCreateInfo cb{}; cb.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + cb.attachmentCount = static_cast(blendAtts.size()); cb.pAttachments = blendAtts.data(); + + // Depth/stencil. + VkPipelineDepthStencilStateCreateInfo dss{}; dss.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + if (desc.depthStencil.has_value()) { + const auto& ds = *desc.depthStencil; + dss.depthTestEnable = ds.depthTestEnabled ? VK_TRUE : VK_FALSE; + dss.depthWriteEnable = ds.depthWriteEnabled ? VK_TRUE : VK_FALSE; + dss.depthCompareOp = toVkCompareOp(ds.depthCompare); + dss.stencilTestEnable = ds.stencilEnabled ? VK_TRUE : VK_FALSE; + dss.front.failOp = toVkStencilOp(ds.stencilFront.failOp); dss.front.passOp = toVkStencilOp(ds.stencilFront.passOp); + dss.front.depthFailOp = toVkStencilOp(ds.stencilFront.depthFailOp); dss.front.compareOp = toVkCompareOp(ds.stencilFront.compare); + dss.front.compareMask = ds.stencilReadMask; dss.front.writeMask = ds.stencilWriteMask; + dss.back = dss.front; + dss.back.failOp = toVkStencilOp(ds.stencilBack.failOp); dss.back.passOp = toVkStencilOp(ds.stencilBack.passOp); + dss.back.depthFailOp = toVkStencilOp(ds.stencilBack.depthFailOp); dss.back.compareOp = toVkCompareOp(ds.stencilBack.compare); + } + + // Dynamic state. + VkDynamicState dynStates[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, + VK_DYNAMIC_STATE_BLEND_CONSTANTS, VK_DYNAMIC_STATE_STENCIL_REFERENCE }; + VkPipelineDynamicStateCreateInfo dyn{}; dyn.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dyn.dynamicStateCount = 4; dyn.pDynamicStates = dynStates; + + // Dynamic rendering. + std::vector colorFmts(desc.colorTargets.size()); + for (usize i = 0; i < desc.colorTargets.size(); ++i) colorFmts[i] = toVkFormat(desc.colorTargets[i].format); + VkPipelineRenderingCreateInfo ri{}; ri.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO; + ri.colorAttachmentCount = static_cast(colorFmts.size()); ri.pColorAttachmentFormats = colorFmts.data(); + if (desc.depthStencil.has_value()) { + VkFormat dsf = toVkFormat(desc.depthStencil->format); + if (hasDepth(desc.depthStencil->format)) ri.depthAttachmentFormat = dsf; + if (hasStencil(desc.depthStencil->format)) ri.stencilAttachmentFormat = dsf; + } + + // Create pipeline (no vertex input / input assembly for mesh shaders). + VkGraphicsPipelineCreateInfo pi{}; pi.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pi.pNext = &ri; pi.stageCount = static_cast(stages.size()); pi.pStages = stages.data(); + pi.pVertexInputState = nullptr; pi.pInputAssemblyState = nullptr; + pi.pViewportState = &vp; pi.pRasterizationState = &rs; pi.pMultisampleState = &ms; + pi.pDepthStencilState = desc.depthStencil.has_value() ? &dss : nullptr; + pi.pColorBlendState = &cb; pi.pDynamicState = &dyn; pi.layout = vkLayout->handle(); + + VkPipelineCache cacheHandle = VK_NULL_HANDLE; + if (desc.cache) cacheHandle = static_cast(desc.cache)->handle(); + + if (vkCreateGraphicsPipelines(device, cacheHandle, 1, &pi, nullptr, &m_pipeline) != VK_SUCCESS) + return ErrorCode::Unknown; + return ErrorCode::Ok; + } + + void cleanup(VkDevice device) { + if (m_pipeline != VK_NULL_HANDLE) { vkDestroyPipeline(device, m_pipeline, nullptr); m_pipeline = VK_NULL_HANDLE; } + } + + [[nodiscard]] VkPipeline handle() const { return m_pipeline; } + [[nodiscard]] VkPipelineLayoutImpl* vkLayout() const { return m_layout; } + +private: + VkPipeline m_pipeline = VK_NULL_HANDLE; + VkPipelineLayoutImpl* m_layout = nullptr; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkModule.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkModule.cppm new file mode 100644 index 00000000..ee9c63c5 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkModule.cppm @@ -0,0 +1,35 @@ +export module rhi.vk; + +export import :conversions; +export import :surface; +export import :adapter; +export import :backend; +export import :buffer; +export import :texture; +export import :texture_view; +export import :sampler; +export import :shader_module; +export import :fence; +export import :query_set; +export import :barrier_helper; +export import :command_buffer; +export import :descriptor_pool_manager; +export import :binding_shifts; +export import :bind_group_layout; +export import :pipeline_layout; +export import :pipeline_cache; +export import :render_pipeline; +export import :compute_pipeline; +export import :bind_group; +export import :swap_chain; +export import :queue; +export import :command_pool; +export import :render_bundle_encoder; +export import :render_pass_encoder; +export import :compute_pass_encoder; +export import :mesh_pipeline; +export import :accel_struct; +export import :ray_tracing_pipeline; +export import :transfer_batch; +export import :command_encoder; +export import :device; diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkPipelineCache.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkPipelineCache.cppm new file mode 100644 index 00000000..d0034a87 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkPipelineCache.cppm @@ -0,0 +1,57 @@ +/// Vulkan implementation of PipelineCache. + +module; + +#include "VkIncludes.h" +#include + +export module rhi.vk:pipeline_cache; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkPipelineCacheImpl : public PipelineCache { +public: + Status init(VkDevice device, const PipelineCacheDesc& desc) { + m_device = device; + + VkPipelineCacheCreateInfo ci{}; + ci.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; + if (desc.initialData.size() > 0) { + ci.initialDataSize = desc.initialData.size(); + ci.pInitialData = desc.initialData.data(); + } + + if (vkCreatePipelineCache(device, &ci, nullptr, &m_cache) != VK_SUCCESS) return ErrorCode::Unknown; + return ErrorCode::Ok; + } + + void cleanup(VkDevice device) { + if (m_cache != VK_NULL_HANDLE) { vkDestroyPipelineCache(device, m_cache, nullptr); m_cache = VK_NULL_HANDLE; } + } + + u32 getDataSize() override { + usize size = 0; + vkGetPipelineCacheData(m_device, m_cache, &size, nullptr); + return static_cast(size); + } + + Status getData(std::span outData) override { + usize size = outData.size(); + if (vkGetPipelineCacheData(m_device, m_cache, &size, outData.data()) != VK_SUCCESS) return ErrorCode::Unknown; + return ErrorCode::Ok; + } + + [[nodiscard]] VkPipelineCache handle() const { return m_cache; } + +private: + VkPipelineCache m_cache = VK_NULL_HANDLE; + VkDevice m_device = VK_NULL_HANDLE; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkPipelineLayout.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkPipelineLayout.cppm new file mode 100644 index 00000000..3d793c4e --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkPipelineLayout.cppm @@ -0,0 +1,60 @@ +/// Vulkan implementation of PipelineLayout. + +module; + +#include "VkIncludes.h" +#include + + +export module rhi.vk:pipeline_layout; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :bind_group_layout; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkPipelineLayoutImpl : public PipelineLayout { +public: + Status init(VkDevice device, const PipelineLayoutDesc& desc) { + std::vector setLayouts(desc.bindGroupLayouts.size()); + for (usize i = 0; i < desc.bindGroupLayouts.size(); ++i) { + auto* vkl = static_cast(desc.bindGroupLayouts[i]); + if (!vkl) return ErrorCode::Unknown; + setLayouts[i] = vkl->handle(); + } + + std::vector pushRanges(desc.pushConstantRanges.size()); + for (usize i = 0; i < desc.pushConstantRanges.size(); ++i) { + pushRanges[i] = {}; + pushRanges[i].stageFlags = toVkShaderStageFlags(desc.pushConstantRanges[i].stages); + pushRanges[i].offset = desc.pushConstantRanges[i].offset; + pushRanges[i].size = desc.pushConstantRanges[i].size; + } + + VkPipelineLayoutCreateInfo ci{}; + ci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + ci.setLayoutCount = static_cast(setLayouts.size()); + ci.pSetLayouts = setLayouts.data(); + ci.pushConstantRangeCount = static_cast(pushRanges.size()); + ci.pPushConstantRanges = pushRanges.data(); + + if (vkCreatePipelineLayout(device, &ci, nullptr, &m_layout) != VK_SUCCESS) return ErrorCode::Unknown; + return ErrorCode::Ok; + } + + void cleanup(VkDevice device) { + if (m_layout != VK_NULL_HANDLE) { vkDestroyPipelineLayout(device, m_layout, nullptr); m_layout = VK_NULL_HANDLE; } + } + + [[nodiscard]] VkPipelineLayout handle() const { return m_layout; } + +private: + VkPipelineLayout m_layout = VK_NULL_HANDLE; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkQuerySet.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkQuerySet.cppm new file mode 100644 index 00000000..d6180eb5 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkQuerySet.cppm @@ -0,0 +1,55 @@ +/// Vulkan implementation of QuerySet. + +module; + +#include "VkIncludes.h" + +export module rhi.vk:query_set; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkQuerySetImpl : public QuerySet { +public: + Status init(VkDevice device, const QuerySetDesc& d) { + type = d.type; + count = d.count; + + VkQueryPoolCreateInfo ci{}; + ci.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO; + ci.queryCount = d.count; + + switch (d.type) { + case QueryType::Timestamp: ci.queryType = VK_QUERY_TYPE_TIMESTAMP; break; + case QueryType::Occlusion: ci.queryType = VK_QUERY_TYPE_OCCLUSION; break; + case QueryType::PipelineStatistics: + ci.queryType = VK_QUERY_TYPE_PIPELINE_STATISTICS; + ci.pipelineStatistics = + VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_VERTICES_BIT | + VK_QUERY_PIPELINE_STATISTIC_INPUT_ASSEMBLY_PRIMITIVES_BIT | + VK_QUERY_PIPELINE_STATISTIC_VERTEX_SHADER_INVOCATIONS_BIT | + VK_QUERY_PIPELINE_STATISTIC_FRAGMENT_SHADER_INVOCATIONS_BIT | + VK_QUERY_PIPELINE_STATISTIC_COMPUTE_SHADER_INVOCATIONS_BIT; + break; + } + + if (vkCreateQueryPool(device, &ci, nullptr, &m_pool) != VK_SUCCESS) return ErrorCode::Unknown; + return ErrorCode::Ok; + } + + void cleanup(VkDevice device) { + if (m_pool != VK_NULL_HANDLE) { vkDestroyQueryPool(device, m_pool, nullptr); m_pool = VK_NULL_HANDLE; } + } + + [[nodiscard]] VkQueryPool handle() const { return m_pool; } + +private: + VkQueryPool m_pool = VK_NULL_HANDLE; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkQueue.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkQueue.cppm new file mode 100644 index 00000000..079a73e3 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkQueue.cppm @@ -0,0 +1,77 @@ +/// Vulkan implementation of Queue. + +module; + +#include "VkIncludes.h" +#include +#include + + +export module rhi.vk:queue; + +import core.stdtypes; +import core.status; +import rhi; +import :command_buffer; +import :fence; +import :transfer_batch; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkDeviceImpl; // forward + +class VkQueueImpl : public Queue { +public: + VkQueueImpl(VkQueue queue, QueueType type, u32 familyIndex, f32 tsPeriod, VkDeviceImpl* device, VkDevice vkDevice, VkPhysicalDevice physDevice) + : m_queue(queue), m_familyIndex(familyIndex), m_tsPeriod(tsPeriod), m_device(device), m_vkDevice(vkDevice), m_physDevice(physDevice) + { queueType = type; } + + // ---- Queue interface ---- + + void submit(std::span cmdBufs) override { + if (cmdBufs.size() == 0) return; + std::vector bufs(cmdBufs.size()); + for (usize i = 0; i < cmdBufs.size(); ++i) + bufs[i] = static_cast(cmdBufs[i])->handle(); + + VkSubmitInfo si{}; si.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + si.commandBufferCount = static_cast(bufs.size()); + si.pCommandBuffers = bufs.data(); + vkQueueSubmit(m_queue, 1, &si, VK_NULL_HANDLE); + } + + void submit(std::span cmdBufs, Fence* signalFence, u64 signalValue) override; + + void submit(std::span cmdBufs, + std::span waitFences, std::span waitValues, + Fence* signalFence, u64 signalValue) override; + + + void waitIdle() override { vkQueueWaitIdle(m_queue); } + + Status createTransferBatch(TransferBatch*& out) override { + out = new VkTransferBatchImpl(m_vkDevice, m_queue, m_familyIndex, m_physDevice); + return ErrorCode::Ok; + } + void destroyTransferBatch(TransferBatch*& batch) override { + if (batch) { static_cast(batch)->destroy(); delete batch; batch = nullptr; } + } + + f32 timestampPeriod() const override { return m_tsPeriod; } + + // ---- Internal ---- + [[nodiscard]] VkQueue handle() const { return m_queue; } + [[nodiscard]] u32 familyIndex() const { return m_familyIndex; } + +private: + VkQueue m_queue = VK_NULL_HANDLE; + u32 m_familyIndex = 0; + f32 m_tsPeriod = 0.0f; + VkDeviceImpl* m_device = nullptr; + VkDevice m_vkDevice = VK_NULL_HANDLE; + VkPhysicalDevice m_physDevice = VK_NULL_HANDLE; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkRayTracingPipeline.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkRayTracingPipeline.cppm new file mode 100644 index 00000000..21b6c67e --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkRayTracingPipeline.cppm @@ -0,0 +1,106 @@ +/// Vulkan implementation of RayTracingPipeline. + +module; + +#include "VkIncludes.h" +#include +#include + + +export module rhi.vk:ray_tracing_pipeline; + +import core.stdtypes; +import core.status; +import rhi; +import :shader_module; +import :pipeline_layout; +import :pipeline_cache; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkRayTracingPipelineImpl : public RayTracingPipeline { +public: + Status init(VkDevice device, const RayTracingPipelineDesc& desc) { + auto* vkLayout = static_cast(desc.layout); + if (!vkLayout) return ErrorCode::Unknown; + layout = desc.layout; + m_layout = vkLayout; + + // Shader stages. + std::vector stages(desc.stages.size()); + std::vector entryStrings(desc.stages.size()); + for (usize i = 0; i < desc.stages.size(); ++i) { + entryStrings[i] = std::u8string(desc.stages[i].entryPoint); + auto* mod = static_cast(desc.stages[i].module); + if (!mod) return ErrorCode::Unknown; + stages[i] = {}; + stages[i].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stages[i].module = mod->handle(); + stages[i].pName = reinterpret_cast(entryStrings[i].c_str()); + // Map stage from desc. + auto s = desc.stages[i].stage; + if (s == ShaderStage::RayGen) stages[i].stage = VK_SHADER_STAGE_RAYGEN_BIT_KHR; + else if (s == ShaderStage::Miss) stages[i].stage = VK_SHADER_STAGE_MISS_BIT_KHR; + else if (s == ShaderStage::ClosestHit) stages[i].stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; + else if (s == ShaderStage::AnyHit) stages[i].stage = VK_SHADER_STAGE_ANY_HIT_BIT_KHR; + else if (s == ShaderStage::Intersection) stages[i].stage = VK_SHADER_STAGE_INTERSECTION_BIT_KHR; + else if (s == ShaderStage::Callable) stages[i].stage = VK_SHADER_STAGE_CALLABLE_BIT_KHR; + else stages[i].stage = VK_SHADER_STAGE_RAYGEN_BIT_KHR; + } + + // Shader groups. + std::vector groups(desc.groups.size()); + for (usize i = 0; i < desc.groups.size(); ++i) { + const auto& g = desc.groups[i]; + groups[i] = {}; + groups[i].sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR; + switch (g.type) { + case RayTracingShaderGroup::Type::General: + groups[i].type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; break; + case RayTracingShaderGroup::Type::TrianglesHitGroup: + groups[i].type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR; break; + case RayTracingShaderGroup::Type::ProceduralHitGroup: + groups[i].type = VK_RAY_TRACING_SHADER_GROUP_TYPE_PROCEDURAL_HIT_GROUP_KHR; break; + } + groups[i].generalShader = g.generalShaderIndex; + groups[i].closestHitShader = g.closestHitShaderIndex; + groups[i].anyHitShader = g.anyHitShaderIndex; + groups[i].intersectionShader = g.intersectionShaderIndex; + } + + VkRayTracingPipelineCreateInfoKHR ci{}; + ci.sType = VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR; + ci.stageCount = static_cast(stages.size()); + ci.pStages = stages.data(); + ci.groupCount = static_cast(groups.size()); + ci.pGroups = groups.data(); + ci.maxPipelineRayRecursionDepth = desc.maxRecursionDepth; + ci.layout = vkLayout->handle(); + + auto pfn = reinterpret_cast( + vkGetDeviceProcAddr(device, "vkCreateRayTracingPipelinesKHR")); + if (!pfn) return ErrorCode::Unknown; + + VkPipelineCache cacheHandle = VK_NULL_HANDLE; + if (desc.cache) cacheHandle = static_cast(desc.cache)->handle(); + + if (pfn(device, VK_NULL_HANDLE, cacheHandle, 1, &ci, nullptr, &m_pipeline) != VK_SUCCESS) + return ErrorCode::Unknown; + return ErrorCode::Ok; + } + + void cleanup(VkDevice device) { + if (m_pipeline != VK_NULL_HANDLE) { vkDestroyPipeline(device, m_pipeline, nullptr); m_pipeline = VK_NULL_HANDLE; } + } + + [[nodiscard]] VkPipeline handle() const { return m_pipeline; } + [[nodiscard]] VkPipelineLayoutImpl* vkLayout() const { return m_layout; } + +private: + VkPipeline m_pipeline = VK_NULL_HANDLE; + VkPipelineLayoutImpl* m_layout = nullptr; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkRenderBundleEncoder.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkRenderBundleEncoder.cppm new file mode 100644 index 00000000..3bf4627a --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkRenderBundleEncoder.cppm @@ -0,0 +1,119 @@ +/// Vulkan implementation of RenderBundleEncoder + RenderBundle. +/// +/// A render bundle is a SECONDARY VkCommandBuffer recorded with +/// VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT and dynamic-rendering inheritance info +/// (the attachment formats it is compatible with). It is replayed into a primary pass - +/// begun with VK_RENDERING_CONTENTS_SECONDARY_COMMAND_BUFFERS_BIT - via vkCmdExecuteCommands +/// (RenderPassEncoder::ExecuteBundles). Because bundles carry no pass-level dynamic state and +/// Vulkan secondaries don't inherit it, the encoder records a full-target viewport + scissor up +/// front (from the desc extent). The draw-recording methods mirror VkRenderPassEncoderImpl. + +module; + +#include "VkIncludes.h" +#include + +export module rhi.vk:render_bundle_encoder; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :buffer; +import :bind_group; +import :render_pipeline; +import :pipeline_layout; + +using namespace draco; + +export namespace draco::rhi::vk { + +// An immutable, replayable secondary command buffer. Valid until the owning pool resets. +class VkRenderBundleImpl : public RenderBundle { +public: + explicit VkRenderBundleImpl(VkCommandBuffer cmdBuf) : m_cmdBuf(cmdBuf) {} + [[nodiscard]] VkCommandBuffer handle() const noexcept { return m_cmdBuf; } +private: + VkCommandBuffer m_cmdBuf = VK_NULL_HANDLE; +}; + +// Records draws into a secondary command buffer. The owning command pool allocates/recycles the +// secondary handle and frees this wrapper (+ the produced bundle) on reset. +class VkRenderBundleEncoderImpl : public RenderBundleEncoder { +public: + explicit VkRenderBundleEncoderImpl(VkCommandBuffer cmdBuf) : m_cmdBuf(cmdBuf) {} + ~VkRenderBundleEncoderImpl() override { delete m_bundle; } // frees the produced bundle wrapper + + void setPipeline(RenderPipeline* pipeline) override { + m_currentPipeline = static_cast(pipeline); + if (m_currentPipeline) + vkCmdBindPipeline(m_cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_currentPipeline->handle()); + } + + void setBindGroup(u32 index, BindGroup* group, std::span dynOffsets) override { + auto* bg = static_cast(group); + auto* layout = m_currentPipeline ? m_currentPipeline->vkLayout() : nullptr; + if (!bg || !layout) return; + VkDescriptorSet set = bg->handle(); + vkCmdBindDescriptorSets(m_cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, layout->handle(), + index, 1, &set, static_cast(dynOffsets.size()), dynOffsets.data()); + } + + void setPushConstants(ShaderStage stages, u32 offset, u32 size, const void* data) override { + auto* layout = m_currentPipeline ? m_currentPipeline->vkLayout() : nullptr; + if (!layout) return; + vkCmdPushConstants(m_cmdBuf, layout->handle(), toVkShaderStageFlags(stages), offset, size, data); + } + + void setVertexBuffer(u32 slot, Buffer* buffer, u64 offset) override { + auto* vkBuf = static_cast(buffer); + if (!vkBuf) return; + VkBuffer handle = vkBuf->handle(); + vkCmdBindVertexBuffers(m_cmdBuf, slot, 1, &handle, &offset); + } + + void setIndexBuffer(Buffer* buffer, IndexFormat format, u64 offset) override { + auto* vkBuf = static_cast(buffer); + if (!vkBuf) return; + vkCmdBindIndexBuffer(m_cmdBuf, vkBuf->handle(), offset, toVkIndexType(format)); + } + + void draw(u32 vertexCount, u32 instanceCount, u32 firstVertex, u32 firstInstance) override { + vkCmdDraw(m_cmdBuf, vertexCount, instanceCount, firstVertex, firstInstance); + } + + void drawIndexed(u32 indexCount, u32 instanceCount, u32 firstIndex, i32 baseVertex, u32 firstInstance) override { + vkCmdDrawIndexed(m_cmdBuf, indexCount, instanceCount, firstIndex, baseVertex, firstInstance); + } + + void drawIndirect(Buffer* buf, u64 offset, u32 drawCount, u32 stride) override { + auto* vkBuf = static_cast(buf); + if (!vkBuf) return; + vkCmdDrawIndirect(m_cmdBuf, vkBuf->handle(), offset, drawCount, stride > 0 ? stride : 16); + } + + void drawIndexedIndirect(Buffer* buf, u64 offset, u32 drawCount, u32 stride) override { + auto* vkBuf = static_cast(buf); + if (!vkBuf) return; + vkCmdDrawIndexedIndirect(m_cmdBuf, vkBuf->handle(), offset, drawCount, stride > 0 ? stride : 20); + } + + // End recording; the produced bundle (owned by the pool) wraps the secondary buffer. + RenderBundle* finish() override { + if (m_finished) { return m_bundle; } + m_finished = true; + vkEndCommandBuffer(m_cmdBuf); + m_bundle = new VkRenderBundleImpl(m_cmdBuf); + return m_bundle; + } + + [[nodiscard]] VkRenderBundleImpl* producedBundle() const noexcept { return m_bundle; } + +private: + VkCommandBuffer m_cmdBuf = VK_NULL_HANDLE; + VkRenderPipelineImpl* m_currentPipeline = nullptr; + VkRenderBundleImpl* m_bundle = nullptr; + bool m_finished = false; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkRenderPassEncoder.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkRenderPassEncoder.cppm new file mode 100644 index 00000000..bccdfd00 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkRenderPassEncoder.cppm @@ -0,0 +1,162 @@ +/// Vulkan implementation of RenderPassEncoder + MeshShaderPassExt. + +module; + +#include "VkIncludes.h" +#include +#include + +export module rhi.vk:render_pass_encoder; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :buffer; +import :bind_group; +import :render_pipeline; +import :compute_pipeline; +import :pipeline_layout; +import :query_set; +import :mesh_pipeline; +import :render_bundle_encoder; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkRenderPassEncoderImpl : public RenderPassEncoder, public MeshShaderPassExt { +public: + MeshShaderPassExt* asMeshShaderExt() noexcept override { return this; } + VkRenderPassEncoderImpl(VkCommandBuffer cmdBuf, VkDevice device) + : m_cmdBuf(cmdBuf), m_device(device) {} + + // ---- RenderPassEncoder ---- + + void setPipeline(RenderPipeline* pipeline) override { + m_currentPipeline = static_cast(pipeline); + m_currentMeshPipeline = nullptr; + if (m_currentPipeline) + vkCmdBindPipeline(m_cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_currentPipeline->handle()); + } + + void setBindGroup(u32 index, BindGroup* group, std::span dynOffsets) override { + auto* bg = static_cast(group); + auto* layout = getCurrentLayout(); + if (!bg || !layout) return; + VkDescriptorSet set = bg->handle(); + vkCmdBindDescriptorSets(m_cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, layout->handle(), + index, 1, &set, static_cast(dynOffsets.size()), dynOffsets.data()); + } + + void setPushConstants(ShaderStage stages, u32 offset, u32 size, const void* data) override { + auto* layout = getCurrentLayout(); + if (!layout) return; + vkCmdPushConstants(m_cmdBuf, layout->handle(), toVkShaderStageFlags(stages), offset, size, data); + } + + void setVertexBuffer(u32 slot, Buffer* buffer, u64 offset) override { + auto* vkBuf = static_cast(buffer); + if (!vkBuf) return; + VkBuffer handle = vkBuf->handle(); + vkCmdBindVertexBuffers(m_cmdBuf, slot, 1, &handle, &offset); + } + + void setIndexBuffer(Buffer* buffer, IndexFormat format, u64 offset) override { + auto* vkBuf = static_cast(buffer); + if (!vkBuf) return; + vkCmdBindIndexBuffer(m_cmdBuf, vkBuf->handle(), offset, toVkIndexType(format)); + } + + void setViewport(f32 x, f32 y, f32 w, f32 h, f32 minDepth, f32 maxDepth) override { + // Flip Y via negative height to match DX12 coordinate system. + VkViewport vp{}; vp.x = x; vp.y = y + h; vp.width = w; vp.height = -h; + vp.minDepth = minDepth; vp.maxDepth = maxDepth; + vkCmdSetViewport(m_cmdBuf, 0, 1, &vp); + } + + void setScissor(i32 x, i32 y, u32 w, u32 h) override { + VkRect2D sc{}; sc.offset = {x, y}; sc.extent = {w, h}; + vkCmdSetScissor(m_cmdBuf, 0, 1, &sc); + } + + void setBlendConstant(f32 r, f32 g, f32 b, f32 a) override { + f32 c[4] = {r, g, b, a}; + vkCmdSetBlendConstants(m_cmdBuf, c); + } + + void setStencilReference(u32 ref) override { + vkCmdSetStencilReference(m_cmdBuf, VK_STENCIL_FACE_FRONT_AND_BACK, ref); + } + + void draw(u32 vertexCount, u32 instanceCount, u32 firstVertex, u32 firstInstance) override { + vkCmdDraw(m_cmdBuf, vertexCount, instanceCount, firstVertex, firstInstance); + } + + void drawIndexed(u32 indexCount, u32 instanceCount, u32 firstIndex, i32 baseVertex, u32 firstInstance) override { + vkCmdDrawIndexed(m_cmdBuf, indexCount, instanceCount, firstIndex, baseVertex, firstInstance); + } + + void drawIndirect(Buffer* buf, u64 offset, u32 drawCount, u32 stride) override { + auto* vkBuf = static_cast(buf); + if (!vkBuf) return; + vkCmdDrawIndirect(m_cmdBuf, vkBuf->handle(), offset, drawCount, stride > 0 ? stride : 16); + } + + void drawIndexedIndirect(Buffer* buf, u64 offset, u32 drawCount, u32 stride) override { + auto* vkBuf = static_cast(buf); + if (!vkBuf) return; + vkCmdDrawIndexedIndirect(m_cmdBuf, vkBuf->handle(), offset, drawCount, stride > 0 ? stride : 20); + } + + void writeTimestamp(QuerySet* qs, u32 index) override { + auto* q = static_cast(qs); + if (q) vkCmdWriteTimestamp(m_cmdBuf, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, q->handle(), index); + } + + void beginOcclusionQuery(QuerySet* qs, u32 index) override { + auto* q = static_cast(qs); + if (q) vkCmdBeginQuery(m_cmdBuf, q->handle(), index, 0); + } + + void endOcclusionQuery(QuerySet* qs, u32 index) override { + auto* q = static_cast(qs); + if (q) vkCmdEndQuery(m_cmdBuf, q->handle(), index); + } + + void executeBundles(std::span bundles) override { + if (bundles.empty()) return; + std::vector secs(bundles.size()); + for (usize i = 0; i < bundles.size(); ++i) + secs[i] = static_cast(bundles[i])->handle(); + vkCmdExecuteCommands(m_cmdBuf, static_cast(secs.size()), secs.data()); + } + + void end() override { + vkCmdEndRendering(m_cmdBuf); + m_currentPipeline = nullptr; + m_currentMeshPipeline = nullptr; + } + + // ---- MeshShaderPassExt ---- + + void setMeshPipeline(MeshPipeline* pipeline) override; + void drawMeshTasks(u32 gx, u32 gy, u32 gz) override; + void drawMeshTasksIndirect(Buffer* buf, u64 offset, u32 drawCount, u32 stride) override; + void drawMeshTasksIndirectCount(Buffer* buf, u64 offset, Buffer* countBuf, u64 countOffset, u32 maxDrawCount, u32 stride) override; + +private: + VkPipelineLayoutImpl* getCurrentLayout(); + + VkCommandBuffer m_cmdBuf = VK_NULL_HANDLE; + VkDevice m_device = VK_NULL_HANDLE; + VkRenderPipelineImpl* m_currentPipeline = nullptr; + VkMeshPipelineImpl* m_currentMeshPipeline = nullptr; + + // Cached device-level mesh shader function pointers. + PFN_vkCmdDrawMeshTasksEXT m_pfnDrawMesh = nullptr; + PFN_vkCmdDrawMeshTasksIndirectEXT m_pfnDrawMeshIndirect = nullptr; + PFN_vkCmdDrawMeshTasksIndirectCountEXT m_pfnDrawMeshIndCount = nullptr; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkRenderPipeline.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkRenderPipeline.cppm new file mode 100644 index 00000000..9df538e2 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkRenderPipeline.cppm @@ -0,0 +1,222 @@ +/// Vulkan implementation of RenderPipeline. + +module; + +#include "VkIncludes.h" +#include +#include +#include + + +export module rhi.vk:render_pipeline; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :shader_module; +import :pipeline_layout; +import :pipeline_cache; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkRenderPipelineImpl : public RenderPipeline { +public: + Status init(VkDevice device, const RenderPipelineDesc& desc) { + auto* vkLayout = static_cast(desc.layout); + if (!vkLayout) return ErrorCode::Unknown; + layout = desc.layout; + m_layout = vkLayout; + + // Shader stages. + std::vector stages; + std::u8string vsEntry = std::u8string(desc.vertex.shader.entryPoint); + std::u8string fsEntry; + + auto* vsMod = static_cast(desc.vertex.shader.module); + if (!vsMod) return ErrorCode::Unknown; + VkPipelineShaderStageCreateInfo vsStage{}; + vsStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vsStage.stage = VK_SHADER_STAGE_VERTEX_BIT; + vsStage.module = vsMod->handle(); + vsStage.pName = reinterpret_cast(vsEntry.c_str()); + stages.push_back(vsStage); + + if (desc.fragment.has_value()) { + fsEntry = std::u8string(desc.fragment->shader.entryPoint); + auto* fsMod = static_cast(desc.fragment->shader.module); + if (fsMod) { + VkPipelineShaderStageCreateInfo fsStage{}; + fsStage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fsStage.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fsStage.module = fsMod->handle(); + fsStage.pName = reinterpret_cast(fsEntry.c_str()); + stages.push_back(fsStage); + } + } + + // Vertex input. + std::vector vertBindings; + std::vector vertAttribs; + for (usize i = 0; i < desc.vertex.buffers.size(); ++i) { + const auto& buf = desc.vertex.buffers[i]; + VkVertexInputBindingDescription b{}; + b.binding = static_cast(i); + b.stride = buf.stride; + b.inputRate = buf.stepMode == VertexStepMode::Instance ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX; + vertBindings.push_back(b); + for (usize j = 0; j < buf.attributes.size(); ++j) { + VkVertexInputAttributeDescription a{}; + a.location = buf.attributes[j].shaderLocation; + a.binding = static_cast(i); + a.format = toVkVertexFormat(buf.attributes[j].format); + a.offset = buf.attributes[j].offset; + vertAttribs.push_back(a); + } + } + VkPipelineVertexInputStateCreateInfo vertexInput{}; + vertexInput.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertexInput.vertexBindingDescriptionCount = static_cast(vertBindings.size()); + vertexInput.pVertexBindingDescriptions = vertBindings.data(); + vertexInput.vertexAttributeDescriptionCount = static_cast(vertAttribs.size()); + vertexInput.pVertexAttributeDescriptions = vertAttribs.data(); + + VkPipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + inputAssembly.topology = toVkTopology(desc.primitive.topology); + + VkPipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewportState.viewportCount = 1; + viewportState.scissorCount = 1; + + VkPipelineRasterizationStateCreateInfo rasterization{}; + rasterization.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterization.depthClampEnable = desc.primitive.depthClipEnabled ? VK_FALSE : VK_TRUE; + rasterization.polygonMode = toVkPolygonMode(desc.primitive.fillMode); + rasterization.cullMode = toVkCullMode(desc.primitive.cullMode); + rasterization.frontFace = toVkFrontFace(desc.primitive.frontFace); + rasterization.lineWidth = 1.0f; + if (desc.depthStencil.has_value()) { + rasterization.depthBiasEnable = (desc.depthStencil->depthBias != 0 || desc.depthStencil->depthBiasSlopeScale != 0) ? VK_TRUE : VK_FALSE; + rasterization.depthBiasConstantFactor = static_cast(desc.depthStencil->depthBias); + rasterization.depthBiasSlopeFactor = desc.depthStencil->depthBiasSlopeScale; + rasterization.depthBiasClamp = desc.depthStencil->depthBiasClamp; + } + + VkPipelineMultisampleStateCreateInfo multisample{}; + multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisample.rasterizationSamples = toVkSampleCount(desc.multisample.count); + multisample.alphaToCoverageEnable = desc.multisample.alphaToCoverageEnabled ? VK_TRUE : VK_FALSE; + u32 sampleMask = desc.multisample.mask; + multisample.pSampleMask = &sampleMask; + + // Color blend. + auto colorTargets = desc.fragment.has_value() ? desc.fragment->targets : std::span(); + std::vector blendAttachments(colorTargets.size()); + for (usize i = 0; i < colorTargets.size(); ++i) { + auto& ba = blendAttachments[i]; + ba = {}; + ba.colorWriteMask = toVkColorWriteMask(colorTargets[i].writeMask); + if (colorTargets[i].blend.has_value()) { + ba.blendEnable = VK_TRUE; + ba.srcColorBlendFactor = toVkBlendFactor(colorTargets[i].blend->color.srcFactor); + ba.dstColorBlendFactor = toVkBlendFactor(colorTargets[i].blend->color.dstFactor); + ba.colorBlendOp = toVkBlendOp(colorTargets[i].blend->color.operation); + ba.srcAlphaBlendFactor = toVkBlendFactor(colorTargets[i].blend->alpha.srcFactor); + ba.dstAlphaBlendFactor = toVkBlendFactor(colorTargets[i].blend->alpha.dstFactor); + ba.alphaBlendOp = toVkBlendOp(colorTargets[i].blend->alpha.operation); + } + } + VkPipelineColorBlendStateCreateInfo colorBlend{}; + colorBlend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + colorBlend.attachmentCount = static_cast(blendAttachments.size()); + colorBlend.pAttachments = blendAttachments.data(); + + // Depth/stencil. + VkPipelineDepthStencilStateCreateInfo depthStencil{}; + depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; + if (desc.depthStencil.has_value()) { + const auto& ds = *desc.depthStencil; + depthStencil.depthTestEnable = ds.depthTestEnabled ? VK_TRUE : VK_FALSE; + depthStencil.depthWriteEnable = ds.depthWriteEnabled ? VK_TRUE : VK_FALSE; + depthStencil.depthCompareOp = toVkCompareOp(ds.depthCompare); + depthStencil.stencilTestEnable = ds.stencilEnabled ? VK_TRUE : VK_FALSE; + depthStencil.front.failOp = toVkStencilOp(ds.stencilFront.failOp); + depthStencil.front.passOp = toVkStencilOp(ds.stencilFront.passOp); + depthStencil.front.depthFailOp = toVkStencilOp(ds.stencilFront.depthFailOp); + depthStencil.front.compareOp = toVkCompareOp(ds.stencilFront.compare); + depthStencil.front.compareMask = ds.stencilReadMask; + depthStencil.front.writeMask = ds.stencilWriteMask; + depthStencil.back.failOp = toVkStencilOp(ds.stencilBack.failOp); + depthStencil.back.passOp = toVkStencilOp(ds.stencilBack.passOp); + depthStencil.back.depthFailOp = toVkStencilOp(ds.stencilBack.depthFailOp); + depthStencil.back.compareOp = toVkCompareOp(ds.stencilBack.compare); + depthStencil.back.compareMask = ds.stencilReadMask; + depthStencil.back.writeMask = ds.stencilWriteMask; + } + + // Dynamic state. + VkDynamicState dynStates[] = { + VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, + VK_DYNAMIC_STATE_BLEND_CONSTANTS, VK_DYNAMIC_STATE_STENCIL_REFERENCE + }; + VkPipelineDynamicStateCreateInfo dynState{}; + dynState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + dynState.dynamicStateCount = 4; + dynState.pDynamicStates = dynStates; + + // Dynamic rendering (Vulkan 1.3). + std::vector colorFormats(colorTargets.size()); + for (usize i = 0; i < colorTargets.size(); ++i) + colorFormats[i] = toVkFormat(colorTargets[i].format); + + VkPipelineRenderingCreateInfo renderingInfo{}; + renderingInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO; + renderingInfo.colorAttachmentCount = static_cast(colorFormats.size()); + renderingInfo.pColorAttachmentFormats = colorFormats.data(); + if (desc.depthStencil.has_value()) { + VkFormat dsf = toVkFormat(desc.depthStencil->format); + if (hasDepth(desc.depthStencil->format)) renderingInfo.depthAttachmentFormat = dsf; + if (hasStencil(desc.depthStencil->format)) renderingInfo.stencilAttachmentFormat = dsf; + } + + // Create pipeline. + VkGraphicsPipelineCreateInfo pi{}; + pi.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pi.pNext = &renderingInfo; + pi.stageCount = static_cast(stages.size()); + pi.pStages = stages.data(); + pi.pVertexInputState = &vertexInput; + pi.pInputAssemblyState = &inputAssembly; + pi.pViewportState = &viewportState; + pi.pRasterizationState = &rasterization; + pi.pMultisampleState = &multisample; + pi.pDepthStencilState = desc.depthStencil.has_value() ? &depthStencil : nullptr; + pi.pColorBlendState = &colorBlend; + pi.pDynamicState = &dynState; + pi.layout = vkLayout->handle(); + + VkPipelineCache cacheHandle = VK_NULL_HANDLE; + if (desc.cache) cacheHandle = static_cast(desc.cache)->handle(); + + if (vkCreateGraphicsPipelines(device, cacheHandle, 1, &pi, nullptr, &m_pipeline) != VK_SUCCESS) + return ErrorCode::Unknown; + return ErrorCode::Ok; + } + + void cleanup(VkDevice device) { + if (m_pipeline != VK_NULL_HANDLE) { vkDestroyPipeline(device, m_pipeline, nullptr); m_pipeline = VK_NULL_HANDLE; } + } + + [[nodiscard]] VkPipeline handle() const { return m_pipeline; } + [[nodiscard]] VkPipelineLayoutImpl* vkLayout() const { return m_layout; } + +private: + VkPipeline m_pipeline = VK_NULL_HANDLE; + VkPipelineLayoutImpl* m_layout = nullptr; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkSampler.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkSampler.cppm new file mode 100644 index 00000000..96132d56 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkSampler.cppm @@ -0,0 +1,58 @@ +/// Vulkan implementation of Sampler. + +module; + +#include "VkIncludes.h" + +export module rhi.vk:sampler; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkSamplerImpl : public Sampler { +public: + Status init(VkDevice device, const SamplerDesc& d) { + desc = d; + + VkSamplerCreateInfo ci{}; + ci.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + ci.magFilter = toVkFilter(d.magFilter); + ci.minFilter = toVkFilter(d.minFilter); + ci.mipmapMode = toVkMipmapMode(d.mipmapFilter); + ci.addressModeU = toVkAddressMode(d.addressU); + ci.addressModeV = toVkAddressMode(d.addressV); + ci.addressModeW = toVkAddressMode(d.addressW); + ci.mipLodBias = d.mipLodBias; + ci.anisotropyEnable = d.maxAnisotropy > 1 ? VK_TRUE : VK_FALSE; + ci.maxAnisotropy = static_cast(d.maxAnisotropy); + ci.minLod = d.minLod; + ci.maxLod = d.maxLod; + ci.borderColor = toVkBorderColor(d.borderColor); + ci.unnormalizedCoordinates = VK_FALSE; + + if (d.compare.has_value()) { + ci.compareEnable = VK_TRUE; + ci.compareOp = toVkCompareOp(d.compare.value()); + } + + if (vkCreateSampler(device, &ci, nullptr, &m_sampler) != VK_SUCCESS) return ErrorCode::Unknown; + return ErrorCode::Ok; + } + + void cleanup(VkDevice device) { + if (m_sampler != VK_NULL_HANDLE) { vkDestroySampler(device, m_sampler, nullptr); m_sampler = VK_NULL_HANDLE; } + } + + [[nodiscard]] VkSampler handle() const { return m_sampler; } + +private: + VkSampler m_sampler = VK_NULL_HANDLE; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkShaderModule.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkShaderModule.cppm new file mode 100644 index 00000000..c1474371 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkShaderModule.cppm @@ -0,0 +1,39 @@ +/// Vulkan implementation of ShaderModule. + +module; + +#include "VkIncludes.h" + +export module rhi.vk:shader_module; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkShaderModuleImpl : public ShaderModule { +public: + Status init(VkDevice device, const ShaderModuleDesc& d) { + VkShaderModuleCreateInfo ci{}; + ci.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + ci.codeSize = d.code.size(); + ci.pCode = reinterpret_cast(d.code.data()); + + if (vkCreateShaderModule(device, &ci, nullptr, &m_module) != VK_SUCCESS) return ErrorCode::Unknown; + return ErrorCode::Ok; + } + + void cleanup(VkDevice device) { + if (m_module != VK_NULL_HANDLE) { vkDestroyShaderModule(device, m_module, nullptr); m_module = VK_NULL_HANDLE; } + } + + [[nodiscard]] VkShaderModule handle() const { return m_module; } + +private: + VkShaderModule m_module = VK_NULL_HANDLE; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkSurface.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkSurface.cppm new file mode 100644 index 00000000..8eb9307c --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkSurface.cppm @@ -0,0 +1,37 @@ +/// Vulkan implementation of Surface. +/// Wraps VkSurfaceKHR + parent VkInstance for cleanup. + +module; + +#include "VkIncludes.h" + +export module rhi.vk:surface; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkSurfaceImpl : public Surface { +public: + VkSurfaceImpl(VkSurfaceKHR surface, VkInstance instance) + : m_surface(surface), m_instance(instance) {} + + [[nodiscard]] VkSurfaceKHR handle() const { return m_surface; } + + void destroy() { + if (m_surface != VK_NULL_HANDLE) { + vkDestroySurfaceKHR(m_instance, m_surface, nullptr); + m_surface = VK_NULL_HANDLE; + } + } + +private: + VkSurfaceKHR m_surface = VK_NULL_HANDLE; + VkInstance m_instance = VK_NULL_HANDLE; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkSwapChain.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkSwapChain.cppm new file mode 100644 index 00000000..be27a6af --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkSwapChain.cppm @@ -0,0 +1,264 @@ +/// Vulkan implementation of SwapChain. + +module; + +#include "VkIncludes.h" +#include +#include +#include + + +export module rhi.vk:swap_chain; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :adapter; +import :surface; +import :texture; +import :texture_view; + +using namespace draco; + +export namespace draco::rhi::vk { + +// Minimal reverse format mapping for swap chain format negotiation. +inline TextureFormat fromVkFormat(VkFormat f) { + switch (f) { + case VK_FORMAT_R8G8B8A8_UNORM: return TextureFormat::RGBA8Unorm; + case VK_FORMAT_R8G8B8A8_SRGB: return TextureFormat::RGBA8UnormSrgb; + case VK_FORMAT_B8G8R8A8_UNORM: return TextureFormat::BGRA8Unorm; + case VK_FORMAT_B8G8R8A8_SRGB: return TextureFormat::BGRA8UnormSrgb; + case VK_FORMAT_R16G16B16A16_SFLOAT: return TextureFormat::RGBA16Float; + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return TextureFormat::RGB10A2Unorm; + default: return TextureFormat::Undefined; + } +} + +class VkDeviceImpl; // forward + +class VkSwapChainImpl : public SwapChain { +public: + Status init(VkDevice device, VkPhysicalDevice physDevice, VkSurfaceKHR surface, + const SwapChainDesc& desc, VkDeviceImpl* owner) { + m_device = device; + m_physDevice = physDevice; + m_surface = surface; + m_owner = owner; + m_presentMode= desc.presentMode; + return createSwapChain(desc.width, desc.height, desc.format, desc.bufferCount, VK_NULL_HANDLE); + } + + // ---- SwapChain interface ---- + TextureFormat format() const override { return m_format; } + u32 width() const override { return m_width; } + u32 height() const override { return m_height; } + u32 bufferCount() const override { return m_bufferCount; } + u32 currentImageIndex() const override { return m_currentImageIndex; } + + Status acquireNextImage() override; + Texture* currentTexture() override { return m_currentImageIndex < m_textures.size() ? m_textures[m_currentImageIndex] : nullptr; } + TextureView* currentTextureView() override { return m_currentImageIndex < m_views.size() ? m_views[m_currentImageIndex] : nullptr; } + Status present(Queue* queue) override; + Status resize(u32 w, u32 h) override; + + void cleanup(); + + // ---- Internal ---- + [[nodiscard]] VkSwapchainKHR handle() const { return m_swapchain; } + + // Semaphore accessors for queue submit integration. + VkSemaphore currentAcquireSemaphore() const { return m_acquireSems[m_frameIndex]; } + VkSemaphore currentPresentSemaphore() const { return m_presentSems[m_currentImageIndex]; } + +private: + Status createSwapChain(u32 w, u32 h, TextureFormat reqFormat, u32 reqCount, VkSwapchainKHR old); + Status retrieveImages(VkFormat format); + void createSyncObjects(); + void cleanupImages(); + void destroySyncObjects(); + + VkSurfaceFormatKHR chooseSurfaceFormat(TextureFormat requested); + VkPresentModeKHR choosePresentMode(PresentMode requested); + + VkDevice m_device = VK_NULL_HANDLE; + VkPhysicalDevice m_physDevice = VK_NULL_HANDLE; + VkSurfaceKHR m_surface = VK_NULL_HANDLE; + VkSwapchainKHR m_swapchain = VK_NULL_HANDLE; + VkDeviceImpl* m_owner = nullptr; + + TextureFormat m_format = TextureFormat::Undefined; + PresentMode m_presentMode = PresentMode::Fifo; + u32 m_width = 0, m_height = 0, m_bufferCount = 0; + u32 m_currentImageIndex = 0; + u32 m_frameIndex = 0; + + std::vector m_textures; + std::vector m_views; + std::vector m_acquireSems; + std::vector m_presentSems; +}; + +// ---- Implementation (inline in module) ---- + +inline Status VkSwapChainImpl::createSwapChain(u32 w, u32 h, TextureFormat reqFormat, u32 reqCount, VkSwapchainKHR old) { + VkSurfaceCapabilitiesKHR caps{}; + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physDevice, m_surface, &caps); + + if (caps.currentExtent.width != ~0u) { m_width = caps.currentExtent.width; m_height = caps.currentExtent.height; } + else { m_width = std::clamp(w, caps.minImageExtent.width, caps.maxImageExtent.width); + m_height = std::clamp(h, caps.minImageExtent.height, caps.maxImageExtent.height); } + if (m_width == 0 || m_height == 0) return ErrorCode::Unknown; + + auto presentMode = choosePresentMode(m_presentMode); + + // Mailbox needs a 3rd image to race ahead of the display without stalling; 2 is fine for + // FIFO/Immediate. + u32 wantCount = reqCount; + if (presentMode == VK_PRESENT_MODE_MAILBOX_KHR && wantCount < 3) { wantCount = 3; } + m_bufferCount = std::max(wantCount, caps.minImageCount); + if (caps.maxImageCount > 0) m_bufferCount = std::min(m_bufferCount, caps.maxImageCount); + + auto surfFmt = chooseSurfaceFormat(reqFormat); + m_format = fromVkFormat(surfFmt.format); + if (m_format == TextureFormat::Undefined) m_format = reqFormat; + + VkImageUsageFlags usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + if (caps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; + if (caps.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_SRC_BIT) usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + + VkCompositeAlphaFlagBitsKHR compositeAlpha = (caps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) + ? VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR : VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + + VkSwapchainCreateInfoKHR ci{}; + ci.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + ci.surface = m_surface; + ci.minImageCount = m_bufferCount; + ci.imageFormat = surfFmt.format; + ci.imageColorSpace = surfFmt.colorSpace; + ci.imageExtent = { m_width, m_height }; + ci.imageArrayLayers = 1; + ci.imageUsage = usage; + ci.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + ci.preTransform = caps.currentTransform; + ci.compositeAlpha = compositeAlpha; + ci.presentMode = presentMode; + ci.clipped = VK_TRUE; + ci.oldSwapchain = old; + + if (vkCreateSwapchainKHR(m_device, &ci, nullptr, &m_swapchain) != VK_SUCCESS) return ErrorCode::Unknown; + if (old != VK_NULL_HANDLE) vkDestroySwapchainKHR(m_device, old, nullptr); + + if (retrieveImages(surfFmt.format) != ErrorCode::Ok) return ErrorCode::Unknown; + createSyncObjects(); + m_frameIndex = 0; + return ErrorCode::Ok; +} + +inline VkSurfaceFormatKHR VkSwapChainImpl::chooseSurfaceFormat(TextureFormat requested) { + u32 count = 0; + vkGetPhysicalDeviceSurfaceFormatsKHR(m_physDevice, m_surface, &count, nullptr); + std::vector fmts(count); + vkGetPhysicalDeviceSurfaceFormatsKHR(m_physDevice, m_surface, &count, fmts.data()); + + VkFormat desired = toVkFormat(requested); + for (auto& f : fmts) if (f.format == desired) return f; + for (auto& f : fmts) if (f.format == VK_FORMAT_B8G8R8A8_SRGB && f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) return f; + for (auto& f : fmts) if (f.format == VK_FORMAT_B8G8R8A8_UNORM) return f; + return fmts[0]; +} + +inline VkPresentModeKHR VkSwapChainImpl::choosePresentMode(PresentMode requested) { + u32 count = 0; + vkGetPhysicalDeviceSurfacePresentModesKHR(m_physDevice, m_surface, &count, nullptr); + std::vector modes(count); + vkGetPhysicalDeviceSurfacePresentModesKHR(m_physDevice, m_surface, &count, modes.data()); + auto has = [&](VkPresentModeKHR m) { for (auto x : modes) if (x == m) return true; return false; }; + + const VkPresentModeKHR desired = toVkPresentMode(requested); + if (has(desired)) { return desired; } + + // Requested mode unavailable: preserve the request's INTENT rather than dropping straight to + // vsync. Immediate and Mailbox both mean "don't block on the refresh" - so fall back to whichever + // uncapped mode the surface does expose (Wayland commonly offers Mailbox but NOT Immediate) before + // settling for FIFO. FIFO is the only mode guaranteed present by the spec. + VkPresentModeKHR chosen = VK_PRESENT_MODE_FIFO_KHR; + if (requested == PresentMode::Immediate || requested == PresentMode::Mailbox) { + if (has(VK_PRESENT_MODE_MAILBOX_KHR)) { chosen = VK_PRESENT_MODE_MAILBOX_KHR; } + else if (has(VK_PRESENT_MODE_IMMEDIATE_KHR)) { chosen = VK_PRESENT_MODE_IMMEDIATE_KHR; } + } else if (requested == PresentMode::FifoRelaxed && has(VK_PRESENT_MODE_FIFO_RELAXED_KHR)) { + chosen = VK_PRESENT_MODE_FIFO_RELAXED_KHR; + } + + logWarningf("[swapchain] requested present mode (vk %u) unavailable; using vk %u", + static_cast(desired), static_cast(chosen)); + return chosen; +} + +inline Status VkSwapChainImpl::retrieveImages(VkFormat format) { + u32 imgCount = 0; + vkGetSwapchainImagesKHR(m_device, m_swapchain, &imgCount, nullptr); + std::vector images(imgCount); + vkGetSwapchainImagesKHR(m_device, m_swapchain, &imgCount, images.data()); + m_bufferCount = imgCount; + + TextureFormat texFmt = fromVkFormat(format); + if (texFmt == TextureFormat::Undefined) texFmt = m_format; + + for (u32 i = 0; i < imgCount; ++i) { + TextureDesc td{}; td.dimension = TextureDimension::Texture2D; td.format = texFmt; + td.width = m_width; td.height = m_height; td.arrayLayerCount = 1; td.mipLevelCount = 1; + td.sampleCount = 1; td.usage = TextureUsage::RenderTarget; + + auto* tex = new VkTextureImpl(); + tex->initFromExisting(images[i], td); + m_textures.push_back(tex); + + TextureViewDesc vd{}; vd.format = texFmt; vd.dimension = TextureViewDimension::Texture2D; + vd.mipLevelCount = 1; vd.arrayLayerCount = 1; + auto* view = new VkTextureViewImpl(); + if (view->init(m_device, tex, vd) != ErrorCode::Ok) { delete view; return ErrorCode::Unknown; } + m_views.push_back(view); + } + return ErrorCode::Ok; +} + +inline void VkSwapChainImpl::createSyncObjects() { + VkSemaphoreCreateInfo ci{}; ci.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + for (u32 i = 0; i < m_bufferCount; ++i) { + VkSemaphore a = VK_NULL_HANDLE, p = VK_NULL_HANDLE; + vkCreateSemaphore(m_device, &ci, nullptr, &a); + vkCreateSemaphore(m_device, &ci, nullptr, &p); + m_acquireSems.push_back(a); + m_presentSems.push_back(p); + } +} + +inline void VkSwapChainImpl::cleanupImages() { + for (auto* v : m_views) { v->cleanup(m_device); delete v; } m_views.clear(); + for (auto* t : m_textures) { t->cleanup(m_device); delete t; } m_textures.clear(); +} + +inline void VkSwapChainImpl::destroySyncObjects() { + for (auto s : m_acquireSems) { vkDestroySemaphore(m_device, s, nullptr); } + m_acquireSems.clear(); + for (auto s : m_presentSems) { vkDestroySemaphore(m_device, s, nullptr); } + m_presentSems.clear(); +} + +inline Status VkSwapChainImpl::resize(u32 w, u32 h) { + vkDeviceWaitIdle(m_device); + cleanupImages(); + destroySyncObjects(); + return createSwapChain(w, h, m_format, m_bufferCount, m_swapchain); +} + +inline void VkSwapChainImpl::cleanup() { + vkDeviceWaitIdle(m_device); + cleanupImages(); + destroySyncObjects(); + if (m_swapchain != VK_NULL_HANDLE) { vkDestroySwapchainKHR(m_device, m_swapchain, nullptr); m_swapchain = VK_NULL_HANDLE; } +} + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkTexture.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkTexture.cppm new file mode 100644 index 00000000..f8227da3 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkTexture.cppm @@ -0,0 +1,141 @@ +/// Vulkan implementation of Texture. + +module; + +#include "VkIncludes.h" +#include +#include + + +export module rhi.vk:texture; + +import core.stdtypes; +import core.status; +import rhi; +import :adapter; +import :conversions; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkTextureImpl : public Texture { +public: + /// Initialize from a TextureDesc (creates VkImage + allocates memory). + Status init(VkDevice device, VkAdapterImpl* adapter, const TextureDesc& d) { + desc = d; + + VkImageCreateInfo ci{}; + ci.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + ci.imageType = toVkImageType(d.dimension); + ci.format = toVkFormat(d.format); + ci.extent = { d.width, d.height, d.depth }; + ci.mipLevels = d.mipLevelCount; + ci.arrayLayers = d.arrayLayerCount; + ci.samples = toVkSampleCount(d.sampleCount); + ci.tiling = VK_IMAGE_TILING_OPTIMAL; + ci.usage = toVkImageUsage(d.usage); + ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + ci.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + if (d.arrayLayerCount >= 6 && d.dimension == TextureDimension::Texture2D) + ci.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + + if (vkCreateImage(device, &ci, nullptr, &m_image) != VK_SUCCESS) return ErrorCode::Unknown; + + VkMemoryRequirements memReqs{}; + vkGetImageMemoryRequirements(device, m_image, &memReqs); + + i32 memType = adapter->findMemoryType( + static_cast(memReqs.memoryTypeBits), VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (memType < 0) { + vkDestroyImage(device, m_image, nullptr); m_image = VK_NULL_HANDLE; + return ErrorCode::Unknown; + } + + VkMemoryAllocateInfo ai{}; + ai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + ai.allocationSize = memReqs.size; + ai.memoryTypeIndex = static_cast(memType); + + if (vkAllocateMemory(device, &ai, nullptr, &m_memory) != VK_SUCCESS) { + vkDestroyImage(device, m_image, nullptr); m_image = VK_NULL_HANDLE; + return ErrorCode::Unknown; + } + + vkBindImageMemory(device, m_image, m_memory, 0); + return ErrorCode::Ok; + } + + /// Initialize from an existing VkImage (e.g. swap chain). Does not own the image. + void initFromExisting(VkImage image, const TextureDesc& d) { + m_image = image; + desc = d; + m_ownsImage = false; + } + + void cleanup(VkDevice device) { + if (m_memory != VK_NULL_HANDLE) { vkFreeMemory(device, m_memory, nullptr); m_memory = VK_NULL_HANDLE; } + if (m_ownsImage && m_image != VK_NULL_HANDLE) vkDestroyImage(device, m_image, nullptr); + m_image = VK_NULL_HANDLE; + m_subresourceLayouts.clear(); + } + + // ---- Internal ---- + [[nodiscard]] VkImage handle() const { return m_image; } + [[nodiscard]] VkFormat vkFormat() const { return toVkFormat(desc.format); } + + /// Whole-resource layout (uniform fast path). + VkImageLayout currentLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + /// Get layout for a specific subresource. + VkImageLayout getSubresourceLayout(u32 mip, u32 layer) const { + if (m_subresourceLayouts.empty()) return currentLayout; + u32 idx = mip + layer * desc.mipLevelCount; + if (idx >= static_cast(m_subresourceLayouts.size())) return currentLayout; + return m_subresourceLayouts[idx]; + } + + /// Update layout for a subresource range. Promotes to per-subresource + /// tracking when needed, collapses back to uniform when all match. + void setSubresourceLayout(u32 baseMip, u32 mipCount, u32 baseLayer, u32 layerCount, + VkImageLayout layout) { + u32 totalMips = desc.mipLevelCount; + u32 totalLayers = std::max(desc.arrayLayerCount, 1u); + u32 mipEnd = (mipCount == ~0u) ? totalMips : std::min(baseMip + mipCount, totalMips); + u32 layerEnd = (layerCount == ~0u) ? totalLayers : std::min(baseLayer + layerCount, totalLayers); + + // All subresources? Collapse to uniform. + if (baseMip == 0 && mipEnd >= totalMips && baseLayer == 0 && layerEnd >= totalLayers) { + currentLayout = layout; + m_subresourceLayouts.clear(); + return; + } + + // Promote to per-subresource. + if (m_subresourceLayouts.empty()) { + if (layout == currentLayout) return; + m_subresourceLayouts.resize(totalMips * totalLayers, currentLayout); + } + + for (u32 l = baseLayer; l < layerEnd; ++l) + for (u32 m = baseMip; m < mipEnd; ++m) + m_subresourceLayouts[m + l * totalMips] = layout; + + // Try to collapse back to uniform. + VkImageLayout first = m_subresourceLayouts[0]; + for (usize i = 1; i < m_subresourceLayouts.size(); ++i) { + if (m_subresourceLayouts[i] != first) return; + } + currentLayout = first; + m_subresourceLayouts.clear(); + } + +private: + VkImage m_image = VK_NULL_HANDLE; + VkDeviceMemory m_memory = VK_NULL_HANDLE; + bool m_ownsImage = true; + std::vector m_subresourceLayouts; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkTextureView.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkTextureView.cppm new file mode 100644 index 00000000..794c936c --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkTextureView.cppm @@ -0,0 +1,77 @@ +/// Vulkan implementation of TextureView. + +module; + +#include "VkIncludes.h" + +export module rhi.vk:texture_view; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :texture; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkTextureViewImpl : public TextureView { +public: + Status init(VkDevice device, VkTextureImpl* tex, const TextureViewDesc& d) { + desc = d; + texture = tex; + // Dimensions are the view's BASE MIP extent, not the texture's mip-0 size - a view onto mip N + // is half-sized per level. Render-pass renderArea/framebuffer is derived from these, so a stale + // mip-0 size here overruns a mip>0 attachment (GPU fault). Shadows only ever target mip 0; the + // IBL prefilter is the first mip>0 render target to exercise this. + m_width = tex->desc.width >> d.baseMipLevel; if (m_width == 0) { m_width = 1; } + m_height = tex->desc.height >> d.baseMipLevel; if (m_height == 0) { m_height = 1; } + + TextureFormat fmt = (d.format == TextureFormat::Undefined) ? tex->desc.format : d.format; + m_format = fmt; + + VkImageViewCreateInfo ci{}; + ci.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + ci.image = tex->handle(); + ci.viewType = toVkImageViewType(d.dimension); + ci.format = toVkFormat(fmt); + ci.components = { VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, + VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY }; + + u32 mipCount = d.mipLevelCount > 0 ? d.mipLevelCount : tex->desc.mipLevelCount - d.baseMipLevel; + u32 layerCount = d.arrayLayerCount > 0 ? d.arrayLayerCount : tex->desc.arrayLayerCount - d.baseArrayLayer; + + VkImageAspectFlags aspect; + switch (d.aspect) { + case TextureAspect::DepthOnly: aspect = VK_IMAGE_ASPECT_DEPTH_BIT; break; + case TextureAspect::StencilOnly: aspect = VK_IMAGE_ASPECT_STENCIL_BIT; break; + default: aspect = getAspectMask(fmt); break; + } + + ci.subresourceRange = { aspect, d.baseMipLevel, mipCount, d.baseArrayLayer, layerCount }; + + if (vkCreateImageView(device, &ci, nullptr, &m_imageView) != VK_SUCCESS) return ErrorCode::Unknown; + return ErrorCode::Ok; + } + + void cleanup(VkDevice device) { + if (m_imageView != VK_NULL_HANDLE) { + vkDestroyImageView(device, m_imageView, nullptr); + m_imageView = VK_NULL_HANDLE; + } + } + + [[nodiscard]] VkImageView handle() const { return m_imageView; } + [[nodiscard]] TextureFormat format() const { return m_format; } + [[nodiscard]] u32 width() const { return m_width; } + [[nodiscard]] u32 height() const { return m_height; } + +private: + VkImageView m_imageView = VK_NULL_HANDLE; + TextureFormat m_format = TextureFormat::Undefined; + u32 m_width = 0; + u32 m_height = 0; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkTransferBatch.cppm b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkTransferBatch.cppm new file mode 100644 index 00000000..f2d5df93 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VkTransferBatch.cppm @@ -0,0 +1,225 @@ +/// Vulkan implementation of TransferBatch. + +module; + +#include "VkIncludes.h" +#include +#include +#include + +#include + +export module rhi.vk:transfer_batch; + +import core.stdtypes; +import core.status; +import rhi; +import :conversions; +import :buffer; +import :texture; +import :fence; + +using namespace draco; + +export namespace draco::rhi::vk { + +class VkTransferBatchImpl : public TransferBatch { +public: + VkTransferBatchImpl(VkDevice device, VkQueue queue, u32 queueFamilyIndex, VkPhysicalDevice physDevice) + : m_device(device), m_physDevice(physDevice), m_queue(queue), m_queueFamilyIndex(queueFamilyIndex) {} + + void writeBuffer(Buffer* dst, u64 dstOffset, std::span data) override { + u64 needed = m_stagingOffset + data.size(); + if (ensureStagingBuffer(needed) != ErrorCode::Ok) return; + void* mapped = m_stagingMapped; + if (!mapped) return; + std::memcpy(static_cast(mapped) + m_stagingOffset, data.data(), data.size()); + m_bufferCopies.push_back({ dst, dstOffset, m_stagingOffset, data.size() }); + m_stagingOffset = (m_stagingOffset + data.size() + 15) & ~static_cast(15); + } + + void writeTexture(Texture* dst, std::span data, + const TextureDataLayout& layout, Extent3D extent, + u32 mipLevel, u32 arrayLayer) override { + u64 needed = m_stagingOffset + data.size(); + if (ensureStagingBuffer(needed) != ErrorCode::Ok) return; + void* mapped = m_stagingMapped; + if (!mapped) return; + std::memcpy(static_cast(mapped) + m_stagingOffset, data.data(), data.size()); + m_textureCopies.push_back({ dst, m_stagingOffset, mipLevel, arrayLayer, extent, layout }); + m_stagingOffset = (m_stagingOffset + data.size() + 15) & ~static_cast(15); + } + + Status submit() override { + if (m_bufferCopies.empty() && m_textureCopies.empty()) return ErrorCode::Ok; + VkCommandBuffer cmdBuf = recordCommands(); + if (cmdBuf == VK_NULL_HANDLE) return ErrorCode::Unknown; + + VkSubmitInfo si{}; si.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + si.commandBufferCount = 1; si.pCommandBuffers = &cmdBuf; + if (vkQueueSubmit(m_queue, 1, &si, VK_NULL_HANDLE) != VK_SUCCESS) return ErrorCode::Unknown; + vkQueueWaitIdle(m_queue); + cleanupCmdPool(); + return ErrorCode::Ok; + } + + Status submitAsync(Fence* fence, u64 signalValue) override { + if (m_bufferCopies.empty() && m_textureCopies.empty()) return ErrorCode::Ok; + VkCommandBuffer cmdBuf = recordCommands(); + if (cmdBuf == VK_NULL_HANDLE) return ErrorCode::Unknown; + + auto* vkFence = static_cast(fence); + if (!vkFence) return ErrorCode::Unknown; + + VkSemaphore sem = vkFence->handle(); + VkTimelineSemaphoreSubmitInfo tsi{}; tsi.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO; + tsi.signalSemaphoreValueCount = 1; tsi.pSignalSemaphoreValues = &signalValue; + + VkSubmitInfo si{}; si.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; si.pNext = &tsi; + si.commandBufferCount = 1; si.pCommandBuffers = &cmdBuf; + si.signalSemaphoreCount = 1; si.pSignalSemaphores = &sem; + + vkQueueSubmit(m_queue, 1, &si, VK_NULL_HANDLE); + m_asyncFence = vkFence; m_asyncValue = signalValue; + return ErrorCode::Ok; + } + + void reset() override { + m_bufferCopies.clear(); m_textureCopies.clear(); + m_stagingOffset = 0; + cleanupCmdPool(); + } + + void destroy() override { + if (m_asyncFence) { m_asyncFence->wait(m_asyncValue, ~0ull); m_asyncFence = nullptr; } + if (m_cmdPool != VK_NULL_HANDLE) { vkDestroyCommandPool(m_device, m_cmdPool, nullptr); m_cmdPool = VK_NULL_HANDLE; } + if (m_stagingMapped) { vkUnmapMemory(m_device, m_stagingMem); m_stagingMapped = nullptr; } + if (m_stagingMem != VK_NULL_HANDLE) { vkFreeMemory(m_device, m_stagingMem, nullptr); m_stagingMem = VK_NULL_HANDLE; } + if (m_stagingBuf != VK_NULL_HANDLE) { vkDestroyBuffer(m_device, m_stagingBuf, nullptr); m_stagingBuf = VK_NULL_HANDLE; } + } + +private: + struct BufCopy { Buffer* dst; u64 dstOffset; u64 stagingOffset; u64 size; }; + struct TexCopy { Texture* dst; u64 stagingOffset; u32 mipLevel; u32 arrayLayer; Extent3D extent; TextureDataLayout layout; }; + + Status ensureStagingBuffer(u64 required) { + if (m_stagingBuf != VK_NULL_HANDLE && m_stagingSize >= required) return ErrorCode::Ok; + u64 newSize = std::max(required, std::max(m_stagingSize * 2, static_cast(4 * 1024 * 1024))); + + // Create staging buffer directly. + VkBufferCreateInfo ci{}; ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + ci.size = newSize; ci.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VkBuffer newBuf = VK_NULL_HANDLE; + if (vkCreateBuffer(m_device, &ci, nullptr, &newBuf) != VK_SUCCESS) return ErrorCode::Unknown; + + VkMemoryRequirements memReqs{}; vkGetBufferMemoryRequirements(m_device, newBuf, &memReqs); + + // Find host-visible + host-coherent memory type. + VkPhysicalDeviceMemoryProperties memProps{}; + vkGetPhysicalDeviceMemoryProperties(m_physDevice, &memProps); + i32 memType = -1; + for (u32 i = 0; i < memProps.memoryTypeCount; ++i) { + if ((memReqs.memoryTypeBits & (1 << i)) && + (memProps.memoryTypes[i].propertyFlags & (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) + == (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) + { memType = static_cast(i); break; } + } + if (memType < 0) { vkDestroyBuffer(m_device, newBuf, nullptr); return ErrorCode::Unknown; } + + VkMemoryAllocateInfo ai{}; ai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + ai.allocationSize = memReqs.size; ai.memoryTypeIndex = static_cast(memType); + VkDeviceMemory newMem = VK_NULL_HANDLE; + if (vkAllocateMemory(m_device, &ai, nullptr, &newMem) != VK_SUCCESS) { + vkDestroyBuffer(m_device, newBuf, nullptr); return ErrorCode::Unknown; + } + vkBindBufferMemory(m_device, newBuf, newMem, 0); + + void* newMapped = nullptr; + vkMapMemory(m_device, newMem, 0, newSize, 0, &newMapped); + + // Copy existing data. + if (m_stagingMapped && m_stagingOffset > 0 && newMapped) + std::memcpy(newMapped, m_stagingMapped, static_cast(m_stagingOffset)); + + // Free old. + if (m_stagingMapped) vkUnmapMemory(m_device, m_stagingMem); + if (m_stagingMem != VK_NULL_HANDLE) vkFreeMemory(m_device, m_stagingMem, nullptr); + if (m_stagingBuf != VK_NULL_HANDLE) vkDestroyBuffer(m_device, m_stagingBuf, nullptr); + + m_stagingBuf = newBuf; m_stagingMem = newMem; m_stagingMapped = newMapped; m_stagingSize = newSize; + return ErrorCode::Ok; + } + + VkCommandBuffer recordCommands() { + if (m_cmdPool == VK_NULL_HANDLE) { + VkCommandPoolCreateInfo ci{}; ci.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + ci.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; ci.queueFamilyIndex = m_queueFamilyIndex; + if (vkCreateCommandPool(m_device, &ci, nullptr, &m_cmdPool) != VK_SUCCESS) return VK_NULL_HANDLE; + } + VkCommandBufferAllocateInfo ai{}; ai.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + ai.commandPool = m_cmdPool; ai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; ai.commandBufferCount = 1; + VkCommandBuffer cb = VK_NULL_HANDLE; + if (vkAllocateCommandBuffers(m_device, &ai, &cb) != VK_SUCCESS) return VK_NULL_HANDLE; + + VkCommandBufferBeginInfo bi{}; bi.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + bi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + vkBeginCommandBuffer(cb, &bi); + + for (const auto& c : m_bufferCopies) { + auto* dst = static_cast(c.dst); + if (!dst) continue; + VkBufferCopy r{}; r.srcOffset = c.stagingOffset; r.dstOffset = c.dstOffset; r.size = c.size; + vkCmdCopyBuffer(cb, m_stagingBuf, dst->handle(), 1, &r); + } + + for (const auto& c : m_textureCopies) { + auto* dst = static_cast(c.dst); + if (!dst) continue; + auto aspect = getAspectMask(dst->desc.format); + + VkImageMemoryBarrier pre{}; pre.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + pre.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + pre.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; pre.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + pre.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; pre.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + pre.image = dst->handle(); + pre.subresourceRange = { aspect, c.mipLevel, 1, c.arrayLayer, 1 }; + vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, 0, nullptr, 0, nullptr, 1, &pre); + + VkBufferImageCopy r{}; r.bufferOffset = c.stagingOffset; + r.imageSubresource = { aspect, c.mipLevel, c.arrayLayer, 1 }; + r.imageExtent = { c.extent.width, c.extent.height, c.extent.depth }; + vkCmdCopyBufferToImage(cb, m_stagingBuf, dst->handle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &r); + + VkImageMemoryBarrier post = pre; + post.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; post.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + post.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; post.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + 0, 0, nullptr, 0, nullptr, 1, &post); + } + + vkEndCommandBuffer(cb); + return cb; + } + + void cleanupCmdPool() { + if (m_cmdPool != VK_NULL_HANDLE) vkResetCommandPool(m_device, m_cmdPool, 0); + } + + VkDevice m_device = VK_NULL_HANDLE; + VkPhysicalDevice m_physDevice = VK_NULL_HANDLE; + VkQueue m_queue = VK_NULL_HANDLE; + u32 m_queueFamilyIndex = 0; + VkCommandPool m_cmdPool = VK_NULL_HANDLE; + VkBuffer m_stagingBuf = VK_NULL_HANDLE; + VkDeviceMemory m_stagingMem = VK_NULL_HANDLE; + void* m_stagingMapped = nullptr; + u64 m_stagingOffset = 0; + u64 m_stagingSize = 0; + std::vector m_bufferCopies; + std::vector m_textureCopies; + VkFenceImpl* m_asyncFence = nullptr; + u64 m_asyncValue = 0; +}; + +} // namespace draco::rhi::vk diff --git a/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VulkanRhi.test.cpp b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VulkanRhi.test.cpp new file mode 100644 index 00000000..91b787c1 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RHI/Vulkan/VulkanRhi.test.cpp @@ -0,0 +1,40 @@ +#include + +import core; +import rhi; +import rhi.vk; + +using namespace draco; +using namespace draco::rhi; + +// These tests require a working Vulkan loader + at least one ICD (a physical +// device or a software rasterizer such as lavapipe). Where no device is +// available the backend still initializes; adapter-dependent checks are guarded. + +TEST_CASE("rhi.vk: backend initializes and enumerates adapters") +{ + Backend* backend = nullptr; + vk::VkBackendDesc desc{}; + REQUIRE(vk::createBackend(desc, backend).isOk()); + REQUIRE(backend != nullptr); + CHECK(backend->isInitialized); + + auto adapters = backend->enumerateAdapters(); + INFO("adapter count: ", adapters.size()); + + for (Adapter* a : adapters) { + const AdapterInfo info = a->info(); + CHECK(!info.name.empty()); + } + + if (!adapters.empty()) { + // Adapters are ordered best-first; a device should be creatable from [0]. + Device* device = nullptr; + CHECK(adapters[0]->createDevice(DeviceDesc{}, device).isOk()); + CHECK(device != nullptr); + // The device must be torn down before the backend destroys the instance. + if (device) device->destroy(); + } + + backend->destroy(); +} diff --git a/Engine/cpp/Runtime/Rendering/RHI/macros.h b/Engine/cpp/Runtime/Rendering/RHI/macros.h deleted file mode 100644 index d055a9cb..00000000 --- a/Engine/cpp/Runtime/Rendering/RHI/macros.h +++ /dev/null @@ -1,29 +0,0 @@ -// rendering/rhi/macros.h - -#pragma once -#include -#include - -#ifndef DRACO_RHI_VALIDATION -#define DRACO_RHI_VALIDATION 1 -#endif - -#if DRACO_RHI_VALIDATION - #define RHI_ASSERT(cond, msg, ...) \ - do { \ - if (!(cond)) { \ - std::println("[RHI ERROR] " msg, ##__VA_ARGS__); \ - std::abort(); \ - } \ - } while(0) - - #define RHI_WARN(cond, msg, ...) \ - do { \ - if (!(cond)) { \ - std::println("[RHI WARNING] " msg, ##__VA_ARGS__); \ - } \ - } while(0) -#else - #define RHI_ASSERT(cond, msg, ...) do { (void)(cond); } while(0) - #define RHI_WARN(cond, msg, ...) do { (void)(cond); } while(0) -#endif diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/BarrierSolver.cppm b/Engine/cpp/Runtime/Rendering/RenderGraph/BarrierSolver.cppm new file mode 100644 index 00000000..e306a108 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/BarrierSolver.cppm @@ -0,0 +1,376 @@ +// Draconic::RenderGraph - :barrier_solver partition +// +// Computes and emits resource barriers between passes. State is tracked at two +// levels: per-resource-handle (buffers; convenience for textures) and per- +// Texture with per-subresource granularity (source of truth for textures, keyed +// by the GPU texture pointer so the same texture unifies across handles). Ported +// from Sedulous.RenderGraph (BarrierSolver.bf). + +module; + +#include + +#include +#include +#include + +export module rendergraph:barrier_solver; + +import core; +import rhi; +import :types; +import :resource; +import :pass; +import :state_tracker; + +using namespace draco; + +export namespace draco::rendergraph +{ + namespace rhi = draco::rhi; + + class BarrierSolver + { + public: + BarrierSolver() = default; + ~BarrierSolver() { clearTrackers(); } + + BarrierSolver(const BarrierSolver&) = delete; + BarrierSolver& operator=(const BarrierSolver&) = delete; + + // Initialize states from the resource list (call once per frame). Persistent + // resources use their last-known state; transient resources start Undefined. + void reset(std::span resources) + { + m_resourceStates.clear(); + clearTrackers(); + + for (i32 i = 0; i < static_cast(resources.size()); ++i) + { + RenderGraphResource* res = resources[static_cast(i)]; + if (res == nullptr) { continue; } + + rhi::ResourceState initialState = rhi::ResourceState::Undefined; + + if (res->lifetime == RGResourceLifetime::Persistent && res->persistentData.get() != nullptr) + { + initialState = res->persistentData->firstFrame + ? (res->texture != nullptr ? res->texture->initialState : rhi::ResourceState::Undefined) + : res->persistentData->lastKnownState; + } + else if (res->lifetime == RGResourceLifetime::Imported) + { + initialState = res->lastKnownState; + } + else + { + // Transient: always start Undefined; the backend uses the texture's + // actual tracked state for the "before" side, so first access always + // gets a correct transition. + initialState = rhi::ResourceState::Undefined; + } + + m_resourceStates.insert_or_assign(i, initialState); + + if (res->resourceType == RGResourceType::Texture && res->texture != nullptr) + { + if (SubresourceStateTracker** existing = mapFind(m_textureStates, res->texture)) + { + // Same GPU texture via another handle - unify. + SubresourceStateTracker* tracker = *existing; + if (initialState == rhi::ResourceState::Undefined && tracker->isUniform() + && tracker->uniformState() != rhi::ResourceState::Undefined) + { + m_resourceStates.insert_or_assign(i, tracker->uniformState()); + } + else if (initialState != rhi::ResourceState::Undefined) + { + tracker->setAll(initialState); + } + } + else + { + const u32 mipCount = res->texture->desc.mipLevelCount; + const u32 layerCount = res->texture->desc.arrayLayerCount; + SubresourceStateTracker* tracker = + new SubresourceStateTracker(mipCount, layerCount, initialState); + + if (res->lifetime == RGResourceLifetime::Persistent && res->persistentData.get() != nullptr + && !res->persistentData->firstFrame + && !res->persistentData->subresourceStates.empty()) + { + tracker->initFromStates(res->persistentData->subresourceStates, initialState); + } + + m_textureStates.insert_or_assign(res->texture, tracker); + } + } + } + } + + // Emit barriers needed before executing `pass`. + void emitBarriers(const RenderGraphPass& pass, std::span resources, + rhi::CommandEncoder& encoder) + { + m_textureBarriers.clear(); + m_bufferBarriers.clear(); + + for (const RGResourceAccess& access : pass.accesses) + { + if (!access.handle.isValid()) { continue; } + const i32 resIdx = static_cast(access.handle.index); + if (static_cast(resIdx) >= resources.size()) { continue; } + + RenderGraphResource* res = resources[static_cast(resIdx)]; + if (res == nullptr) { continue; } + + const rhi::ResourceState requiredState = access.toResourceState(); + const bool accessIsReadWrite = isRead(access.type) && isWrite(access.type); + + if (res->resourceType == RGResourceType::Texture && res->texture != nullptr) + { + SubresourceStateTracker** found = mapFind(m_textureStates, res->texture); + if (found == nullptr) { continue; } + SubresourceStateTracker* tracker = *found; + + emitTextureBarriers(*tracker, res->texture, access.subresource, requiredState, accessIsReadWrite); + tracker->setState(access.subresource, requiredState); + m_resourceStates.insert_or_assign(resIdx, requiredState); + } + else if (res->resourceType == RGResourceType::Buffer && res->buffer != nullptr) + { + rhi::ResourceState currentState = rhi::ResourceState::Undefined; + if (rhi::ResourceState* p = mapFind(m_resourceStates, resIdx)) { currentState = *p; } + if (currentState == requiredState) { continue; } + + rhi::BufferBarrier bb{}; + bb.buffer = res->buffer; + bb.oldState = currentState; + bb.newState = requiredState; + m_bufferBarriers.push_back(bb); + + m_resourceStates.insert_or_assign(resIdx, requiredState); + } + } + + flushBarriers(encoder); + } + + // After a pass executes: transition resources marked ReadableAfterWrite + // (written by the pass) to ShaderRead so external bind groups can sample them. + void emitReadableAfterWriteBarriers(const RenderGraphPass& pass, + std::span resources, + rhi::CommandEncoder& encoder) + { + m_textureBarriers.clear(); + m_bufferBarriers.clear(); + + for (const RGResourceAccess& access : pass.accesses) + { + if (!access.isWrite() || !access.handle.isValid()) { continue; } + const i32 resIdx = static_cast(access.handle.index); + if (static_cast(resIdx) >= resources.size()) { continue; } + + RenderGraphResource* res = resources[static_cast(resIdx)]; + if (res == nullptr || !res->readableAfterWrite) { continue; } + if (res->resourceType != RGResourceType::Texture || res->texture == nullptr) { continue; } + + SubresourceStateTracker** found = mapFind(m_textureStates, res->texture); + if (found == nullptr) { continue; } + SubresourceStateTracker* tracker = *found; + + emitTextureBarriers(*tracker, res->texture, access.subresource, rhi::ResourceState::ShaderRead, false); + tracker->setState(access.subresource, rhi::ResourceState::ShaderRead); + m_resourceStates.insert_or_assign(resIdx, rhi::ResourceState::ShaderRead); + } + + flushBarriers(encoder); + } + + // Transition imported resources to their requested final state. + void emitFinalTransitions(std::span resources, rhi::CommandEncoder& encoder) + { + m_textureBarriers.clear(); + m_bufferBarriers.clear(); + + for (i32 i = 0; i < static_cast(resources.size()); ++i) + { + RenderGraphResource* res = resources[static_cast(i)]; + if (res == nullptr || !res->finalState.has_value()) { continue; } + + const rhi::ResourceState finalState = res->finalState.value(); + if (res->texture != nullptr) + { + SubresourceStateTracker** found = mapFind(m_textureStates, res->texture); + if (found == nullptr) { continue; } + SubresourceStateTracker* tracker = *found; + + emitTextureBarriers(*tracker, res->texture, RGSubresourceRange::all(), finalState, false); + tracker->setAll(finalState); + m_resourceStates.insert_or_assign(i, finalState); + } + } + + flushBarriers(encoder); + } + + // Write tracked states back into persistent/imported resources for next frame. + void updatePersistentStates(std::span resources) + { + for (i32 i = 0; i < static_cast(resources.size()); ++i) + { + RenderGraphResource* res = resources[static_cast(i)]; + if (res == nullptr) { continue; } + + if (res->resourceType == RGResourceType::Texture && res->texture != nullptr) + { + SubresourceStateTracker** found = mapFind(m_textureStates, res->texture); + if (found == nullptr) { continue; } + SubresourceStateTracker* tracker = *found; + + if (tracker->isUniform()) + { + res->lastKnownState = tracker->uniformState(); + if (res->persistentData.get() != nullptr) + { + res->persistentData->lastKnownState = tracker->uniformState(); + res->persistentData->firstFrame = false; + res->persistentData->subresourceStates.clear(); + } + } + else + { + res->lastKnownState = tracker->getState(0, 0); + if (res->persistentData.get() != nullptr) + { + res->persistentData->lastKnownState = tracker->getState(0, 0); + res->persistentData->firstFrame = false; + res->persistentData->subresourceStates = tracker->copyStates(); + } + } + } + else if (rhi::ResourceState* p = mapFind(m_resourceStates, i)) + { + res->lastKnownState = *p; + if (res->persistentData.get() != nullptr) + { + res->persistentData->lastKnownState = *p; + res->persistentData->firstFrame = false; + } + } + } + } + + [[nodiscard]] rhi::ResourceState getState(i32 resourceIndex) + { + if (rhi::ResourceState* p = mapFind(m_resourceStates, resourceIndex)) { return *p; } + return rhi::ResourceState::Undefined; + } + + [[nodiscard]] rhi::ResourceState getTextureState(rhi::Texture* texture) + { + if (texture == nullptr) { return rhi::ResourceState::Undefined; } + if (SubresourceStateTracker** found = mapFind(m_textureStates, texture)) + { + SubresourceStateTracker* tracker = *found; + return tracker->isUniform() ? tracker->uniformState() : tracker->getState(0, 0); + } + return rhi::ResourceState::Undefined; + } + + [[nodiscard]] SubresourceStateTracker* getTextureTracker(rhi::Texture* texture) + { + if (texture == nullptr) { return nullptr; } + SubresourceStateTracker** found = mapFind(m_textureStates, texture); + return found != nullptr ? *found : nullptr; + } + + private: + void emitTextureBarriers(SubresourceStateTracker& tracker, rhi::Texture* texture, + RGSubresourceRange subresource, rhi::ResourceState requiredState, + bool accessIsReadWrite) + { + const u32 totalMips = tracker.mipCount(); + const u32 totalLayers = tracker.layerCount(); + + if (tracker.isUniform()) + { + const rhi::ResourceState currentState = tracker.uniformState(); + if (currentState == requiredState && !accessIsReadWrite) { return; } + + rhi::TextureBarrier barrier{}; + barrier.texture = texture; + barrier.oldState = currentState; + barrier.newState = requiredState; + if (!subresource.isAll()) + { + barrier.baseMipLevel = subresource.baseMipLevel; + barrier.mipLevelCount = subresource.mipLevelCount == 0 ? 0xFFFFFFFFu : subresource.mipLevelCount; + barrier.baseArrayLayer = subresource.baseArrayLayer; + barrier.arrayLayerCount = subresource.arrayLayerCount == 0 ? 0xFFFFFFFFu : subresource.arrayLayerCount; + } + m_textureBarriers.push_back(barrier); + } + else + { + const u32 baseMip = subresource.baseMipLevel; + const u32 mipEnd = subresource.mipLevelCount == 0 ? totalMips + : std::min(baseMip + subresource.mipLevelCount, totalMips); + const u32 baseLayer = subresource.baseArrayLayer; + const u32 layerEnd = subresource.arrayLayerCount == 0 ? totalLayers + : std::min(baseLayer + subresource.arrayLayerCount, totalLayers); + + for (u32 layer = baseLayer; layer < layerEnd; ++layer) + { + for (u32 mip = baseMip; mip < mipEnd; ++mip) + { + const rhi::ResourceState currentState = tracker.getState(mip, layer); + if (currentState == requiredState && !accessIsReadWrite) { continue; } + + rhi::TextureBarrier barrier{}; + barrier.texture = texture; + barrier.oldState = currentState; + barrier.newState = requiredState; + barrier.baseMipLevel = mip; + barrier.mipLevelCount = 1; + barrier.baseArrayLayer = layer; + barrier.arrayLayerCount = 1; + m_textureBarriers.push_back(barrier); + } + } + } + } + + void flushBarriers(rhi::CommandEncoder& encoder) + { + if (m_textureBarriers.empty() && m_bufferBarriers.empty()) { return; } + + rhi::BarrierGroup group{}; + if (!m_textureBarriers.empty()) + { + group.textureBarriers = + std::span(m_textureBarriers.data(), m_textureBarriers.size()); + } + if (!m_bufferBarriers.empty()) + { + group.bufferBarriers = + std::span(m_bufferBarriers.data(), m_bufferBarriers.size()); + } + encoder.barrier(group); + } + + void clearTrackers() + { + for (auto& entry : m_textureStates) + { + delete (entry.second); + } + m_textureStates.clear(); + } + + std::unordered_map m_resourceStates; + std::unordered_map m_textureStates; + std::vector m_textureBarriers; + std::vector m_bufferBarriers; + }; +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/BarrierTests.test.cpp b/Engine/cpp/Runtime/Rendering/RenderGraph/BarrierTests.test.cpp new file mode 100644 index 00000000..653ab7fc --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/BarrierTests.test.cpp @@ -0,0 +1,527 @@ +// BarrierSolver directly via a recording mock encoder and plain RHI textures +// (rhi::Texture is concrete, so no texture mock is needed; its pointer identity +// is what the solver keys on). + +#include +#include + + +import core; +import rhi; +import rendergraph; + +using namespace draco; +using namespace draco::rendergraph; +namespace rhi = draco::rhi; +using RS = rhi::ResourceState; +using AT = RGAccessType; + +namespace +{ + // Records emitted barriers; stubs the rest of the CommandEncoder interface. + class MockEncoder final : public rhi::CommandEncoder + { + public: + std::vector textureBarriers; + std::vector bufferBarriers; + + void barrier(const rhi::BarrierGroup& group) override + { + for (usize i = 0; i < group.textureBarriers.size(); ++i) { textureBarriers.push_back(group.textureBarriers[i]); } + for (usize i = 0; i < group.bufferBarriers.size(); ++i) { bufferBarriers.push_back(group.bufferBarriers[i]); } + } + + rhi::RenderPassEncoder* beginRenderPass(const rhi::RenderPassDesc&) override { return nullptr; } + rhi::ComputePassEncoder* beginComputePass(std::u8string_view) override { return nullptr; } + rhi::RenderBundleEncoder* createRenderBundleEncoder(const rhi::RenderBundleDesc&) override { return nullptr; } + void copyBufferToBuffer(rhi::Buffer*, u64, rhi::Buffer*, u64, u64) override {} + void copyBufferToTexture(rhi::Buffer*, rhi::Texture*, const rhi::BufferTextureCopyRegion&) override {} + void copyTextureToBuffer(rhi::Texture*, rhi::Buffer*, const rhi::BufferTextureCopyRegion&) override {} + void copyTextureToTexture(rhi::Texture*, rhi::Texture*, const rhi::TextureCopyRegion&) override {} + void blit(rhi::Texture*, rhi::Texture*) override {} + void generateMipmaps(rhi::Texture*) override {} + void resolveTexture(rhi::Texture*, rhi::Texture*) override {} + void resetQuerySet(rhi::QuerySet*, u32, u32) override {} + void writeTimestamp(rhi::QuerySet*, u32) override {} + void resolveQuerySet(rhi::QuerySet*, u32, u32, rhi::Buffer*, u64) override {} + void beginDebugLabel(std::u8string_view, f32, f32, f32, f32) override {} + void endDebugLabel() override {} + void insertDebugLabel(std::u8string_view, f32, f32, f32, f32) override {} + rhi::CommandBuffer* finish() override { return nullptr; } + }; + + rhi::Texture makeTexture(RS initialState, u32 mips = 1, u32 layers = 1) + { + rhi::Texture tex; + tex.desc.mipLevelCount = mips; + tex.desc.arrayLayerCount = layers; + tex.initialState = initialState; + return tex; + } + + RGResourceAccess access(u32 index, AT type, RGSubresourceRange sub = {}) + { + return RGResourceAccess{ RGHandle{ index, 0 }, type, sub }; + } +} + +TEST_CASE("barriers: same texture via two handles emits one transition") +{ + BarrierSolver solver; + MockEncoder encoder; + rhi::Texture shadow = makeTexture(RS::Undefined); + + RenderGraphResource res0(u8"ShadowWrite", RGResourceType::Texture, RGResourceLifetime::Imported); + res0.texture = &shadow; + RenderGraphResource res1(u8"ShadowRead", RGResourceType::Texture, RGResourceLifetime::Imported); + res1.texture = &shadow; // same GPU texture + RenderGraphResource* resources[] = { &res0, &res1 }; + const std::span span(resources, 2); + + solver.reset(span); + + RenderGraphPass writePass(u8"ShadowPass", RGPassType::Render); + writePass.accesses.push_back(access(0, AT::WriteDepthTarget)); + solver.emitBarriers(writePass, span, encoder); + encoder.textureBarriers.clear(); + + RenderGraphPass readPass(u8"ForwardPass", RGPassType::Render); + readPass.accesses.push_back(access(1, AT::ReadTexture)); + solver.emitBarriers(readPass, span, encoder); + + REQUIRE(encoder.textureBarriers.size() == 1u); + CHECK(encoder.textureBarriers[0].oldState == RS::DepthStencilWrite); + CHECK(encoder.textureBarriers[0].newState == RS::ShaderRead); + CHECK(encoder.textureBarriers[0].texture == &shadow); +} + +TEST_CASE("barriers: single handle read-after-write") +{ + BarrierSolver solver; + MockEncoder encoder; + rhi::Texture tex = makeTexture(RS::Undefined); + + RenderGraphResource res(u8"Color", RGResourceType::Texture, RGResourceLifetime::Transient); + res.texture = &tex; + RenderGraphResource* resources[] = { &res }; + const std::span span(resources, 1); + solver.reset(span); + + RenderGraphPass writePass(u8"Writer", RGPassType::Render); + writePass.accesses.push_back(access(0, AT::WriteColorTarget)); + solver.emitBarriers(writePass, span, encoder); + encoder.textureBarriers.clear(); + + RenderGraphPass readPass(u8"Reader", RGPassType::Render); + readPass.accesses.push_back(access(0, AT::ReadTexture)); + solver.emitBarriers(readPass, span, encoder); + + REQUIRE(encoder.textureBarriers.size() == 1u); + CHECK(encoder.textureBarriers[0].oldState == RS::RenderTarget); + CHECK(encoder.textureBarriers[0].newState == RS::ShaderRead); +} + +TEST_CASE("barriers: compute write then render read") +{ + BarrierSolver solver; + MockEncoder encoder; + rhi::Texture tex = makeTexture(RS::Undefined); + + RenderGraphResource res(u8"Volume", RGResourceType::Texture, RGResourceLifetime::Imported); + res.texture = &tex; + RenderGraphResource* resources[] = { &res }; + const std::span span(resources, 1); + solver.reset(span); + + RenderGraphPass computePass(u8"Compute", RGPassType::Compute); + computePass.accesses.push_back(access(0, AT::WriteStorage)); + solver.emitBarriers(computePass, span, encoder); + encoder.textureBarriers.clear(); + + RenderGraphPass renderPass(u8"Render", RGPassType::Render); + renderPass.accesses.push_back(access(0, AT::ReadTexture)); + solver.emitBarriers(renderPass, span, encoder); + + REQUIRE(encoder.textureBarriers.size() == 1u); + CHECK(encoder.textureBarriers[0].oldState == RS::ShaderWrite); + CHECK(encoder.textureBarriers[0].newState == RS::ShaderRead); +} + +TEST_CASE("barriers: final transition uses texture-keyed state") +{ + BarrierSolver solver; + MockEncoder encoder; + rhi::Texture tex = makeTexture(RS::Undefined); + + RenderGraphResource res(u8"Backbuffer", RGResourceType::Texture, RGResourceLifetime::Imported); + res.texture = &tex; + res.finalState = RS::Present; + RenderGraphResource* resources[] = { &res }; + const std::span span(resources, 1); + solver.reset(span); + + RenderGraphPass pass(u8"FinalBlit", RGPassType::Render); + pass.accesses.push_back(access(0, AT::WriteColorTarget)); + solver.emitBarriers(pass, span, encoder); + encoder.textureBarriers.clear(); + + solver.emitFinalTransitions(span, encoder); + + REQUIRE(encoder.textureBarriers.size() == 1u); + CHECK(encoder.textureBarriers[0].oldState == RS::RenderTarget); + CHECK(encoder.textureBarriers[0].newState == RS::Present); +} + +TEST_CASE("barriers: none when already in the correct state") +{ + BarrierSolver solver; + MockEncoder encoder; + rhi::Texture tex = makeTexture(RS::Undefined); + + RenderGraphResource res(u8"Tex", RGResourceType::Texture, RGResourceLifetime::Imported); + res.texture = &tex; + res.lastKnownState = RS::ShaderRead; + RenderGraphResource* resources[] = { &res }; + const std::span span(resources, 1); + solver.reset(span); + + RenderGraphPass pass(u8"Reader", RGPassType::Render); + pass.accesses.push_back(access(0, AT::ReadTexture)); + solver.emitBarriers(pass, span, encoder); + + CHECK(encoder.textureBarriers.size() == 0u); +} + +TEST_CASE("barriers: per-layer writes emit individual barriers") +{ + BarrierSolver solver; + MockEncoder encoder; + rhi::Texture tex = makeTexture(RS::Undefined, 1, 4); // 4 cascades + + RenderGraphResource res(u8"ShadowArray", RGResourceType::Texture, RGResourceLifetime::Imported); + res.texture = &tex; + res.lastKnownState = RS::ShaderRead; + RenderGraphResource* resources[] = { &res }; + const std::span span(resources, 1); + solver.reset(span); + + RenderGraphPass pass0(u8"Cascade0", RGPassType::Render); + pass0.accesses.push_back(access(0, AT::WriteDepthTarget, RGSubresourceRange{ 0, 1, 0, 1 })); + solver.emitBarriers(pass0, span, encoder); + REQUIRE(encoder.textureBarriers.size() == 1u); + CHECK(encoder.textureBarriers[0].oldState == RS::ShaderRead); + CHECK(encoder.textureBarriers[0].newState == RS::DepthStencilWrite); + CHECK(encoder.textureBarriers[0].baseArrayLayer == 0u); + CHECK(encoder.textureBarriers[0].arrayLayerCount == 1u); + encoder.textureBarriers.clear(); + + RenderGraphPass pass2(u8"Cascade2", RGPassType::Render); + pass2.accesses.push_back(access(0, AT::WriteDepthTarget, RGSubresourceRange{ 0, 1, 2, 1 })); + solver.emitBarriers(pass2, span, encoder); + REQUIRE(encoder.textureBarriers.size() == 1u); + CHECK(encoder.textureBarriers[0].baseArrayLayer == 2u); + CHECK(encoder.textureBarriers[0].arrayLayerCount == 1u); +} + +TEST_CASE("barriers: non-uniform whole-resource read emits per-subresource") +{ + BarrierSolver solver; + MockEncoder encoder; + rhi::Texture tex = makeTexture(RS::Undefined, 1, 2); + + RenderGraphResource res(u8"Tex", RGResourceType::Texture, RGResourceLifetime::Imported); + res.texture = &tex; + res.lastKnownState = RS::ShaderRead; + RenderGraphResource* resources[] = { &res }; + const std::span span(resources, 1); + solver.reset(span); + + RenderGraphPass writePass(u8"Writer", RGPassType::Render); + writePass.accesses.push_back(access(0, AT::WriteColorTarget, RGSubresourceRange{ 0, 1, 0, 1 })); + solver.emitBarriers(writePass, span, encoder); + encoder.textureBarriers.clear(); + + RenderGraphPass readPass(u8"Reader", RGPassType::Render); + readPass.accesses.push_back(access(0, AT::ReadTexture)); + solver.emitBarriers(readPass, span, encoder); + + REQUIRE(encoder.textureBarriers.size() == 1u); // only layer 0 transitions + CHECK(encoder.textureBarriers[0].oldState == RS::RenderTarget); + CHECK(encoder.textureBarriers[0].newState == RS::ShaderRead); + CHECK(encoder.textureBarriers[0].baseArrayLayer == 0u); +} + +TEST_CASE("barriers: all layers written collapses to uniform") +{ + BarrierSolver solver; + MockEncoder encoder; + rhi::Texture tex = makeTexture(RS::Undefined, 1, 2); + + RenderGraphResource res(u8"Tex", RGResourceType::Texture, RGResourceLifetime::Imported); + res.texture = &tex; + res.lastKnownState = RS::ShaderRead; + RenderGraphResource* resources[] = { &res }; + const std::span span(resources, 1); + solver.reset(span); + + RenderGraphPass p0(u8"W0", RGPassType::Render); + p0.accesses.push_back(access(0, AT::WriteColorTarget, RGSubresourceRange{ 0, 1, 0, 1 })); + solver.emitBarriers(p0, span, encoder); + encoder.textureBarriers.clear(); + + RenderGraphPass p1(u8"W1", RGPassType::Render); + p1.accesses.push_back(access(0, AT::WriteColorTarget, RGSubresourceRange{ 0, 1, 1, 1 })); + solver.emitBarriers(p1, span, encoder); + encoder.textureBarriers.clear(); + + RenderGraphPass readPass(u8"Read", RGPassType::Render); + readPass.accesses.push_back(access(0, AT::ReadTexture)); + solver.emitBarriers(readPass, span, encoder); + + REQUIRE(encoder.textureBarriers.size() == 1u); // whole-resource fast path + CHECK(encoder.textureBarriers[0].oldState == RS::RenderTarget); + CHECK(encoder.textureBarriers[0].newState == RS::ShaderRead); + CHECK(encoder.textureBarriers[0].mipLevelCount == 0xFFFFFFFFu); + CHECK(encoder.textureBarriers[0].arrayLayerCount == 0xFFFFFFFFu); +} + +TEST_CASE("barriers: SampleDepth on a written cascade array reads as DepthStencilRead") +{ + // The real CSM scenario: each cascade writes one layer (DepthStencilWrite), then the + // forward pass samples the WHOLE array. SampleDepthStencil must transition it to + // DepthStencilRead (DEPTH_STENCIL_READ_ONLY_OPTIMAL) - the layout a depth sampler needs - + // not ShaderRead. After all layers are written uniform, the read is a whole-resource barrier. + BarrierSolver solver; + MockEncoder encoder; + rhi::Texture tex = makeTexture(RS::Undefined, 1, 4); // 4 cascades + + RenderGraphResource res(u8"ShadowArray", RGResourceType::Texture, RGResourceLifetime::Imported); + res.texture = &tex; + res.lastKnownState = RS::DepthStencilRead; + RenderGraphResource* resources[] = { &res }; + const std::span span(resources, 1); + solver.reset(span); + + for (u32 layer = 0; layer < 4; ++layer) + { + RenderGraphPass cascade(u8"Cascade", RGPassType::Render); + cascade.accesses.push_back(access(0, AT::WriteDepthTarget, RGSubresourceRange{ 0, 1, layer, 1 })); + solver.emitBarriers(cascade, span, encoder); + } + encoder.textureBarriers.clear(); + + RenderGraphPass forward(u8"Forward", RGPassType::Render); + forward.accesses.push_back(access(0, AT::SampleDepthStencil)); + solver.emitBarriers(forward, span, encoder); + + REQUIRE(encoder.textureBarriers.size() == 1u); // whole-resource (all layers uniform) + CHECK(encoder.textureBarriers[0].oldState == RS::DepthStencilWrite); + CHECK(encoder.textureBarriers[0].newState == RS::DepthStencilRead); + CHECK(encoder.textureBarriers[0].arrayLayerCount == 0xFFFFFFFFu); +} + +TEST_CASE("barriers: per-mip different states") +{ + BarrierSolver solver; + MockEncoder encoder; + rhi::Texture tex = makeTexture(RS::Undefined, 4, 1); + + RenderGraphResource res(u8"Tex", RGResourceType::Texture, RGResourceLifetime::Imported); + res.texture = &tex; + res.lastKnownState = RS::Undefined; + RenderGraphResource* resources[] = { &res }; + const std::span span(resources, 1); + solver.reset(span); + + RenderGraphPass p0(u8"WriteMip0", RGPassType::Render); + p0.accesses.push_back(access(0, AT::WriteColorTarget, RGSubresourceRange{ 0, 1, 0, 1 })); + solver.emitBarriers(p0, span, encoder); + encoder.textureBarriers.clear(); + + RenderGraphPass p1(u8"WriteMip1", RGPassType::Render); + p1.accesses.push_back(access(0, AT::WriteColorTarget, RGSubresourceRange{ 1, 1, 0, 1 })); + solver.emitBarriers(p1, span, encoder); + + REQUIRE(encoder.textureBarriers.size() == 1u); + CHECK(encoder.textureBarriers[0].oldState == RS::Undefined); + CHECK(encoder.textureBarriers[0].newState == RS::RenderTarget); + CHECK(encoder.textureBarriers[0].baseMipLevel == 1u); + CHECK(encoder.textureBarriers[0].mipLevelCount == 1u); +} + +TEST_CASE("barriers: readable-after-write is subresource aware") +{ + BarrierSolver solver; + MockEncoder encoder; + rhi::Texture tex = makeTexture(RS::Undefined, 1, 4); + + RenderGraphResource res(u8"Tex", RGResourceType::Texture, RGResourceLifetime::Imported); + res.texture = &tex; + res.lastKnownState = RS::ShaderRead; + res.readableAfterWrite = true; + RenderGraphResource* resources[] = { &res }; + const std::span span(resources, 1); + solver.reset(span); + + RenderGraphPass writePass(u8"Writer", RGPassType::Render); + writePass.accesses.push_back(access(0, AT::WriteDepthTarget, RGSubresourceRange{ 0, 1, 1, 1 })); + solver.emitBarriers(writePass, span, encoder); + encoder.textureBarriers.clear(); + + solver.emitReadableAfterWriteBarriers(writePass, span, encoder); + + REQUIRE(encoder.textureBarriers.size() == 1u); + CHECK(encoder.textureBarriers[0].oldState == RS::DepthStencilWrite); + CHECK(encoder.textureBarriers[0].newState == RS::ShaderRead); + CHECK(encoder.textureBarriers[0].baseArrayLayer == 1u); + CHECK(encoder.textureBarriers[0].arrayLayerCount == 1u); +} + +TEST_CASE("barriers: final transition non-uniform emits per-subresource") +{ + BarrierSolver solver; + MockEncoder encoder; + rhi::Texture tex = makeTexture(RS::Undefined, 1, 2); + + RenderGraphResource res(u8"Swapchain", RGResourceType::Texture, RGResourceLifetime::Imported); + res.texture = &tex; + res.lastKnownState = RS::Undefined; + res.finalState = RS::Present; + RenderGraphResource* resources[] = { &res }; + const std::span span(resources, 1); + solver.reset(span); + + RenderGraphPass writePass(u8"Blit", RGPassType::Render); + writePass.accesses.push_back(access(0, AT::WriteColorTarget, RGSubresourceRange{ 0, 1, 0, 1 })); + solver.emitBarriers(writePass, span, encoder); + encoder.textureBarriers.clear(); + + solver.emitFinalTransitions(span, encoder); + + REQUIRE(encoder.textureBarriers.size() == 2u); + bool foundLayer0 = false, foundLayer1 = false; + for (usize i = 0; i < encoder.textureBarriers.size(); ++i) + { + const rhi::TextureBarrier& b = encoder.textureBarriers[i]; + if (b.baseArrayLayer == 0 && b.oldState == RS::RenderTarget && b.newState == RS::Present) { foundLayer0 = true; } + if (b.baseArrayLayer == 1 && b.oldState == RS::Undefined && b.newState == RS::Present) { foundLayer1 = true; } + } + CHECK(foundLayer0); + CHECK(foundLayer1); +} + +TEST_CASE("barriers: non-overlapping subresource access emits no false barrier") +{ + BarrierSolver solver; + MockEncoder encoder; + rhi::Texture tex = makeTexture(RS::Undefined, 1, 4); + + RenderGraphResource res(u8"Array", RGResourceType::Texture, RGResourceLifetime::Imported); + res.texture = &tex; + res.lastKnownState = RS::ShaderRead; + RenderGraphResource* resources[] = { &res }; + const std::span span(resources, 1); + solver.reset(span); + + RenderGraphPass writePass(u8"WriteLayer0", RGPassType::Render); + writePass.accesses.push_back(access(0, AT::WriteColorTarget, RGSubresourceRange{ 0, 1, 0, 1 })); + solver.emitBarriers(writePass, span, encoder); + encoder.textureBarriers.clear(); + + RenderGraphPass readPass(u8"ReadLayer2", RGPassType::Render); + readPass.accesses.push_back(access(0, AT::ReadTexture, RGSubresourceRange{ 0, 1, 2, 1 })); + solver.emitBarriers(readPass, span, encoder); + + CHECK(encoder.textureBarriers.size() == 0u); +} + +TEST_CASE("barriers: persistent resource preserves per-subresource state") +{ + BarrierSolver solver; + MockEncoder encoder; + rhi::Texture tex = makeTexture(RS::Undefined, 1, 2); + + RenderGraphResource res(u8"Persistent", RGResourceType::Texture, RGResourceLifetime::Persistent); + res.texture = &tex; + res.persistentData = std::make_unique(&tex, static_cast(nullptr)); + res.persistentData->firstFrame = false; + res.persistentData->lastKnownState = RS::ShaderRead; + RenderGraphResource* resources[] = { &res }; + const std::span span(resources, 1); + solver.reset(span); + + RenderGraphPass writePass(u8"Writer", RGPassType::Render); + writePass.accesses.push_back(access(0, AT::WriteColorTarget, RGSubresourceRange{ 0, 1, 0, 1 })); + solver.emitBarriers(writePass, span, encoder); + + solver.updatePersistentStates(span); + + CHECK(res.persistentData->subresourceStates.size() == 2u); +} + +TEST_CASE("barriers: transient first access emits Undefined -> RenderTarget") +{ + BarrierSolver solver; + MockEncoder encoder; + rhi::Texture tex = makeTexture(RS::Undefined); + + RenderGraphResource res(u8"PipelineOutput", RGResourceType::Texture, RGResourceLifetime::Transient); + res.texture = &tex; + RenderGraphResource* resources[] = { &res }; + const std::span span(resources, 1); + solver.reset(span); + + RenderGraphPass writePass(u8"ForwardOpaque", RGPassType::Render); + writePass.accesses.push_back(access(0, AT::WriteColorTarget)); + solver.emitBarriers(writePass, span, encoder); + + REQUIRE(encoder.textureBarriers.size() == 1u); + CHECK(encoder.textureBarriers[0].oldState == RS::Undefined); + CHECK(encoder.textureBarriers[0].newState == RS::RenderTarget); +} + +TEST_CASE("barriers: transient reused across frames starts from Undefined") +{ + BarrierSolver solver; + MockEncoder encoder; + rhi::Texture tex = makeTexture(RS::Undefined); // same pooled texture both frames + + // Frame 1 + { + RenderGraphResource res(u8"PipelineOutput", RGResourceType::Texture, RGResourceLifetime::Transient); + res.texture = &tex; + RenderGraphResource* resources[] = { &res }; + const std::span span(resources, 1); + solver.reset(span); + + RenderGraphPass writePass(u8"ForwardOpaque", RGPassType::Render); + writePass.accesses.push_back(access(0, AT::WriteColorTarget)); + solver.emitBarriers(writePass, span, encoder); + encoder.textureBarriers.clear(); + + RenderGraphPass readPass(u8"PostProcess", RGPassType::Render); + readPass.accesses.push_back(access(0, AT::ReadTexture)); + solver.emitBarriers(readPass, span, encoder); + REQUIRE(encoder.textureBarriers.size() == 1u); + CHECK(encoder.textureBarriers[0].oldState == RS::RenderTarget); + CHECK(encoder.textureBarriers[0].newState == RS::ShaderRead); + encoder.textureBarriers.clear(); + } + + // Frame 2: same texture, fresh resource - must restart from Undefined. + { + RenderGraphResource res(u8"PipelineOutput", RGResourceType::Texture, RGResourceLifetime::Transient); + res.texture = &tex; + RenderGraphResource* resources[] = { &res }; + const std::span span(resources, 1); + solver.reset(span); + + RenderGraphPass writePass(u8"ForwardOpaque", RGPassType::Render); + writePass.accesses.push_back(access(0, AT::WriteColorTarget)); + solver.emitBarriers(writePass, span, encoder); + + REQUIRE(encoder.textureBarriers.size() == 1u); + CHECK(encoder.textureBarriers[0].oldState == RS::Undefined); + CHECK(encoder.textureBarriers[0].newState == RS::RenderTarget); + } +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/BundlePassTests.test.cpp b/Engine/cpp/Runtime/Rendering/RenderGraph/BundlePassTests.test.cpp new file mode 100644 index 00000000..f49d7ba6 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/BundlePassTests.test.cpp @@ -0,0 +1,87 @@ +// Render-bundle pass: a render pass whose body is supplied by render bundles (the rendergraph +// extension that lets parallel command recording run inside the frame graph). Driven on the +// Null RHI so Execute actually runs the pass. +#include + +import core; +import rhi; +import rhi.null; +import rendergraph; + +using namespace draco; +using namespace draco::rendergraph; +namespace rhi = draco::rhi; + +namespace { +struct GraphHarness { + rhi::null::NullDevice device; + rhi::Texture* tex = nullptr; + rhi::TextureView* view = nullptr; + rhi::CommandPool* pool = nullptr; + rhi::CommandEncoder* enc = nullptr; + + bool init() { + if (!device.createTexture(rhi::TextureDesc::renderTarget(rhi::TextureFormat::BGRA8Unorm, 64, 64), tex).isOk()) { return false; } + rhi::TextureViewDesc vd{}; vd.format = rhi::TextureFormat::BGRA8Unorm; + if (!device.createTextureView(tex, vd, view).isOk()) { return false; } + if (!device.createCommandPool(rhi::QueueType::Graphics, pool).isOk()) { return false; } + return pool->createEncoder(enc).isOk(); + } + ~GraphHarness() { + if (pool) { device.destroyCommandPool(pool); } + if (view) { device.destroyTextureView(view); } + if (tex) { device.destroyTexture(tex); } + } +}; +} + +TEST_CASE("rg.bundle: a bundle pass records bundles before the pass + executes them") +{ + GraphHarness h; + REQUIRE(h.init()); + + RenderGraph graph(&h.device); + graph.setOutputSize(64, 64); + graph.beginFrame(0); + const RGHandle color = graph.importTarget(u8"BB", h.tex, h.view, rhi::ResourceState::Present); + + bool ran = false; + usize bundleCount = 0; + graph.addRenderPass(u8"BundlePass", [&](PassBuilder& b) { + b.setColorTarget(0, color, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.neverCull(); + b.setBundleExecute([&](rhi::CommandEncoder& enc, std::vector& out) { + ran = true; // called with the encoder in the recording state, before the pass begins + rhi::RenderBundleDesc bd{}; + bd.colorFormats[0] = rhi::TextureFormat::BGRA8Unorm; bd.colorFormatCount = 1; + bd.width = 64; bd.height = 64; + if (rhi::RenderBundleEncoder* be = enc.createRenderBundleEncoder(bd)) { out.push_back(be->finish()); } + bundleCount = out.size(); + }); + }); + + CHECK(graph.execute(h.enc).isOk()); + CHECK(ran); // the bundle callback ran during execution + CHECK(bundleCount == 1u); // it produced a bundle for the graph to ExecuteBundles +} + +TEST_CASE("rg.bundle: a bundle pass that produces no bundles still runs (clears only)") +{ + GraphHarness h; + REQUIRE(h.init()); + + RenderGraph graph(&h.device); + graph.setOutputSize(64, 64); + graph.beginFrame(0); + const RGHandle color = graph.importTarget(u8"BB", h.tex, h.view, rhi::ResourceState::Present); + + bool ran = false; + graph.addRenderPass(u8"EmptyBundlePass", [&](PassBuilder& b) { + b.setColorTarget(0, color, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.neverCull(); + b.setBundleExecute([&](rhi::CommandEncoder&, std::vector&) { ran = true; }); + }); + + CHECK(graph.execute(h.enc).isOk()); + CHECK(ran); +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/Callbacks.cppm b/Engine/cpp/Runtime/Rendering/RenderGraph/Callbacks.cppm new file mode 100644 index 00000000..fbd7545e --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/Callbacks.cppm @@ -0,0 +1,32 @@ +// Draconic::RenderGraph - :callbacks partition +// +// Pass execution callbacks. Sedulous uses Beef delegates; here they are Core +// Function objects over the RHI encoder a pass records into. + +module; + +#include +#include + +export module rendergraph:callbacks; + +import core; +import rhi; + +using namespace draco; + +export namespace draco::rendergraph +{ + namespace rhi = draco::rhi; + + using RenderPassExecuteCallback = std::function; + using ComputePassExecuteCallback = std::function; + using CopyPassExecuteCallback = std::function; + + // A render pass whose body is supplied by render bundles (recorded off-thread). Called with + // the command encoder in the RECORDING state (before the render pass begins, so bundles can + // be created) and an out-list to fill with the bundles to replay; the graph then begins the + // pass with secondary-command-buffer contents and ExecuteBundles them in order. This is what + // lets parallel command recording run inside the frame graph. + using RenderBundlePassCallback = std::function&)>; +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/CullingTests.test.cpp b/Engine/cpp/Runtime/Rendering/RenderGraph/CullingTests.test.cpp new file mode 100644 index 00000000..b1b3673e --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/CullingTests.test.cpp @@ -0,0 +1,88 @@ +#include + + +import core; +import rhi; +import rendergraph; + +using namespace draco; +using namespace draco::rendergraph; +namespace rhi = draco::rhi; + +TEST_CASE("rg.cull: unused pass is culled") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle color = graph.createTransient(u8"Color", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + + graph.addRenderPass(u8"Unused", [&](PassBuilder& b) { + b.setColorTarget(0, color, rhi::LoadOp::Clear, rhi::StoreOp::Store); + }); + + REQUIRE(graph.compile().isOk()); + CHECK(graph.culledPassCount() == 1u); +} + +TEST_CASE("rg.cull: NeverCull prevents culling") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle color = graph.createTransient(u8"Color", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + + graph.addRenderPass(u8"Important", [&](PassBuilder& b) { + b.setColorTarget(0, color, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.neverCull(); + }); + + REQUIRE(graph.compile().isOk()); + CHECK(graph.culledPassCount() == 0u); +} + +TEST_CASE("rg.cull: HasSideEffects prevents culling") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + graph.addComputePass(u8"SideEffect", [](PassBuilder& b) { b.hasSideEffects(); }); + + REQUIRE(graph.compile().isOk()); + CHECK(graph.culledPassCount() == 0u); +} + +TEST_CASE("rg.cull: backward propagation keeps dependencies alive") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle depth = graph.createTransient(u8"Depth", RGTextureDesc(rhi::TextureFormat::Depth32Float)); + const RGHandle color = graph.createTransient(u8"Color", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + + graph.addRenderPass(u8"DepthPrepass", [&](PassBuilder& b) { + b.setDepthTarget(depth, rhi::LoadOp::Clear, rhi::StoreOp::Store); + }); + graph.addRenderPass(u8"ForwardOpaque", [&](PassBuilder& b) { + b.readTexture(depth); + b.setColorTarget(0, color, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.neverCull(); + }); + + REQUIRE(graph.compile().isOk()); + CHECK(graph.culledPassCount() == 0u); // DepthPrepass kept alive by ForwardOpaque +} + +TEST_CASE("rg.cull: imported with final state prevents culling") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle backbuffer = graph.importTarget(u8"BB", nullptr, nullptr, rhi::ResourceState::Present); + const RGHandle color = graph.createTransient(u8"Color", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + + graph.addRenderPass(u8"Render", [&](PassBuilder& b) { + b.setColorTarget(0, color, rhi::LoadOp::Clear, rhi::StoreOp::Store); + }); + graph.addRenderPass(u8"Blit", [&](PassBuilder& b) { + b.readTexture(color); + b.setColorTarget(0, backbuffer, rhi::LoadOp::Clear, rhi::StoreOp::Store); + }); + + REQUIRE(graph.compile().isOk()); + CHECK(graph.culledPassCount() == 0u); +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/DebugTests.test.cpp b/Engine/cpp/Runtime/Rendering/RenderGraph/DebugTests.test.cpp new file mode 100644 index 00000000..b83aeeee --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/DebugTests.test.cpp @@ -0,0 +1,85 @@ +#include + + +import core; +import rhi; +import rendergraph; + +using namespace draco; +using namespace draco::rendergraph; +namespace rhi = draco::rhi; + +namespace +{ + bool contains(std::u8string_view hay, std::u8string_view needle) + { + if (needle.size() > hay.size()) { return false; } + for (usize i = 0; i + needle.size() <= hay.size(); ++i) + { + if (hay.substr(i, needle.size()) == needle) { return true; } + } + return false; + } +} + +TEST_CASE("rg.debug: ExportDOT produces valid syntax") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle color = graph.createTransient(u8"SceneColor", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + const RGHandle depth = graph.createTransient(u8"Depth", RGTextureDesc(rhi::TextureFormat::Depth32Float)); + + graph.addRenderPass(u8"DepthPrepass", [&](PassBuilder& b) { + b.setDepthTarget(depth, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.neverCull(); + }); + graph.addRenderPass(u8"ForwardOpaque", [&](PassBuilder& b) { + b.readTexture(depth); + b.setColorTarget(0, color, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.neverCull(); + }); + + std::u8string dot; + GraphDebug::exportDOT(graph, dot); + const std::u8string_view v = dot; + CHECK(contains(v, u8"digraph")); + CHECK(contains(v, u8"DepthPrepass")); + CHECK(contains(v, u8"ForwardOpaque")); + CHECK(contains(v, u8"SceneColor")); + CHECK(contains(v, u8"}")); +} + +TEST_CASE("rg.debug: ExportSummary includes counts") +{ + RenderGraph graph(nullptr); + graph.setOutputSize(1920, 1080); + graph.beginFrame(0); + const RGHandle color = graph.createTransient(u8"Color", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + graph.addRenderPass(u8"Pass1", [&](PassBuilder& b) { + b.setColorTarget(0, color, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.neverCull(); + }); + REQUIRE(graph.compile().isOk()); + + std::u8string summary; + GraphDebug::exportSummary(graph, summary); + const std::u8string_view v = summary; + CHECK(contains(v, u8"1920x1080")); + CHECK(contains(v, u8"Pass1")); + CHECK(contains(v, u8"Render")); +} + +TEST_CASE("rg.debug: DOT marks culled passes dashed") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle tex = graph.createTransient(u8"Tex", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + graph.addRenderPass(u8"Culled", [&](PassBuilder& b) { + b.setColorTarget(0, tex, rhi::LoadOp::Clear, rhi::StoreOp::Store); + }); + REQUIRE(graph.compile().isOk()); + + std::u8string dot; + GraphDebug::exportDOT(graph, dot); + CHECK(contains(dot, u8"dashed")); +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/DependencyTests.test.cpp b/Engine/cpp/Runtime/Rendering/RenderGraph/DependencyTests.test.cpp new file mode 100644 index 00000000..7ea4e9d2 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/DependencyTests.test.cpp @@ -0,0 +1,145 @@ +#include + + +import core; +import rhi; +import rendergraph; + +using namespace draco; +using namespace draco::rendergraph; +namespace rhi = draco::rhi; + +namespace +{ + std::u8string_view passName(RenderGraph& g, i32 orderSlot) + { + return g.passes()[static_cast(g.executionOrder()[static_cast(orderSlot)])]->name; + } +} + +TEST_CASE("rg.dep: reader depends on writer") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle tex = graph.createTransient(u8"Tex", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + + graph.addRenderPass(u8"Writer", [&](PassBuilder& b) { + b.setColorTarget(0, tex, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.neverCull(); + }); + graph.addRenderPass(u8"Reader", [&](PassBuilder& b) { + b.readTexture(tex); + b.neverCull(); + }); + + REQUIRE(graph.compile().isOk()); + REQUIRE(graph.executionOrder().size() == 2u); + CHECK(passName(graph, 0) == u8"Writer"); +} + +TEST_CASE("rg.dep: multiple readers fan out, writer first") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle tex = graph.createTransient(u8"Tex", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + + graph.addRenderPass(u8"Writer", [&](PassBuilder& b) { + b.setColorTarget(0, tex, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.neverCull(); + }); + graph.addRenderPass(u8"ReaderA", [&](PassBuilder& b) { b.readTexture(tex); b.neverCull(); }); + graph.addRenderPass(u8"ReaderB", [&](PassBuilder& b) { b.readTexture(tex); b.neverCull(); }); + + REQUIRE(graph.compile().isOk()); + REQUIRE(graph.executionOrder().size() == 3u); + CHECK(passName(graph, 0) == u8"Writer"); +} + +TEST_CASE("rg.dep: subresource writes are independent, reader last") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + RGTextureDesc atlasDesc(rhi::TextureFormat::Depth32Float); + atlasDesc.arrayLayerCount = 4; + const RGHandle atlas = graph.createTransient(u8"ShadowAtlas", atlasDesc); + + graph.addRenderPass(u8"Cascade0", [&](PassBuilder& b) { + b.setDepthTarget(atlas, rhi::LoadOp::Clear, rhi::StoreOp::Store, 1.0f, RGSubresourceRange{ 0, 1, 0, 1 }); + b.neverCull(); + }); + graph.addRenderPass(u8"Cascade1", [&](PassBuilder& b) { + b.setDepthTarget(atlas, rhi::LoadOp::Clear, rhi::StoreOp::Store, 1.0f, RGSubresourceRange{ 0, 1, 1, 1 }); + b.neverCull(); + }); + graph.addRenderPass(u8"Forward", [&](PassBuilder& b) { b.readTexture(atlas); b.neverCull(); }); + + REQUIRE(graph.compile().isOk()); + REQUIRE(graph.executionOrder().size() == 3u); + CHECK(passName(graph, 2) == u8"Forward"); +} + +TEST_CASE("rg.dep: LoadOp on a color target creates a dependency on the writer") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle color = graph.createTransient(u8"SceneColor", RGTextureDesc(rhi::TextureFormat::RGBA16Float)); + + graph.addRenderPass(u8"ForwardOpaque", [&](PassBuilder& b) { + b.setColorTarget(0, color, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.neverCull(); + }); + graph.addRenderPass(u8"Terrain", [&](PassBuilder& b) { + b.setColorTarget(0, color, rhi::LoadOp::Load, rhi::StoreOp::Store); + b.neverCull(); + }); + + REQUIRE(graph.compile().isOk()); + REQUIRE(graph.executionOrder().size() == 2u); + CHECK(passName(graph, 0) == u8"ForwardOpaque"); + CHECK(passName(graph, 1) == u8"Terrain"); +} + +TEST_CASE("rg.dep: LoadOp on a depth target creates a dependency on the writer") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle depth = graph.createTransient(u8"Depth", RGTextureDesc(rhi::TextureFormat::Depth32Float)); + + graph.addRenderPass(u8"DepthPrepass", [&](PassBuilder& b) { + b.setDepthTarget(depth, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.neverCull(); + }); + graph.addRenderPass(u8"ForwardOpaque", [&](PassBuilder& b) { + b.setDepthTarget(depth, rhi::LoadOp::Load, rhi::StoreOp::Store); + b.neverCull(); + }); + + REQUIRE(graph.compile().isOk()); + REQUIRE(graph.executionOrder().size() == 2u); + CHECK(passName(graph, 0) == u8"DepthPrepass"); + CHECK(passName(graph, 1) == u8"ForwardOpaque"); +} + +TEST_CASE("rg.dep: writer chain orders correctly") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle tex = graph.createTransient(u8"Tex", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + + graph.addRenderPass(u8"Write1", [&](PassBuilder& b) { + b.setColorTarget(0, tex, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.neverCull(); + }); + graph.addComputePass(u8"Process", [&](PassBuilder& b) { + b.readTexture(tex); + b.writeStorage(tex); + b.neverCull(); + }); + graph.addRenderPass(u8"FinalRead", [&](PassBuilder& b) { b.readTexture(tex); b.neverCull(); }); + + REQUIRE(graph.compile().isOk()); + REQUIRE(graph.executionOrder().size() == 3u); + CHECK(passName(graph, 0) == u8"Write1"); + CHECK(passName(graph, 1) == u8"Process"); + CHECK(passName(graph, 2) == u8"FinalRead"); +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/DescriptorTests.test.cpp b/Engine/cpp/Runtime/Rendering/RenderGraph/DescriptorTests.test.cpp new file mode 100644 index 00000000..7d4f3d54 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/DescriptorTests.test.cpp @@ -0,0 +1,74 @@ +#include + + +import core; +import rhi; +import rendergraph; + +using namespace draco; +using namespace draco::rendergraph; +namespace rhi = draco::rhi; + +TEST_CASE("rg.descriptor: full size resolves") +{ + RGTextureDesc desc(rhi::TextureFormat::RGBA8Unorm, SizeMode::FullSize); + desc.resolve(1920, 1080); + CHECK(desc.width == 1920u); + CHECK(desc.height == 1080u); +} + +TEST_CASE("rg.descriptor: half size resolves") +{ + RGTextureDesc desc(rhi::TextureFormat::RGBA8Unorm, SizeMode::HalfSize); + desc.resolve(1920, 1080); + CHECK(desc.width == 960u); + CHECK(desc.height == 540u); +} + +TEST_CASE("rg.descriptor: quarter size resolves") +{ + RGTextureDesc desc(rhi::TextureFormat::RGBA8Unorm, SizeMode::QuarterSize); + desc.resolve(1920, 1080); + CHECK(desc.width == 480u); + CHECK(desc.height == 270u); +} + +TEST_CASE("rg.descriptor: custom is not resolved") +{ + RGTextureDesc desc(rhi::TextureFormat::RGBA8Unorm, 256, 256); + desc.resolve(1920, 1080); + CHECK(desc.width == 256u); + CHECK(desc.height == 256u); +} + +TEST_CASE("rg.descriptor: half size clamps to at least one") +{ + RGTextureDesc desc(rhi::TextureFormat::RGBA8Unorm, SizeMode::HalfSize); + desc.resolve(1, 1); + CHECK(desc.width >= 1u); + CHECK(desc.height >= 1u); +} + +TEST_CASE("rg.descriptor: color target defaults") +{ + RGColorTarget target{}; + target.handle = RGHandle{ 0, 1 }; + CHECK(target.loadOp == rhi::LoadOp::Clear); + CHECK(target.storeOp == rhi::StoreOp::Store); +} + +TEST_CASE("rg.descriptor: depth target defaults") +{ + RGDepthTarget target{}; + target.handle = RGHandle{ 0, 1 }; + CHECK(target.depthLoadOp == rhi::LoadOp::Clear); + CHECK(target.depthStoreOp == rhi::StoreOp::Store); + CHECK(target.depthClearValue == 1.0f); + CHECK_FALSE(target.readOnly); +} + +TEST_CASE("rg.descriptor: config defaults") +{ + RenderGraphConfig config{}; + CHECK(config.frameBufferCount == 2); +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/Descriptors.cppm b/Engine/cpp/Runtime/Rendering/RenderGraph/Descriptors.cppm new file mode 100644 index 00000000..0cbb6b1a --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/Descriptors.cppm @@ -0,0 +1,109 @@ +// Draconic::RenderGraph - :descriptors partition +// +// Render-graph resource descriptors (transient texture/buffer) and pass target +// resolves size-relative-to-output and converts to an RHI TextureDesc. + +module; + +#include + +#include + +export module rendergraph:descriptors; + +import core; +import rhi; +import :types; + +using namespace draco; + +export namespace draco::rendergraph +{ + namespace rhi = draco::rhi; + + // Describes a transient texture resource in the graph. + struct RGTextureDesc + { + rhi::TextureFormat format = rhi::TextureFormat::Undefined; + SizeMode sizeMode = SizeMode::FullSize; + u32 width = 0; // used only when sizeMode == Custom + u32 height = 0; + u32 arrayLayerCount = 1; + u32 mipLevelCount = 1; + u32 sampleCount = 1; + rhi::TextureUsage usage = rhi::TextureUsage::None; + + RGTextureDesc() = default; + RGTextureDesc(rhi::TextureFormat fmt, SizeMode mode = SizeMode::FullSize) noexcept + : format(fmt), sizeMode(mode) {} + RGTextureDesc(rhi::TextureFormat fmt, u32 w, u32 h) noexcept + : format(fmt), sizeMode(SizeMode::Custom), width(w), height(h) {} + + // Resolves actual dimensions from the graph output size. + void resolve(u32 outputWidth, u32 outputHeight) noexcept + { + switch (sizeMode) + { + case SizeMode::FullSize: + width = outputWidth; height = outputHeight; break; + case SizeMode::HalfSize: + width = std::max(1u, outputWidth / 2u); height = std::max(1u, outputHeight / 2u); break; + case SizeMode::QuarterSize: + width = std::max(1u, outputWidth / 4u); height = std::max(1u, outputHeight / 4u); break; + case SizeMode::Custom: + break; // already set + } + } + + [[nodiscard]] rhi::TextureDesc toTextureDesc(std::u8string_view label) const + { + rhi::TextureDesc desc{}; + desc.format = format; + desc.width = width; + desc.height = height; + desc.arrayLayerCount = arrayLayerCount; + desc.mipLevelCount = mipLevelCount; + desc.sampleCount = sampleCount; + desc.usage = usage; + desc.label = label; + return desc; + } + }; + + // Describes a transient buffer resource in the graph. + struct RGBufferDesc + { + u64 size = 0; + rhi::BufferUsage usage = rhi::BufferUsage::None; + }; + + // Color target attachment for a render pass. + struct RGColorTarget + { + RGHandle handle = RGHandle::invalid(); + rhi::LoadOp loadOp = rhi::LoadOp::Clear; + rhi::StoreOp storeOp = rhi::StoreOp::Store; + rhi::ClearColor clearValue = rhi::ClearColor::black(); + RGSubresourceRange subresource; + }; + + // Depth/stencil target attachment for a render pass. + struct RGDepthTarget + { + RGHandle handle = RGHandle::invalid(); + rhi::LoadOp depthLoadOp = rhi::LoadOp::Clear; + rhi::StoreOp depthStoreOp = rhi::StoreOp::Store; + f32 depthClearValue = 1.0f; + bool readOnly = false; + rhi::LoadOp stencilLoadOp = rhi::LoadOp::DontCare; + rhi::StoreOp stencilStoreOp = rhi::StoreOp::DontCare; + u32 stencilClearValue = 0; + RGSubresourceRange subresource; + }; + + // Configuration for the render graph. + struct RenderGraphConfig + { + i32 frameBufferCount = 2; // multi-buffering slots (typically 2 or 3) + }; +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/GraphCoreTests.test.cpp b/Engine/cpp/Runtime/Rendering/RenderGraph/GraphCoreTests.test.cpp new file mode 100644 index 00000000..e68548f1 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/GraphCoreTests.test.cpp @@ -0,0 +1,118 @@ +#include + + +import core; +import rhi; +import rendergraph; + +using namespace draco; +using namespace draco::rendergraph; +namespace rhi = draco::rhi; + +TEST_CASE("rg.graph: create transient returns a valid handle") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle handle = graph.createTransient(u8"Test", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm, SizeMode::FullSize)); + CHECK(handle.isValid()); + CHECK(graph.resourceCount() == 1u); +} + +TEST_CASE("rg.graph: multiple resources get unique handles") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle h1 = graph.createTransient(u8"A", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + const RGHandle h2 = graph.createTransient(u8"B", RGTextureDesc(rhi::TextureFormat::Depth32Float)); + CHECK(h1 != h2); + CHECK(graph.resourceCount() == 2u); +} + +TEST_CASE("rg.graph: get resource by name") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle h1 = graph.createTransient(u8"SceneColor", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + CHECK(graph.getResource(u8"SceneColor") == h1); + CHECK_FALSE(graph.getResource(u8"NonExistent").isValid()); +} + +TEST_CASE("rg.graph: pass count is correct") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle color = graph.createTransient(u8"Color", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + + graph.addRenderPass(u8"Pass1", [&](PassBuilder& b) { + b.setColorTarget(0, color, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.neverCull(); + }); + graph.addComputePass(u8"Pass2", [](PassBuilder& b) { b.hasSideEffects(); }); + + CHECK(graph.passCount() == 2u); +} + +TEST_CASE("rg.graph: set output size affects resolution") +{ + RenderGraph graph(nullptr); + graph.setOutputSize(1920, 1080); + CHECK(graph.outputWidth() == 1920u); + CHECK(graph.outputHeight() == 1080u); +} + +TEST_CASE("rg.graph: import target with final state") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle handle = graph.importTarget(u8"Backbuffer", nullptr, nullptr, rhi::ResourceState::Present); + CHECK(handle.isValid()); +} + +TEST_CASE("rg.graph: reset keeps persistent, drops transient") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + graph.registerPersistent(u8"Shadow", nullptr, nullptr); + graph.createTransient(u8"Temp", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + + graph.reset(); + + CHECK(graph.getResource(u8"Shadow").isValid()); + CHECK_FALSE(graph.getResource(u8"Temp").isValid()); +} + +TEST_CASE("rg.graph: SetViewport records a per-pass viewport override") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle color = graph.createTransient(u8"Color", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + + graph.addRenderPass(u8"ViewportPass", [&](PassBuilder& b) { + b.setColorTarget(0, color, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.setViewport(10, 20, 100, 200); + b.neverCull(); + }); + + REQUIRE(graph.passes().size() == 1u); + const RenderGraphPass* pass = graph.passes()[0]; + CHECK(pass->hasViewport); + CHECK(pass->viewportX == 10); + CHECK(pass->viewportY == 20); + CHECK(pass->viewportW == 100u); + CHECK(pass->viewportH == 200u); +} + +TEST_CASE("rg.graph: a pass without SetViewport has no viewport override (full attachment)") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle color = graph.createTransient(u8"Color", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + + graph.addRenderPass(u8"FullPass", [&](PassBuilder& b) { + b.setColorTarget(0, color, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.neverCull(); + }); + + REQUIRE(graph.passes().size() == 1u); + CHECK_FALSE(graph.passes()[0]->hasViewport); +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/GraphDebug.cppm b/Engine/cpp/Runtime/Rendering/RenderGraph/GraphDebug.cppm new file mode 100644 index 00000000..82c471ce --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/GraphDebug.cppm @@ -0,0 +1,182 @@ +// Draconic::RenderGraph - :debug partition +// +// Debug visualization/reporting: Graphviz DOT export and a text summary. Ported +// from Sedulous.RenderGraph (GraphDebug.bf). + +module; + +#include +#include +#include + +export module rendergraph:debug; + +import core; +import :types; +import :pass; +import :resource; +import :graph; + +using namespace draco; + +export namespace draco::rendergraph +{ + namespace detail + { + [[nodiscard]] inline std::u8string_view passColor(RGPassType type) + { + switch (type) + { + case RGPassType::Render: return u8"#4488cc"; + case RGPassType::Compute: return u8"#cc8844"; + case RGPassType::Copy: return u8"#44aa44"; + } + return u8"#888888"; + } + [[nodiscard]] inline std::u8string_view lifetimeLabel(RGResourceLifetime lifetime) + { + switch (lifetime) + { + case RGResourceLifetime::Transient: return u8"transient"; + case RGResourceLifetime::Persistent: return u8"persistent"; + case RGResourceLifetime::Imported: return u8"imported"; + } + return u8"?"; + } + [[nodiscard]] inline std::u8string_view accessLabel(RGAccessType type) + { + switch (type) + { + case RGAccessType::ReadTexture: return u8"read"; + case RGAccessType::ReadBuffer: return u8"read"; + case RGAccessType::ReadDepthStencil: return u8"depth-read"; + case RGAccessType::SampleDepthStencil: return u8"depth-sample"; + case RGAccessType::ReadCopySrc: return u8"copy-src"; + case RGAccessType::WriteColorTarget: return u8"color-out"; + case RGAccessType::WriteDepthTarget: return u8"depth-out"; + case RGAccessType::WriteStorage: return u8"storage-write"; + case RGAccessType::WriteCopyDst: return u8"copy-dst"; + case RGAccessType::ReadWriteStorage: return u8"rw-storage"; + case RGAccessType::ReadWriteDepthTarget: return u8"depth-rw"; + case RGAccessType::ReadWriteColorTarget: return u8"color-rw"; + } + return u8"?"; + } + [[nodiscard]] inline std::u8string_view passTypeLabel(RGPassType type) + { + switch (type) + { + case RGPassType::Render: return u8"Render"; + case RGPassType::Compute: return u8"Compute"; + case RGPassType::Copy: return u8"Copy"; + } + return u8"?"; + } + } + + class GraphDebug + { + public: + // Graphviz DOT: pass nodes (boxes) + resource nodes (ellipse/diamond) + + // access edges. Culled passes/edges are dashed/gray. + static void exportDOT(RenderGraph& graph, std::u8string& out) + { + const std::vector& passes = graph.passes(); + const std::vector& resources = graph.resources(); + + out.append(u8"digraph RenderGraph {\n"); + out.append(u8" rankdir=LR;\n"); + out.append(u8" node [fontname=\"Helvetica\"];\n\n"); + + for (usize i = 0; i < passes.size(); ++i) + { + RenderGraphPass* pass = passes[i]; + const std::u8string_view style = pass->isCulled ? std::u8string_view(u8"dashed") : std::u8string_view(u8"filled"); + const std::u8string_view fontColor = pass->isCulled ? std::u8string_view(u8"gray") : std::u8string_view(u8"white"); + appendFormat(out, + u8" pass{} [label=\"{}\" shape=box style={} fillcolor=\"{}\" fontcolor=\"{}\"", + i, pass->name, style, detail::passColor(pass->type), fontColor); + if (pass->isCulled) { out.append(u8" color=gray"); } + out.append(u8"];\n"); + } + out.append(u8"\n"); + + for (usize i = 0; i < resources.size(); ++i) + { + RenderGraphResource* res = resources[i]; + if (res == nullptr) { continue; } + const std::u8string_view shape = res->resourceType == RGResourceType::Texture + ? std::u8string_view(u8"ellipse") : std::u8string_view(u8"diamond"); + appendFormat(out, u8" res{} [label=\"{}\\n({})\" shape={}];\n", + i, res->name, detail::lifetimeLabel(res->lifetime), shape); + } + out.append(u8"\n"); + + for (usize passIdx = 0; passIdx < passes.size(); ++passIdx) + { + RenderGraphPass* pass = passes[passIdx]; + for (const RGResourceAccess& access : pass->accesses) + { + if (!access.handle.isValid() || access.handle.index >= resources.size()) { continue; } + if (resources[access.handle.index] == nullptr) { continue; } + + const std::u8string_view label = detail::accessLabel(access.type); + if (access.isRead()) + { + appendFormat(out, u8" res{} -> pass{} [label=\"{}\"", access.handle.index, passIdx, label); + if (pass->isCulled) { out.append(u8" style=dashed color=gray"); } + out.append(u8"];\n"); + } + if (access.isWrite()) + { + appendFormat(out, u8" pass{} -> res{} [label=\"{}\"", passIdx, access.handle.index, label); + if (pass->isCulled) { out.append(u8" style=dashed color=gray"); } + out.append(u8"];\n"); + } + } + } + + out.append(u8"}\n"); + } + + // Human-readable text summary (counts + execution order). + static void exportSummary(RenderGraph& graph, std::u8string& out) + { + const std::vector& passes = graph.passes(); + const std::vector& resources = graph.resources(); + const std::vector& executionOrder = graph.executionOrder(); + + usize activeCount = 0, culledCount = 0; + for (RenderGraphPass* p : passes) { if (p->isCulled) { ++culledCount; } else { ++activeCount; } } + + usize resCount = 0, transientCount = 0, persistentCount = 0, importedCount = 0; + for (RenderGraphResource* r : resources) + { + if (r == nullptr) { continue; } + ++resCount; + switch (r->lifetime) + { + case RGResourceLifetime::Transient: ++transientCount; break; + case RGResourceLifetime::Persistent: ++persistentCount; break; + case RGResourceLifetime::Imported: ++importedCount; break; + } + } + + out.append(u8"=== Render Graph Summary ===\n"); + appendFormat(out, u8"Passes: {} active, {} culled, {} total\n", activeCount, culledCount, passes.size()); + appendFormat(out, u8"Resources: {} total ({} transient, {} persistent, {} imported)\n", + resCount, transientCount, persistentCount, importedCount); + appendFormat(out, u8"Output: {}x{}\n\n", graph.outputWidth(), graph.outputHeight()); + + if (!executionOrder.empty()) + { + out.append(u8"Execution order:\n"); + for (usize i = 0; i < executionOrder.size(); ++i) + { + RenderGraphPass* pass = passes[static_cast(executionOrder[i])]; + appendFormat(out, u8" {}. [{}] {}\n", i + 1, detail::passTypeLabel(pass->type), pass->name); + } + } + } + }; +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/GraphProfiler.cppm b/Engine/cpp/Runtime/Rendering/RenderGraph/GraphProfiler.cppm new file mode 100644 index 00000000..598356b9 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/GraphProfiler.cppm @@ -0,0 +1,171 @@ +// Draconic::RenderGraph - :profiler partition +// +// Optional GPU profiler: per-pass timing via timestamp queries. BeginPass/EndPass +// write timestamps around each pass; Resolve copies the query results to a +// readback buffer; ReadResults (after a fence wait) maps it and builds a report. + +module; + +#include + +#include +#include +#include + +export module rendergraph:profiler; + +import core; +import rhi; +import :types; + +using namespace draco; + +export namespace draco::rendergraph +{ + namespace rhi = draco::rhi; + + class GraphProfiler + { + public: + bool enabled = true; + + ~GraphProfiler() { destroy(); } + + GraphProfiler() = default; + GraphProfiler(const GraphProfiler&) = delete; + GraphProfiler& operator=(const GraphProfiler&) = delete; + + // Two timestamp queries per pass (begin + end) + a GpuToCpu readback buffer. + [[nodiscard]] Status init(rhi::Device& device, i32 maxPasses = 64) + { + m_device = &device; + m_maxPasses = maxPasses; + + rhi::QuerySetDesc queryDesc{}; + queryDesc.type = rhi::QueryType::Timestamp; + queryDesc.count = static_cast(maxPasses * 2); + queryDesc.label = u8"RG_Profiler_Queries"; + if (!device.createQuerySet(queryDesc, m_querySet).isOk()) { return Status{ ErrorCode::Unknown }; } + + rhi::BufferDesc bufDesc{}; + bufDesc.size = static_cast(maxPasses) * 2u * sizeof(u64); + bufDesc.usage = rhi::BufferUsage::CopyDst; + bufDesc.memory = rhi::MemoryLocation::GpuToCpu; + bufDesc.label = u8"RG_Profiler_Readback"; + if (!device.createBuffer(bufDesc, m_readbackBuffer).isOk()) { return Status{ ErrorCode::Unknown }; } + + m_passTimesMs.resize(static_cast(maxPasses)); + m_initialized = true; + return Status{}; + } + + // Reset the query set at the START of the frame's encoder - timestamps can only be written + // into a freshly-reset pool, and the reset must be outside any render pass. + void beginFrame(rhi::CommandEncoder& encoder) + { + if (!m_initialized || !enabled) { return; } + encoder.resetQuerySet(m_querySet, 0, static_cast(m_maxPasses * 2)); + m_passNames.clear(); + } + + void beginPass(rhi::CommandEncoder& encoder, i32 passIndex, std::u8string_view passName) + { + if (!m_initialized || !enabled || passIndex >= m_maxPasses) { return; } + while (static_cast(m_passNames.size()) <= passIndex) { m_passNames.push_back(std::u8string{}); } + m_passNames[static_cast(passIndex)] = std::u8string(passName); + encoder.writeTimestamp(m_querySet, static_cast(passIndex * 2)); + } + + void endPass(rhi::CommandEncoder& encoder, i32 passIndex) + { + if (!m_initialized || !enabled || passIndex >= m_maxPasses) { return; } + encoder.writeTimestamp(m_querySet, static_cast(passIndex * 2 + 1)); + } + + // Resolve queries into the readback buffer (call after recording all passes). The pool was + // already reset by BeginFrame, so this only copies the written timestamps out. + void resolve(rhi::CommandEncoder& encoder, i32 passCount) + { + if (!m_initialized || !enabled || passCount == 0) { return; } + const u32 queryCount = static_cast(std::min(passCount * 2, m_maxPasses * 2)); + encoder.resolveQuerySet(m_querySet, 0, queryCount, m_readbackBuffer, 0); + } + + // Read results and append a timing report (call after the GPU has finished). + void readResults(i32 passCount, std::u8string& outReport) + { + if (!m_initialized || !enabled || passCount == 0) { return; } + + const u64* mapped = static_cast(m_readbackBuffer->map()); + if (mapped == nullptr) { return; } + + const i32 count = std::min(passCount, m_maxPasses); + f32 totalMs = 0.0f; + + outReport.append(u8"=== GPU Pass Timing ===\n"); + for (i32 i = 0; i < count; ++i) + { + const u64 begin = mapped[i * 2]; + const u64 end = mapped[i * 2 + 1]; + const u64 ticks = end > begin ? end - begin : 0; + const f32 ms = static_cast(ticks) * m_gpuTimestampPeriod / 1000000.0f; + m_passTimesMs[static_cast(i)] = ms; + totalMs += ms; + + const std::u8string_view name = i < static_cast(m_passNames.size()) + ? m_passNames[static_cast(i)] : std::u8string_view(u8"???"); + appendFormat(outReport, u8" {} ms {}\n", ms, name); + } + appendFormat(outReport, u8" --------\n {} ms TOTAL\n", totalMs); + + // Aggregate by pass NAME so many same-named passes (e.g. 24x probes.prefilter) read as one + // line - a "which pass category is expensive" summary, sorted most-expensive first. + struct Agg { std::u8string_view name; f32 sum = 0.0f; i32 n = 0; }; + std::vector agg; + for (i32 i = 0; i < count; ++i) + { + const std::u8string_view nm = i < static_cast(m_passNames.size()) + ? m_passNames[static_cast(i)] : std::u8string_view(u8"???"); + bool found = false; + for (Agg& a : agg) { if (a.name == nm) { a.sum += m_passTimesMs[static_cast(i)]; ++a.n; found = true; break; } } + if (!found) { Agg a; a.name = nm; a.sum = m_passTimesMs[static_cast(i)]; a.n = 1; agg.push_back(a); } + } + for (usize i = 0; i < agg.size(); ++i) // selection sort by total (few entries) + for (usize j = i + 1; j < agg.size(); ++j) + if (agg[j].sum > agg[i].sum) { const Agg t = agg[i]; agg[i] = agg[j]; agg[j] = t; } + outReport.append(u8"=== GPU by pass name (expensive first) ===\n"); + for (const Agg& a : agg) { appendFormat(outReport, u8" {} ms (x{}) {}\n", a.sum, a.n, a.name); } + + m_readbackBuffer->unmap(); + } + + [[nodiscard]] f32 getPassTimeMs(i32 passIndex) const + { + if (passIndex < 0 || passIndex >= static_cast(m_passTimesMs.size())) { return 0.0f; } + return m_passTimesMs[static_cast(passIndex)]; + } + + // GPU timestamp period (nanoseconds per tick); backend-specific. + void setTimestampPeriod(f32 nanosecondsPerTick) noexcept { m_gpuTimestampPeriod = nanosecondsPerTick; } + + void destroy() + { + if (m_device != nullptr) + { + if (m_readbackBuffer != nullptr) { m_device->destroyBuffer(m_readbackBuffer); } + if (m_querySet != nullptr) { m_device->destroyQuerySet(m_querySet); } + } + m_initialized = false; + } + + private: + rhi::Device* m_device = nullptr; + rhi::QuerySet* m_querySet = nullptr; + rhi::Buffer* m_readbackBuffer = nullptr; + i32 m_maxPasses = 0; + bool m_initialized = false; + std::vector m_passNames; + std::vector m_passTimesMs; + f32 m_gpuTimestampPeriod = 0.0f; + }; +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/GraphValidator.cppm b/Engine/cpp/Runtime/Rendering/RenderGraph/GraphValidator.cppm new file mode 100644 index 00000000..49c49321 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/GraphValidator.cppm @@ -0,0 +1,163 @@ +// Draconic::RenderGraph - :validator partition +// +// Validates a graph for common authoring errors: reads of never-written +// resources (error), passes with no execute callback (warning), and redundant +// (GraphValidator.bf). + +module; + +#include +#include +#include +#include +#include + +export module rendergraph:validator; + +import core; +import :types; +import :pass; +import :resource; +import :graph; + +using namespace draco; + +export namespace draco::rendergraph +{ + enum class ValidationSeverity { Warning, Error }; + + struct ValidationMessage + { + ValidationSeverity severity = ValidationSeverity::Warning; + std::u8string message; + }; + + namespace detail + { + [[nodiscard]] inline std::u8string_view resName(const std::vector& resources, u32 index) + { + return (index < resources.size() && resources[index] != nullptr) + ? resources[index]->name : std::u8string_view(u8"???"); + } + } + + class GraphValidator + { + public: + static void validate(RenderGraph& graph, std::vector& out) + { + checkUninitializedReads(graph, out); + checkEmptyPasses(graph, out); + checkRedundantWrites(graph, out); + } + + static void validateToString(RenderGraph& graph, std::u8string& out) + { + std::vector messages; + validate(graph, messages); + + if (messages.empty()) + { + out.append(u8"Render graph validation: OK (no issues)\n"); + return; + } + + appendFormat(out, u8"Render graph validation: {} issue(s)\n", messages.size()); + for (const ValidationMessage& msg : messages) + { + const std::u8string_view prefix = msg.severity == ValidationSeverity::Error ? std::u8string_view(u8"ERROR") + : std::u8string_view(u8"WARNING"); + appendFormat(out, u8" [{}] {}\n", prefix, msg.message); + } + } + + private: + // Reads of resources that no prior pass wrote (transient only - imported + // and persistent are considered externally initialized). + static void checkUninitializedReads(RenderGraph& graph, std::vector& out) + { + const std::vector& resources = graph.resources(); + std::unordered_set written; + + for (u32 i = 0; i < resources.size(); ++i) + { + RenderGraphResource* res = resources[i]; + if (res != nullptr && (res->lifetime == RGResourceLifetime::Imported + || res->lifetime == RGResourceLifetime::Persistent)) + { + written.insert(i); + } + } + + for (RenderGraphPass* pass : graph.passes()) + { + for (const RGResourceAccess& access : pass->accesses) + { + if (access.isRead() && access.handle.isValid() && !written.contains(access.handle.index)) + { + ValidationMessage msg; + msg.severity = ValidationSeverity::Error; + appendFormat(msg.message, + u8"Pass '{}' reads resource '{}' (index {}) which has not been written to", + pass->name, detail::resName(resources, access.handle.index), access.handle.index); + out.push_back(static_cast(msg)); + } + } + for (const RGResourceAccess& access : pass->accesses) + { + if (access.isWrite() && access.handle.isValid()) { written.insert(access.handle.index); } + } + } + } + + static void checkEmptyPasses(RenderGraph& graph, std::vector& out) + { + for (RenderGraphPass* pass : graph.passes()) + { + bool hasCallback = false; + switch (pass->type) + { + case RGPassType::Render: hasCallback = static_cast(pass->executeCallback); break; + case RGPassType::Compute: hasCallback = static_cast(pass->computeCallback); break; + case RGPassType::Copy: hasCallback = static_cast(pass->copyCallback); break; + } + if (!hasCallback) + { + ValidationMessage msg; + msg.severity = ValidationSeverity::Warning; + appendFormat(msg.message, u8"Pass '{}' has no execute callback", pass->name); + out.push_back(static_cast(msg)); + } + } + } + + // Resources written twice with no read in between. + static void checkRedundantWrites(RenderGraph& graph, std::vector& out) + { + const std::vector& resources = graph.resources(); + std::unordered_map lastWriter; + + for (RenderGraphPass* pass : graph.passes()) + { + for (const RGResourceAccess& access : pass->accesses) + { + if (access.isRead() && access.handle.isValid()) { lastWriter.erase(access.handle.index); } + } + for (const RGResourceAccess& access : pass->accesses) + { + if (!access.isWrite() || !access.handle.isValid()) { continue; } + if (std::u8string* prev = mapFind(lastWriter, access.handle.index)) + { + ValidationMessage msg; + msg.severity = ValidationSeverity::Warning; + appendFormat(msg.message, + u8"Resource '{}' written by pass '{}' was already written by '{}' without being read", + detail::resName(resources, access.handle.index), pass->name, *prev); + out.push_back(static_cast(msg)); + } + lastWriter.insert_or_assign(access.handle.index, std::u8string(pass->name)); + } + } + } + }; +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/PassBuilder.cppm b/Engine/cpp/Runtime/Rendering/RenderGraph/PassBuilder.cppm new file mode 100644 index 00000000..c239632e --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/PassBuilder.cppm @@ -0,0 +1,185 @@ +// Draconic::RenderGraph - :pass_builder partition +// +// Fluent builder handed to a pass's setup callback to declare reads/writes, +// attachments, dependencies, flags, and the execute callback. Ported from +// Sedulous.RenderGraph (PassBuilder.bf). Methods return *this for chaining. + +module; + +#include +#include + +export module rendergraph:pass_builder; + +import core; +import rhi; +import :types; +import :descriptors; +import :callbacks; +import :pass; + +using namespace draco; + +export namespace draco::rendergraph +{ + namespace rhi = draco::rhi; + + class PassBuilder + { + public: + explicit PassBuilder(RenderGraphPass& pass) noexcept : m_pass(&pass) {} + + // --- texture / buffer reads --- + PassBuilder& readTexture(RGHandle handle, RGSubresourceRange subresource = {}) + { + m_pass->accesses.push_back(RGResourceAccess{ handle, RGAccessType::ReadTexture, subresource }); + return *this; + } + + // Sample a DEPTH texture in this pass's shader (e.g. a shadow map). Like ReadTexture - a read + // dependency + barrier, NOT a depth attachment (unlike ReadDepth) - but transitions to + // DepthStencilRead (DEPTH_STENCIL_READ_ONLY_OPTIMAL), the layout a depth sampler expects. + PassBuilder& sampleDepth(RGHandle handle, RGSubresourceRange subresource = {}) + { + m_pass->accesses.push_back(RGResourceAccess{ handle, RGAccessType::SampleDepthStencil, subresource }); + return *this; + } + + PassBuilder& readDepth(RGHandle handle, RGSubresourceRange subresource = {}) + { + RGDepthTarget dt{}; + dt.handle = handle; + dt.depthLoadOp = rhi::LoadOp::Load; + dt.depthStoreOp = rhi::StoreOp::Store; + dt.readOnly = true; + dt.subresource = subresource; + m_pass->depthTarget = dt; + m_pass->accesses.push_back(RGResourceAccess{ handle, RGAccessType::ReadDepthStencil, subresource }); + return *this; + } + + PassBuilder& readBuffer(RGHandle handle) + { + m_pass->accesses.push_back(RGResourceAccess{ handle, RGAccessType::ReadBuffer, {} }); + return *this; + } + + // --- render targets --- + PassBuilder& setColorTarget(i32 slot, RGHandle handle, + rhi::LoadOp loadOp = rhi::LoadOp::Clear, + rhi::StoreOp storeOp = rhi::StoreOp::Store, + rhi::ClearColor clearValue = rhi::ClearColor::black(), + RGSubresourceRange subresource = {}) + { + RGColorTarget target{}; + target.handle = handle; + target.loadOp = loadOp; + target.storeOp = storeOp; + target.clearValue = clearValue; + target.subresource = subresource; + + while (static_cast(m_pass->colorTargets.size()) <= slot) { m_pass->colorTargets.push_back(RGColorTarget{}); } + m_pass->colorTargets[static_cast(slot)] = target; + + if (loadOp == rhi::LoadOp::Load && storeOp == rhi::StoreOp::Store) + { + m_pass->accesses.push_back(RGResourceAccess{ handle, RGAccessType::ReadWriteColorTarget, subresource }); + } + else if (storeOp == rhi::StoreOp::Store) + { + m_pass->accesses.push_back(RGResourceAccess{ handle, RGAccessType::WriteColorTarget, subresource }); + } + return *this; + } + + PassBuilder& setDepthTarget(RGHandle handle, + rhi::LoadOp loadOp = rhi::LoadOp::Clear, + rhi::StoreOp storeOp = rhi::StoreOp::Store, + f32 clearDepth = 1.0f, + RGSubresourceRange subresource = {}) + { + RGDepthTarget dt{}; + dt.handle = handle; + dt.depthLoadOp = loadOp; + dt.depthStoreOp = storeOp; + dt.depthClearValue = clearDepth; + dt.readOnly = false; + dt.stencilLoadOp = rhi::LoadOp::DontCare; + dt.stencilStoreOp = rhi::StoreOp::DontCare; + dt.subresource = subresource; + m_pass->depthTarget = dt; + + if (loadOp == rhi::LoadOp::Load && storeOp == rhi::StoreOp::Store) + { + m_pass->accesses.push_back(RGResourceAccess{ handle, RGAccessType::ReadWriteDepthTarget, subresource }); + } + else if (storeOp == rhi::StoreOp::Store) + { + m_pass->accesses.push_back(RGResourceAccess{ handle, RGAccessType::WriteDepthTarget, subresource }); + } + return *this; + } + + // Read-only depth (depth test, no write): transitions to DepthStencilRead. + PassBuilder& setReadOnlyDepthTarget(RGHandle handle, RGSubresourceRange subresource = {}) + { + RGDepthTarget dt{}; + dt.handle = handle; + dt.depthLoadOp = rhi::LoadOp::Load; + dt.depthStoreOp = rhi::StoreOp::Store; + dt.depthClearValue = 1.0f; + dt.readOnly = true; + dt.subresource = subresource; + m_pass->depthTarget = dt; + m_pass->accesses.push_back(RGResourceAccess{ handle, RGAccessType::ReadDepthStencil, subresource }); + return *this; + } + + // --- storage (UAV) --- + PassBuilder& writeStorage(RGHandle handle, RGSubresourceRange subresource = {}) + { + m_pass->accesses.push_back(RGResourceAccess{ handle, RGAccessType::WriteStorage, subresource }); + return *this; + } + PassBuilder& readWriteStorage(RGHandle handle, RGSubresourceRange subresource = {}) + { + m_pass->accesses.push_back(RGResourceAccess{ handle, RGAccessType::ReadWriteStorage, subresource }); + return *this; + } + + // --- copy --- + PassBuilder& copySrc(RGHandle handle) + { + m_pass->accesses.push_back(RGResourceAccess{ handle, RGAccessType::ReadCopySrc, {} }); + return *this; + } + PassBuilder& copyDst(RGHandle handle) + { + m_pass->accesses.push_back(RGResourceAccess{ handle, RGAccessType::WriteCopyDst, {} }); + return *this; + } + + // --- dependencies / flags --- + PassBuilder& dependsOn(PassHandle pass) { m_pass->dependencies.push_back(pass); return *this; } + // Override the pass's viewport + scissor (a sub-rect of the attachment, e.g. split-screen). + // Without it the pass covers the full attachment. + PassBuilder& setViewport(i32 x, i32 y, u32 w, u32 h) { + m_pass->hasViewport = true; m_pass->viewportX = x; m_pass->viewportY = y; + m_pass->viewportW = w; m_pass->viewportH = h; return *this; + } + PassBuilder& neverCull() { m_pass->neverCull = true; return *this; } + PassBuilder& hasSideEffects() { m_pass->hasSideEffects = true; return *this; } + PassBuilder& enableIf(std::function condition) { m_pass->condition = std::move(condition); return *this; } + + // --- execute callbacks --- + PassBuilder& setExecute(RenderPassExecuteCallback callback) { m_pass->executeCallback = std::move(callback); return *this; } + // A render pass whose body is supplied by render bundles (parallel command recording). + // The graph begins the pass with secondary-command-buffer contents + ExecuteBundles them. + PassBuilder& setBundleExecute(RenderBundlePassCallback callback) { m_pass->bundleCallback = std::move(callback); return *this; } + PassBuilder& setComputeExecute(ComputePassExecuteCallback callback) { m_pass->computeCallback = std::move(callback); return *this; } + PassBuilder& setCopyExecute(CopyPassExecuteCallback callback) { m_pass->copyCallback = std::move(callback); return *this; } + + private: + RenderGraphPass* m_pass; + }; +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/PassBuilderTests.test.cpp b/Engine/cpp/Runtime/Rendering/RenderGraph/PassBuilderTests.test.cpp new file mode 100644 index 00000000..92757ec2 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/PassBuilderTests.test.cpp @@ -0,0 +1,179 @@ +#include + + +import core; +import rhi; +import rendergraph; + +using namespace draco; +using namespace draco::rendergraph; +namespace rhi = draco::rhi; + +TEST_CASE("rg.builder: ReadTexture adds an access") +{ + RenderGraphPass pass(u8"Test", RGPassType::Render); + PassBuilder builder(pass); + const RGHandle handle{ 0, 1 }; + builder.readTexture(handle); + + REQUIRE(pass.accesses.size() == 1u); + CHECK(pass.accesses[0].handle == handle); + CHECK(pass.accesses[0].type == RGAccessType::ReadTexture); + CHECK(pass.accesses[0].subresource.isAll()); +} + +TEST_CASE("rg.builder: ReadTexture with subresource") +{ + RenderGraphPass pass(u8"Test", RGPassType::Render); + PassBuilder builder(pass); + builder.readTexture(RGHandle{ 0, 1 }, RGSubresourceRange{ 0, 1, 2, 1 }); + + CHECK(pass.accesses[0].subresource.baseArrayLayer == 2u); + CHECK(pass.accesses[0].subresource.arrayLayerCount == 1u); +} + +TEST_CASE("rg.builder: SetColorTarget adds access + attachment") +{ + RenderGraphPass pass(u8"Test", RGPassType::Render); + PassBuilder builder(pass); + const RGHandle handle{ 0, 1 }; + builder.setColorTarget(0, handle, rhi::LoadOp::Clear, rhi::StoreOp::Store); + + REQUIRE(pass.accesses.size() == 1u); + CHECK(pass.accesses[0].type == RGAccessType::WriteColorTarget); + REQUIRE(pass.colorTargets.size() == 1u); + CHECK(pass.colorTargets[0].handle == handle); + CHECK(pass.colorTargets[0].loadOp == rhi::LoadOp::Clear); +} + +TEST_CASE("rg.builder: SetDepthTarget adds access + attachment") +{ + RenderGraphPass pass(u8"Test", RGPassType::Render); + PassBuilder builder(pass); + const RGHandle handle{ 0, 1 }; + builder.setDepthTarget(handle, rhi::LoadOp::Clear, rhi::StoreOp::Store, 1.0f); + + REQUIRE(pass.accesses.size() == 1u); + CHECK(pass.accesses[0].type == RGAccessType::WriteDepthTarget); + REQUIRE(pass.depthTarget.has_value()); + CHECK(pass.depthTarget.value().handle == handle); + CHECK(pass.depthTarget.value().depthClearValue == 1.0f); +} + +TEST_CASE("rg.builder: ReadDepth sets read-only") +{ + RenderGraphPass pass(u8"Test", RGPassType::Render); + PassBuilder builder(pass); + builder.readDepth(RGHandle{ 0, 1 }); + + REQUIRE(pass.depthTarget.has_value()); + CHECK(pass.depthTarget.value().readOnly); + REQUIRE(pass.accesses.size() == 1u); + CHECK(pass.accesses[0].type == RGAccessType::ReadDepthStencil); +} + +TEST_CASE("rg.builder: SampleDepth reads without an attachment") +{ + RenderGraphPass pass(u8"Test", RGPassType::Render); + PassBuilder builder(pass); + builder.sampleDepth(RGHandle{ 0, 1 }); + + // Unlike ReadDepth, sampling a depth texture in a shader is NOT a depth attachment: + // it adds a read access only, leaving depthTarget unset. + CHECK_FALSE(pass.depthTarget.has_value()); + REQUIRE(pass.accesses.size() == 1u); + CHECK(pass.accesses[0].type == RGAccessType::SampleDepthStencil); + CHECK(pass.accesses[0].isRead()); + CHECK_FALSE(pass.accesses[0].isWrite()); +} + +TEST_CASE("rg.builder: SampleDepth with subresource") +{ + RenderGraphPass pass(u8"Test", RGPassType::Render); + PassBuilder builder(pass); + builder.sampleDepth(RGHandle{ 0, 1 }, RGSubresourceRange{ 0, 1, 3, 2 }); + + REQUIRE(pass.accesses.size() == 1u); + CHECK(pass.accesses[0].subresource.baseArrayLayer == 3u); + CHECK(pass.accesses[0].subresource.arrayLayerCount == 2u); +} + +TEST_CASE("rg.builder: NeverCull / HasSideEffects flags") +{ + { + RenderGraphPass pass(u8"Test", RGPassType::Render); + PassBuilder(pass).neverCull(); + CHECK(pass.neverCull); + CHECK(pass.shouldSurviveCulling()); + } + { + RenderGraphPass pass(u8"Test", RGPassType::Render); + PassBuilder(pass).hasSideEffects(); + CHECK(pass.hasSideEffects); + CHECK(pass.shouldSurviveCulling()); + } +} + +TEST_CASE("rg.builder: EnableIf stores a runtime condition") +{ + RenderGraphPass pass(u8"Test", RGPassType::Render); + PassBuilder(pass).enableIf([]() { return true; }); + REQUIRE(static_cast(pass.condition)); + CHECK(pass.condition()); +} + +TEST_CASE("rg.builder: storage + copy accesses") +{ + { + RenderGraphPass pass(u8"Test", RGPassType::Compute); + PassBuilder(pass).writeStorage(RGHandle{ 0, 1 }); + REQUIRE(pass.accesses.size() == 1u); + CHECK(pass.accesses[0].type == RGAccessType::WriteStorage); + } + { + RenderGraphPass pass(u8"Test", RGPassType::Compute); + PassBuilder(pass).readWriteStorage(RGHandle{ 0, 1 }); + REQUIRE(pass.accesses.size() == 1u); + CHECK(pass.accesses[0].type == RGAccessType::ReadWriteStorage); + CHECK(pass.accesses[0].isRead()); + CHECK(pass.accesses[0].isWrite()); + } + { + RenderGraphPass pass(u8"Test", RGPassType::Copy); + PassBuilder(pass).copySrc(RGHandle{ 0, 1 }).copyDst(RGHandle{ 1, 1 }); + REQUIRE(pass.accesses.size() == 2u); + CHECK(pass.accesses[0].type == RGAccessType::ReadCopySrc); + CHECK(pass.accesses[1].type == RGAccessType::WriteCopyDst); + } +} + +TEST_CASE("rg.builder: fluent chaining") +{ + RenderGraphPass pass(u8"Test", RGPassType::Render); + PassBuilder(pass) + .readTexture(RGHandle{ 2, 1 }) + .setColorTarget(0, RGHandle{ 0, 1 }, rhi::LoadOp::Clear, rhi::StoreOp::Store) + .setDepthTarget(RGHandle{ 1, 1 }, rhi::LoadOp::Load, rhi::StoreOp::Store) + .neverCull(); + + CHECK(pass.accesses.size() == 3u); // read + write-color + readwrite-depth (Load+Store) + CHECK(pass.colorTargets.size() == 1u); + CHECK(pass.depthTarget.has_value()); + CHECK(pass.neverCull); +} + +TEST_CASE("rg.builder: GetInputs folds LoadOp into a read") +{ + RenderGraphPass pass(u8"Test", RGPassType::Render); + const RGHandle handle{ 0, 1 }; + PassBuilder(pass).setColorTarget(0, handle, rhi::LoadOp::Load, rhi::StoreOp::Store); + + std::vector inputs; + pass.getInputs(inputs); + bool hasRead = false; + for (const RGResourceAccess& input : inputs) + { + if (input.handle == handle && input.isRead()) { hasRead = true; } + } + CHECK(hasRead); +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/PersistentResource.cppm b/Engine/cpp/Runtime/Rendering/RenderGraph/PersistentResource.cppm new file mode 100644 index 00000000..1bab4fc9 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/PersistentResource.cppm @@ -0,0 +1,81 @@ +// Draconic::RenderGraph - :persistent_resource partition +// +// A persistent resource that survives across frames with tracked state. +// Externally owned - the graph never creates or destroys these. The ping-pong +// variant carries two slots (current + previous frame) for temporal effects. + +module; + +#include + +export module rendergraph:persistent_resource; + +import core; +import rhi; + +using namespace draco; + +export namespace draco::rendergraph +{ + namespace rhi = draco::rhi; + + class PersistentResource + { + public: + static constexpr i32 kSlotCount = 2; // current + one previous + + PersistentResource(rhi::Texture* texture, rhi::TextureView* view) noexcept + { + m_textures[0] = texture; + m_views[0] = view; + } + + // Ping-pong (double-buffered) variant. + PersistentResource(rhi::Texture* tex0, rhi::Texture* tex1, + rhi::TextureView* view0, rhi::TextureView* view1) noexcept + : m_isPingPong(true) + { + m_textures[0] = tex0; m_textures[1] = tex1; + m_views[0] = view0; m_views[1] = view1; + } + + [[nodiscard]] rhi::Texture* currentTexture() const noexcept { return m_textures[m_currentIndex]; } + [[nodiscard]] rhi::TextureView* currentView() const noexcept { return m_views[m_currentIndex]; } + [[nodiscard]] rhi::Texture* previousTexture() const noexcept + { + return m_isPingPong ? m_textures[(m_currentIndex + kSlotCount - 1) % kSlotCount] + : m_textures[m_currentIndex]; + } + [[nodiscard]] rhi::TextureView* previousView() const noexcept + { + return m_isPingPong ? m_views[(m_currentIndex + kSlotCount - 1) % kSlotCount] + : m_views[m_currentIndex]; + } + [[nodiscard]] bool isPingPong() const noexcept { return m_isPingPong; } + + void swap() noexcept + { + if (m_isPingPong) { m_currentIndex = (m_currentIndex + 1) % kSlotCount; } + } + + // Re-point the active slot (e.g. when the external texture is recreated on resize). + void updateTexture(rhi::Texture* texture, rhi::TextureView* view) noexcept + { + m_textures[m_currentIndex] = texture; + m_views[m_currentIndex] = view; + } + + // Cross-frame tracking the graph reads/writes (persists across reset()). + bool firstFrame = true; + rhi::ResourceState lastKnownState = rhi::ResourceState::Undefined; + // Non-empty when the resource ended a frame in a non-uniform state; + // otherwise lastKnownState is the uniform value. + std::vector subresourceStates; + + private: + rhi::Texture* m_textures[kSlotCount] = {}; + rhi::TextureView* m_views[kSlotCount] = {}; + i32 m_currentIndex = 0; + bool m_isPingPong = false; + }; +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/ProfilerTests.test.cpp b/Engine/cpp/Runtime/Rendering/RenderGraph/ProfilerTests.test.cpp new file mode 100644 index 00000000..7a6adc5c --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/ProfilerTests.test.cpp @@ -0,0 +1,25 @@ +// GPU-free guard/bounds coverage for GraphProfiler (Sedulous ships no profiler +// test; the timing path needs a real device + fence wait, exercised in samples). +#include + + +import core; +import rendergraph; + +using namespace draco; +using namespace draco::rendergraph; + +TEST_CASE("rg.profiler: uninitialized is safe and reports zero") +{ + GraphProfiler profiler; + CHECK(profiler.enabled); + + // No init() called: queries are zero, out-of-range indices are clamped. + CHECK(profiler.getPassTimeMs(0) == 0.0f); + CHECK(profiler.getPassTimeMs(-1) == 0.0f); + CHECK(profiler.getPassTimeMs(1000) == 0.0f); + + profiler.setTimestampPeriod(1.0f); // no-op without GPU state + profiler.destroy(); // safe with no device + CHECK(profiler.getPassTimeMs(0) == 0.0f); +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraph.cpp b/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraph.cpp deleted file mode 100644 index 31d7e2e1..00000000 --- a/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraph.cpp +++ /dev/null @@ -1,121 +0,0 @@ -module; - -#include -#include - -#include - -module rendering.rendergraph; - -import rendering.rhi; - -namespace draco::rendering::rendergraph { - - static void sortMaterial(std::vector& packets) - { - std::sort(packets.begin(), packets.end(), - [](const rhi::RenderPacket& a, const rhi::RenderPacket& b) - { - // Pipeline first - if (a.pipeline != b.pipeline) - return a.pipeline.value < b.pipeline.value; - - // Texture second - if (a.textureHandle != b.textureHandle) - return a.textureHandle.value < b.textureHandle.value; - - // Vertex buffer third - if (a.vertexBuffer != b.vertexBuffer) - return a.vertexBuffer.value < b.vertexBuffer.value; - - // Index buffer fallback - return a.indexBuffer.value < b.indexBuffer.value; - }); - } - - // Placeholder until depth sorting exists - static void sortFrontToBack(std::vector& packets) - { - sortMaterial(packets); - } - - static void sortBackToFront(std::vector& packets) - { - sortMaterial(packets); - } - - static void sortPackets(std::vector& packets, SortMode mode) - { - switch (mode) - { - case SortMode::None: - break; - - case SortMode::Material: - sortMaterial(packets); - break; - - case SortMode::FrontToBack: - sortFrontToBack(packets); - break; - - case SortMode::BackToFront: - sortBackToFront(packets); - break; - } - } - - void RenderGraph::reset() - { - passes.clear(); // Directly clear - } - - Pass& RenderGraph::addPass(const std::string& name) - { - passes.emplace_back(); - - auto& pass = passes.back(); - pass.name = name; - - return pass; - } - - Pass* RenderGraph::getPass(const std::string& name) - { - for (auto& p : passes) - { - if (p.name == name) - return &p; - } - - return nullptr; - } - - void RenderGraph::execute() - { - for (auto& pass : passes) - { - // Future dependency handling hook - for (const auto& dep : pass.dependencies) - { - (void)dep; - } - - sortPackets(pass.packets, pass.sortMode); - - rhi::applyView(pass.view, {pass.framebuffer, 0, 0, pass.width, pass.height, pass.clearFlags, pass.clearColor}); - - rhi::setViewProjection(pass.view, pass.viewMatrix, pass.projMatrix); - - if (pass.clearFlags) - { - bgfx::setViewClear(pass.view, pass.clearFlags, pass.clearColor); - } - - for (auto& pkt : pass.packets) - { - rhi::submit(pkt, pass.view); - } - } - } -} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraph.cppm b/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraph.cppm index a8616691..b70f0828 100644 --- a/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraph.cppm +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraph.cppm @@ -1,79 +1,888 @@ +// Draconic::RenderGraph - :graph partition +// +// The orchestrator. GPU work is declared as passes with resource accesses; the +// graph builds dependencies, culls unused work, topologically sorts, allocates/ +// aliases transient resources, inserts barriers (via BarrierSolver), and runs +// (RenderGraph.bf). compile() works without an encoder so graph logic is +// testable headless. + module; +#include #include +#include +#include +#include #include +#include + +export module rendergraph:graph; -export module rendering.rendergraph; +import core; +import rhi; +import :types; +import :descriptors; +import :callbacks; +import :resource; +import :persistent_resource; +import :pass; +import :pass_builder; +import :barrier_solver; +import :profiler; +import :transient_pool; -import core.stdtypes; -import rendering.rhi; +using namespace draco; -export namespace draco::rendering::rendergraph { +export namespace draco::rendergraph +{ + namespace rhi = draco::rhi; - enum class PassType : u8 + // NOTE: local stand-in. Replace with a core timing/clock utility once draco.core + // provides one (Sedulous's core::GetTicks). + // Monotonic CPU tick counter for the profiler (nanoseconds since the epoch). + [[nodiscard]] inline u64 getTicks() noexcept { - Graphics, - Transparent, - Shadow, - PostProcess, - UI - }; + return static_cast(std::chrono::steady_clock::now().time_since_epoch().count()); + } - enum class SortMode : u8 + // getTicks() deltas are nanoseconds (steady_clock); convert to milliseconds for reports. + [[nodiscard]] inline f32 ticksToMilliseconds(u64 ticks) noexcept { - None, - Material, - FrontToBack, - BackToFront - }; + return static_cast(ticks) / 1000000.0f; + } - struct Pass + class RenderGraph { - std::string name; + public: + explicit RenderGraph(rhi::Device* device, RenderGraphConfig config = {}) + : m_device(device), m_config(config) + { + if (device != nullptr) { m_texturePool = std::make_unique(*device); } + const i32 slots = config.frameBufferCount > 0 ? config.frameBufferCount : 1; + for (i32 i = 0; i < slots; ++i) { m_deferredDeletions.push_back(std::vector{}); } + } - PassType type = PassType::Graphics; - SortMode sortMode = SortMode::Material; + // Turn on per-pass GPU timestamp profiling (lazy; needs the device). Idempotent. + void enableGpuProfiling() + { + if (m_gpuProfiler.get() != nullptr || m_device == nullptr) { return; } + m_gpuProfiler = std::make_unique(); + if (!m_gpuProfiler->init(*m_device).isOk()) { m_gpuProfiler.reset(); return; } + if (rhi::Queue* q = m_device->getQueue(rhi::QueueType::Graphics)) { m_gpuProfiler->setTimestampPeriod(q->timestampPeriod()); } + } - std::vector dependencies; + // The GPU profiler (null if not enabled). Read its results after the GPU has finished (e.g. + // after WaitIdle on a P-key dump). `LastProfiledPassCount` is how many passes were timed. + [[nodiscard]] GraphProfiler* gpuProfiler() noexcept { return m_gpuProfiler.get(); } + [[nodiscard]] i32 lastProfiledPassCount() const noexcept { return m_lastProfiledPassCount; } - rhi::ViewID view = 0; - rhi::FramebufferHandle framebuffer = rhi::InvalidFramebuffer; + // Aggregate this frame's per-pass CPU RECORD time by pass name (most-expensive first). The graph + // execute is often CPU-bound (recording/bundle build) while the GPU is idle - this shows where. + void appendCpuPassReport(std::u8string& out) const { + struct Agg { std::u8string_view name; u64 ticks = 0; i32 n = 0; }; + std::vector agg; u64 total = 0; + for (const PassCpu& p : m_passCpu) { + total += p.ticks; + bool found = false; + for (Agg& a : agg) { if (a.name == p.name) { a.ticks += p.ticks; ++a.n; found = true; break; } } + if (!found) { Agg a; a.name = p.name; a.ticks = p.ticks; a.n = 1; agg.push_back(a); } + } + for (usize i = 0; i < agg.size(); ++i) + for (usize j = i + 1; j < agg.size(); ++j) + if (agg[j].ticks > agg[i].ticks) { const Agg t = agg[i]; agg[i] = agg[j]; agg[j] = t; } + out.append(u8"=== CPU by pass name (record cost, expensive first) ===\n"); + for (const Agg& a : agg) { appendFormat(out, u8" {} ms (x{}) {}\n", ticksToMilliseconds(a.ticks), a.n, a.name); } + appendFormat(out, u8" --------\n {} ms TOTAL (pass record)\n", ticksToMilliseconds(total)); + } - std::vector packets; + ~RenderGraph() + { + for (std::vector& list : m_deferredDeletions) + { + for (DeferredDeletion& d : list) { d.execute(m_device); } + } + for (RenderGraphPass* pass : m_passes) { delete (pass); } + for (RenderGraphResource* res : m_resources) { if (res != nullptr) { delete (res); } } + } - f32 viewMatrix[16] = { - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - - f32 projMatrix[16] = { - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; + RenderGraph(const RenderGraph&) = delete; + RenderGraph& operator=(const RenderGraph&) = delete; - u16 width = 0; - u16 height = 0; + // --- output dimensions (for SizeMode resolution) --- + void setOutputSize(u32 width, u32 height) noexcept { m_outputWidth = width; m_outputHeight = height; } + [[nodiscard]] u32 outputWidth() const noexcept { return m_outputWidth; } + [[nodiscard]] u32 outputHeight() const noexcept { return m_outputHeight; } - u32 clearFlags = 0; - u32 clearColor = 0; - }; + // --- frame lifecycle --- + void beginFrame(i32 frameIndex) + { + m_frameIndex = frameIndex; + flushDeferred(frameIndex); + clearPasses(); + recycleNonPersistent(); + m_isCompiled = false; + } - class RenderGraph - { - public: - void reset(); + // Compile only (cull, sort, allocate). Safe without an encoder (tests). + [[nodiscard]] Status compile() + { + if (m_passes.empty()) { return Status{}; } + + buildResourceReferences(); + cullPasses(); + buildDependencies(); + if (!topologicalSort().isOk()) { return Status{ ErrorCode::Unknown }; } + allocateTransientResources(); + + m_isCompiled = true; + return Status{}; + } + + // Compile (if needed) and execute into `encoder` (null = compile-only). + [[nodiscard]] Status execute(rhi::CommandEncoder* encoder) + { + if (!m_isCompiled) + { + if (!compile().isOk()) { return Status{ ErrorCode::Unknown }; } + } + if (encoder == nullptr) { return Status{}; } + + m_barrierSolver.reset(resourceSpan()); + + // GPU profiling: reset the timestamp pool up front (must be outside any render pass), + // then bracket each executed pass with begin/end timestamps. + const bool prof = (m_gpuProfiler.get() != nullptr); + if (prof) { m_gpuProfiler->beginFrame(*encoder); m_passCpu.clear(); } + i32 profiledPassCount = 0; + + for (i32 passIdx : m_executionOrder) + { + RenderGraphPass* pass = m_passes[static_cast(passIdx)]; + if (pass->isCulled) { continue; } + if (static_cast(pass->condition) && !pass->condition()) { continue; } + + // Per-pass CPU cost (barriers + record/execute, incl. any bundle build/wait): the graph's + // execute is often CPU-bound while the GPU is idle, so this shows which pass RECORDING is slow. + const u64 cpuStart = prof ? getTicks() : 0; + encoder->beginDebugLabel(pass->name); + if (prof) { m_gpuProfiler->beginPass(*encoder, profiledPassCount, pass->name); } + m_barrierSolver.emitBarriers(*pass, resourceSpan(), *encoder); + + switch (pass->type) + { + case RGPassType::Render: executeRenderPass(*pass, *encoder); break; + case RGPassType::Compute: executeComputePass(*pass, *encoder); break; + case RGPassType::Copy: executeCopyPass(*pass, *encoder); break; + } + + m_barrierSolver.emitReadableAfterWriteBarriers(*pass, resourceSpan(), *encoder); + if (prof) { m_gpuProfiler->endPass(*encoder, profiledPassCount); ++profiledPassCount; } + encoder->endDebugLabel(); + if (prof) { m_passCpu.push_back(PassCpu{ pass->name, getTicks() - cpuStart }); } + } - Pass& addPass(const std::string& name); + if (m_gpuProfiler.get() != nullptr) { m_gpuProfiler->resolve(*encoder, profiledPassCount); m_lastProfiledPassCount = profiledPassCount; } - Pass* getPass(const std::string& name); + m_barrierSolver.emitFinalTransitions(resourceSpan(), *encoder); + m_barrierSolver.updatePersistentStates(resourceSpan()); - void execute(); + // Defer-delete per-subresource views created this frame. + if (!m_subresourceViews.empty()) + { + std::vector& deletions = deferredSlot(); + for (rhi::TextureView* view : m_subresourceViews) + { + DeferredDeletion d{}; d.view = view; deletions.push_back(d); + } + m_subresourceViews.clear(); + } + + returnTransientResources(); + return Status{}; + } + + void endFrame() + { + m_isCompiled = false; + if (m_texturePool.get() != nullptr) { m_texturePool->endFrame(); } + } + + // Clear passes but keep persistent resource state (multi-view rendering). + void reset() + { + returnTransientResources(); + clearPasses(); + recycleNonPersistent(); + m_isCompiled = false; + } + + // --- resource creation --- + RGHandle createTransient(std::u8string_view name, RGTextureDesc desc) + { + RenderGraphResource* res = new RenderGraphResource(name, RGResourceType::Texture, RGResourceLifetime::Transient); + desc.resolve(m_outputWidth, m_outputHeight); + res->textureDesc = desc; + return addResource(res); + } + + RGHandle createTransientBuffer(std::u8string_view name, RGBufferDesc desc) + { + RenderGraphResource* res = new RenderGraphResource(name, RGResourceType::Buffer, RGResourceLifetime::Transient); + res->bufferDesc = desc; + return addResource(res); + } + + RGHandle registerPersistent(std::u8string_view name, rhi::Texture* texture, rhi::TextureView* view) + { + RenderGraphResource* res = new RenderGraphResource(name, RGResourceType::Texture, RGResourceLifetime::Persistent); + res->texture = texture; + res->textureView = view; + res->persistentData = std::make_unique(texture, view); + return addResource(res); + } + + RGHandle registerPersistentPingPong(std::u8string_view name, rhi::Texture* tex0, rhi::Texture* tex1, + rhi::TextureView* view0, rhi::TextureView* view1) + { + RenderGraphResource* res = new RenderGraphResource(name, RGResourceType::Texture, RGResourceLifetime::Persistent); + res->texture = tex0; + res->textureView = view0; + res->persistentData = std::make_unique(tex0, tex1, view0, view1); + return addResource(res); + } + + RGHandle importTarget(std::u8string_view name, rhi::Texture* texture, rhi::TextureView* view, + std::optional finalState = {}, + std::optional currentState = {}) + { + RenderGraphResource* res = new RenderGraphResource(name, RGResourceType::Texture, RGResourceLifetime::Imported); + res->texture = texture; + res->textureView = view; + res->finalState = finalState; + res->lastKnownState = currentState.has_value() ? currentState.value() + : (texture != nullptr ? texture->initialState : rhi::ResourceState::Undefined); + return addResource(res); + } + + // Depth import variant carrying a depth-only view for shader sampling. + RGHandle importTarget(std::u8string_view name, rhi::Texture* texture, rhi::TextureView* view, + rhi::TextureView* depthOnlyView, + std::optional finalState = {}, + std::optional currentState = {}) + { + RenderGraphResource* res = new RenderGraphResource(name, RGResourceType::Texture, RGResourceLifetime::Imported); + res->texture = texture; + res->textureView = view; + res->depthOnlyView = depthOnlyView; + res->finalState = finalState; + res->lastKnownState = currentState.has_value() ? currentState.value() + : (texture != nullptr ? texture->initialState : rhi::ResourceState::Undefined); + return addResource(res); + } + + RGHandle importBuffer(std::u8string_view name, rhi::Buffer* buffer) + { + RenderGraphResource* res = new RenderGraphResource(name, RGResourceType::Buffer, RGResourceLifetime::Imported); + res->buffer = buffer; + return addResource(res); + } + + void requireReadableAfterWrite(RGHandle handle) + { + if (RenderGraphResource* res = resolve(handle)) { res->readableAfterWrite = true; } + } + + // --- pass creation (setup callable receives PassBuilder&) --- + template + PassHandle addRenderPass(std::u8string_view name, Setup&& setup) { return addPassOfType(name, RGPassType::Render, setup); } + template + PassHandle addComputePass(std::u8string_view name, Setup&& setup) { return addPassOfType(name, RGPassType::Compute, setup); } + template + PassHandle addCopyPass(std::u8string_view name, Setup&& setup) { return addPassOfType(name, RGPassType::Copy, setup); } + + // --- resource access (during execute callbacks) --- + [[nodiscard]] rhi::Texture* getTexture(RGHandle handle) + { + RenderGraphResource* res = resolveChecked(handle); + if (res == nullptr) { return nullptr; } + return res->persistentData.get() != nullptr ? res->persistentData->currentTexture() : res->texture; + } + [[nodiscard]] rhi::TextureView* getTextureView(RGHandle handle) + { + RenderGraphResource* res = resolveChecked(handle); + if (res == nullptr) { return nullptr; } + return res->persistentData.get() != nullptr ? res->persistentData->currentView() : res->textureView; + } + + // A stable id for the GPU texture currently backing `handle`. For a transient it changes when + // the graph (re)allocates a different physical texture (e.g. on resize) - even if the new view + // happens to reuse a freed address. A bind-group cache over GetTextureView MUST also key on + // this to stay correct across resizes. 0 when unresolved. + [[nodiscard]] u64 getTextureGeneration(RGHandle handle) + { + RenderGraphResource* res = resolveChecked(handle); + return res != nullptr ? res->textureGeneration : 0; + } + [[nodiscard]] rhi::TextureView* getDepthOnlyTextureView(RGHandle handle) + { + RenderGraphResource* res = resolveChecked(handle); + return res != nullptr ? res->depthOnlyView : nullptr; + } + [[nodiscard]] rhi::Buffer* getBuffer(RGHandle handle) + { + RenderGraphResource* res = resolveChecked(handle); + return res != nullptr ? res->buffer : nullptr; + } + + void swapPingPong(RGHandle handle) + { + RenderGraphResource* res = resolve(handle); + if (res != nullptr && res->persistentData.get() != nullptr) + { + res->persistentData->swap(); + res->texture = res->persistentData->currentTexture(); + res->textureView = res->persistentData->currentView(); + } + } + + // --- queries --- + [[nodiscard]] usize passCount() const noexcept { return m_passes.size(); } + [[nodiscard]] usize resourceCount() const noexcept + { + usize count = 0; + for (RenderGraphResource* r : m_resources) { if (r != nullptr) { ++count; } } + return count; + } + [[nodiscard]] usize culledPassCount() const noexcept + { + usize count = 0; + for (RenderGraphPass* p : m_passes) { if (p->isCulled) { ++count; } } + return count; + } + [[nodiscard]] RGHandle getResource(std::u8string_view name) const + { + for (usize i = 0; i < m_resources.size(); ++i) + { + RenderGraphResource* res = m_resources[i]; + if (res != nullptr && res->name == name) { return RGHandle{ static_cast(i), res->generation }; } + } + return RGHandle::invalid(); + } + [[nodiscard]] rhi::ResourceState getResourceState(RGHandle handle) + { + RenderGraphResource* res = resolveChecked(handle); + return res != nullptr ? res->lastKnownState : rhi::ResourceState::Undefined; + } + + [[nodiscard]] const std::vector& executionOrder() const noexcept { return m_executionOrder; } + [[nodiscard]] const std::vector& passes() const noexcept { return m_passes; } + [[nodiscard]] const std::vector& resources() const noexcept { return m_resources; } private: - std::vector passes; + struct DeferredDeletion + { + rhi::Texture* texture = nullptr; + rhi::TextureView* view = nullptr; + rhi::TextureView* view2 = nullptr; + rhi::Buffer* buffer = nullptr; + + void execute(rhi::Device* device) + { + if (device == nullptr) { return; } + if (view2 != nullptr) { device->destroyTextureView(view2); } + if (view != nullptr) { device->destroyTextureView(view); } + if (texture != nullptr) { device->destroyTexture(texture); } + if (buffer != nullptr) { device->destroyBuffer(buffer); } + } + }; + + [[nodiscard]] std::span resourceSpan() const + { + return std::span(m_resources.data(), m_resources.size()); + } + + [[nodiscard]] std::vector& deferredSlot() + { + const i32 slot = m_frameIndex % static_cast(m_deferredDeletions.size()); + return m_deferredDeletions[static_cast(slot)]; + } + + void flushDeferred(i32 frameIndex) + { + const i32 slot = frameIndex % static_cast(m_deferredDeletions.size()); + std::vector& deletions = m_deferredDeletions[static_cast(slot)]; + for (DeferredDeletion& d : deletions) { d.execute(m_device); } + deletions.clear(); + } + + // Validate a handle (bounds + generation); null on mismatch. + [[nodiscard]] RenderGraphResource* resolveChecked(RGHandle handle) + { + if (!handle.isValid() || handle.index >= m_resources.size()) { return nullptr; } + RenderGraphResource* res = m_resources[handle.index]; + if (res == nullptr || res->generation != handle.generation) { return nullptr; } + return res; + } + // Validate a handle (bounds only; ignores generation) - for mutators. + [[nodiscard]] RenderGraphResource* resolve(RGHandle handle) + { + if (!handle.isValid() || handle.index >= m_resources.size()) { return nullptr; } + return m_resources[handle.index]; + } + + RGHandle addResource(RenderGraphResource* res) + { + if (!m_freeResourceSlots.empty()) + { + const i32 idx = m_freeResourceSlots.back(); + m_freeResourceSlots.pop_back(); + m_resources[static_cast(idx)] = res; + return RGHandle{ static_cast(idx), res->generation }; + } + const u32 idx = static_cast(m_resources.size()); + m_resources.push_back(res); + return RGHandle{ idx, res->generation }; + } + + template + PassHandle addPassOfType(std::u8string_view name, RGPassType type, Setup&& setup) + { + RenderGraphPass* pass = new RenderGraphPass(name, type); + PassBuilder builder(*pass); + setup(builder); + const u32 idx = static_cast(m_passes.size()); + m_passes.push_back(pass); + return PassHandle{ idx }; + } + + void clearPasses() + { + for (RenderGraphPass* p : m_passes) { delete (p); } + m_passes.clear(); + m_executionOrder.clear(); + } + + void recycleNonPersistent() + { + for (usize i = 0; i < m_resources.size(); ++i) + { + RenderGraphResource* res = m_resources[i]; + if (res == nullptr) { continue; } + if (res->lifetime != RGResourceLifetime::Persistent) + { + m_freeResourceSlots.push_back(static_cast(i)); + delete (res); + m_resources[i] = nullptr; + } + else + { + res->resetTracking(); + } + } + } + + // === compilation pipeline === + void buildResourceReferences() + { + for (usize passIdx = 0; passIdx < m_passes.size(); ++passIdx) + { + RenderGraphPass* pass = m_passes[passIdx]; + const PassHandle passHandle{ static_cast(passIdx) }; + + for (const RGResourceAccess& access : pass->accesses) + { + if (!access.handle.isValid() || access.handle.index >= m_resources.size()) { continue; } + RenderGraphResource* res = m_resources[access.handle.index]; + if (res == nullptr) { continue; } + + ++res->refCount; + if (res->firstUsePass < 0 || static_cast(passIdx) < res->firstUsePass) { res->firstUsePass = static_cast(passIdx); } + if (static_cast(passIdx) > res->lastUsePass) { res->lastUsePass = static_cast(passIdx); } + if (access.isWrite()) { res->firstWriter = passHandle; } + if (access.isRead()) { res->lastReader = passHandle; } + } + } + } + + void cullPasses() + { + for (RenderGraphPass* pass : m_passes) { pass->isCulled = true; } + for (RenderGraphPass* pass : m_passes) { if (pass->shouldSurviveCulling()) { pass->isCulled = false; } } + + // Passes writing imported resources with a final state stay alive. + for (RenderGraphPass* pass : m_passes) + { + if (!pass->isCulled) { continue; } + std::vector outputs; + pass->getOutputs(outputs); + for (const RGResourceAccess& output : outputs) + { + if (output.handle.isValid() && output.handle.index < m_resources.size()) + { + RenderGraphResource* res = m_resources[output.handle.index]; + if (res != nullptr && res->finalState.has_value()) { pass->isCulled = false; break; } + } + } + } + + // Backward propagation: a live pass keeps its overlapping upstream writers alive. + bool changed = true; + while (changed) + { + changed = false; + for (RenderGraphPass* pass : m_passes) + { + if (pass->isCulled) { continue; } + std::vector inputs; + pass->getInputs(inputs); + + for (const RGResourceAccess& input : inputs) + { + if (!input.handle.isValid() || input.handle.index >= m_resources.size()) { continue; } + for (usize i = m_passes.size(); i-- > 0;) + { + RenderGraphPass* candidate = m_passes[i]; + if (!candidate->isCulled) { continue; } + std::vector candidateOutputs; + candidate->getOutputs(candidateOutputs); + for (const RGResourceAccess& output : candidateOutputs) + { + if (output.handle == input.handle + && (input.subresource.isAll() || output.subresource.isAll() + || input.subresource.overlaps(output.subresource))) + { + candidate->isCulled = false; + changed = true; + } + } + } + } + } + } + } + + void buildDependencies() + { + for (usize passIdx = 0; passIdx < m_passes.size(); ++passIdx) + { + RenderGraphPass* pass = m_passes[passIdx]; + if (pass->isCulled) { continue; } + + std::vector readAccesses; + pass->getInputs(readAccesses); + + for (const RGResourceAccess& readAccess : readAccesses) + { + if (!readAccess.handle.isValid() || readAccess.handle.index >= m_resources.size()) { continue; } + RenderGraphResource* res = m_resources[readAccess.handle.index]; + const u32 totalMips = res != nullptr ? res->totalMipLevels() : 1u; + const u32 totalLayers = res != nullptr ? res->totalArrayLayers() : 1u; + + for (usize j = passIdx; j-- > 0;) + { + RenderGraphPass* writer = m_passes[j]; + if (writer->isCulled) { continue; } + std::vector writerOutputs; + writer->getOutputs(writerOutputs); + + bool overlaps = false; + for (const RGResourceAccess& writerAccess : writerOutputs) + { + if (writerAccess.handle == readAccess.handle + && (readAccess.subresource.isAll() || writerAccess.subresource.isAll() + || readAccess.subresource.overlaps(writerAccess.subresource, totalMips, totalLayers))) + { + overlaps = true; + break; + } + } + if (overlaps) { addDependencyIfNew(*pass, PassHandle{ static_cast(j) }); break; } + } + } + } + } + + static void addDependencyIfNew(RenderGraphPass& pass, PassHandle dep) + { + for (PassHandle existing : pass.dependencies) { if (existing == dep) { return; } } + pass.dependencies.push_back(dep); + } + + [[nodiscard]] Status topologicalSort() + { + m_executionOrder.clear(); + const usize passCount = m_passes.size(); + + std::vector inDegree; + inDegree.resize(passCount); + std::vector> adjacency; + for (usize i = 0; i < passCount; ++i) { adjacency.push_back(std::vector{}); } + + for (usize i = 0; i < passCount; ++i) + { + RenderGraphPass* pass = m_passes[i]; + if (pass->isCulled) { continue; } + for (PassHandle dep : pass->dependencies) + { + if (dep.isValid() && dep.index < passCount) + { + adjacency[dep.index].push_back(static_cast(i)); + ++inDegree[i]; + } + } + } + + std::vector queue; + for (usize i = 0; i < passCount; ++i) + { + if (!m_passes[i]->isCulled && inDegree[i] == 0) { queue.push_back(static_cast(i)); } + } + + while (!queue.empty()) + { + const i32 node = queue[0]; + queue.erase(queue.begin()); + m_executionOrder.push_back(node); + m_passes[static_cast(node)]->executionOrder = static_cast(m_executionOrder.size()) - 1; + + for (i32 neighbor : adjacency[static_cast(node)]) + { + if (--inDegree[static_cast(neighbor)] == 0) { queue.push_back(neighbor); } + } + } + + usize nonCulled = 0; + for (RenderGraphPass* p : m_passes) { if (!p->isCulled) { ++nonCulled; } } + return m_executionOrder.size() == nonCulled ? Status{} : Status{ ErrorCode::Unknown }; // cycle + } + + void allocateTransientResources() + { + for (RenderGraphResource* res : m_resources) + { + if (res == nullptr || res->lifetime != RGResourceLifetime::Transient || res->refCount == 0) { continue; } + + if (res->resourceType == RGResourceType::Texture && m_device != nullptr) + { + rhi::TextureDesc rhiDesc = res->textureDesc.toTextureDesc(res->name); + rhi::Texture* tex = nullptr; + rhi::TextureView* view = nullptr; + u64 pooledGen = 0; + if (m_texturePool.get() != nullptr && m_texturePool->tryAcquire(rhiDesc, tex, view, pooledGen)) + { + res->texture = tex; + res->textureView = view; + res->textureGeneration = pooledGen; // reused physical texture keeps its id + if (rhi::isDepthFormat(res->textureDesc.format) && rhi::hasStencil(res->textureDesc.format)) + { + rhi::TextureViewDesc depthDesc{}; + depthDesc.aspect = rhi::TextureAspect::DepthOnly; + depthDesc.label = u8"RGDepthOnlyView"; + rhi::TextureView* depthOnly = nullptr; + if (m_device->createTextureView(tex, depthDesc, depthOnly).isOk()) { res->depthOnlyView = depthOnly; } + } + } + else + { + (void)res->allocateTexture(*m_device); + res->textureGeneration = ++m_nextTransientGeneration; // freshly created -> new id + } + } + else if (res->resourceType == RGResourceType::Buffer && m_device != nullptr) + { + (void)res->allocateBuffer(*m_device); + } + } + } + + void returnTransientResources() + { + std::vector& deletions = deferredSlot(); + for (RenderGraphResource* res : m_resources) + { + if (res == nullptr || res->lifetime != RGResourceLifetime::Transient) { continue; } + + if (res->resourceType == RGResourceType::Texture && res->texture != nullptr) + { + if (m_texturePool.get() != nullptr) + { + const rhi::TextureDesc rhiDesc = res->textureDesc.toTextureDesc(res->name); + m_texturePool->returnToPool(rhiDesc, res->texture, res->textureView, res->textureGeneration); + if (res->depthOnlyView != nullptr) { DeferredDeletion d{}; d.view = res->depthOnlyView; deletions.push_back(d); } + } + else + { + DeferredDeletion d{}; d.texture = res->texture; d.view = res->textureView; d.view2 = res->depthOnlyView; + deletions.push_back(d); + } + res->texture = nullptr; + res->textureView = nullptr; + res->depthOnlyView = nullptr; + } + else if (res->resourceType == RGResourceType::Buffer && res->buffer != nullptr) + { + DeferredDeletion d{}; d.buffer = res->buffer; deletions.push_back(d); + res->buffer = nullptr; + } + } + } + + // === pass execution === + rhi::TextureView* createSubresourceView(RGHandle handle, RGSubresourceRange subresource) + { + if (m_device == nullptr) { return nullptr; } + rhi::Texture* texture = getTexture(handle); + if (texture == nullptr) { return nullptr; } + + rhi::TextureViewDesc viewDesc{}; + viewDesc.baseMipLevel = subresource.baseMipLevel; + viewDesc.mipLevelCount = subresource.mipLevelCount == 0 ? 1u : subresource.mipLevelCount; + viewDesc.baseArrayLayer = subresource.baseArrayLayer; + viewDesc.arrayLayerCount = subresource.arrayLayerCount == 0 ? 1u : subresource.arrayLayerCount; + viewDesc.dimension = viewDesc.arrayLayerCount == 1 ? rhi::TextureViewDimension::Texture2D + : rhi::TextureViewDimension::Texture2DArray; + rhi::TextureView* view = nullptr; + if (m_device->createTextureView(texture, viewDesc, view).isOk()) + { + m_subresourceViews.push_back(view); + return view; + } + return nullptr; + } + + // The full-target render area for a pass, from an attachment's resolved dimensions (the + // transient desc, else the backing texture). Used to set the parent pass's viewport + + // scissor before ExecuteBundles: render bundles INHERIT viewport/scissor from the parent + // pass (WebGPU + DX12 bundles cannot set them), so the parent must. Returns false if no + // attachment yields dimensions. + [[nodiscard]] bool passRenderArea(RenderGraphPass& pass, u32& outW, u32& outH) + { + auto fromHandle = [&](RGHandle h, u32& w, u32& h2) -> bool { + if (RenderGraphResource* res = resolve(h)) { + if (res->textureDesc.width > 0 && res->textureDesc.height > 0) { + w = res->textureDesc.width; h2 = res->textureDesc.height; return true; + } + } + if (rhi::TextureView* v = getTextureView(h)) { + if (v->texture != nullptr && v->texture->desc.width > 0 && v->texture->desc.height > 0) { + w = v->texture->desc.width; h2 = v->texture->desc.height; return true; + } + } + return false; + }; + for (const RGColorTarget& ct : pass.colorTargets) { if (fromHandle(ct.handle, outW, outH)) { return true; } } + if (pass.depthTarget.has_value()) { if (fromHandle(pass.depthTarget.value().handle, outW, outH)) { return true; } } + return false; + } + + void executeRenderPass(RenderGraphPass& pass, rhi::CommandEncoder& encoder) + { + const bool hasBundles = static_cast(pass.bundleCallback); + if (!static_cast(pass.executeCallback) && !hasBundles) { return; } + + // A bundle pass records its bundles NOW (encoder in recording state, before the pass + // begins); the graph then begins with secondary contents + replays them. Done before + // building the pass desc so the encoder is still recording. + std::vector bundles; + if (hasBundles) { pass.bundleCallback(encoder, bundles); } + + rhi::RenderPassDesc rpDesc{}; + rpDesc.label = pass.name; + if (hasBundles) { rpDesc.contents = rhi::RenderPassContents::SecondaryCommandBuffers; } + + for (usize i = 0; i < pass.colorTargets.size(); ++i) + { + const RGColorTarget& ct = pass.colorTargets[i]; + rhi::TextureView* view = getTextureView(ct.handle); + if (view == nullptr) { continue; } + if (!ct.subresource.isAll()) + { + if (rhi::TextureView* subView = createSubresourceView(ct.handle, ct.subresource)) { view = subView; } + } + rhi::ColorAttachment attachment{}; + attachment.view = view; + attachment.loadOp = ct.loadOp; + attachment.storeOp = ct.storeOp; + attachment.clearValue = ct.clearValue; + rpDesc.colorAttachments.push_back(attachment); + } + + if (pass.depthTarget.has_value()) + { + const RGDepthTarget& dt = pass.depthTarget.value(); + rhi::TextureView* view = getTextureView(dt.handle); + if (view != nullptr) + { + if (!dt.subresource.isAll()) + { + if (rhi::TextureView* subView = createSubresourceView(dt.handle, dt.subresource)) { view = subView; } + } + rhi::DepthStencilAttachment dsa{}; + dsa.view = view; + dsa.depthLoadOp = dt.depthLoadOp; + dsa.depthStoreOp = dt.depthStoreOp; + dsa.depthClearValue = dt.depthClearValue; + dsa.depthReadOnly = dt.readOnly; + dsa.stencilLoadOp = dt.stencilLoadOp; + dsa.stencilStoreOp = dt.stencilStoreOp; + dsa.stencilClearValue = dt.stencilClearValue; + rpDesc.depthStencilAttachment = dsa; + } + } + + rhi::RenderPassEncoder* rp = encoder.beginRenderPass(rpDesc); + // Viewport/scissor: a per-pass override (split-screen sub-rect) if set, else the full + // attachment. Set here for bundle passes (bundles inherit it from the parent - WebGPU/ + // DX12 can't set it inside a bundle); a plain execute callback may also rely on it. + i32 vpX = 0, vpY = 0; u32 vpW = 0, vpH = 0; + if (pass.hasViewport) { vpX = pass.viewportX; vpY = pass.viewportY; vpW = pass.viewportW; vpH = pass.viewportH; } + else { (void)passRenderArea(pass, vpW, vpH); } + if (vpW > 0 && vpH > 0) { + rp->setViewport(static_cast(vpX), static_cast(vpY), static_cast(vpW), static_cast(vpH)); + rp->setScissor(vpX, vpY, vpW, vpH); + } + if (hasBundles) { + if (!bundles.empty()) { + rp->executeBundles(std::span{ bundles.data(), bundles.size() }); + } + } else { + pass.executeCallback(*rp); + } + rp->end(); + } + + void executeComputePass(RenderGraphPass& pass, rhi::CommandEncoder& encoder) + { + if (!static_cast(pass.computeCallback)) { return; } + rhi::ComputePassEncoder* cp = encoder.beginComputePass(pass.name); + pass.computeCallback(*cp); + cp->end(); + } + + void executeCopyPass(RenderGraphPass& pass, rhi::CommandEncoder& encoder) + { + if (!static_cast(pass.copyCallback)) { return; } + pass.copyCallback(encoder); + } + + rhi::Device* m_device; + RenderGraphConfig m_config; + std::vector m_resources; + std::vector m_freeResourceSlots; + std::vector m_passes; + std::vector m_executionOrder; + bool m_isCompiled = false; + std::unique_ptr m_gpuProfiler; // optional per-pass GPU timing + i32 m_lastProfiledPassCount = 0; + struct PassCpu { std::u8string_view name; u64 ticks = 0; }; // per-pass CPU record time (name -> pass->name, valid pre-Reset) + std::vector m_passCpu; + BarrierSolver m_barrierSolver; + std::unique_ptr m_texturePool; + std::vector> m_deferredDeletions; + std::vector m_subresourceViews; + i32 m_frameIndex = 0; + u32 m_outputWidth = 1920; + u32 m_outputHeight = 1080; + u64 m_nextTransientGeneration = 0; // monotonic id stamped on each freshly created transient texture }; } diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraphModule.cppm b/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraphModule.cppm new file mode 100644 index 00000000..45727967 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraphModule.cppm @@ -0,0 +1,23 @@ +// Draconic::RenderGraph - the `rendergraph` module. +// +// A render graph over the RHI: passes declare resource accesses, the graph +// resolves dependencies, allocates/aliases transient resources, and inserts the +// the same one Draconic's RHI is a faithful port of). One named module composed of +// partitions, re-exported here. + +export module rendergraph; + +export import :types; +export import :callbacks; +export import :descriptors; +export import :persistent_resource; +export import :resource; +export import :pass; +export import :state_tracker; +export import :barrier_solver; +export import :pass_builder; +export import :transient_pool; +export import :graph; +export import :validator; +export import :debug; +export import :profiler; diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraphPass.cppm b/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraphPass.cppm new file mode 100644 index 00000000..0dd4242a --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraphPass.cppm @@ -0,0 +1,124 @@ +// Draconic::RenderGraph - :pass partition +// +// A single pass: its declared resource accesses, attachments, dependencies, and +// typed execute callback. GetInputs/GetOutputs fold attachment load/store ops +// into the read/write access set the compiler reasons about. Ported from +// Sedulous.RenderGraph (RenderGraphPass.bf). + +module; + +#include +#include +#include +#include +#include + +export module rendergraph:pass; + +import core; +import rhi; +import :types; +import :descriptors; +import :callbacks; + +using namespace draco; + +export namespace draco::rendergraph +{ + namespace rhi = draco::rhi; + + class RenderGraphPass + { + public: + RenderGraphPass(std::u8string_view passName, RGPassType passType) + : name(passName), type(passType) {} + + // Explicitly defaulted so the (move-only, due to Function members) special + // members are synthesized in this module and usable by importers - GCC's + // module support otherwise reports the implicit destructor as deleted. + ~RenderGraphPass() = default; + RenderGraphPass(RenderGraphPass&&) = default; + RenderGraphPass& operator=(RenderGraphPass&&) = default; + RenderGraphPass(const RenderGraphPass&) = delete; + RenderGraphPass& operator=(const RenderGraphPass&) = delete; + + // Resource handles this pass reads (declared accesses + Load attachments). + void getInputs(std::vector& out) const + { + for (const RGResourceAccess& access : accesses) + { + if (access.isRead()) { out.push_back(access); } + } + for (const RGColorTarget& ct : colorTargets) + { + if (ct.loadOp == rhi::LoadOp::Load) + { + out.push_back(RGResourceAccess{ ct.handle, RGAccessType::ReadTexture, ct.subresource }); + } + } + if (depthTarget.has_value()) + { + const RGDepthTarget& dt = depthTarget.value(); + if (dt.depthLoadOp == rhi::LoadOp::Load || dt.readOnly) + { + out.push_back(RGResourceAccess{ dt.handle, RGAccessType::ReadDepthStencil, dt.subresource }); + } + } + } + + // Resource handles this pass writes (declared accesses + Store attachments). + void getOutputs(std::vector& out) const + { + for (const RGResourceAccess& access : accesses) + { + if (access.isWrite()) { out.push_back(access); } + } + for (const RGColorTarget& ct : colorTargets) + { + if (ct.storeOp == rhi::StoreOp::Store) + { + out.push_back(RGResourceAccess{ ct.handle, RGAccessType::WriteColorTarget, ct.subresource }); + } + } + if (depthTarget.has_value()) + { + const RGDepthTarget& dt = depthTarget.value(); + if (dt.depthStoreOp == rhi::StoreOp::Store && !dt.readOnly) + { + out.push_back(RGResourceAccess{ dt.handle, RGAccessType::WriteDepthTarget, dt.subresource }); + } + } + } + + [[nodiscard]] bool shouldSurviveCulling() const noexcept { return neverCull || hasSideEffects; } + + // --- identity --- + std::u8string name; + RGPassType type; + rhi::QueueType queueType = rhi::QueueType::Graphics; + + // --- declared work --- + std::vector accesses; + std::vector colorTargets; + std::optional depthTarget; + std::vector dependencies; + + // --- optional per-pass viewport/scissor override (else the full attachment is used) --- + bool hasViewport = false; + i32 viewportX = 0, viewportY = 0; + u32 viewportW = 0, viewportH = 0; + + // --- compile flags --- + bool isCulled = false; + bool neverCull = false; + bool hasSideEffects = false; + std::function condition; // optional runtime skip condition + i32 executionOrder = -1; // assigned during topological sort + + // --- typed execute callbacks (one is set per pass type) --- + RenderPassExecuteCallback executeCallback; + RenderBundlePassCallback bundleCallback; // render pass whose body is executed bundles + ComputePassExecuteCallback computeCallback; + CopyPassExecuteCallback copyCallback; + }; +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraphResource.cppm b/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraphResource.cppm new file mode 100644 index 00000000..0efb2e7a --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraphResource.cppm @@ -0,0 +1,146 @@ +// Draconic::RenderGraph - :resource partition +// +// A resource managed by the graph (texture or buffer): its descriptor, the +// allocated GPU object, reference/lifetime tracking computed during compile, and +// Fields are public data the graph orchestrator manipulates directly. + +module; + +#include +#include +#include +#include + +export module rendergraph:resource; + +import core; +import rhi; +import :types; +import :descriptors; +import :persistent_resource; + +using namespace draco; + +export namespace draco::rendergraph +{ + namespace rhi = draco::rhi; + + class RenderGraphResource + { + public: + RenderGraphResource(std::u8string_view resourceName, RGResourceType type, RGResourceLifetime life) + : name(resourceName), resourceType(type), lifetime(life) {} + + // Allocate GPU resources for a transient texture. + [[nodiscard]] Status allocateTexture(rhi::Device& device) + { + rhi::TextureDesc rhiDesc = textureDesc.toTextureDesc(name); + rhiDesc.usage = rhiDesc.usage | rhi::TextureUsage::Sampled; // may be sampled + if (rhi::isDepthFormat(textureDesc.format)) { rhiDesc.usage = rhiDesc.usage | rhi::TextureUsage::DepthStencil; } + else { rhiDesc.usage = rhiDesc.usage | rhi::TextureUsage::RenderTarget; } + + rhi::Texture* tex = nullptr; + if (!device.createTexture(rhiDesc, tex).isOk()) { return Status{ ErrorCode::Unknown }; } + texture = tex; + lastKnownState = tex->initialState; + + rhi::TextureView* view = nullptr; + if (!device.createTextureView(tex, rhi::TextureViewDesc{}, view).isOk()) { return Status{ ErrorCode::Unknown }; } + textureView = view; + + // Depth-only view for depth/stencil textures (shader sampling of depth). + if (rhi::isDepthFormat(textureDesc.format) && rhi::hasStencil(textureDesc.format)) + { + rhi::TextureViewDesc depthDesc{}; + depthDesc.aspect = rhi::TextureAspect::DepthOnly; + depthDesc.label = u8"RGDepthOnlyView"; + rhi::TextureView* depthOnly = nullptr; + if (!device.createTextureView(tex, depthDesc, depthOnly).isOk()) { return Status{ ErrorCode::Unknown }; } + depthOnlyView = depthOnly; + } + return Status{}; + } + + // Allocate GPU resources for a transient buffer. + [[nodiscard]] Status allocateBuffer(rhi::Device& device) + { + rhi::BufferDesc rhiDesc{}; + rhiDesc.size = bufferDesc.size; + rhiDesc.usage = bufferDesc.usage; + rhiDesc.label = name; + + rhi::Buffer* buf = nullptr; + if (!device.createBuffer(rhiDesc, buf).isOk()) { return Status{ ErrorCode::Unknown }; } + buffer = buf; + lastKnownState = rhi::ResourceState::Undefined; + return Status{}; + } + + // Release GPU resources for a transient resource (no-op otherwise). + void releaseTransient(rhi::Device& device) + { + if (lifetime != RGResourceLifetime::Transient) { return; } + if (depthOnlyView != nullptr) { device.destroyTextureView(depthOnlyView); } + if (textureView != nullptr) { device.destroyTextureView(textureView); } + if (texture != nullptr) { device.destroyTexture(texture); } + if (buffer != nullptr) { device.destroyBuffer(buffer); } + } + + [[nodiscard]] u32 totalMipLevels() const + { + if (texture != nullptr) { return texture->desc.mipLevelCount; } + if (resourceType == RGResourceType::Texture) { return textureDesc.mipLevelCount; } + return 1; + } + [[nodiscard]] u32 totalArrayLayers() const + { + if (texture != nullptr) { return texture->desc.arrayLayerCount; } + if (resourceType == RGResourceType::Texture) { return textureDesc.arrayLayerCount; } + return 1; + } + + void resetTracking() + { + refCount = 0; + firstWriter = PassHandle::invalid(); + lastReader = PassHandle::invalid(); + firstUsePass = -1; + lastUsePass = -1; + } + + // --- identity / lifetime --- + std::u8string name; + RGResourceType resourceType; + RGResourceLifetime lifetime; + u32 generation = 1; + + // --- reference tracking (computed during compile) --- + i32 refCount = 0; + PassHandle firstWriter = PassHandle::invalid(); + PassHandle lastReader = PassHandle::invalid(); + i32 firstUsePass = -1; // for aliasing + i32 lastUsePass = -1; + + // --- texture data --- + RGTextureDesc textureDesc; + rhi::Texture* texture = nullptr; + rhi::TextureView* textureView = nullptr; + rhi::TextureView* depthOnlyView = nullptr; + // Stable id of the backing physical texture; changes when a transient is (re)allocated a + // different texture (e.g. on resize). Consumers caching a bind group over textureView key on + // this so a reused-address view does not alias a stale, destroyed texture. + u64 textureGeneration = 0; + + // --- buffer data --- + RGBufferDesc bufferDesc; + rhi::Buffer* buffer = nullptr; + + // --- state tracking --- + rhi::ResourceState lastKnownState = rhi::ResourceState::Undefined; + std::optional finalState; // transition-to after last use (imported) + bool readableAfterWrite = false; // transition to ShaderRead after last writer + + // --- persistent data (null for transient/imported) --- + std::unique_ptr persistentData; + }; +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraphTests.test.cpp b/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraphTests.test.cpp new file mode 100644 index 00000000..cc7552f4 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/RenderGraphTests.test.cpp @@ -0,0 +1,87 @@ +// Direct unit tests for pieces without a dedicated Sedulous test file +// (SubresourceStateTracker, PersistentResource ping-pong, resource tracking). +// The Sedulous suite is ported in Type/Descriptor/PassBuilder/Dependency/ +// Culling/GraphCore/Barrier Tests. + +#include + + +import core; +import rhi; +import rendergraph; + +using namespace draco; +using namespace draco::rendergraph; +namespace rhi = draco::rhi; + +TEST_CASE("rg.persistent: ping-pong swap") +{ + auto* a = reinterpret_cast(0x10); + auto* b = reinterpret_cast(0x20); + auto* va = reinterpret_cast(0x30); + auto* vb = reinterpret_cast(0x40); + + PersistentResource single(a, va); + CHECK_FALSE(single.isPingPong()); + CHECK(single.currentTexture() == a); + CHECK(single.previousTexture() == a); + single.swap(); + CHECK(single.currentTexture() == a); + + PersistentResource pp(a, b, va, vb); + CHECK(pp.isPingPong()); + CHECK(pp.currentTexture() == a); + CHECK(pp.previousTexture() == b); + pp.swap(); + CHECK(pp.currentTexture() == b); + CHECK(pp.previousTexture() == a); +} + +TEST_CASE("rg.resource: tracking + totals from descriptor") +{ + RenderGraphResource res(u8"gbuffer", RGResourceType::Texture, RGResourceLifetime::Transient); + res.textureDesc.mipLevelCount = 4; + res.textureDesc.arrayLayerCount = 6; + + CHECK(res.totalMipLevels() == 4u); // no GPU texture -> from descriptor + CHECK(res.totalArrayLayers() == 6u); + + res.refCount = 3; + res.firstUsePass = 2; + res.resetTracking(); + CHECK(res.refCount == 0); + CHECK(res.firstUsePass == -1); + CHECK_FALSE(res.firstWriter.isValid()); + CHECK_FALSE(res.finalState.has_value()); +} + +TEST_CASE("rg.state_tracker: uniform fast path, divergence, collapse") +{ + using RS = rhi::ResourceState; + SubresourceStateTracker t(4, 2, RS::Undefined); + CHECK(t.isUniform()); + CHECK(t.getState(0, 0) == RS::Undefined); + + t.setState(RGSubresourceRange::all(), RS::ShaderRead); + CHECK(t.isUniform()); + CHECK(t.getState(3, 1) == RS::ShaderRead); + + t.setState(0, 1, 0, 1, RS::RenderTarget); + CHECK_FALSE(t.isUniform()); + CHECK(t.getState(0, 0) == RS::RenderTarget); + CHECK(t.getState(1, 0) == RS::ShaderRead); + + std::vector snapshot = t.copyStates(); + CHECK(snapshot.size() == 8u); + + t.setAll(RS::ShaderRead); + CHECK(t.isUniform()); + + SubresourceStateTracker restored(4, 2, RS::Undefined); + restored.initFromStates(snapshot, RS::Undefined); + CHECK_FALSE(restored.isUniform()); + CHECK(restored.getState(0, 0) == RS::RenderTarget); + + restored.setState(0, 1, 0, 1, RS::ShaderRead); + CHECK(restored.isUniform()); +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/SubresourceStateTracker.cppm b/Engine/cpp/Runtime/Rendering/RenderGraph/SubresourceStateTracker.cppm new file mode 100644 index 00000000..cfc439eb --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/SubresourceStateTracker.cppm @@ -0,0 +1,141 @@ +// Draconic::RenderGraph - :state_tracker partition +// +// Tracks ResourceState per subresource (mip x layer) with a uniform fast path: +// while all subresources share a state, only one value is stored; a per- +// subresource array is materialized lazily when states diverge, and collapsed +// (SubresourceStateTracker.bf); Beef's null-means-uniform becomes an empty Array. + +module; + +#include + +#include + +export module rendergraph:state_tracker; + +import core; +import rhi; +import :types; + +using namespace draco; + +export namespace draco::rendergraph +{ + namespace rhi = draco::rhi; + + class SubresourceStateTracker + { + public: + SubresourceStateTracker(u32 mipCount, u32 layerCount, rhi::ResourceState initialState) + : m_mipCount(std::max(mipCount, 1u)), m_layerCount(std::max(layerCount, 1u)), m_uniformState(initialState) {} + + [[nodiscard]] u32 mipCount() const noexcept { return m_mipCount; } + [[nodiscard]] u32 layerCount() const noexcept { return m_layerCount; } + [[nodiscard]] bool isUniform() const noexcept { return m_states.empty(); } + [[nodiscard]] rhi::ResourceState uniformState() const noexcept { return m_uniformState; } + + [[nodiscard]] rhi::ResourceState getState(u32 mip, u32 layer) const + { + if (m_states.empty()) { return m_uniformState; } + const u32 idx = mip + layer * m_mipCount; + if (idx >= m_states.size()) { return m_uniformState; } + return m_states[idx]; + } + + // count of 0 or ~0u means "all remaining from base". + void setState(u32 baseMip, u32 mipCount, u32 baseLayer, u32 layerCount, rhi::ResourceState state) + { + const u32 mipEnd = resolveEnd(baseMip, mipCount, m_mipCount); + const u32 layerEnd = resolveEnd(baseLayer, layerCount, m_layerCount); + + // Whole resource? Collapse to uniform. + if (baseMip == 0 && mipEnd >= m_mipCount && baseLayer == 0 && layerEnd >= m_layerCount) + { + m_uniformState = state; + m_states.clear(); + return; + } + + // Materialize per-subresource storage on first divergence. + if (m_states.empty()) + { + if (state == m_uniformState) { return; } + m_states.resize(m_mipCount * m_layerCount); + for (u32 i = 0; i < m_states.size(); ++i) { m_states[i] = m_uniformState; } + } + + for (u32 layer = baseLayer; layer < layerEnd; ++layer) + { + for (u32 mip = baseMip; mip < mipEnd; ++mip) + { + m_states[mip + layer * m_mipCount] = state; + } + } + + tryCollapseToUniform(); + } + + void setState(RGSubresourceRange range, rhi::ResourceState state) + { + const u32 mipCount = range.mipLevelCount == 0 ? 0xFFFFFFFFu : range.mipLevelCount; + const u32 layerCount = range.arrayLayerCount == 0 ? 0xFFFFFFFFu : range.arrayLayerCount; + setState(range.baseMipLevel, mipCount, range.baseArrayLayer, layerCount, state); + } + + void setAll(rhi::ResourceState state) + { + m_uniformState = state; + m_states.clear(); + } + + // Snapshot per-subresource states; empty if uniform. Caller owns the copy. + [[nodiscard]] std::vector copyStates() const + { + std::vector copy; + if (!m_states.empty()) + { + copy.resize(m_states.size()); + for (u32 i = 0; i < m_states.size(); ++i) { copy[i] = m_states[i]; } + } + return copy; + } + + // Restore from a per-subresource snapshot (empty/mismatched => uniform fallback). + void initFromStates(const std::vector& states, rhi::ResourceState uniformFallback) + { + if (states.size() != static_cast(m_mipCount) * m_layerCount) + { + m_uniformState = uniformFallback; + m_states.clear(); + return; + } + m_states.resize(states.size()); + for (usize i = 0; i < states.size(); ++i) { m_states[i] = states[i]; } + tryCollapseToUniform(); + } + + private: + void tryCollapseToUniform() + { + if (m_states.empty()) { return; } + const rhi::ResourceState first = m_states[0]; + for (usize i = 1; i < m_states.size(); ++i) + { + if (m_states[i] != first) { return; } + } + m_uniformState = first; + m_states.clear(); + } + + static u32 resolveEnd(u32 base, u32 count, u32 total) noexcept + { + if (count == 0 || count == 0xFFFFFFFFu) { return total; } + return std::min(base + count, total); + } + + u32 m_mipCount; + u32 m_layerCount; + rhi::ResourceState m_uniformState; + std::vector m_states; // empty => uniform + }; +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/TransientGenerationTests.test.cpp b/Engine/cpp/Runtime/Rendering/RenderGraph/TransientGenerationTests.test.cpp new file mode 100644 index 00000000..ea09906a --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/TransientGenerationTests.test.cpp @@ -0,0 +1,110 @@ +// Transient texture generation: each transient carries a stable id for its backing physical texture, +// surfaced via GetTextureGeneration. A bind-group cache over a transient's view keys on this (not the +// raw pointer) so a reused-address view can't alias a stale, destroyed texture across a resize. +// Driven on the Null RHI so Execute actually allocates/returns the transient. +#include + +import core; +import rhi; +import rhi.null; +import rendergraph; + +using namespace draco; +using namespace draco::rendergraph; +namespace rhi = draco::rhi; + +namespace { +struct Harness { + rhi::null::NullDevice device; + rhi::Texture* bb = nullptr; + rhi::TextureView* bbView = nullptr; + rhi::CommandPool* pool = nullptr; + rhi::CommandEncoder* enc = nullptr; + + bool init() { + if (!device.createTexture(rhi::TextureDesc::renderTarget(rhi::TextureFormat::BGRA8Unorm, 64, 64), bb).isOk()) { return false; } + rhi::TextureViewDesc vd{}; vd.format = rhi::TextureFormat::BGRA8Unorm; + if (!device.createTextureView(bb, vd, bbView).isOk()) { return false; } + if (!device.createCommandPool(rhi::QueueType::Graphics, pool).isOk()) { return false; } + return pool->createEncoder(enc).isOk(); + } + ~Harness() { + if (pool) { device.destroyCommandPool(pool); } + if (bbView) { device.destroyTextureView(bbView); } + if (bb) { device.destroyTexture(bb); } + } +}; + +// One frame: a transient HDR target written by one pass and read by a backbuffer pass (so it isn't +// culled). Captures the transient's generation + view inside the writing pass's execute. +void runFrame(RenderGraph& graph, Harness& h, i32 frameIndex, u64& outGen, rhi::TextureView*& outView) { + graph.setOutputSize(64, 64); + graph.beginFrame(frameIndex); + const RGHandle bbH = graph.importTarget(u8"BB", h.bb, h.bbView, rhi::ResourceState::Present); + const RGHandle hdr = graph.createTransient(u8"HDR", RGTextureDesc(rhi::TextureFormat::RGBA16Float, 64, 64)); + graph.addRenderPass(u8"WriteHDR", [&](PassBuilder& b) { + b.setColorTarget(0, hdr, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.neverCull(); + b.setExecute([&](rhi::RenderPassEncoder&) { + outGen = graph.getTextureGeneration(hdr); + outView = graph.getTextureView(hdr); + }); + }); + graph.addRenderPass(u8"ReadHDR", [&](PassBuilder& b) { + b.setColorTarget(0, bbH, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.readTexture(hdr); + b.neverCull(); + b.setExecute([](rhi::RenderPassEncoder&) {}); + }); + CHECK(graph.execute(h.enc).isOk()); + graph.endFrame(); +} +} + +TEST_CASE("rg.transient: generation is non-zero and stable across pool reuse") +{ + Harness h; REQUIRE(h.init()); + RenderGraph graph(&h.device); + + u64 gen0 = 0, gen1 = 0; + rhi::TextureView* v0 = nullptr; rhi::TextureView* v1 = nullptr; + runFrame(graph, h, 0, gen0, v0); + runFrame(graph, h, 1, gen1, v1); + + CHECK(gen0 != 0); // a freshly allocated transient gets a real id + CHECK(v0 != nullptr); + CHECK(gen1 == gen0); // same desc -> pool reuse -> SAME physical texture -> stable generation + CHECK(v1 == v0); // the same pooled view comes back (the case the cache optimizes for) +} + +TEST_CASE("rg.transient: distinct transients get distinct generations") +{ + Harness h; REQUIRE(h.init()); + RenderGraph graph(&h.device); + graph.setOutputSize(64, 64); + graph.beginFrame(0); + + const RGHandle bbH = graph.importTarget(u8"BB", h.bb, h.bbView, rhi::ResourceState::Present); + const RGHandle a = graph.createTransient(u8"A", RGTextureDesc(rhi::TextureFormat::RGBA16Float, 64, 64)); + const RGHandle b = graph.createTransient(u8"B", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm, 32, 32)); + + u64 genA = 0, genB = 0; + graph.addRenderPass(u8"PA", [&](PassBuilder& pb) { + pb.setColorTarget(0, a, rhi::LoadOp::Clear, rhi::StoreOp::Store); pb.neverCull(); + pb.setExecute([&](rhi::RenderPassEncoder&) { genA = graph.getTextureGeneration(a); }); + }); + graph.addRenderPass(u8"PB", [&](PassBuilder& pb) { + pb.setColorTarget(0, b, rhi::LoadOp::Clear, rhi::StoreOp::Store); pb.neverCull(); + pb.setExecute([&](rhi::RenderPassEncoder&) { genB = graph.getTextureGeneration(b); }); + }); + graph.addRenderPass(u8"Sink", [&](PassBuilder& pb) { + pb.setColorTarget(0, bbH, rhi::LoadOp::Clear, rhi::StoreOp::Store); + pb.readTexture(a); pb.readTexture(b); pb.neverCull(); + pb.setExecute([](rhi::RenderPassEncoder&) {}); + }); + + CHECK(graph.execute(h.enc).isOk()); + CHECK(genA != 0); + CHECK(genB != 0); + CHECK(genA != genB); // two distinct physical allocations -> distinct ids +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/TransientTexturePool.cppm b/Engine/cpp/Runtime/Rendering/RenderGraph/TransientTexturePool.cppm new file mode 100644 index 00000000..51c59990 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/TransientTexturePool.cppm @@ -0,0 +1,112 @@ +// Draconic::RenderGraph - :transient_pool partition +// +// Pools GPU textures for reuse across frames by transient resources, avoiding +// per-frame allocation thrashing. Matches by exact descriptor; ages out unused + +module; + +#include + +export module rendergraph:transient_pool; + +import core; +import rhi; + +using namespace draco; + +export namespace draco::rendergraph +{ + namespace rhi = draco::rhi; + + class TransientTexturePool + { + public: + explicit TransientTexturePool(rhi::Device& device) noexcept : m_device(&device) {} + ~TransientTexturePool() { destroyAll(); } + + TransientTexturePool(const TransientTexturePool&) = delete; + TransientTexturePool& operator=(const TransientTexturePool&) = delete; + + i32 maxUnusedFrames = 4; + + // Acquire a matching texture from the pool; true if one was found. `generation` receives the + // physical texture's stable id (preserved while it lives in the pool), so consumers can detect + // when a transient is backed by a DIFFERENT physical texture (a freed view address can reuse). + [[nodiscard]] bool tryAcquire(const rhi::TextureDesc& desc, rhi::Texture*& texture, rhi::TextureView*& view, u64& generation) + { + for (usize i = 0; i < m_pool.size(); ++i) + { + if (descriptorsMatch(m_pool[i].desc, desc)) + { + texture = m_pool[i].texture; + view = m_pool[i].view; + generation = m_pool[i].generation; + m_pool.erase(m_pool.begin() + i); + return true; + } + } + texture = nullptr; + view = nullptr; + generation = 0; + return false; + } + + void returnToPool(const rhi::TextureDesc& desc, rhi::Texture* texture, rhi::TextureView* view, u64 generation) + { + m_pool.push_back(PooledTexture{ desc, texture, view, generation, 0 }); + } + + // Age out entries unused for more than maxUnusedFrames. + void endFrame() + { + for (usize i = m_pool.size(); i-- > 0;) + { + ++m_pool[i].unusedFrames; + if (m_pool[i].unusedFrames > maxUnusedFrames) + { + rhi::Texture* tex = m_pool[i].texture; + rhi::TextureView* view = m_pool[i].view; + if (view != nullptr) { m_device->destroyTextureView(view); } + if (tex != nullptr) { m_device->destroyTexture(tex); } + m_pool.erase(m_pool.begin() + i); + } + } + } + + void destroyAll() + { + for (PooledTexture& entry : m_pool) + { + if (entry.view != nullptr) { m_device->destroyTextureView(entry.view); } + if (entry.texture != nullptr) { m_device->destroyTexture(entry.texture); } + } + m_pool.clear(); + } + + private: + struct PooledTexture + { + rhi::TextureDesc desc; + rhi::Texture* texture = nullptr; + rhi::TextureView* view = nullptr; + u64 generation = 0; // stable id of this physical texture (carried across pool reuse) + i32 unusedFrames = 0; + }; + + static bool descriptorsMatch(const rhi::TextureDesc& a, const rhi::TextureDesc& b) noexcept + { + return a.dimension == b.dimension + && a.format == b.format + && a.width == b.width + && a.height == b.height + && a.depth == b.depth + && a.arrayLayerCount == b.arrayLayerCount + && a.mipLevelCount == b.mipLevelCount + && a.sampleCount == b.sampleCount + && a.usage == b.usage; + } + + rhi::Device* m_device; + std::vector m_pool; + }; +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/TypeTests.test.cpp b/Engine/cpp/Runtime/Rendering/RenderGraph/TypeTests.test.cpp new file mode 100644 index 00000000..5922502f --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/TypeTests.test.cpp @@ -0,0 +1,102 @@ +#include +#include +#include + + +import core; +import rhi; +import rendergraph; + +using namespace draco; +using namespace draco::rendergraph; +namespace rhi = draco::rhi; + +namespace std { + template <> struct hash { + [[nodiscard]] std::size_t operator()(const draco::rendergraph::RGHandle& h) const noexcept { + return (static_cast(h.index) * 1099511628211ull) ^ h.generation; + } + }; +} + + +TEST_CASE("rg.type: handle equality") +{ + CHECK(RGHandle{ 1, 1 } == RGHandle{ 1, 1 }); + CHECK(RGHandle{ 1, 1 } != RGHandle{ 2, 1 }); + CHECK(RGHandle{ 1, 1 } != RGHandle{ 1, 2 }); // generation matters +} + +TEST_CASE("rg.type: handle validity") +{ + CHECK_FALSE(RGHandle::invalid().isValid()); + CHECK(RGHandle{ 0, 1 }.isValid()); + CHECK_FALSE(PassHandle::invalid().isValid()); + CHECK(PassHandle{ 0 }.isValid()); +} + +TEST_CASE("rg.type: handle works as a hash-map key") +{ + std::unordered_map map; + map.insert_or_assign(RGHandle{ 1, 1 }, 7); + const i32* found = mapFind(map, RGHandle{ 1, 1 }); // equal handle + REQUIRE(found != nullptr); + CHECK(*found == 7); + CHECK(mapFind(map, RGHandle{ 2, 1 }) == nullptr); +} + +TEST_CASE("rg.type: access IsRead / IsWrite") +{ + CHECK(isRead(RGAccessType::ReadTexture)); + CHECK(isRead(RGAccessType::ReadBuffer)); + CHECK(isRead(RGAccessType::ReadDepthStencil)); + CHECK(isRead(RGAccessType::SampleDepthStencil)); + CHECK(isRead(RGAccessType::ReadCopySrc)); + CHECK(isRead(RGAccessType::ReadWriteStorage)); + CHECK_FALSE(isRead(RGAccessType::WriteColorTarget)); + CHECK_FALSE(isRead(RGAccessType::WriteStorage)); + + CHECK(isWrite(RGAccessType::WriteColorTarget)); + CHECK(isWrite(RGAccessType::WriteDepthTarget)); + CHECK(isWrite(RGAccessType::WriteStorage)); + CHECK(isWrite(RGAccessType::WriteCopyDst)); + CHECK(isWrite(RGAccessType::ReadWriteStorage)); + CHECK_FALSE(isWrite(RGAccessType::ReadTexture)); + CHECK_FALSE(isWrite(RGAccessType::ReadBuffer)); + CHECK_FALSE(isWrite(RGAccessType::SampleDepthStencil)); +} + +TEST_CASE("rg.type: access -> resource state") +{ + using RS = rhi::ResourceState; + CHECK(toResourceState(RGAccessType::ReadTexture) == RS::ShaderRead); + CHECK(toResourceState(RGAccessType::WriteColorTarget) == RS::RenderTarget); + CHECK(toResourceState(RGAccessType::WriteDepthTarget) == RS::DepthStencilWrite); + CHECK(toResourceState(RGAccessType::ReadDepthStencil) == RS::DepthStencilRead); + // Sampling a depth texture in a shader uses the same read-only depth layout as a + // read-only depth attachment (DEPTH_STENCIL_READ_ONLY_OPTIMAL), not ShaderRead. + CHECK(toResourceState(RGAccessType::SampleDepthStencil) == RS::DepthStencilRead); + CHECK(toResourceState(RGAccessType::ReadCopySrc) == RS::CopySrc); + CHECK(toResourceState(RGAccessType::WriteCopyDst) == RS::CopyDst); + CHECK(toResourceState(RGAccessType::WriteStorage) == RS::ShaderWrite); +} + +TEST_CASE("rg.type: subresource All + overlap") +{ + const RGSubresourceRange all = RGSubresourceRange::all(); + CHECK(all.isAll()); + CHECK(all.baseMipLevel == 0u); + CHECK(all.mipLevelCount == 0u); + + const RGSubresourceRange layer0{ 0, 1, 0, 1 }; + const RGSubresourceRange layer1{ 0, 1, 1, 1 }; + CHECK_FALSE(layer0.overlaps(layer1, 1, 4)); + CHECK(all.overlaps(layer0, 1, 4)); + CHECK(all.overlaps(layer1, 1, 4)); + CHECK(layer0.overlaps(layer0, 1, 4)); + + const RGSubresourceRange mip0{ 0, 1, 0, 0 }; + const RGSubresourceRange mip1{ 1, 1, 0, 0 }; + CHECK_FALSE(mip0.overlaps(mip1, 4, 1)); + CHECK(mip0.overlaps(mip0, 4, 1)); +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/Types.cppm b/Engine/cpp/Runtime/Rendering/RenderGraph/Types.cppm new file mode 100644 index 00000000..a05a150c --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/Types.cppm @@ -0,0 +1,216 @@ +// Draconic::RenderGraph - :types partition +// +// Core value types: resource/pass handles, pass + access enums, subresource +// ResourceState, the currency the barrier solver works in. + +module; + +#include +#include +#include +#include + +export module rendergraph:types; + +import core; +import rhi; + +using namespace draco; + +export namespace draco::rendergraph +{ + namespace rhi = draco::rhi; + + // NOTE: local stand-in. Replace with a core container helper once draco.core + // provides a map with pointer-or-null lookup (Sedulous's HashMap::Find). + // Look up a key in a std map/set-like container, returning a pointer to the + // mapped value or nullptr (mirrors the pointer-or-null lookup the solver uses). + template + [[nodiscard]] auto* mapFind(Map& m, const Key& k) noexcept + { + auto it = m.find(k); + using P = decltype(&it->second); + return it != m.end() ? &it->second : P{nullptr}; + } + + // NOTE: local stand-in. Replace with a core text-formatting facility once + // draco.core provides one (Sedulous's AppendFormat). Substitutes each "{}" in + // `fmt` with the next argument (integers, floats, and UTF-8 string views). + namespace detail + { + inline void rgAppendArg(std::u8string& out, std::u8string_view v) { out.append(v); } + template requires std::is_arithmetic_v + void rgAppendArg(std::u8string& out, T v) + { + const std::string s = std::to_string(v); + out.append(reinterpret_cast(s.data()), s.size()); + } + } + + inline void appendFormat(std::u8string& out, std::u8string_view fmt) { out.append(fmt); } + + template + void appendFormat(std::u8string& out, std::u8string_view fmt, A&& a, Rest&&... rest) + { + const auto pos = fmt.find(u8"{}"); + if (pos == std::u8string_view::npos) { out.append(fmt); return; } + out.append(fmt.substr(0, pos)); + detail::rgAppendArg(out, std::forward(a)); + appendFormat(out, fmt.substr(pos + 2), std::forward(rest)...); + } + + // Handle to a graph resource (texture or buffer); generation-checked for staleness. + struct RGHandle + { + u32 index = 0xFFFFFFFFu; + u32 generation = 0; + + [[nodiscard]] static constexpr RGHandle invalid() noexcept { return RGHandle{ 0xFFFFFFFFu, 0 }; } + [[nodiscard]] constexpr bool isValid() const noexcept { return index != 0xFFFFFFFFu; } + }; + + [[nodiscard]] constexpr bool operator==(RGHandle a, RGHandle b) noexcept + { + return a.index == b.index && a.generation == b.generation; + } + [[nodiscard]] constexpr bool operator!=(RGHandle a, RGHandle b) noexcept { return !(a == b); } + + // Handle to a graph pass. + struct PassHandle + { + u32 index = 0xFFFFFFFFu; + + [[nodiscard]] static constexpr PassHandle invalid() noexcept { return PassHandle{ 0xFFFFFFFFu }; } + [[nodiscard]] constexpr bool isValid() const noexcept { return index != 0xFFFFFFFFu; } + }; + + [[nodiscard]] constexpr bool operator==(PassHandle a, PassHandle b) noexcept { return a.index == b.index; } + [[nodiscard]] constexpr bool operator!=(PassHandle a, PassHandle b) noexcept { return a.index != b.index; } + + enum class RGPassType : u8 { Render, Compute, Copy }; + + // Type of resource access declared by a pass. + enum class RGAccessType : u8 + { + // Reads + ReadTexture, ReadBuffer, ReadDepthStencil, ReadCopySrc, + // Sample a DEPTH texture in a shader: like ReadTexture (sampled, not an attachment) but the + // layout must be DepthStencilRead (DEPTH_STENCIL_READ_ONLY_OPTIMAL), not ShaderRead. + SampleDepthStencil, + // Writes + WriteColorTarget, WriteDepthTarget, WriteStorage, WriteCopyDst, + // Read + Write + ReadWriteStorage, ReadWriteDepthTarget, ReadWriteColorTarget, + }; + + [[nodiscard]] constexpr bool isRead(RGAccessType type) noexcept + { + switch (type) + { + case RGAccessType::ReadTexture: + case RGAccessType::ReadBuffer: + case RGAccessType::ReadDepthStencil: + case RGAccessType::SampleDepthStencil: + case RGAccessType::ReadCopySrc: + case RGAccessType::ReadWriteStorage: + case RGAccessType::ReadWriteDepthTarget: + case RGAccessType::ReadWriteColorTarget: + return true; + default: + return false; + } + } + + [[nodiscard]] constexpr bool isWrite(RGAccessType type) noexcept + { + switch (type) + { + case RGAccessType::WriteColorTarget: + case RGAccessType::WriteDepthTarget: + case RGAccessType::WriteStorage: + case RGAccessType::WriteCopyDst: + case RGAccessType::ReadWriteStorage: + case RGAccessType::ReadWriteDepthTarget: + case RGAccessType::ReadWriteColorTarget: + return true; + default: + return false; + } + } + + [[nodiscard]] constexpr rhi::ResourceState toResourceState(RGAccessType type) noexcept + { + using RS = rhi::ResourceState; + switch (type) + { + case RGAccessType::ReadTexture: return RS::ShaderRead; + case RGAccessType::ReadBuffer: return RS::ShaderRead; + case RGAccessType::ReadDepthStencil: return RS::DepthStencilRead; + case RGAccessType::SampleDepthStencil: return RS::DepthStencilRead; + case RGAccessType::ReadCopySrc: return RS::CopySrc; + case RGAccessType::WriteColorTarget: return RS::RenderTarget; + case RGAccessType::WriteDepthTarget: return RS::DepthStencilWrite; + case RGAccessType::WriteStorage: return RS::ShaderWrite; + case RGAccessType::WriteCopyDst: return RS::CopyDst; + case RGAccessType::ReadWriteStorage: return RS::ShaderWrite | RS::ShaderRead; + case RGAccessType::ReadWriteDepthTarget: return RS::DepthStencilWrite; + case RGAccessType::ReadWriteColorTarget: return RS::RenderTarget; + } + return RS::Undefined; + } + + // Subresource range for fine-grained access tracking (e.g. shadow cascades). + // A count of 0 means "all remaining from the base". + struct RGSubresourceRange + { + u32 baseMipLevel = 0; + u32 mipLevelCount = 0; + u32 baseArrayLayer = 0; + u32 arrayLayerCount = 0; + + [[nodiscard]] static constexpr RGSubresourceRange all() noexcept { return RGSubresourceRange{}; } + + [[nodiscard]] constexpr bool isAll() const noexcept + { + return baseMipLevel == 0 && mipLevelCount == 0 && baseArrayLayer == 0 && arrayLayerCount == 0; + } + + [[nodiscard]] constexpr bool overlaps(RGSubresourceRange other, u32 totalMips = 1, u32 totalLayers = 1) const noexcept + { + const u32 myMipEnd = mipLevelCount == 0 ? totalMips : baseMipLevel + mipLevelCount; + const u32 otherMipEnd = other.mipLevelCount == 0 ? totalMips : other.baseMipLevel + other.mipLevelCount; + const u32 myLayerEnd = arrayLayerCount == 0 ? totalLayers : baseArrayLayer + arrayLayerCount; + const u32 otherLayerEnd = other.arrayLayerCount == 0 ? totalLayers : other.baseArrayLayer + other.arrayLayerCount; + + const bool mipOverlap = baseMipLevel < otherMipEnd && other.baseMipLevel < myMipEnd; + const bool layerOverlap = baseArrayLayer < otherLayerEnd && other.baseArrayLayer < myLayerEnd; + return mipOverlap && layerOverlap; + } + }; + + [[nodiscard]] constexpr bool operator==(RGSubresourceRange a, RGSubresourceRange b) noexcept + { + return a.baseMipLevel == b.baseMipLevel && a.mipLevelCount == b.mipLevelCount + && a.baseArrayLayer == b.baseArrayLayer && a.arrayLayerCount == b.arrayLayerCount; + } + [[nodiscard]] constexpr bool operator!=(RGSubresourceRange a, RGSubresourceRange b) noexcept { return !(a == b); } + + // A single resource access declared by a pass. + struct RGResourceAccess + { + RGHandle handle = RGHandle::invalid(); + RGAccessType type = RGAccessType::ReadTexture; + RGSubresourceRange subresource; + + [[nodiscard]] bool isRead() const noexcept { return rendergraph::isRead(type); } + [[nodiscard]] bool isWrite() const noexcept { return rendergraph::isWrite(type); } + [[nodiscard]] rhi::ResourceState toResourceState() const noexcept { return rendergraph::toResourceState(type); } + }; + + // How transient resource dimensions resolve relative to the graph output. + enum class SizeMode : u8 { FullSize, HalfSize, QuarterSize, Custom }; + + enum class RGResourceLifetime : u8 { Transient, Persistent, Imported }; + + enum class RGResourceType : u8 { Texture, Buffer }; +} diff --git a/Engine/cpp/Runtime/Rendering/RenderGraph/ValidationTests.test.cpp b/Engine/cpp/Runtime/Rendering/RenderGraph/ValidationTests.test.cpp new file mode 100644 index 00000000..d210a4c9 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/RenderGraph/ValidationTests.test.cpp @@ -0,0 +1,110 @@ +#include + + +import core; +import rhi; +import rendergraph; + +using namespace draco; +using namespace draco::rendergraph; +namespace rhi = draco::rhi; + +namespace +{ + bool contains(std::u8string_view hay, std::u8string_view needle) + { + if (needle.size() > hay.size()) { return false; } + for (usize i = 0; i + needle.size() <= hay.size(); ++i) + { + if (hay.substr(i, needle.size()) == needle) { return true; } + } + return false; + } + + bool hasSeverity(const std::vector& messages, ValidationSeverity severity) + { + for (const ValidationMessage& m : messages) { if (m.severity == severity) { return true; } } + return false; + } +} + +TEST_CASE("rg.validation: uninitialized read is an error") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle tex = graph.createTransient(u8"Tex", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + graph.addRenderPass(u8"BadPass", [&](PassBuilder& b) { b.readTexture(tex); b.neverCull(); }); + + std::vector messages; + GraphValidator::validate(graph, messages); + CHECK(hasSeverity(messages, ValidationSeverity::Error)); +} + +TEST_CASE("rg.validation: reading an imported resource is fine") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle imported = graph.importTarget(u8"External", nullptr, nullptr); + graph.addRenderPass(u8"ReadImported", [&](PassBuilder& b) { b.readTexture(imported); b.neverCull(); }); + + std::vector messages; + GraphValidator::validate(graph, messages); + CHECK_FALSE(hasSeverity(messages, ValidationSeverity::Error)); +} + +TEST_CASE("rg.validation: empty pass is a warning") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + graph.addRenderPass(u8"Empty", [](PassBuilder& b) { b.neverCull(); }); + + std::vector messages; + GraphValidator::validate(graph, messages); + CHECK(hasSeverity(messages, ValidationSeverity::Warning)); +} + +TEST_CASE("rg.validation: redundant write is a warning") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle tex = graph.createTransient(u8"Tex", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + graph.addRenderPass(u8"Write1", [&](PassBuilder& b) { b.setColorTarget(0, tex, rhi::LoadOp::Clear, rhi::StoreOp::Store); b.neverCull(); }); + graph.addRenderPass(u8"Write2", [&](PassBuilder& b) { b.setColorTarget(0, tex, rhi::LoadOp::Clear, rhi::StoreOp::Store); b.neverCull(); }); + + std::vector messages; + GraphValidator::validate(graph, messages); + CHECK(hasSeverity(messages, ValidationSeverity::Warning)); +} + +TEST_CASE("rg.validation: a clean graph produces no messages") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle tex = graph.createTransient(u8"Tex", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + graph.addRenderPass(u8"Write", [&](PassBuilder& b) { + b.setColorTarget(0, tex, rhi::LoadOp::Clear, rhi::StoreOp::Store); + b.neverCull(); + b.setExecute([](rhi::RenderPassEncoder&) {}); + }); + graph.addRenderPass(u8"Read", [&](PassBuilder& b) { + b.readTexture(tex); + b.neverCull(); + b.setExecute([](rhi::RenderPassEncoder&) {}); + }); + + std::vector messages; + GraphValidator::validate(graph, messages); + CHECK(messages.size() == 0u); +} + +TEST_CASE("rg.validation: ValidateToString formats output") +{ + RenderGraph graph(nullptr); + graph.beginFrame(0); + const RGHandle tex = graph.createTransient(u8"Tex", RGTextureDesc(rhi::TextureFormat::RGBA8Unorm)); + graph.addRenderPass(u8"BadRead", [&](PassBuilder& b) { b.readTexture(tex); b.neverCull(); }); + + std::u8string result; + GraphValidator::validateToString(graph, result); + CHECK(contains(result, u8"issue")); +} diff --git a/Engine/cpp/Runtime/Rendering/Renderer/Renderer.cpp b/Engine/cpp/Runtime/Rendering/Renderer/Renderer.cpp deleted file mode 100644 index eaea237b..00000000 --- a/Engine/cpp/Runtime/Rendering/Renderer/Renderer.cpp +++ /dev/null @@ -1,150 +0,0 @@ -module; - -#include -#include -#include -#include -#include - -#include - -module rendering.renderer; - -import core.stdtypes; -import core.math.transform; -import rendering.rhi; -import rendering.rhi.uniform_registry; -import rendering.rendergraph; -import rendering.mesh; -import rendering.material; -import rendering.quad; - -namespace draco::rendering::renderer -{ - static constexpr const char* MAIN_PASS = "MainPass"; - - void init(u16 width, u16 height) - { - g_ctx.screenWidth = width; - g_ctx.screenHeight = height; - } - - void resize(u16 width, u16 height) - { - g_ctx.screenWidth = width; - g_ctx.screenHeight = height; - } - - void beginFrame(const Camera& cam) - { - rhi::beginFrame(); - - g_ctx.mainCamera = cam; - g_ctx.graph.reset(); - - // Create main pass once per frame - auto& pass = g_ctx.graph.addPass(MAIN_PASS); - - pass.view = 0; - pass.framebuffer = rhi::InvalidFramebuffer; - - pass.width = g_ctx.screenWidth; - pass.height = g_ctx.screenHeight; - - pass.clearFlags = BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH; - pass.clearColor = 0x303030ff; - - rhi::lookAt(pass.viewMatrix, cam.position.data(), cam.target.data(), cam.up.data()); - const f32 aspect = static_cast(g_ctx.screenWidth) / static_cast(std::max(g_ctx.screenHeight, 1)); - rhi::perspective(pass.projMatrix, cam.fov, aspect, cam.nearPlane, cam.farPlane); - } - - static void buildUniforms(const material::Material& mat, std::vector& out) - { - out.clear(); - out.reserve(mat.uniforms.size()); - - for (const auto& u : mat.uniforms) - { - rhi::UniformBind bind{}; - - bind.handle = rhi::getUniform(u.nameHash); - - bind.data = u.data; - bind.num = u.count; - - if (bind.handle == rhi::InvalidUniform) - { - std::println("[Renderer] Missing uniform hash: {}", u.nameHash); - continue; - } - - out.push_back(bind); - } - } - - void submitEntity(const rhi::RenderPacket& packet) - { - auto* pass = g_ctx.graph.getPass(MAIN_PASS); - if (!pass) return; - - pass->packets.push_back(packet); - } - - void submitRenderable(const draco::math::Transform& transform, const material::Material& material, mesh::MeshHandle mesh_id) - { - const auto* m = mesh::get(mesh_id); - if (!m) return; - - rhi::RenderPacket p{}; - - p.vertexBuffer = m->vbh; - p.indexBuffer = m->ibh; - - p.pipeline = material.pipeline; - p.textureHandle = material.texture; - p.textureUnit = material.texture_unit; - p.samplerUniform = material.sampler; - - buildUniforms(material, p.uniforms); - - transform.toMatrix(p.model); - - submitEntity(p); - } - - void submitUI(quad::QuadRenderer& quad_renderer) - { - auto& ui_pass = g_ctx.graph.addPass("UIPass"); - - ui_pass.view = 1; - ui_pass.sortMode = rendergraph::SortMode::None; - - ui_pass.framebuffer = rhi::InvalidFramebuffer; - - ui_pass.width = g_ctx.screenWidth; - ui_pass.height = g_ctx.screenHeight; - - ui_pass.clearFlags = 0; - - quad::OrthoCamera ortho; - - quad::QuadRenderer::buildOrtho(ortho, g_ctx.screenWidth, g_ctx.screenHeight); - - std::memcpy(ui_pass.viewMatrix, ortho.view, sizeof(f32) * 16); - std::memcpy(ui_pass.projMatrix, ortho.proj, sizeof(f32) * 16); - - quad_renderer.flushToPass(ui_pass); - } - - void endFrame() - { - g_ctx.graph.execute(); - rhi::endFrame(); - } - - rendergraph::RenderGraph& getGraph() - { - return g_ctx.graph; - } -} diff --git a/Engine/cpp/Runtime/Rendering/Renderer/Renderer.cppm b/Engine/cpp/Runtime/Rendering/Renderer/Renderer.cppm deleted file mode 100644 index 6ab3efb8..00000000 --- a/Engine/cpp/Runtime/Rendering/Renderer/Renderer.cppm +++ /dev/null @@ -1,48 +0,0 @@ -module; - -#include - -export module rendering.renderer; - -import core.stdtypes; -import core.math.transform; -import rendering.rhi; -import rendering.rendergraph; -import rendering.quad; -import rendering.material; -import rendering.mesh; - -export namespace draco::rendering::renderer { - - struct Camera { - std::array position = {0.0f, 0.0f, 0.0f}; - std::array target = {0.0f, 0.0f, 0.0f}; - std::array up = {0.0f, 1.0f, 0.0f}; - f32 fov = 60.0f; - f32 nearPlane = 0.1f; - f32 farPlane = 1000.0f; - }; - - struct SceneContext { - u16 screenWidth = 0; - u16 screenHeight = 0; - Camera mainCamera; - - rendergraph::RenderGraph graph; - }; - - inline SceneContext g_ctx; - - void init(u16 width, u16 height); - void resize(u16 width, u16 height); - - void beginFrame(const Camera& cam); - - void submitEntity(rhi::RenderPacket& packet, u16 view); - void submitRenderable(const math::Transform& transform, const material::Material& material, mesh::MeshHandle mesh_id); - void submitUI(quad::QuadRenderer& quad_renderer); - - void endFrame(); - - rendergraph::RenderGraph& getGraph(); -} diff --git a/Engine/cpp/Runtime/Rendering/Rendering.cppm b/Engine/cpp/Runtime/Rendering/Rendering.cppm deleted file mode 100644 index 36f000b2..00000000 --- a/Engine/cpp/Runtime/Rendering/Rendering.cppm +++ /dev/null @@ -1,9 +0,0 @@ -export module rendering; -export import rendering.rhi; -export import rendering.rhi.uniform_registry; -export import rendering.rhi.vertex; -export import rendering.rendergraph; -export import rendering.renderer; -export import rendering.mesh; -export import rendering.material; -export import rendering.quad; diff --git a/Engine/cpp/Runtime/Rendering/Shaders/Compiler.cppm b/Engine/cpp/Runtime/Rendering/Shaders/Compiler.cppm new file mode 100644 index 00000000..9db2bb14 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Shaders/Compiler.cppm @@ -0,0 +1,295 @@ +/// DXC shader compiler - loads dxcompiler.dll/libdxcompiler.so at runtime. + +module; + +#include "DxcIncludes.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#endif + +export module shaders:compiler; + +import core.stdtypes; +import core.status; +import :types; + +using namespace draco; + +export namespace draco::shaders { + +/// Configuration for compiler creation. +struct CompilerDesc { + /// Optional override path to the DXC shared library. + std::u8string_view dxcompilerPath{}; +}; + +/// HLSL shader compiler backed by DXC (IDxcCompiler3). +struct Compiler { + void* state = nullptr; + + [[nodiscard]] Status compile(const u8* source, usize sourceSize, + ShaderStage stage, std::u8string_view entryPoint, + ShaderTarget target, const CompileOptions& options, + CompileResult& out); + + void freeResult(CompileResult& result); + void destroy(); +}; + +[[nodiscard]] Status createCompiler(const CompilerDesc& desc, Compiler*& out); + +} // namespace draco::shaders (exported) + +// ---- Implementation ---- + +namespace draco::shaders { + +#ifdef _WIN32 +using DynLibHandle = HMODULE; +#else +using DynLibHandle = void*; +#endif + +struct CompilerState { + DynLibHandle dxcompiler = nullptr; + DxcCreateInstanceProc createInst = nullptr; + IDxcCompiler3* dxc = nullptr; + IDxcUtils* utils = nullptr; + IDxcIncludeHandler* includeHdlr = nullptr; +}; + +static CompilerState* stateOf(Compiler* c) { return static_cast(c->state); } + +// UTF-8 view -> std::wstring for DXC. Decodes UTF-8 codepoints, then on Windows +// (wchar_t = UTF-16) emits surrogate pairs for astral code points; on Linux +// (wchar_t = UTF-32) emits the codepoint directly. +static std::wstring widen(std::u8string_view s) { + std::wstring out; + out.reserve(s.size()); + usize i = 0; + while (i < s.size()) { + const u8 lead = static_cast(s.data()[i]); ++i; + char32_t cp; int extra; + if (lead < 0x80u) { cp = lead; extra = 0; } + else if ((lead & 0xE0u) == 0xC0u) { cp = lead & 0x1Fu; extra = 1; } + else if ((lead & 0xF0u) == 0xE0u) { cp = lead & 0x0Fu; extra = 2; } + else if ((lead & 0xF8u) == 0xF0u) { cp = lead & 0x07u; extra = 3; } + else { cp = 0xFFFDu; extra = 0; } + for (int k = 0; k < extra && i < s.size(); ++k) { + cp = (cp << 6) | (static_cast(s.data()[i]) & 0x3Fu); ++i; + } +#ifdef _WIN32 + if (cp <= 0xFFFFu) { out.push_back(static_cast(cp)); } + else { + cp -= 0x10000u; + out.push_back(static_cast(0xD800u + (cp >> 10))); + out.push_back(static_cast(0xDC00u + (cp & 0x3FFu))); + } +#else + out.push_back(static_cast(cp)); +#endif + } + return out; +} + +static const wchar_t* stagePrefix(ShaderStage stage) { + switch (stage) { + case ShaderStage::Vertex: return L"vs"; + case ShaderStage::Fragment: return L"ps"; + case ShaderStage::Compute: return L"cs"; + case ShaderStage::Mesh: return L"ms"; + case ShaderStage::Task: return L"as"; + case ShaderStage::RayGen: + case ShaderStage::ClosestHit: + case ShaderStage::AnyHit: + case ShaderStage::Miss: + case ShaderStage::Intersection: + case ShaderStage::Callable: return L"lib"; + } + return L"vs"; +} + +Status Compiler::compile(const u8* source, usize sourceSize, + ShaderStage stage, std::u8string_view entryPoint, + ShaderTarget target, const CompileOptions& options, + CompileResult& out) { + auto* s = stateOf(this); + out = {}; + + std::vector argStorage; + argStorage.reserve(64); + auto push = [&](const wchar_t* a) { argStorage.emplace_back(a); }; + auto pushS = [&](std::wstring a) { argStorage.emplace_back(std::move(a)); }; + + push(L"-E"); + pushS(entryPoint.empty() ? L"main" : widen(entryPoint)); + + std::wstring profile; + profile.append(stagePrefix(stage)); + profile.append(L"_"); + profile.append(widen(options.shaderModel)); + push(L"-T"); pushS(std::move(profile)); + + if (target == ShaderTarget::SPIRV) { + push(L"-spirv"); + push(L"-fspv-target-env=vulkan1.3"); + for (u32 set = 0; set < options.bindingShiftSets; ++set) { + wchar_t setBuf[16]; swprintf(setBuf, 16, L"%u", set); + std::wstring setStr = setBuf; + auto pushShift = [&](const wchar_t* flag, u32 shift) { + if (shift == 0) return; + wchar_t shBuf[16]; swprintf(shBuf, 16, L"%u", shift); + push(flag); pushS(shBuf); pushS(setStr); + }; + pushShift(L"-fvk-b-shift", options.bindingShifts.constantBufferShift); + pushShift(L"-fvk-t-shift", options.bindingShifts.textureShift); + pushShift(L"-fvk-s-shift", options.bindingShifts.samplerShift); + pushShift(L"-fvk-u-shift", options.bindingShifts.uavShift); + } + } + + push(options.rowMajorMatrices ? L"-Zpr" : L"-Zpc"); + + switch (options.optimizationLevel) { + case 0: push(L"-O0"); break; case 1: push(L"-O1"); break; + case 2: push(L"-O2"); break; default: push(L"-O3"); break; + } + if (options.enableDebugInfo) push(L"-Zi"); + + for (usize i = 0; i < options.defines.size(); ++i) { + std::wstring arg = L"-D"; + arg.append(widen(options.defines[i].name)); + if (!options.defines[i].value.empty()) { arg.append(L"="); arg.append(widen(options.defines[i].value)); } + pushS(std::move(arg)); + } + for (usize i = 0; i < options.includePaths.size(); ++i) { + push(L"-I"); pushS(widen(options.includePaths[i])); + } + push(L"-Wno-ignored-attributes"); + + std::vector args; + args.reserve(argStorage.size()); + for (const auto& w : argStorage) args.push_back(w.c_str()); + + DxcBuffer src{}; src.Ptr = source; src.Size = sourceSize; src.Encoding = DXC_CP_UTF8; + IDxcResult* result = nullptr; + HRESULT hr = s->dxc->Compile(&src, args.data(), static_cast(args.size()), s->includeHdlr, IID_PPV_ARGS(&result)); + + if (FAILED(hr) || !result) { + std::string msg = "IDxcCompiler3::Compile returned HRESULT 0x" + std::to_string(static_cast(hr)); + out.messagesSize = msg.size(); + out.messages = new char[msg.size() + 1]; + std::memcpy(out.messages, msg.data(), msg.size()); out.messages[msg.size()] = '\0'; + return ErrorCode::Unknown; + } + + IDxcBlobUtf8* errBlob = nullptr; IDxcBlobWide* errName = nullptr; + if (SUCCEEDED(result->GetOutput(DXC_OUT_ERRORS, IID_PPV_ARGS(&errBlob), &errName)) && errBlob && errBlob->GetStringLength() > 0) { + auto n = errBlob->GetStringLength(); + out.messagesSize = n; out.messages = new char[n + 1]; + std::memcpy(out.messages, errBlob->GetStringPointer(), n); out.messages[n] = '\0'; + } else { out.messagesSize = 0; out.messages = new char[1]; out.messages[0] = '\0'; } + if (errBlob) { errBlob->Release(); } + if (errName) { errName->Release(); } + + HRESULT status = S_OK; result->GetStatus(&status); + if (SUCCEEDED(status)) { + IDxcBlob* objBlob = nullptr; IDxcBlobWide* objName = nullptr; + if (SUCCEEDED(result->GetOutput(DXC_OUT_OBJECT, IID_PPV_ARGS(&objBlob), &objName)) && objBlob && objBlob->GetBufferSize() > 0) { + auto sz = objBlob->GetBufferSize(); + out.bytecodeSize = sz; out.bytecode = new u8[sz]; + std::memcpy(out.bytecode, objBlob->GetBufferPointer(), sz); out.success = true; + } + if (objBlob) { objBlob->Release(); } + if (objName) { objName->Release(); } + } + result->Release(); + return out.success ? ErrorCode::Ok : ErrorCode::Unknown; +} + +void Compiler::freeResult(CompileResult& r) { delete[] r.bytecode; delete[] r.messages; r = {}; } + +void Compiler::destroy() { + auto* s = stateOf(this); + if (!s) return; + if (s->includeHdlr) { s->includeHdlr->Release(); s->includeHdlr = nullptr; } + if (s->utils) { s->utils->Release(); s->utils = nullptr; } + if (s->dxc) { s->dxc->Release(); s->dxc = nullptr; } +#ifdef _WIN32 + if (s->dxcompiler) { FreeLibrary(s->dxcompiler); s->dxcompiler = nullptr; } +#else + if (s->dxcompiler) { dlclose(s->dxcompiler); s->dxcompiler = nullptr; } +#endif + delete s; + this->state = nullptr; +} + +Status createCompiler(const CompilerDesc& desc, Compiler*& out) { + out = nullptr; + auto* c = new Compiler(); + auto* s = new CompilerState(); + c->state = s; + +#ifdef _WIN32 + { + std::wstring wpath; + if (!desc.dxcompilerPath.empty()) { + wpath = widen(desc.dxcompilerPath); +#ifdef DRACO_DXC_PATH + } else { + wpath = widen(reinterpret_cast(DRACO_DXC_PATH)); + s->dxcompiler = LoadLibraryW(wpath.c_str()); + if (!s->dxcompiler) wpath = L"dxcompiler.dll"; +#else + } else { + wpath = L"dxcompiler.dll"; +#endif + } + if (!s->dxcompiler) s->dxcompiler = LoadLibraryW(wpath.c_str()); + } + if (!s->dxcompiler) { + std::fprintf(stderr, "draco.shaders: LoadLibraryW(dxcompiler.dll) failed (error %lu)\n", GetLastError()); + c->destroy(); delete c; return ErrorCode::Unknown; + } + s->createInst = reinterpret_cast(GetProcAddress(s->dxcompiler, "DxcCreateInstance")); +#else + { + std::string path; + if (desc.dxcompilerPath.empty()) { +#ifdef DRACO_DXC_PATH + path = DRACO_DXC_PATH; +#else + path = "libdxcompiler.so"; +#endif + } else { + // dxcompilerPath is already UTF-8 - feed it to dlopen directly. + path.assign(reinterpret_cast(desc.dxcompilerPath.data()), desc.dxcompilerPath.size()); + } + s->dxcompiler = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL); + } + if (!s->dxcompiler) { + std::fprintf(stderr, "draco.shaders: dlopen(libdxcompiler.so) failed: %s\n", dlerror()); + c->destroy(); delete c; return ErrorCode::Unknown; + } + s->createInst = reinterpret_cast(dlsym(s->dxcompiler, "DxcCreateInstance")); +#endif + + if (!s->createInst) { std::fprintf(stderr, "draco.shaders: DXC library missing DxcCreateInstance\n"); c->destroy(); delete c; return ErrorCode::Unknown; } + if (FAILED(s->createInst(CLSID_DxcCompiler, IID_PPV_ARGS(&s->dxc)))) { std::fprintf(stderr, "draco.shaders: DxcCreateInstance(IDxcCompiler3) failed\n"); c->destroy(); delete c; return ErrorCode::Unknown; } + if (FAILED(s->createInst(CLSID_DxcUtils, IID_PPV_ARGS(&s->utils)))) { std::fprintf(stderr, "draco.shaders: DxcCreateInstance(IDxcUtils) failed\n"); c->destroy(); delete c; return ErrorCode::Unknown; } + if (FAILED(s->utils->CreateDefaultIncludeHandler(&s->includeHdlr))) { std::fprintf(stderr, "draco.shaders: CreateDefaultIncludeHandler failed\n"); c->destroy(); delete c; return ErrorCode::Unknown; } + + out = c; + return ErrorCode::Ok; +} + +} // namespace draco::shaders diff --git a/Engine/cpp/Runtime/Rendering/Shaders/DxcIncludes.h b/Engine/cpp/Runtime/Rendering/Shaders/DxcIncludes.h new file mode 100644 index 00000000..618cf76c --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Shaders/DxcIncludes.h @@ -0,0 +1,25 @@ +#ifndef DRACO_SHADERS_DXC_INCLUDES_H_ +#define DRACO_SHADERS_DXC_INCLUDES_H_ + +// DXC uses __uuidof (MSVC extension) and has non-standard enum values. +// Suppress these diagnostics for all TUs that include this header. +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wlanguage-extension-token" +# pragma clang diagnostic ignored "-Wmicrosoft-enum-value" +#endif + +#ifdef _WIN32 +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include +# include +#endif + +#include + +#endif // DRACO_SHADERS_DXC_INCLUDES_H_ diff --git a/Engine/cpp/Runtime/Rendering/Shaders/Flags.cppm b/Engine/cpp/Runtime/Rendering/Shaders/Flags.cppm new file mode 100644 index 00000000..21d3607c --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Shaders/Flags.cppm @@ -0,0 +1,82 @@ +/// Shader feature flags + variant key. Flags are compile-time permutation bits: +/// each set flag becomes a `#define` prepended before compilation, so shaders +/// #ifdef-gate features into specialized, branch-free permutations. Render state +/// (blend/cull/depth) is NOT here - that's PipelineConfig in the material layer. + +module; + +#include +#include + +export module shaders:flags; + +import core.stdtypes; +import :types; + +using namespace draco; + +export namespace draco::shaders { + +enum class ShaderFlags : u32 { + None = 0, + Skinned = 1u << 0, // -> #define SKINNED + Instanced = 1u << 1, // -> #define INSTANCED + AlphaTest = 1u << 2, // -> #define ALPHA_TEST + NormalMap = 1u << 3, // -> #define NORMAL_MAP + Emissive = 1u << 4, // -> #define EMISSIVE + VertexColors = 1u << 5, // -> #define VERTEX_COLORS + ReceiveShadows = 1u << 6, // -> #define RECEIVE_SHADOWS + GBuffer = 1u << 7, // -> #define GBUFFER (forward MRT: also output view-normal + motion) +}; + +[[nodiscard]] constexpr ShaderFlags operator|(ShaderFlags a, ShaderFlags b) noexcept +{ + return static_cast(static_cast(a) | static_cast(b)); +} +[[nodiscard]] constexpr ShaderFlags operator&(ShaderFlags a, ShaderFlags b) noexcept +{ + return static_cast(static_cast(a) & static_cast(b)); +} +constexpr ShaderFlags& operator|=(ShaderFlags& a, ShaderFlags b) noexcept { a = a | b; return a; } +[[nodiscard]] constexpr bool hasFlag(ShaderFlags v, ShaderFlags f) noexcept +{ + return (static_cast(v) & static_cast(f)) != 0u; +} + +// Append a `#define NAME 1` for each set flag (static-literal names - safe to +// reference for the duration of a compile). +inline void appendDefines(ShaderFlags flags, std::vector& out) +{ + if (hasFlag(flags, ShaderFlags::Skinned)) { out.push_back(ShaderDefine{ u8"SKINNED", u8"1" }); } + if (hasFlag(flags, ShaderFlags::Instanced)) { out.push_back(ShaderDefine{ u8"INSTANCED", u8"1" }); } + if (hasFlag(flags, ShaderFlags::AlphaTest)) { out.push_back(ShaderDefine{ u8"ALPHA_TEST", u8"1" }); } + if (hasFlag(flags, ShaderFlags::GBuffer)) { out.push_back(ShaderDefine{ u8"GBUFFER", u8"1" }); } + if (hasFlag(flags, ShaderFlags::NormalMap)) { out.push_back(ShaderDefine{ u8"NORMAL_MAP", u8"1" }); } + if (hasFlag(flags, ShaderFlags::Emissive)) { out.push_back(ShaderDefine{ u8"EMISSIVE", u8"1" }); } + if (hasFlag(flags, ShaderFlags::VertexColors)) { out.push_back(ShaderDefine{ u8"VERTEX_COLORS", u8"1" }); } + if (hasFlag(flags, ShaderFlags::ReceiveShadows)) { out.push_back(ShaderDefine{ u8"RECEIVE_SHADOWS", u8"1" }); } +} + +// Stable FNV-1a hash of a shader name (used to key sources + variants without +// storing the name string in every key). +[[nodiscard]] inline u64 shaderNameHash(std::u8string_view name) noexcept +{ + u64 h = 1469598103934665603ull; // FNV offset basis + for (char8_t c : name) { h ^= static_cast(c); h *= 1099511628211ull; } // FNV prime + return h; +} + +// Identifies one compiled permutation of a named shader. Trivially copyable +// (16 bytes, no padding) so the generic hash keys it directly. +struct ShaderVariantKey { + u64 nameHash = 0; + ShaderStage stage = ShaderStage::Vertex; + ShaderFlags flags = ShaderFlags::None; + + [[nodiscard]] bool operator==(const ShaderVariantKey& o) const noexcept + { + return nameHash == o.nameHash && stage == o.stage && flags == o.flags; + } +}; + +} // namespace draco::shaders diff --git a/Engine/cpp/Runtime/Rendering/Shaders/Shaders.test.cpp b/Engine/cpp/Runtime/Rendering/Shaders/Shaders.test.cpp new file mode 100644 index 00000000..45fa6e8f --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Shaders/Shaders.test.cpp @@ -0,0 +1,63 @@ +#include + +#include + +import core; +import shaders; + +using namespace draco; +using namespace draco::shaders; + +namespace +{ + constexpr const char* kVertexHlsl = + "float4 main(uint id : SV_VertexID) : SV_Position {\n" + " return float4(0.0, 0.0, 0.0, 1.0);\n" + "}\n"; +} + +TEST_CASE("shaders: DXC compiles HLSL to SPIR-V") +{ + Compiler* compiler = nullptr; + if (!createCompiler(CompilerDesc{}, compiler).isOk() || compiler == nullptr) + { + MESSAGE("DXC runtime unavailable; skipping shader compilation test"); + return; + } + + const auto* source = reinterpret_cast(kVertexHlsl); + const usize sourceSize = std::strlen(kVertexHlsl); + + CompileResult result{}; + const Status status = compiler->compile( + source, sourceSize, ShaderStage::Vertex, u8"main", + ShaderTarget::SPIRV, CompileOptions{}, result); + + CHECK(status.isOk()); + CHECK(result.success); + CHECK(result.bytecode != nullptr); + CHECK(result.bytecodeSize > 0u); + // SPIR-V magic number (0x07230203) in the first word. + if (result.bytecode != nullptr && result.bytecodeSize >= 4) + { + u32 magic = 0; + std::memcpy(&magic, result.bytecode, 4); + CHECK(magic == 0x07230203u); + } + + compiler->destroy(); +} + +TEST_CASE("shaders: a compile error is reported, not a crash") +{ + Compiler* compiler = nullptr; + if (!createCompiler(CompilerDesc{}, compiler).isOk() || compiler == nullptr) { return; } + + const char* bad = "this is not valid hlsl @#$"; + CompileResult result{}; + (void)compiler->compile(reinterpret_cast(bad), std::strlen(bad), + ShaderStage::Vertex, u8"main", ShaderTarget::SPIRV, CompileOptions{}, result); + CHECK_FALSE(result.success); + + compiler->destroy(); +} diff --git a/Engine/cpp/Runtime/Rendering/Shaders/ShadersModule.cppm b/Engine/cpp/Runtime/Rendering/Shaders/ShadersModule.cppm new file mode 100644 index 00000000..116f9256 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Shaders/ShadersModule.cppm @@ -0,0 +1,5 @@ +export module shaders; + +export import :types; +export import :flags; +export import :compiler; diff --git a/Engine/cpp/Runtime/Rendering/Shaders/System/ShaderSystem.cppm b/Engine/cpp/Runtime/Rendering/Shaders/System/ShaderSystem.cppm new file mode 100644 index 00000000..0f07a06c --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Shaders/System/ShaderSystem.cppm @@ -0,0 +1,186 @@ +/// The `shaders.system` module. +/// +/// Compile-on-demand + cache for shader VARIANTS. A shader is registered by name +/// per stage (its HLSL source); getVariant(name, stage, flags) compiles the +/// permutation (flags -> #defines) via DXC, creates the GPU ShaderModule, and +/// caches it by (nameHash, stage, flags). This is the layer above the stateless +/// shaders Compiler that the material/PSO layers build on. Needs the RHI to +/// create modules, so it is separate from the RHI-free shaders module. + +module; + +#include +#include +#include +#include +#include + +export module shaders.system; + +import core.stdtypes; +import core.status; +import rhi; +import shaders; + +using namespace draco; +namespace rhi = draco::rhi; + +export namespace draco::shaders { + +// Hash for the variant key so it can key an unordered_map (ShaderVariantKey is a +// trivially-copyable 16-byte struct with operator==). +struct ShaderVariantKeyHash { + usize operator()(const ShaderVariantKey& k) const noexcept { + u64 h = k.nameHash; + h = (h * 1099511628211ull) ^ static_cast(k.stage); + h = (h * 1099511628211ull) ^ static_cast(k.flags); + return static_cast(h); + } +}; + +// Compile-on-demand variant cache. The Compiler and Device are borrowed (owned by +// the caller). Sources are registered per (name, stage) - vertex and fragment are +// separate HLSL with `main` entry points. +class ShaderSystem { +public: + ShaderSystem(Compiler& compiler, rhi::Device& device) noexcept + : m_compiler(&compiler), m_device(&device) {} + + ~ShaderSystem() { destroyAll(); } + + ShaderSystem(const ShaderSystem&) = delete; + ShaderSystem& operator=(const ShaderSystem&) = delete; + + // Register a shader's HLSL source for a stage (owned copy). + void registerSource(std::u8string_view name, ShaderStage stage, std::u8string_view hlsl) + { + m_sources.insert_or_assign(sourceKey(name, stage), std::u8string(hlsl)); + } + + // Include search paths for DXC #include resolution of shared .hlsli (owned). + void setIncludePaths(std::span paths) + { + m_includePaths.clear(); + for (usize i = 0; i < paths.size(); ++i) { m_includePaths.push_back(std::u8string(paths[i])); } + } + + // Get (compile-on-demand + cache) the GPU module for a variant. Returns null + // if the source is unknown or compilation fails (failures are NOT cached, so a + // later request retries - e.g. after a fix). + [[nodiscard]] rhi::ShaderModule* getVariant(std::u8string_view name, ShaderStage stage, ShaderFlags flags) + { + const ShaderVariantKey key{ shaderNameHash(name), stage, flags }; + if (auto it = m_cache.find(key); it != m_cache.end()) { return it->second; } + + auto sit = m_sources.find(sourceKey(name, stage)); + if (sit == m_sources.end()) { return nullptr; } + + rhi::ShaderModule* module = compile(sit->second, stage, flags); + if (module == nullptr) { return nullptr; } + + m_cache.insert_or_assign(key, module); + return module; + } + + // Drop + destroy every cached variant of a shader and BUMP its version (the + // reload signal consumers poll). Call on a shader reload. The next getVariant + // recompiles. Returns how many variants were invalidated. + usize invalidateShader(std::u8string_view name) + { + const u64 nameHash = shaderNameHash(name); + std::vector toRemove; + for (auto& [k, v] : m_cache) + { + if (k.nameHash == nameHash) + { + if (v != nullptr) { m_device->destroyShaderModule(v); } + toRemove.push_back(k); + } + } + for (const ShaderVariantKey& k : toRemove) { m_cache.erase(k); } + bumpVersion(nameHash); + return toRemove.size(); + } + + // Monotonic version of a shader: bumped each invalidateShader (i.e. each + // reload). The PSO cache stamps pipelines with this and rebuilds when it + // changes. 0 if the shader was never registered/invalidated. + [[nodiscard]] u64 version(std::u8string_view name) noexcept + { + auto it = m_versions.find(shaderNameHash(name)); + return (it != m_versions.end()) ? it->second : 0ull; + } + +private: + [[nodiscard]] rhi::ShaderModule* compile(std::u8string_view source, ShaderStage stage, ShaderFlags flags) + { + const bool isDX12 = (m_device->type == rhi::DeviceType::DX12); + const ShaderTarget target = isDX12 ? ShaderTarget::DXIL : ShaderTarget::SPIRV; + + std::vector defines; + appendDefines(flags, defines); + + std::vector includeViews; + for (const std::u8string& p : m_includePaths) { includeViews.push_back(p); } + + CompileOptions opts{}; + opts.shaderModel = u8"6_0"; + opts.optimizationLevel = 3; + opts.defines = std::span(defines.data(), defines.size()); + opts.includePaths = std::span(includeViews.data(), includeViews.size()); + if (!isDX12) + { + // Vulkan: shift register spaces so HLSL b/t/u/s registers don't collide + // in SPIR-V (matches the sample framework's compileToModule). + opts.bindingShifts.constantBufferShift = 0; + opts.bindingShifts.textureShift = 1000; + opts.bindingShifts.uavShift = 2000; + opts.bindingShifts.samplerShift = 3000; + opts.bindingShiftSets = 4; + } + + CompileResult cr{}; + const Status r = m_compiler->compile( + reinterpret_cast(source.data()), source.size(), + stage, u8"main", target, opts, cr); + + if (r != ErrorCode::Ok || !cr.success) + { + if (cr.messages != nullptr) { rhi::logErrorf("Shader variant compile failed: %s", cr.messages); } + m_compiler->freeResult(cr); + return nullptr; + } + + rhi::ShaderModuleDesc desc{}; + desc.code = std::span(cr.bytecode, cr.bytecodeSize); + rhi::ShaderModule* module = nullptr; + const Status mr = m_device->createShaderModule(desc, module); + m_compiler->freeResult(cr); + return (mr == ErrorCode::Ok) ? module : nullptr; + } + + void destroyAll() + { + for (auto& kv : m_cache) { if (kv.second != nullptr) { m_device->destroyShaderModule(kv.second); } } + m_cache.clear(); + } + + [[nodiscard]] static u64 sourceKey(std::u8string_view name, ShaderStage stage) noexcept + { + return (shaderNameHash(name) * 1099511628211ull) ^ static_cast(stage); + } + + void bumpVersion(u64 nameHash) + { + ++m_versions[nameHash]; // default-constructs to 0, then increments + } + + Compiler* m_compiler; // borrowed + rhi::Device* m_device; // borrowed + std::unordered_map m_sources; // (name,stage) -> HLSL + std::unordered_map m_cache; // variant -> GPU module (owned) + std::unordered_map m_versions; // nameHash -> version + std::vector m_includePaths; +}; + +} // namespace draco::shaders diff --git a/Engine/cpp/Runtime/Rendering/Shaders/System/ShaderSystem.test.cpp b/Engine/cpp/Runtime/Rendering/Shaders/System/ShaderSystem.test.cpp new file mode 100644 index 00000000..faa0ff04 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Shaders/System/ShaderSystem.test.cpp @@ -0,0 +1,94 @@ +// Headless tests for the shader variant system: compile-on-demand, flags->defines, +// caching, and invalidation. Compiles real SPIR-V via DXC; creates modules on the +// Null RHI backend (so distinct compiles yield distinct module objects). +#include + +import core; +import rhi; +import rhi.null; +import shaders; +import shaders.system; + +using namespace draco; +using namespace draco::shaders; +namespace rhi = draco::rhi; + +namespace +{ + // Fails to compile unless NORMAL_MAP is defined - proves flags->defines apply. + constexpr const char8_t* kNeedsNormalMap = + u8"float4 main() : SV_Target {\n" + u8"#ifndef NORMAL_MAP\n" + u8"#error NORMAL_MAP required\n" + u8"#endif\n" + u8" return float4(1, 0, 0, 1);\n" + u8"}\n"; + + // Compiles regardless of flags. + constexpr const char8_t* kTrivialVertex = + u8"float4 main(uint id : SV_VertexID) : SV_Position { return float4(0, 0, 0, 1); }\n"; + + Compiler* makeCompiler() + { + Compiler* c = nullptr; + if (!createCompiler(CompilerDesc{}, c).isOk()) { return nullptr; } + return c; + } +} + +TEST_CASE("shader system: flags become defines; failures aren't cached") +{ + Compiler* compiler = makeCompiler(); + if (compiler == nullptr) { MESSAGE("DXC unavailable; skipping"); return; } + + rhi::null::NullDevice device; + { + ShaderSystem ss(*compiler, device); + ss.registerSource(u8"guarded", ShaderStage::Fragment, kNeedsNormalMap); + + // Without the flag, NORMAL_MAP is undefined -> compile error -> null (not cached). + CHECK(ss.getVariant(u8"guarded", ShaderStage::Fragment, ShaderFlags::None) == nullptr); + + // With NormalMap -> #define NORMAL_MAP -> compiles. + rhi::ShaderModule* m = ss.getVariant(u8"guarded", ShaderStage::Fragment, ShaderFlags::NormalMap); + CHECK(m != nullptr); + + // Same variant is cached (same module object). + CHECK(ss.getVariant(u8"guarded", ShaderStage::Fragment, ShaderFlags::NormalMap) == m); + + // A different flag doesn't define NORMAL_MAP -> still fails (specific mapping). + CHECK(ss.getVariant(u8"guarded", ShaderStage::Fragment, ShaderFlags::Emissive) == nullptr); + + // Unknown shader / wrong stage -> null. + CHECK(ss.getVariant(u8"missing", ShaderStage::Fragment, ShaderFlags::NormalMap) == nullptr); + CHECK(ss.getVariant(u8"guarded", ShaderStage::Vertex, ShaderFlags::NormalMap) == nullptr); + } + + compiler->destroy(); +} + +TEST_CASE("shader system: distinct variants cache separately; invalidate recompiles") +{ + Compiler* compiler = makeCompiler(); + if (compiler == nullptr) { return; } + + rhi::null::NullDevice device; + { + ShaderSystem ss(*compiler, device); + ss.registerSource(u8"vs", ShaderStage::Vertex, kTrivialVertex); + + rhi::ShaderModule* a = ss.getVariant(u8"vs", ShaderStage::Vertex, ShaderFlags::None); + rhi::ShaderModule* b = ss.getVariant(u8"vs", ShaderStage::Vertex, ShaderFlags::Skinned); + REQUIRE(a != nullptr); + REQUIRE(b != nullptr); + CHECK(a != b); // different variants + CHECK(ss.getVariant(u8"vs", ShaderStage::Vertex, ShaderFlags::None) == a); // cached + + // Invalidate drops the cached variants; a later request recompiles. + CHECK(ss.invalidateShader(u8"vs") == 2u); + rhi::ShaderModule* a2 = ss.getVariant(u8"vs", ShaderStage::Vertex, ShaderFlags::None); + CHECK(a2 != nullptr); + } + + compiler->destroy(); +} diff --git a/Engine/cpp/Runtime/Rendering/Shaders/Types.cppm b/Engine/cpp/Runtime/Rendering/Shaders/Types.cppm new file mode 100644 index 00000000..c31f35c6 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Shaders/Types.cppm @@ -0,0 +1,57 @@ +/// Shader compilation types. + +module; + +#include +#include + +export module shaders:types; + +import core.stdtypes; + +using namespace draco; + +export namespace draco::shaders { + +enum class ShaderStage : u32 { + Vertex, Fragment, Compute, Mesh, Task, + RayGen, ClosestHit, AnyHit, Miss, Intersection, Callable, +}; + +enum class ShaderTarget : u32 { + SPIRV, + DXIL, +}; + +struct ShaderDefine { + std::u8string_view name; + std::u8string_view value; +}; + +struct BindingShifts { + u32 constantBufferShift = 0; + u32 textureShift = 0; + u32 samplerShift = 0; + u32 uavShift = 0; +}; + +struct CompileOptions { + std::u8string_view shaderModel = u8"6_0"; + i32 optimizationLevel = 3; + bool enableDebugInfo = false; + bool rowMajorMatrices = false; + std::span defines; + std::span includePaths; + BindingShifts bindingShifts; + u32 bindingShiftSets = 1; +}; + +struct CompileResult { + u8* bytecode = nullptr; + usize bytecodeSize = 0; + char* messages = nullptr; + usize messagesSize = 0; + bool success = false; +}; + +} // namespace draco::shaders diff --git a/Engine/cpp/Runtime/Rendering/Shaders/fs.sc b/Engine/cpp/Runtime/Rendering/Shaders/fs.sc deleted file mode 100644 index c707ade0..00000000 --- a/Engine/cpp/Runtime/Rendering/Shaders/fs.sc +++ /dev/null @@ -1,18 +0,0 @@ -$input v_normal, v_texcoord0 - -#include - -SAMPLER2D(s_texColor, 0); - -uniform vec4 u_tint; - -void main() -{ - vec3 lightDir = normalize(vec3(0.4, 1.0, 0.2)); - - float NdotL = max(dot(normalize(v_normal), lightDir), 0.2); - - vec4 texColor = texture2D(s_texColor, v_texcoord0); - - gl_FragColor = texColor * u_tint * NdotL; -} \ No newline at end of file diff --git a/Engine/cpp/Runtime/Rendering/Shaders/fs_quad.sc b/Engine/cpp/Runtime/Rendering/Shaders/fs_quad.sc deleted file mode 100644 index 9dc0e8ba..00000000 --- a/Engine/cpp/Runtime/Rendering/Shaders/fs_quad.sc +++ /dev/null @@ -1,12 +0,0 @@ -$input v_texcoord0, v_color0 - -#include - -SAMPLER2D(s_texColor, 0); - -void main() -{ - vec4 tex = texture2D(s_texColor, v_texcoord0); - - gl_FragColor = tex * v_color0; -} diff --git a/Engine/cpp/Runtime/Rendering/Shaders/varying.def.sc b/Engine/cpp/Runtime/Rendering/Shaders/varying.def.sc deleted file mode 100644 index 4e4a36e2..00000000 --- a/Engine/cpp/Runtime/Rendering/Shaders/varying.def.sc +++ /dev/null @@ -1,6 +0,0 @@ -vec3 a_position : POSITION; -vec3 a_normal : NORMAL; -vec2 a_texcoord0 : TEXCOORD0; - -vec3 v_normal : NORMAL; -vec2 v_texcoord0 : TEXCOORD0; \ No newline at end of file diff --git a/Engine/cpp/Runtime/Rendering/Shaders/varying_quad.def.sc b/Engine/cpp/Runtime/Rendering/Shaders/varying_quad.def.sc deleted file mode 100644 index a80b6016..00000000 --- a/Engine/cpp/Runtime/Rendering/Shaders/varying_quad.def.sc +++ /dev/null @@ -1,6 +0,0 @@ -vec3 a_position : POSITION; -vec2 a_texcoord0 : TEXCOORD0; -vec4 a_color0 : COLOR0; - -vec2 v_texcoord0 : TEXCOORD0; -vec4 v_color0 : COLOR0; diff --git a/Engine/cpp/Runtime/Rendering/Shaders/vs.sc b/Engine/cpp/Runtime/Rendering/Shaders/vs.sc deleted file mode 100644 index f9ebb00f..00000000 --- a/Engine/cpp/Runtime/Rendering/Shaders/vs.sc +++ /dev/null @@ -1,12 +0,0 @@ -$input a_position, a_normal, a_texcoord0 -$output v_normal, v_texcoord0 - -#include - -void main() -{ - gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); - - v_normal = a_normal; - v_texcoord0 = a_texcoord0; -} \ No newline at end of file diff --git a/Engine/cpp/Runtime/Rendering/Shaders/vs_quad.sc b/Engine/cpp/Runtime/Rendering/Shaders/vs_quad.sc deleted file mode 100644 index 92a21415..00000000 --- a/Engine/cpp/Runtime/Rendering/Shaders/vs_quad.sc +++ /dev/null @@ -1,21 +0,0 @@ -$input a_position, a_texcoord0, a_color0 -$output v_texcoord0, v_color0 - -#include - -void main() -{ - // Grab the actual screen dimensions automatically tracked by bgfx - float width = u_viewRect.z; - float height = u_viewRect.w; - - // Convert pixel coordinates [0, width] and [0, height] directly to NDC [-1, 1] - float ndcX = (a_position.x / width) * 2.0 - 1.0; - float ndcY = 1.0 - (a_position.y / height) * 2.0; // Flip Y so 0 is top of the screen - - // Lock Z to 0.0 and W to 1.0 - gl_Position = vec4(ndcX, ndcY, 0.0, 1.0); - - v_texcoord0 = a_texcoord0; - v_color0 = a_color0; -} \ No newline at end of file diff --git a/Engine/cpp/Runtime/Scene/CMakeLists.txt b/Engine/cpp/Runtime/Scene/CMakeLists.txt deleted file mode 100644 index 4ae18357..00000000 --- a/Engine/cpp/Runtime/Scene/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -add_modules_library(Camera) -add_modules_library(TransformComponent) -add_modules_library(Renderable) - -target_link_libraries(Camera PUBLIC Core Rendering bx) -target_link_libraries(TransformComponent PUBLIC Core) -target_link_libraries(Renderable PUBLIC TransformComponent Core Rendering) diff --git a/Engine/cpp/Runtime/Scene/Camera/CameraController.cpp b/Engine/cpp/Runtime/Scene/Camera/CameraController.cpp deleted file mode 100644 index 9ede1ef5..00000000 --- a/Engine/cpp/Runtime/Scene/Camera/CameraController.cpp +++ /dev/null @@ -1,98 +0,0 @@ -module; - -#include -#include - -module scene.camera.controller; - -namespace draco::scene -{ - void CameraController::init(f32 px, f32 py, f32 pz) - { - x = px; - y = py; - z = pz; - - yaw = 0.0f; - pitch = 0.0f; - - speed = 5.0f; // units per second - sensitivity = 0.002f; // mouse sensitivity - } - - void CameraController::update(f32 dt, const CameraInput& input) - { - yaw += input.mouseDx * sensitivity; - pitch -= input.mouseDy * sensitivity; // Temp fix to flip mouse input - - // Clamp pitch - if (pitch > 1.5f) pitch = 1.5f; - if (pitch < -1.5f) pitch = -1.5f; - - bx::Vec3 forward = { - cosf(pitch) * sinf(yaw), - sinf(pitch), - cosf(pitch) * cosf(yaw) - }; - - bx::Vec3 right = { - sinf(yaw - bx::kPiHalf), - 0.0f, - cosf(yaw - bx::kPiHalf) - }; - - f32 velocity = speed * dt; - - if (input.moveForward) - { - x += forward.x * velocity; - y += forward.y * velocity; - z += forward.z * velocity; - } - - if (input.moveBackward) - { - x -= forward.x * velocity; - y -= forward.y * velocity; - z -= forward.z * velocity; - } - - if (input.moveLeft) - { - x += right.x * velocity; - z += right.z * velocity; - } - - if (input.moveRight) - { - x -= right.x * velocity; - z -= right.z * velocity; - } - } - - rendering::renderer::Camera CameraController::getCamera() const - { - const bx::Vec3 forward = { - cosf(pitch) * sinf(yaw), - sinf(pitch), - cosf(pitch) * cosf(yaw) - }; - - rendering::renderer::Camera cam{}; - - cam.position = { x, y, z }; - cam.target = { - x + forward.x, - y + forward.y, - z + forward.z - }; - - cam.up = { 0.0f, 1.0f, 0.0f }; - - cam.fov = 60.0f; - cam.nearPlane = 0.1f; - cam.farPlane = 100.0f; - - return cam; - } -} diff --git a/Engine/cpp/Runtime/Scene/Camera/CameraController.cppm b/Engine/cpp/Runtime/Scene/Camera/CameraController.cppm deleted file mode 100644 index 669719a9..00000000 --- a/Engine/cpp/Runtime/Scene/Camera/CameraController.cppm +++ /dev/null @@ -1,35 +0,0 @@ -export module scene.camera.controller; - -import core.stdtypes; -import rendering; - -export namespace draco::scene -{ - // Per-frame input for the camera, supplied by the caller. Keeps the - // controller decoupled from any specific input/shell backend. - struct CameraInput - { - f32 mouseDx = 0.0f; - f32 mouseDy = 0.0f; - bool moveForward = false; - bool moveBackward = false; - bool moveLeft = false; - bool moveRight = false; - }; - - struct CameraController - { - void init(f32 x = 0.0f, f32 y = 0.0f, f32 z = -2.0f); - - void update(f32 dt, const CameraInput& input); - - [[nodiscard]] rendering::renderer::Camera getCamera() const; - - private: - f32 x = 0.0f, y = 0.0f, z = 0.0f; - f32 yaw = 0.0f; - f32 pitch = 0.0f; - f32 speed = 5.0f; - f32 sensitivity = 0.1f; - }; -} \ No newline at end of file diff --git a/Engine/cpp/Runtime/Scene/Renderable/Renderable.cppm b/Engine/cpp/Runtime/Scene/Renderable/Renderable.cppm deleted file mode 100644 index 599fcb35..00000000 --- a/Engine/cpp/Runtime/Scene/Renderable/Renderable.cppm +++ /dev/null @@ -1,15 +0,0 @@ -export module scene.renderable; - -import rendering.mesh; -import rendering.material; -import core.math.transform; - -export namespace draco::scene::renderable -{ - struct Renderable - { - rendering::mesh::MeshHandle mesh{}; - math::Transform transform{}; - rendering::material::Material material{}; - }; -} \ No newline at end of file diff --git a/Engine/cpp/Runtime/Scene/Scene.cppm b/Engine/cpp/Runtime/Scene/Scene.cppm deleted file mode 100644 index 00768e1f..00000000 --- a/Engine/cpp/Runtime/Scene/Scene.cppm +++ /dev/null @@ -1,17 +0,0 @@ -module; - -#include - -export module scene; - -export import scene.renderable; -export import scene.transform_component; -export import scene.camera.controller; - -export namespace draco::scene -{ - struct Scene - { - std::vector renderables; - }; -} diff --git a/Engine/cpp/Runtime/Scene/TransformComponent/TransformComponent.cpp b/Engine/cpp/Runtime/Scene/TransformComponent/TransformComponent.cpp deleted file mode 100644 index fbadc4ef..00000000 --- a/Engine/cpp/Runtime/Scene/TransformComponent/TransformComponent.cpp +++ /dev/null @@ -1,9 +0,0 @@ -module scene.transform_component; - -namespace draco::scene -{ - void markDirty(TransformComponent& t) - { - t.dirty = true; - } -} diff --git a/Engine/cpp/Runtime/Scene/TransformComponent/TransformComponent.cppm b/Engine/cpp/Runtime/Scene/TransformComponent/TransformComponent.cppm deleted file mode 100644 index d867f1bd..00000000 --- a/Engine/cpp/Runtime/Scene/TransformComponent/TransformComponent.cppm +++ /dev/null @@ -1,15 +0,0 @@ -export module scene.transform_component; - -import core.math.transform; - -export namespace draco::scene -{ - struct TransformComponent - { - math::Transform local{}; - math::Transform world{}; - bool dirty = true; - }; - - void markDirty(TransformComponent& t); -} diff --git a/Engine/cpp/ThirdParty/CMakeLists.txt b/Engine/cpp/ThirdParty/CMakeLists.txt index b76d5b72..f0dcbb64 100644 --- a/Engine/cpp/ThirdParty/CMakeLists.txt +++ b/Engine/cpp/ThirdParty/CMakeLists.txt @@ -1,23 +1,14 @@ -set(BGFX_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) -set(BGFX_BUILD_TOOLS OFF CACHE BOOL "" FORCE) -set(BGFX_BUILD_TOOLS_SHADER ON CACHE BOOL "" FORCE) -set(BGFX_BUILD_TOOLS_TEXTURE OFF CACHE BOOL "" FORCE) -set(BGFX_BUILD_TOOLS_GEOMETRY OFF CACHE BOOL "" FORCE) - -set(BGFX_INSTALL OFF CACHE BOOL "" FORCE) -set(BGFX_LIBRARY_TYPE "STATIC") - set(SDL_SHARED OFF CACHE BOOL "" FORCE) set(SDL_STATIC ON CACHE BOOL "" FORCE) set(SDL_MAIN_HANDLED ON CACHE BOOL "" FORCE) set(SDL_STATIC_PIC ON CACHE BOOL "" FORCE) set(SDL_TEST OFF CACHE BOOL "" FORCE) -set(SDL_AUDIO OFF CACHE BOOL "" FORCE) -set(SDL_VIDEO ON CACHE BOOL "" FORCE) -set(SDL_RENDER OFF CACHE BOOL "" FORCE) # Off because bgfx handles the rendering! -set(SDL_CAMERA OFF CACHE BOOL "" FORCE) -set(SDL_JOYSTICK ON CACHE BOOL "" FORCE) # Required by SDL_INIT_GAMEPAD (shell.desktop input) +set(SDL_AUDIO OFF CACHE BOOL "" FORCE) +set(SDL_VIDEO ON CACHE BOOL "" FORCE) +set(SDL_RENDER OFF CACHE BOOL "" FORCE) # Off: the engine owns rendering +set(SDL_CAMERA OFF CACHE BOOL "" FORCE) +set(SDL_JOYSTICK ON CACHE BOOL "" FORCE) # Required by SDL_INIT_GAMEPAD (shell.desktop input) set(SDL_HAPTIC OFF CACHE BOOL "" FORCE) # Not needed: gamepad rumble uses SDL_RumbleGamepad (joystick), not the haptic subsystem set(SDL_HIDAPI ON CACHE BOOL "" FORCE) # Controller backends (PS/Switch/Xbox over USB/BT) @@ -32,7 +23,7 @@ elseif(WIN32) set(SDL_X11 OFF CACHE BOOL "" FORCE) else() set(SDL_WAYLAND ON CACHE BOOL "" FORCE) - set(SDL_LIBDECOR ON CACHE BOOL "" FORCE) + set(SDL_LIBDECOR ON CACHE BOOL "" FORCE) set(SDL_X11 ON CACHE BOOL "" FORCE) set(SDL_X11_XSCRNSAVER OFF CACHE BOOL "" FORCE) set(SDL_WAYLAND_SHARED OFF CACHE BOOL "" FORCE) @@ -41,14 +32,6 @@ else() set(SDL_PULSEAUDIO_SHARED OFF CACHE BOOL "" FORCE) endif() -set(BX_DIR ${CMAKE_CURRENT_SOURCE_DIR}/bx CACHE STRING "" FORCE) -set(BIMG_DIR ${CMAKE_CURRENT_SOURCE_DIR}/bimg CACHE STRING "" FORCE) -set(BGFX_DIR ${CMAKE_CURRENT_SOURCE_DIR}/bgfx CACHE STRING "" FORCE) - -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/cmake/bx bx-build) -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/cmake/bimg bimg-build) -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/cmake/bgfx bgfx-build) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/sdl sdl-build) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/stb) - -target_link_libraries(bgfx PUBLIC bx bimg) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/DXC) diff --git a/Engine/cpp/ThirdParty/DXC/CMakeLists.txt b/Engine/cpp/ThirdParty/DXC/CMakeLists.txt new file mode 100644 index 00000000..11089a06 --- /dev/null +++ b/Engine/cpp/ThirdParty/DXC/CMakeLists.txt @@ -0,0 +1,20 @@ +# DXC (DirectX Shader Compiler). The headers are used at compile time; the runtime +# library (dxcompiler.dll / libdxcompiler.so) is loaded on demand by draco.shaders +# via LoadLibrary/dlopen, so there is no import library to link. +# +# Draco::DXCHeaders carries the include dir AND the DRACO_DXC_PATH definition (the +# absolute path draco.shaders falls back to when no override is given), so a target +# that links it gets both. + +add_library(DracoDXCHeaders INTERFACE) +add_library(Draco::DXCHeaders ALIAS DracoDXCHeaders) + +target_include_directories(DracoDXCHeaders INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) + +if(WIN32) + set(_dxc_runtime "${CMAKE_CURRENT_SOURCE_DIR}/lib/win-x64/dxcompiler.dll") +else() + set(_dxc_runtime "${CMAKE_CURRENT_SOURCE_DIR}/lib/linux-x86_64/libdxcompiler.so") +endif() + +target_compile_definitions(DracoDXCHeaders INTERFACE DRACO_DXC_PATH="${_dxc_runtime}") diff --git a/Engine/cpp/ThirdParty/DXC/include/dxc/Support/ErrorCodes.h b/Engine/cpp/ThirdParty/DXC/include/dxc/Support/ErrorCodes.h new file mode 100644 index 00000000..5239c811 --- /dev/null +++ b/Engine/cpp/ThirdParty/DXC/include/dxc/Support/ErrorCodes.h @@ -0,0 +1,160 @@ +/////////////////////////////////////////////////////////////////////////////// +// // +// ErrorCodes.h // +// Copyright (C) Microsoft Corporation. All rights reserved. // +// This file is distributed under the University of Illinois Open Source // +// License. See LICENSE.TXT for details. // +// // +// Provides error code values for the DirectX compiler and tools. // +// // +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +// Redeclare some macros to not depend on winerror.h +#define DXC_SEVERITY_ERROR 1 +#define DXC_MAKE_HRESULT(sev, fac, code) \ + ((HRESULT)(((unsigned long)(sev) << 31) | ((unsigned long)(fac) << 16) | \ + ((unsigned long)(code)))) + +#define HRESULT_IS_WIN32ERR(hr) \ + ((HRESULT)(hr & 0xFFFF0000) == \ + MAKE_HRESULT(SEVERITY_ERROR, FACILITY_WIN32, 0)) +#define HRESULT_AS_WIN32ERR(hr) (HRESULT_CODE(hr)) + +// Error codes from C libraries (0n150) - 0x8096xxxx +#define FACILITY_ERRNO (0x96) +#define HRESULT_FROM_ERRNO(x) \ + MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_ERRNO, (x)) + +// Error codes from DXC libraries (0n170) - 0x8013xxxx +#define FACILITY_DXC (0xAA) + +// 0x00000000 - The operation succeeded. +#define DXC_S_OK 0 // _HRESULT_TYPEDEF_(0x00000000L) + +// 0x80AA0001 - The operation failed because overlapping semantics were found. +#define DXC_E_OVERLAPPING_SEMANTICS \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0001)) + +// 0x80AA0002 - The operation failed because multiple depth semantics were +// found. +#define DXC_E_MULTIPLE_DEPTH_SEMANTICS \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0002)) + +// 0x80AA0003 - Input file is too large. +#define DXC_E_INPUT_FILE_TOO_LARGE \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0003)) + +// 0x80AA0004 - Error parsing DXBC container. +#define DXC_E_INCORRECT_DXBC \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0004)) + +// 0x80AA0005 - Error parsing DXBC bytecode. +#define DXC_E_ERROR_PARSING_DXBC_BYTECODE \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0005)) + +// 0x80AA0006 - Data is too large. +#define DXC_E_DATA_TOO_LARGE \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0006)) + +// 0x80AA0007 - Incompatible converter options. +#define DXC_E_INCOMPATIBLE_CONVERTER_OPTIONS \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0007)) + +// 0x80AA0008 - Irreducible control flow graph. +#define DXC_E_IRREDUCIBLE_CFG \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0008)) + +// 0x80AA0009 - IR verification error. +#define DXC_E_IR_VERIFICATION_FAILED \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0009)) + +// 0x80AA000A - Scope-nested control flow recovery failed. +#define DXC_E_SCOPE_NESTED_FAILED \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x000A)) + +// 0x80AA000B - Operation is not supported. +#define DXC_E_NOT_SUPPORTED \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x000B)) + +// 0x80AA000C - Unable to encode string. +#define DXC_E_STRING_ENCODING_FAILED \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x000C)) + +// 0x80AA000D - DXIL container is invalid. +#define DXC_E_CONTAINER_INVALID \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x000D)) + +// 0x80AA000E - DXIL container is missing the DXIL part. +#define DXC_E_CONTAINER_MISSING_DXIL \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x000E)) + +// 0x80AA000F - Unable to parse DxilModule metadata. +#define DXC_E_INCORRECT_DXIL_METADATA \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x000F)) + +// 0x80AA0010 - Error parsing DDI signature. +#define DXC_E_INCORRECT_DDI_SIGNATURE \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0010)) + +// 0x80AA0011 - Duplicate part exists in dxil container. +#define DXC_E_DUPLICATE_PART \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0011)) + +// 0x80AA0012 - Error finding part in dxil container. +#define DXC_E_MISSING_PART \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0012)) + +// 0x80AA0013 - Malformed DXIL Container. +#define DXC_E_MALFORMED_CONTAINER \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0013)) + +// 0x80AA0014 - Incorrect Root Signature for shader. +#define DXC_E_INCORRECT_ROOT_SIGNATURE \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0014)) + +// 0X80AA0015 - DXIL container is missing DebugInfo part. +#define DXC_E_CONTAINER_MISSING_DEBUG \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0015)) + +// 0X80AA0016 - Unexpected failure in macro expansion. +#define DXC_E_MACRO_EXPANSION_FAILURE \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0016)) + +// 0X80AA0017 - DXIL optimization pass failed. +#define DXC_E_OPTIMIZATION_FAILED \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0017)) + +// 0X80AA0018 - General internal error. +#define DXC_E_GENERAL_INTERNAL_ERROR \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0018)) + +// 0X80AA0019 - Abort compilation error. +#define DXC_E_ABORT_COMPILATION_ERROR \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x0019)) + +// 0X80AA001A - Error in extension mechanism. +#define DXC_E_EXTENSION_ERROR \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x001A)) + +// 0X80AA001B - LLVM Fatal Error +#define DXC_E_LLVM_FATAL_ERROR \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x001B)) + +// 0X80AA001C - LLVM Unreachable code +#define DXC_E_LLVM_UNREACHABLE \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x001C)) + +// 0X80AA001D - LLVM Cast Failure +#define DXC_E_LLVM_CAST_ERROR \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x001D)) + +// 0X80AA001E - External validator (DXIL.dll) required, and missing. +#define DXC_E_VALIDATOR_MISSING \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x001E)) + +// 0X80AA001F - DXIL container Program Version mismatches Dxil module shader +// model +#define DXC_E_INCORRECT_PROGRAM_VERSION \ + DXC_MAKE_HRESULT(DXC_SEVERITY_ERROR, FACILITY_DXC, (0x001F)) \ No newline at end of file diff --git a/Engine/cpp/ThirdParty/DXC/include/dxc/WinAdapter.h b/Engine/cpp/ThirdParty/DXC/include/dxc/WinAdapter.h new file mode 100644 index 00000000..98614921 --- /dev/null +++ b/Engine/cpp/ThirdParty/DXC/include/dxc/WinAdapter.h @@ -0,0 +1,1056 @@ +//===- WinAdapter.h - Windows Adapter for non-Windows platforms -*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines Windows-specific types, macros, and SAL annotations used +// in the codebase for non-Windows platforms. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_WIN_ADAPTER_H +#define LLVM_SUPPORT_WIN_ADAPTER_H + +#ifndef _WIN32 + +#ifdef __cplusplus +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif // __cplusplus + +#define COM_NO_WINDOWS_H // needed to inform d3d headers that this isn't windows + +//===----------------------------------------------------------------------===// +// +// Begin: Macro Definitions +// +//===----------------------------------------------------------------------===// +#define C_ASSERT(expr) static_assert((expr), "") +#define ATLASSERT assert + +#define CoTaskMemAlloc malloc +#define CoTaskMemFree free + +#define ARRAYSIZE(array) (sizeof(array) / sizeof(array[0])) + +#define _countof(a) (sizeof(a) / sizeof(*(a))) + +// If it is GCC, there is no UUID support and we must emulate it. +// Clang support depends on the -fms-extensions compiler flag. +#if !defined(__clang__) || !defined(_MSC_EXTENSIONS) +#define __EMULATE_UUID 1 +#endif // __clang__ + +#ifdef __EMULATE_UUID +#define __declspec(x) +#endif // __EMULATE_UUID + +#define DECLSPEC_SELECTANY + +#ifdef __EMULATE_UUID +#define uuid(id) +#endif // __EMULATE_UUID + +#define STDMETHODCALLTYPE +#define STDMETHODIMP_(type) type STDMETHODCALLTYPE +#define STDMETHODIMP STDMETHODIMP_(HRESULT) +#define STDMETHOD_(type, name) virtual STDMETHODIMP_(type) name +#define STDMETHOD(name) STDMETHOD_(HRESULT, name) +#define EXTERN_C extern "C" + +#define UNREFERENCED_PARAMETER(P) (void)(P) + +#define RtlEqualMemory(Destination, Source, Length) \ + (!memcmp((Destination), (Source), (Length))) +#define RtlMoveMemory(Destination, Source, Length) \ + memmove((Destination), (Source), (Length)) +#define RtlCopyMemory(Destination, Source, Length) \ + memcpy((Destination), (Source), (Length)) +#define RtlFillMemory(Destination, Length, Fill) \ + memset((Destination), (Fill), (Length)) +#define RtlZeroMemory(Destination, Length) memset((Destination), 0, (Length)) +#define MoveMemory RtlMoveMemory +#define CopyMemory RtlCopyMemory +#define FillMemory RtlFillMemory +#define ZeroMemory RtlZeroMemory + +#define FALSE 0 +#define TRUE 1 + +// We ignore the code page completely on Linux. +#define GetConsoleOutputCP() 0 + +#define _HRESULT_TYPEDEF_(_sc) ((HRESULT)_sc) +#define DISP_E_BADINDEX _HRESULT_TYPEDEF_(0x8002000BL) +#define REGDB_E_CLASSNOTREG _HRESULT_TYPEDEF_(0x80040154L) + +// This is an unsafe conversion. If needed, we can later implement a safe +// conversion that throws exceptions for overflow cases. +#define UIntToInt(uint_arg, int_ptr_arg) *int_ptr_arg = uint_arg + +#define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1) + +// Use errno to implement {Get|Set}LastError +#define GetLastError() errno +#define SetLastError(ERR) errno = ERR + +// Map these errors to equivalent errnos. +#define ERROR_SUCCESS 0L +#define ERROR_ARITHMETIC_OVERFLOW EOVERFLOW +#define ERROR_FILE_NOT_FOUND ENOENT +#define ERROR_FUNCTION_NOT_CALLED ENOSYS +#define ERROR_IO_DEVICE EIO +#define ERROR_INSUFFICIENT_BUFFER ENOBUFS +#define ERROR_INVALID_HANDLE EBADF +#define ERROR_INVALID_PARAMETER EINVAL +#define ERROR_OUT_OF_STRUCTURES ENOMEM +#define ERROR_NOT_CAPABLE EPERM +#define ERROR_NOT_FOUND ENOTSUP +#define ERROR_UNHANDLED_EXCEPTION EBADF +#define ERROR_BROKEN_PIPE EPIPE + +// Used by HRESULT <--> WIN32 error code conversion +#define SEVERITY_ERROR 1 +#define FACILITY_WIN32 7 +#define HRESULT_CODE(hr) ((hr) & 0xFFFF) +#define MAKE_HRESULT(severity, facility, code) \ + ((HRESULT)(((unsigned long)(severity) << 31) | \ + ((unsigned long)(facility) << 16) | ((unsigned long)(code)))) + +#define FILE_TYPE_UNKNOWN 0x0000 +#define FILE_TYPE_DISK 0x0001 +#define FILE_TYPE_CHAR 0x0002 +#define FILE_TYPE_PIPE 0x0003 +#define FILE_TYPE_REMOTE 0x8000 + +#define FILE_ATTRIBUTE_NORMAL 0x00000080 +#define FILE_ATTRIBUTE_DIRECTORY 0x00000010 +#define INVALID_FILE_ATTRIBUTES ((DWORD)-1) + +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + +// STGTY ENUMS +#define STGTY_STORAGE 1 +#define STGTY_STREAM 2 +#define STGTY_LOCKBYTES 3 +#define STGTY_PROPERTY 4 + +// Storage errors +#define STG_E_INVALIDFUNCTION 1L +#define STG_E_ACCESSDENIED 2L + +#define STREAM_SEEK_SET 0 +#define STREAM_SEEK_CUR 1 +#define STREAM_SEEK_END 2 + +#define HEAP_NO_SERIALIZE 0x1 +#define HEAP_ZERO_MEMORY 0x8 + +#define MB_ERR_INVALID_CHARS 0x00000008 // error for invalid chars + +// File IO + +#define CREATE_ALWAYS 2 +#define CREATE_NEW 1 +#define OPEN_ALWAYS 4 +#define OPEN_EXISTING 3 +#define TRUNCATE_EXISTING 5 + +#define FILE_SHARE_DELETE 0x00000004 +#define FILE_SHARE_READ 0x00000001 +#define FILE_SHARE_WRITE 0x00000002 + +#define GENERIC_READ 0x80000000 +#define GENERIC_WRITE 0x40000000 + +#define _atoi64 atoll +#define sprintf_s snprintf +#define _strdup strdup +#define _strnicmp strnicmp + +#define vsnprintf_s vsnprintf +#define strcat_s strcat +#define strcpy_s(dst, n, src) strncpy(dst, src, n) +#define _vscwprintf vwprintf +#define vswprintf_s vswprintf +#define swprintf_s swprintf + +#define StringCchCopyW(dst, n, src) wcsncpy(dst, src, n) + +#define OutputDebugStringW(msg) fputws(msg, stderr) + +#define OutputDebugStringA(msg) fputs(msg, stderr) +#define OutputDebugFormatA(...) fprintf(stderr, __VA_ARGS__) + +// Event Tracing for Windows (ETW) provides application programmers the ability +// to start and stop event tracing sessions, instrument an application to +// provide trace events, and consume trace events. +#define DxcEtw_DXCompilerCreateInstance_Start() +#define DxcEtw_DXCompilerCreateInstance_Stop(hr) +#define DxcEtw_DXCompilerCompile_Start() +#define DxcEtw_DXCompilerCompile_Stop(hr) +#define DxcEtw_DXCompilerDisassemble_Start() +#define DxcEtw_DXCompilerDisassemble_Stop(hr) +#define DxcEtw_DXCompilerPreprocess_Start() +#define DxcEtw_DXCompilerPreprocess_Stop(hr) +#define DxcEtw_DxcValidation_Start() +#define DxcEtw_DxcValidation_Stop(hr) + +#define UInt32Add UIntAdd +#define Int32ToUInt32 IntToUInt + +//===--------------------- HRESULT Related Macros -------------------------===// + +#define S_OK ((HRESULT)0L) +#define S_FALSE ((HRESULT)1L) + +#define E_ABORT (HRESULT)0x80004004 +#define E_ACCESSDENIED (HRESULT)0x80070005 +#define E_BOUNDS (HRESULT)0x8000000B +#define E_FAIL (HRESULT)0x80004005 +#define E_HANDLE (HRESULT)0x80070006 +#define E_INVALIDARG (HRESULT)0x80070057 +#define E_NOINTERFACE (HRESULT)0x80004002 +#define E_NOTIMPL (HRESULT)0x80004001 +#define E_NOT_VALID_STATE (HRESULT)0x8007139F +#define E_OUTOFMEMORY (HRESULT)0x8007000E +#define E_POINTER (HRESULT)0x80004003 +#define E_UNEXPECTED (HRESULT)0x8000FFFF + +#define SUCCEEDED(hr) (((HRESULT)(hr)) >= 0) +#define FAILED(hr) (((HRESULT)(hr)) < 0) +#define DXC_FAILED(hr) (((HRESULT)(hr)) < 0) + +#define HRESULT_FROM_WIN32(x) \ + (HRESULT)(x) <= 0 ? (HRESULT)(x) \ + : (HRESULT)(((x) & 0x0000FFFF) | (7 << 16) | 0x80000000) + +//===----------------------------------------------------------------------===// +// +// Begin: Disable SAL Annotations +// +//===----------------------------------------------------------------------===// +#define _In_ +#define _In_z_ +#define _In_opt_ +#define _In_opt_count_(size) +#define _In_opt_z_ +#define _In_count_(size) +#define _In_bytecount_(size) + +#define _Out_ +#define _Out_opt_ +#define _Outptr_ +#define _Outptr_opt_ +#define _Outptr_result_z_ +#define _Outptr_opt_result_z_ +#define _Outptr_result_maybenull_ +#define _Outptr_result_nullonfailure_ +#define _Outptr_result_buffer_maybenull_(ptr) +#define _Outptr_result_buffer_(ptr) + +#define _COM_Outptr_ +#define _COM_Outptr_opt_ +#define _COM_Outptr_result_maybenull_ +#define _COM_Outptr_opt_result_maybenull_ + +#define THIS_ +#define THIS +#define PURE = 0 + +#define _Maybenull_ + +#define __debugbreak() + +// GCC produces erros on calling convention attributes. +#ifdef __GNUC__ +#define __cdecl +#define __CRTDECL +#define __stdcall +#define __vectorcall +#define __thiscall +#define __fastcall +#define __clrcall +#endif // __GNUC__ + +//===----------------------------------------------------------------------===// +// +// Begin: Type Definitions +// +//===----------------------------------------------------------------------===// + +#ifdef __cplusplus + +typedef unsigned char BYTE, UINT8; +typedef unsigned char *LPBYTE; + +typedef BYTE BOOLEAN; +typedef BOOLEAN *PBOOLEAN; + +typedef bool BOOL; +typedef BOOL *LPBOOL; + +typedef int INT; +typedef long LONG; +typedef unsigned int UINT; +typedef unsigned long ULONG; +typedef long long LONGLONG; +typedef long long LONG_PTR; +typedef unsigned long long ULONG_PTR; +typedef unsigned long long ULONGLONG; + +typedef uint16_t WORD; +typedef uint32_t DWORD; +typedef DWORD *LPDWORD; + +typedef uint32_t UINT32; +typedef uint64_t UINT64; + +typedef signed char INT8, *PINT8; +typedef signed int INT32, *PINT32; + +typedef size_t SIZE_T; +typedef const char *LPCSTR; +typedef const char *PCSTR; + +typedef int errno_t; + +typedef wchar_t WCHAR; +typedef wchar_t *LPWSTR; +typedef wchar_t *PWCHAR; +typedef const wchar_t *LPCWSTR; +typedef const wchar_t *PCWSTR; + +typedef WCHAR OLECHAR; +typedef OLECHAR *BSTR; +typedef OLECHAR *LPOLESTR; +typedef char *LPSTR; + +typedef void *LPVOID; +typedef const void *LPCVOID; + +typedef std::nullptr_t nullptr_t; + +typedef signed int HRESULT; + +//===--------------------- Handle Types -----------------------------------===// + +typedef void *HANDLE; +typedef void *RPC_IF_HANDLE; + +#define DECLARE_HANDLE(name) \ + struct name##__ { \ + int unused; \ + }; \ + typedef struct name##__ *name +DECLARE_HANDLE(HINSTANCE); + +typedef void *HMODULE; + +#define STD_INPUT_HANDLE ((DWORD)-10) +#define STD_OUTPUT_HANDLE ((DWORD)-11) +#define STD_ERROR_HANDLE ((DWORD)-12) + +//===--------------------- ID Types and Macros for COM --------------------===// + +#ifdef __EMULATE_UUID +struct GUID +#else // __EMULATE_UUID +// These specific definitions are required by clang -fms-extensions. +typedef struct _GUID +#endif // __EMULATE_UUID +{ + uint32_t Data1; + uint16_t Data2; + uint16_t Data3; + uint8_t Data4[8]; +} +#ifdef __EMULATE_UUID +; +#else // __EMULATE_UUID +GUID; +#endif // __EMULATE_UUID +typedef GUID CLSID; +typedef const GUID &REFGUID; +typedef const GUID &REFCLSID; + +typedef GUID IID; +typedef IID *LPIID; +typedef const IID &REFIID; +inline bool IsEqualGUID(REFGUID rguid1, REFGUID rguid2) { + // Optimization: + if (&rguid1 == &rguid2) + return true; + + return !memcmp(&rguid1, &rguid2, sizeof(GUID)); +} + +inline bool operator==(REFGUID guidOne, REFGUID guidOther) { + return !!IsEqualGUID(guidOne, guidOther); +} + +inline bool operator!=(REFGUID guidOne, REFGUID guidOther) { + return !(guidOne == guidOther); +} + +inline bool IsEqualIID(REFIID riid1, REFIID riid2) { + return IsEqualGUID(riid1, riid2); +} + +inline bool IsEqualCLSID(REFCLSID rclsid1, REFCLSID rclsid2) { + return IsEqualGUID(rclsid1, rclsid2); +} + +//===--------------------- Struct Types -----------------------------------===// + +typedef struct _FILETIME { + DWORD dwLowDateTime; + DWORD dwHighDateTime; +} FILETIME, *PFILETIME, *LPFILETIME; + +typedef struct _BY_HANDLE_FILE_INFORMATION { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD dwVolumeSerialNumber; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD nNumberOfLinks; + DWORD nFileIndexHigh; + DWORD nFileIndexLow; +} BY_HANDLE_FILE_INFORMATION, *PBY_HANDLE_FILE_INFORMATION, + *LPBY_HANDLE_FILE_INFORMATION; + +typedef struct _WIN32_FIND_DATAW { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD dwReserved0; + DWORD dwReserved1; + WCHAR cFileName[260]; + WCHAR cAlternateFileName[14]; +} WIN32_FIND_DATAW, *PWIN32_FIND_DATAW, *LPWIN32_FIND_DATAW; + +typedef union _LARGE_INTEGER { + struct { + DWORD LowPart; + DWORD HighPart; + } u; + LONGLONG QuadPart; +} LARGE_INTEGER; + +typedef LARGE_INTEGER *PLARGE_INTEGER; + +typedef union _ULARGE_INTEGER { + struct { + DWORD LowPart; + DWORD HighPart; + } u; + ULONGLONG QuadPart; +} ULARGE_INTEGER; + +typedef ULARGE_INTEGER *PULARGE_INTEGER; + +typedef struct tagSTATSTG { + LPOLESTR pwcsName; + DWORD type; + ULARGE_INTEGER cbSize; + FILETIME mtime; + FILETIME ctime; + FILETIME atime; + DWORD grfMode; + DWORD grfLocksSupported; + CLSID clsid; + DWORD grfStateBits; + DWORD reserved; +} STATSTG; + +enum tagSTATFLAG { + STATFLAG_DEFAULT = 0, + STATFLAG_NONAME = 1, + STATFLAG_NOOPEN = 2 +}; + +//===--------------------- UUID Related Macros ----------------------------===// + +#ifdef __EMULATE_UUID + +// The following macros are defined to facilitate the lack of 'uuid' on Linux. + +constexpr uint8_t nybble_from_hex(char c) { + return ((c >= '0' && c <= '9') + ? (c - '0') + : ((c >= 'a' && c <= 'f') + ? (c - 'a' + 10) + : ((c >= 'A' && c <= 'F') ? (c - 'A' + 10) + : /* Should be an error */ -1))); +} + +constexpr uint8_t byte_from_hex(char c1, char c2) { + return nybble_from_hex(c1) << 4 | nybble_from_hex(c2); +} + +constexpr uint8_t byte_from_hexstr(const char str[2]) { + return nybble_from_hex(str[0]) << 4 | nybble_from_hex(str[1]); +} + +constexpr GUID guid_from_string(const char str[37]) { + return GUID{static_cast(byte_from_hexstr(str)) << 24 | + static_cast(byte_from_hexstr(str + 2)) << 16 | + static_cast(byte_from_hexstr(str + 4)) << 8 | + byte_from_hexstr(str + 6), + static_cast( + static_cast(byte_from_hexstr(str + 9)) << 8 | + byte_from_hexstr(str + 11)), + static_cast( + static_cast(byte_from_hexstr(str + 14)) << 8 | + byte_from_hexstr(str + 16)), + {byte_from_hexstr(str + 19), byte_from_hexstr(str + 21), + byte_from_hexstr(str + 24), byte_from_hexstr(str + 26), + byte_from_hexstr(str + 28), byte_from_hexstr(str + 30), + byte_from_hexstr(str + 32), byte_from_hexstr(str + 34)}}; +} + +template inline GUID __emulated_uuidof(); + +#define CROSS_PLATFORM_UUIDOF(interface, spec) \ + struct interface; \ + template <> inline GUID __emulated_uuidof() { \ + static const IID _IID = guid_from_string(spec); \ + return _IID; \ + } + +#define __uuidof(T) __emulated_uuidof::type>() + +#define IID_PPV_ARGS(ppType) \ + __uuidof(decltype(**(ppType))), reinterpret_cast(ppType) + +#else // __EMULATE_UUID + +#ifndef CROSS_PLATFORM_UUIDOF +// Warning: This macro exists in dxcapi.h as well +#define CROSS_PLATFORM_UUIDOF(interface, spec) \ + struct __declspec(uuid(spec)) interface; +#endif + +template inline void **IID_PPV_ARGS_Helper(T **pp) { + return reinterpret_cast(pp); +} +#define IID_PPV_ARGS(ppType) __uuidof(**(ppType)), IID_PPV_ARGS_Helper(ppType) + +#endif // __EMULATE_UUID + +// Needed for d3d headers, but fail to create actual interfaces +#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + const GUID name = {l, w1, w2, {b1, b2, b3, b4, b5, b6, b7, b8}} +#define DECLSPEC_UUID(x) +#define MIDL_INTERFACE(x) struct DECLSPEC_UUID(x) +#define DECLARE_INTERFACE(iface) struct iface +#define DECLARE_INTERFACE_(iface, parent) DECLARE_INTERFACE(iface) : parent + +//===--------------------- COM Interfaces ---------------------------------===// + +CROSS_PLATFORM_UUIDOF(IUnknown, "00000000-0000-0000-C000-000000000046") +struct IUnknown { + IUnknown(){}; + virtual HRESULT QueryInterface(REFIID riid, void **ppvObject) = 0; + virtual ULONG AddRef() = 0; + virtual ULONG Release() = 0; + template HRESULT QueryInterface(Q **pp) { + return QueryInterface(__uuidof(Q), (void **)pp); + } +}; + +CROSS_PLATFORM_UUIDOF(INoMarshal, "ECC8691B-C1DB-4DC0-855E-65F6C551AF49") +struct INoMarshal : public IUnknown {}; + +CROSS_PLATFORM_UUIDOF(IMalloc, "00000002-0000-0000-C000-000000000046") +struct IMalloc : public IUnknown { + virtual void *Alloc(SIZE_T size) = 0; + virtual void *Realloc(void *ptr, SIZE_T size) = 0; + virtual void Free(void *ptr) = 0; + virtual SIZE_T GetSize(void *pv) = 0; + virtual int DidAlloc(void *pv) = 0; + virtual void HeapMinimize(void) = 0; +}; + +CROSS_PLATFORM_UUIDOF(ISequentialStream, "0C733A30-2A1C-11CE-ADE5-00AA0044773D") +struct ISequentialStream : public IUnknown { + virtual HRESULT Read(void *pv, ULONG cb, ULONG *pcbRead) = 0; + virtual HRESULT Write(const void *pv, ULONG cb, ULONG *pcbWritten) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IStream, "0000000c-0000-0000-C000-000000000046") +struct IStream : public ISequentialStream { + virtual HRESULT Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, + ULARGE_INTEGER *plibNewPosition) = 0; + virtual HRESULT SetSize(ULARGE_INTEGER libNewSize) = 0; + virtual HRESULT CopyTo(IStream *pstm, ULARGE_INTEGER cb, + ULARGE_INTEGER *pcbRead, + ULARGE_INTEGER *pcbWritten) = 0; + + virtual HRESULT Commit(DWORD grfCommitFlags) = 0; + + virtual HRESULT Revert(void) = 0; + + virtual HRESULT LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, + DWORD dwLockType) = 0; + + virtual HRESULT UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, + DWORD dwLockType) = 0; + + virtual HRESULT Stat(STATSTG *pstatstg, DWORD grfStatFlag) = 0; + + virtual HRESULT Clone(IStream **ppstm) = 0; +}; + +// These don't need stub implementations as they come from the DirectX Headers +// They still need the __uuidof() though +CROSS_PLATFORM_UUIDOF(ID3D12LibraryReflection, + "8E349D19-54DB-4A56-9DC9-119D87BDB804") +CROSS_PLATFORM_UUIDOF(ID3D12ShaderReflection, + "5A58797D-A72C-478D-8BA2-EFC6B0EFE88E") + +//===--------------------- COM Pointer Types ------------------------------===// + +class CAllocator { +public: + static void *Reallocate(void *p, size_t nBytes) throw(); + static void *Allocate(size_t nBytes) throw(); + static void Free(void *p) throw(); +}; + +template class CComPtrBase { +protected: + CComPtrBase() throw() { p = nullptr; } + CComPtrBase(T *lp) throw() { + p = lp; + if (p != nullptr) + p->AddRef(); + } + void Swap(CComPtrBase &other) { + T *pTemp = p; + p = other.p; + other.p = pTemp; + } + +public: + ~CComPtrBase() throw() { + if (p) { + p->Release(); + p = nullptr; + } + } + operator T *() const throw() { return p; } + T &operator*() const { return *p; } + T *operator->() const { return p; } + T **operator&() throw() { + assert(p == nullptr); + return &p; + } + bool operator!() const throw() { return (p == nullptr); } + bool operator<(T *pT) const throw() { return p < pT; } + bool operator!=(T *pT) const { return !operator==(pT); } + bool operator==(T *pT) const throw() { return p == pT; } + + // Release the interface and set to nullptr + void Release() throw() { + T *pTemp = p; + if (pTemp) { + p = nullptr; + pTemp->Release(); + } + } + + // Attach to an existing interface (does not AddRef) + void Attach(T *p2) throw() { + if (p) { + ULONG ref = p->Release(); + (void)(ref); + // Attaching to the same object only works if duplicate references are + // being coalesced. Otherwise re-attaching will cause the pointer to be + // released and may cause a crash on a subsequent dereference. + assert(ref != 0 || p2 != p); + } + p = p2; + } + + // Detach the interface (does not Release) + T *Detach() throw() { + T *pt = p; + p = nullptr; + return pt; + } + + HRESULT CopyTo(T **ppT) throw() { + assert(ppT != nullptr); + if (ppT == nullptr) + return E_POINTER; + *ppT = p; + if (p) + p->AddRef(); + return S_OK; + } + + template HRESULT QueryInterface(Q **pp) const throw() { + assert(pp != nullptr); + return p->QueryInterface(__uuidof(Q), (void **)pp); + } + + T *p; +}; + +template class CComPtr : public CComPtrBase { +public: + CComPtr() throw() {} + CComPtr(T *lp) throw() : CComPtrBase(lp) {} + CComPtr(const CComPtr &lp) throw() : CComPtrBase(lp.p) {} + T *operator=(T *lp) throw() { + if (*this != lp) { + CComPtr(lp).Swap(*this); + } + return *this; + } + + inline bool IsEqualObject(IUnknown *pOther) throw() { + if (this->p == nullptr && pOther == nullptr) + return true; // They are both NULL objects + + if (this->p == nullptr || pOther == nullptr) + return false; // One is NULL the other is not + + CComPtr punk1; + CComPtr punk2; + this->p->QueryInterface(__uuidof(IUnknown), (void **)&punk1); + pOther->QueryInterface(__uuidof(IUnknown), (void **)&punk2); + return punk1 == punk2; + } + + void ComPtrAssign(IUnknown **pp, IUnknown *lp, REFIID riid) { + IUnknown *pTemp = *pp; // takes ownership + if (lp == nullptr || FAILED(lp->QueryInterface(riid, (void **)pp))) + *pp = nullptr; + if (pTemp) + pTemp->Release(); + } + + template T *operator=(const CComPtr &lp) throw() { + if (!this->IsEqualObject(lp)) { + ComPtrAssign((IUnknown **)&this->p, lp, __uuidof(T)); + } + return *this; + } + + // NOTE: This conversion constructor is not part of the official CComPtr spec; + // however, it is needed to convert CComPtr to CComPtr where T derives + // from Q on Clang. MSVC compiles this conversion as first a call to + // CComPtr::operator T*, followed by CComPtr(T*), but Clang fails to + // compile with error: no viable conversion from 'CComPtr' to 'CComPtr'. + template + CComPtr(const CComPtr &lp) throw() : CComPtrBase(lp.p) {} + + T *operator=(const CComPtr &lp) throw() { + if (*this != lp) { + CComPtr(lp).Swap(*this); + } + return *this; + } + + CComPtr(CComPtr &&lp) throw() : CComPtrBase() { lp.Swap(*this); } + + T *operator=(CComPtr &&lp) throw() { + if (*this != lp) { + CComPtr(static_cast(lp)).Swap(*this); + } + return *this; + } +}; + +template class CSimpleArray : public std::vector { +public: + bool Add(const T &t) { + this->push_back(t); + return true; + } + int GetSize() { return this->size(); } + T *GetData() { return this->data(); } + void RemoveAll() { this->clear(); } +}; + +template class CHeapPtrBase { +protected: + CHeapPtrBase() throw() : m_pData(NULL) {} + CHeapPtrBase(CHeapPtrBase &p) throw() { + m_pData = p.Detach(); // Transfer ownership + } + explicit CHeapPtrBase(T *pData) throw() : m_pData(pData) {} + +public: + ~CHeapPtrBase() throw() { Free(); } + +protected: + CHeapPtrBase &operator=(CHeapPtrBase &p) throw() { + if (m_pData != p.m_pData) + Attach(p.Detach()); // Transfer ownership + return *this; + } + +public: + operator T *() const throw() { return m_pData; } + T *operator->() const throw() { + assert(m_pData != NULL); + return m_pData; + } + + T **operator&() throw() { + assert(m_pData == NULL); + return &m_pData; + } + + // Allocate a buffer with the given number of bytes + bool AllocateBytes(size_t nBytes) throw() { + assert(m_pData == NULL); + m_pData = static_cast(Allocator::Allocate(nBytes * sizeof(char))); + if (m_pData == NULL) + return false; + + return true; + } + + // Attach to an existing pointer (takes ownership) + void Attach(T *pData) throw() { + Allocator::Free(m_pData); + m_pData = pData; + } + + // Detach the pointer (releases ownership) + T *Detach() throw() { + T *pTemp = m_pData; + m_pData = NULL; + return pTemp; + } + + // Free the memory pointed to, and set the pointer to NULL + void Free() throw() { + Allocator::Free(m_pData); + m_pData = NULL; + } + + // Reallocate the buffer to hold a given number of bytes + bool ReallocateBytes(size_t nBytes) throw() { + T *pNew; + pNew = + static_cast(Allocator::Reallocate(m_pData, nBytes * sizeof(char))); + if (pNew == NULL) + return false; + m_pData = pNew; + + return true; + } + +public: + T *m_pData; +}; + +template +class CHeapPtr : public CHeapPtrBase { +public: + CHeapPtr() throw() {} + CHeapPtr(CHeapPtr &p) throw() : CHeapPtrBase(p) {} + explicit CHeapPtr(T *p) throw() : CHeapPtrBase(p) {} + CHeapPtr &operator=(CHeapPtr &p) throw() { + CHeapPtrBase::operator=(p); + return *this; + } + + // Allocate a buffer with the given number of elements + bool Allocate(size_t nElements = 1) throw() { + size_t nBytes = nElements * sizeof(T); + return this->AllocateBytes(nBytes); + } + + // Reallocate the buffer to hold a given number of elements + bool Reallocate(size_t nElements) throw() { + size_t nBytes = nElements * sizeof(T); + return this->ReallocateBytes(nBytes); + } +}; + +#define CComHeapPtr CHeapPtr + +//===--------------------------- BSTR Allocation --------------------------===// + +void SysFreeString(BSTR bstrString); +// Allocate string with length prefix +BSTR SysAllocStringLen(const OLECHAR *strIn, UINT ui); + +//===--------------------------- BSTR Length ------------------------------===// +unsigned int SysStringLen(const BSTR bstrString); + +//===--------------------- UTF-8 Related Types ----------------------------===// + +// Code Page +#define CP_ACP 0 +#define CP_UTF8 65001 // UTF-8 translation. + +// RAII style mechanism for setting/unsetting a locale for the specified Windows +// codepage +class ScopedLocale { + locale_t Utf8Locale = nullptr; + locale_t PrevLocale = nullptr; + +public: + explicit ScopedLocale(uint32_t CodePage) { + assert((CodePage == CP_UTF8) && + "Support for Linux only handles UTF8 code pages"); + Utf8Locale = newlocale(LC_CTYPE_MASK, "C.UTF-8", NULL); + if (!Utf8Locale) + Utf8Locale = newlocale(LC_CTYPE_MASK, "C.utf8", NULL); + if (!Utf8Locale) + Utf8Locale = newlocale(LC_CTYPE_MASK, "en_US.UTF-8", NULL); + assert(Utf8Locale && "Failed to create UTF-8 locale"); + if (!Utf8Locale) + return; + PrevLocale = uselocale(Utf8Locale); + assert(PrevLocale && "Failed to set locale to UTF-8"); + if (!PrevLocale) { + freelocale(Utf8Locale); + Utf8Locale = nullptr; + } + } + ~ScopedLocale() { + if (PrevLocale != nullptr) + uselocale(PrevLocale); + if (Utf8Locale) + freelocale(Utf8Locale); + PrevLocale = nullptr; + Utf8Locale = nullptr; + } +}; + +// The t_nBufferLength parameter is part of the published interface, but not +// used here. +template class CW2AEX { +public: + CW2AEX(LPCWSTR psz) { + ScopedLocale locale(CP_UTF8); + + if (!psz) { + m_psz = NULL; + return; + } + + int len = (wcslen(psz) + 1) * 4; + m_psz = new char[len]; + std::wcstombs(m_psz, psz, len); + } + + ~CW2AEX() { delete[] m_psz; } + + operator LPSTR() const { return m_psz; } + + char *m_psz; +}; +typedef CW2AEX<> CW2A; + +// The t_nBufferLength parameter is part of the published interface, but not +// used here. +template class CA2WEX { +public: + CA2WEX(LPCSTR psz) { + ScopedLocale locale(CP_UTF8); + + if (!psz) { + m_psz = NULL; + return; + } + + int len = strlen(psz) + 1; + m_psz = new wchar_t[len]; + std::mbstowcs(m_psz, psz, len); + } + + ~CA2WEX() { delete[] m_psz; } + + operator LPWSTR() const { return m_psz; } + + wchar_t *m_psz; +}; + +typedef CA2WEX<> CA2W; + +//===--------- File IO Related Types ----------------===// + +class CHandle { +public: + CHandle(HANDLE h); + ~CHandle(); + operator HANDLE() const throw(); + +private: + HANDLE m_h; +}; + +///////////////////////////////////////////////////////////////////////////// +// CComBSTR + +class CComBSTR { +public: + BSTR m_str; + CComBSTR() : m_str(nullptr){}; + CComBSTR(int nSize, LPCWSTR sz); + ~CComBSTR() throw() { SysFreeString(m_str); } + unsigned int Length() const throw() { return SysStringLen(m_str); } + operator BSTR() const throw() { return m_str; } + + bool operator==(const CComBSTR &bstrSrc) const throw(); + + BSTR *operator&() throw() { return &m_str; } + + BSTR Detach() throw() { + BSTR s = m_str; + m_str = NULL; + return s; + } + + void Empty() throw() { + SysFreeString(m_str); + m_str = NULL; + } +}; + +//===--------- Convert argv to wchar ----------------===// +class WArgV { + std::vector WStringVector; + std::vector WCharPtrVector; + +public: + WArgV(int argc, const char **argv); + const wchar_t **argv() { return WCharPtrVector.data(); } +}; + +#endif // __cplusplus + +#endif // _WIN32 + +#endif // LLVM_SUPPORT_WIN_ADAPTER_H diff --git a/Engine/cpp/ThirdParty/DXC/include/dxc/dxcapi.h b/Engine/cpp/ThirdParty/DXC/include/dxc/dxcapi.h new file mode 100644 index 00000000..95cc56a0 --- /dev/null +++ b/Engine/cpp/ThirdParty/DXC/include/dxc/dxcapi.h @@ -0,0 +1,1309 @@ + +/////////////////////////////////////////////////////////////////////////////// +// // +// dxcapi.h // +// Copyright (C) Microsoft Corporation. All rights reserved. // +// This file is distributed under the University of Illinois Open Source // +// License. See LICENSE.TXT for details. // +// // +// Provides declarations for the DirectX Compiler API entry point. // +// // +/////////////////////////////////////////////////////////////////////////////// + +#ifndef __DXC_API__ +#define __DXC_API__ + +#ifdef _WIN32 +#ifndef DXC_API_IMPORT +#define DXC_API_IMPORT __declspec(dllimport) +#endif +#else +#ifndef DXC_API_IMPORT +#define DXC_API_IMPORT __attribute__((visibility("default"))) +#endif +#endif + +#ifdef _WIN32 + +#ifndef CROSS_PLATFORM_UUIDOF +// Warning: This macro exists in WinAdapter.h as well +#define CROSS_PLATFORM_UUIDOF(interface, spec) \ + struct __declspec(uuid(spec)) interface; +#endif + +#else + +#include "WinAdapter.h" +#include +#endif + +struct IMalloc; + +struct IDxcIncludeHandler; + +/// \brief Typedef for DxcCreateInstance function pointer. +/// +/// This can be used with GetProcAddress to get the DxcCreateInstance function. +typedef HRESULT(__stdcall *DxcCreateInstanceProc)(_In_ REFCLSID rclsid, + _In_ REFIID riid, + _Out_ LPVOID *ppv); + +/// \brief Typedef for DxcCreateInstance2 function pointer. +/// +/// This can be used with GetProcAddress to get the DxcCreateInstance2 function. +typedef HRESULT(__stdcall *DxcCreateInstance2Proc)(_In_ IMalloc *pMalloc, + _In_ REFCLSID rclsid, + _In_ REFIID riid, + _Out_ LPVOID *ppv); + +/// \brief Creates a single uninitialized object of the class associated with a +/// specified CLSID. +/// +/// \param rclsid The CLSID associated with the data and code that will be used +/// to create the object. +/// +/// \param riid A reference to the identifier of the interface to be used to +/// communicate with the object. +/// +/// \param ppv Address of pointer variable that receives the interface pointer +/// requested in riid. Upon successful return, *ppv contains the requested +/// interface pointer. Upon failure, *ppv contains NULL. +/// +/// While this function is similar to CoCreateInstance, there is no COM +/// involvement. +extern "C" DXC_API_IMPORT + HRESULT __stdcall DxcCreateInstance(_In_ REFCLSID rclsid, _In_ REFIID riid, + _Out_ LPVOID *ppv); + +/// \brief Version of DxcCreateInstance that takes an IMalloc interface. +/// +/// This can be used to create an instance of the compiler with a custom memory +/// allocator. +extern "C" DXC_API_IMPORT + HRESULT __stdcall DxcCreateInstance2(_In_ IMalloc *pMalloc, + _In_ REFCLSID rclsid, _In_ REFIID riid, + _Out_ LPVOID *ppv); + +// For convenience, equivalent definitions to CP_UTF8 and CP_UTF16. +#define DXC_CP_UTF8 65001 +#define DXC_CP_UTF16 1200 +#define DXC_CP_UTF32 12000 +// Use DXC_CP_ACP for: Binary; ANSI Text; Autodetect UTF with BOM +#define DXC_CP_ACP 0 + +/// Codepage for "wide" characters - UTF16 on Windows, UTF32 on other platforms. +#ifdef _WIN32 +#define DXC_CP_WIDE DXC_CP_UTF16 +#else +#define DXC_CP_WIDE DXC_CP_UTF32 +#endif + +/// Indicates that the shader hash was computed taking into account source +/// information (-Zss). +#define DXC_HASHFLAG_INCLUDES_SOURCE 1 + +/// Hash digest type for ShaderHash. +typedef struct DxcShaderHash { + UINT32 Flags; ///< DXC_HASHFLAG_* + BYTE HashDigest[16]; ///< The hash digest +} DxcShaderHash; + +#define DXC_FOURCC(ch0, ch1, ch2, ch3) \ + ((UINT32)(UINT8)(ch0) | (UINT32)(UINT8)(ch1) << 8 | \ + (UINT32)(UINT8)(ch2) << 16 | (UINT32)(UINT8)(ch3) << 24) +#define DXC_PART_PDB DXC_FOURCC('I', 'L', 'D', 'B') +#define DXC_PART_PDB_NAME DXC_FOURCC('I', 'L', 'D', 'N') +#define DXC_PART_PRIVATE_DATA DXC_FOURCC('P', 'R', 'I', 'V') +#define DXC_PART_ROOT_SIGNATURE DXC_FOURCC('R', 'T', 'S', '0') +#define DXC_PART_DXIL DXC_FOURCC('D', 'X', 'I', 'L') +#define DXC_PART_REFLECTION_DATA DXC_FOURCC('S', 'T', 'A', 'T') +#define DXC_PART_SHADER_HASH DXC_FOURCC('H', 'A', 'S', 'H') +#define DXC_PART_INPUT_SIGNATURE DXC_FOURCC('I', 'S', 'G', '1') +#define DXC_PART_OUTPUT_SIGNATURE DXC_FOURCC('O', 'S', 'G', '1') +#define DXC_PART_PATCH_CONSTANT_SIGNATURE DXC_FOURCC('P', 'S', 'G', '1') + +// Some option arguments are defined here for continuity with D3DCompile +// interface. +#define DXC_ARG_DEBUG L"-Zi" +#define DXC_ARG_SKIP_VALIDATION L"-Vd" +#define DXC_ARG_SKIP_OPTIMIZATIONS L"-Od" +#define DXC_ARG_PACK_MATRIX_ROW_MAJOR L"-Zpr" +#define DXC_ARG_PACK_MATRIX_COLUMN_MAJOR L"-Zpc" +#define DXC_ARG_AVOID_FLOW_CONTROL L"-Gfa" +#define DXC_ARG_PREFER_FLOW_CONTROL L"-Gfp" +#define DXC_ARG_ENABLE_STRICTNESS L"-Ges" +#define DXC_ARG_ENABLE_BACKWARDS_COMPATIBILITY L"-Gec" +#define DXC_ARG_IEEE_STRICTNESS L"-Gis" +#define DXC_ARG_OPTIMIZATION_LEVEL0 L"-O0" +#define DXC_ARG_OPTIMIZATION_LEVEL1 L"-O1" +#define DXC_ARG_OPTIMIZATION_LEVEL2 L"-O2" +#define DXC_ARG_OPTIMIZATION_LEVEL3 L"-O3" +#define DXC_ARG_WARNINGS_ARE_ERRORS L"-WX" +#define DXC_ARG_RESOURCES_MAY_ALIAS L"-res_may_alias" +#define DXC_ARG_ALL_RESOURCES_BOUND L"-all_resources_bound" +#define DXC_ARG_DEBUG_NAME_FOR_SOURCE L"-Zss" +#define DXC_ARG_DEBUG_NAME_FOR_BINARY L"-Zsb" + +CROSS_PLATFORM_UUIDOF(IDxcBlob, "8BA5FB08-5195-40e2-AC58-0D989C3A0102") +/// \brief A sized buffer that can be passed in and out of DXC APIs. +/// +/// This is an alias of ID3D10Blob and ID3DBlob. +struct IDxcBlob : public IUnknown { +public: + /// \brief Retrieves a pointer to the blob's data. + virtual LPVOID STDMETHODCALLTYPE GetBufferPointer(void) = 0; + + /// \brief Retrieves the size, in bytes, of the blob's data. + virtual SIZE_T STDMETHODCALLTYPE GetBufferSize(void) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcBlobEncoding, "7241d424-2646-4191-97c0-98e96e42fc68") +/// \brief A blob that might have a known encoding. +struct IDxcBlobEncoding : public IDxcBlob { +public: + /// \brief Retrieve the encoding for this blob. + /// + /// \param pKnown Pointer to a variable that will be set to TRUE if the + /// encoding is known. + /// + /// \param pCodePage Pointer to variable that will be set to the encoding used + /// for this blog. + /// + /// If the encoding is not known then pCodePage will be set to CP_ACP. + virtual HRESULT STDMETHODCALLTYPE GetEncoding(_Out_ BOOL *pKnown, + _Out_ UINT32 *pCodePage) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcBlobWide, "A3F84EAB-0FAA-497E-A39C-EE6ED60B2D84") +/// \brief A blob containing a null-terminated wide string. +/// +/// This uses the native wide character encoding (utf16 on Windows, utf32 on +/// Linux). +/// +/// The value returned by GetBufferSize() is the size of the buffer, in bytes, +/// including the null-terminator. +/// +/// This interface is used to return output name strings DXC. Other string +/// output blobs, such as errors/warnings, preprocessed HLSL, or other text are +/// returned using encodings based on the -encoding option passed to the +/// compiler. +struct IDxcBlobWide : public IDxcBlobEncoding { +public: + /// \brief Retrieves a pointer to the string stored in this blob. + virtual LPCWSTR STDMETHODCALLTYPE GetStringPointer(void) = 0; + + /// \brief Retrieves the length of the string stored in this blob, in + /// characters, excluding the null-terminator. + virtual SIZE_T STDMETHODCALLTYPE GetStringLength(void) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcBlobUtf8, "3DA636C9-BA71-4024-A301-30CBF125305B") +/// \brief A blob containing a UTF-8 encoded string. +/// +/// The value returned by GetBufferSize() is the size of the buffer, in bytes, +/// including the null-terminator. +/// +/// Depending on the -encoding option passed to the compiler, this interface is +/// used to return string output blobs, such as errors/warnings, preprocessed +/// HLSL, or other text. Output name strings always use IDxcBlobWide. +struct IDxcBlobUtf8 : public IDxcBlobEncoding { +public: + /// \brief Retrieves a pointer to the string stored in this blob. + virtual LPCSTR STDMETHODCALLTYPE GetStringPointer(void) = 0; + + /// \brief Retrieves the length of the string stored in this blob, in + /// characters, excluding the null-terminator. + virtual SIZE_T STDMETHODCALLTYPE GetStringLength(void) = 0; +}; + +#ifdef _WIN32 +/// IDxcBlobUtf16 is a legacy alias for IDxcBlobWide on Win32. +typedef IDxcBlobWide IDxcBlobUtf16; +#endif + +CROSS_PLATFORM_UUIDOF(IDxcIncludeHandler, + "7f61fc7d-950d-467f-b3e3-3c02fb49187c") +/// \brief Interface for handling include directives. +/// +/// This interface can be implemented to customize handling of include +/// directives. +/// +/// Use IDxcUtils::CreateDefaultIncludeHandler to create a default +/// implementation that reads include files from the filesystem. +/// +struct IDxcIncludeHandler : public IUnknown { + /// \brief Load a source file to be included by the compiler. + /// + /// \param pFilename Candidate filename. + /// + /// \param ppIncludeSource Resultant source object for included file, nullptr + /// if not found. + virtual HRESULT STDMETHODCALLTYPE + LoadSource(_In_z_ LPCWSTR pFilename, + _COM_Outptr_result_maybenull_ IDxcBlob **ppIncludeSource) = 0; +}; + +/// \brief Structure for supplying bytes or text input to Dxc APIs. +typedef struct DxcBuffer { + /// \brief Pointer to the start of the buffer. + LPCVOID Ptr; + + /// \brief Size of the buffer in bytes. + SIZE_T Size; + + /// \brief Encoding of the buffer. + /// + /// Use Encoding = 0 for non-text bytes, ANSI text, or unknown with BOM. + UINT Encoding; +} DxcText; + +/// \brief Structure for supplying defines to Dxc APIs. +struct DxcDefine { + LPCWSTR Name; ///< The define name. + _Maybenull_ LPCWSTR Value; ///< Optional value for the define. +}; + +CROSS_PLATFORM_UUIDOF(IDxcCompilerArgs, "73EFFE2A-70DC-45F8-9690-EFF64C02429D") +/// \brief Interface for managing arguments passed to DXC. +/// +/// Use IDxcUtils::BuildArguments to create an instance of this interface. +struct IDxcCompilerArgs : public IUnknown { + /// \brief Retrieve the array of arguments. + /// + /// This can be passed directly to the pArguments parameter of the Compile() + /// method. + virtual LPCWSTR *STDMETHODCALLTYPE GetArguments() = 0; + + /// \brief Retrieve the number of arguments. + /// + /// This can be passed directly to the argCount parameter of the Compile() + /// method. + virtual UINT32 STDMETHODCALLTYPE GetCount() = 0; + + /// \brief Add additional arguments to this list of compiler arguments. + virtual HRESULT STDMETHODCALLTYPE AddArguments( + _In_opt_count_(argCount) + LPCWSTR *pArguments, ///< Array of pointers to arguments to add. + _In_ UINT32 argCount ///< Number of arguments to add. + ) = 0; + + /// \brief Add additional UTF-8 encoded arguments to this list of compiler + /// arguments. + virtual HRESULT STDMETHODCALLTYPE AddArgumentsUTF8( + _In_opt_count_(argCount) + LPCSTR *pArguments, ///< Array of pointers to UTF-8 arguments to add. + _In_ UINT32 argCount ///< Number of arguments to add. + ) = 0; + + /// \brief Add additional defines to this list of compiler arguments. + virtual HRESULT STDMETHODCALLTYPE AddDefines( + _In_count_(defineCount) const DxcDefine *pDefines, ///< Array of defines. + _In_ UINT32 defineCount ///< Number of defines. + ) = 0; +}; + +////////////////////////// +// Legacy Interfaces +///////////////////////// + +CROSS_PLATFORM_UUIDOF(IDxcLibrary, "e5204dc7-d18c-4c3c-bdfb-851673980fe7") +/// \deprecated IDxcUtils replaces IDxcLibrary; please use IDxcUtils insted. +struct IDxcLibrary : public IUnknown { + /// \deprecated + virtual HRESULT STDMETHODCALLTYPE SetMalloc(_In_opt_ IMalloc *pMalloc) = 0; + + /// \deprecated + virtual HRESULT STDMETHODCALLTYPE + CreateBlobFromBlob(_In_ IDxcBlob *pBlob, UINT32 offset, UINT32 length, + _COM_Outptr_ IDxcBlob **ppResult) = 0; + + /// \deprecated + virtual HRESULT STDMETHODCALLTYPE + CreateBlobFromFile(_In_z_ LPCWSTR pFileName, _In_opt_ UINT32 *codePage, + _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; + + /// \deprecated + virtual HRESULT STDMETHODCALLTYPE CreateBlobWithEncodingFromPinned( + _In_bytecount_(size) LPCVOID pText, UINT32 size, UINT32 codePage, + _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; + + /// \deprecated + virtual HRESULT STDMETHODCALLTYPE CreateBlobWithEncodingOnHeapCopy( + _In_bytecount_(size) LPCVOID pText, UINT32 size, UINT32 codePage, + _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; + + /// \deprecated + virtual HRESULT STDMETHODCALLTYPE CreateBlobWithEncodingOnMalloc( + _In_bytecount_(size) LPCVOID pText, IMalloc *pIMalloc, UINT32 size, + UINT32 codePage, _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; + + /// \deprecated + virtual HRESULT STDMETHODCALLTYPE + CreateIncludeHandler(_COM_Outptr_ IDxcIncludeHandler **ppResult) = 0; + + /// \deprecated + virtual HRESULT STDMETHODCALLTYPE CreateStreamFromBlobReadOnly( + _In_ IDxcBlob *pBlob, _COM_Outptr_ IStream **ppStream) = 0; + + /// \deprecated + virtual HRESULT STDMETHODCALLTYPE GetBlobAsUtf8( + _In_ IDxcBlob *pBlob, _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; + + // Renamed from GetBlobAsUtf16 to GetBlobAsWide + /// \deprecated + virtual HRESULT STDMETHODCALLTYPE GetBlobAsWide( + _In_ IDxcBlob *pBlob, _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) = 0; + +#ifdef _WIN32 + // Alias to GetBlobAsWide on Win32 + /// \deprecated + inline HRESULT GetBlobAsUtf16(_In_ IDxcBlob *pBlob, + _COM_Outptr_ IDxcBlobEncoding **pBlobEncoding) { + return this->GetBlobAsWide(pBlob, pBlobEncoding); + } +#endif +}; + +CROSS_PLATFORM_UUIDOF(IDxcOperationResult, + "CEDB484A-D4E9-445A-B991-CA21CA157DC2") +/// \brief The results of a DXC operation. +/// +/// Note: IDxcResult replaces IDxcOperationResult and should be used wherever +/// possible. +struct IDxcOperationResult : public IUnknown { + /// \brief Retrieve the overall status of the operation. + virtual HRESULT STDMETHODCALLTYPE GetStatus(_Out_ HRESULT *pStatus) = 0; + + /// \brief Retrieve the primary output of the operation. + /// + /// This corresponds to: + /// * DXC_OUT_OBJECT - Compile() with shader or library target + /// * DXC_OUT_DISASSEMBLY - Disassemble() + /// * DXC_OUT_HLSL - Compile() with -P + /// * DXC_OUT_ROOT_SIGNATURE - Compile() with rootsig_* target + virtual HRESULT STDMETHODCALLTYPE + GetResult(_COM_Outptr_result_maybenull_ IDxcBlob **ppResult) = 0; + + /// \brief Retrieves the error buffer from the operation, if there is one. + /// + // This corresponds to calling IDxcResult::GetOutput() with DXC_OUT_ERRORS. + virtual HRESULT STDMETHODCALLTYPE + GetErrorBuffer(_COM_Outptr_result_maybenull_ IDxcBlobEncoding **ppErrors) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcCompiler, "8c210bf3-011f-4422-8d70-6f9acb8db617") +/// \deprecated Please use IDxcCompiler3 instead. +struct IDxcCompiler : public IUnknown { + /// \brief Compile a single entry point to the target shader model. + /// + /// \deprecated Please use IDxcCompiler3::Compile() instead. + virtual HRESULT STDMETHODCALLTYPE Compile( + _In_ IDxcBlob *pSource, // Source text to compile. + _In_opt_z_ LPCWSTR pSourceName, // Optional file name for pSource. Used in + // errors and include handlers. + _In_opt_z_ LPCWSTR pEntryPoint, // Entry point name. + _In_z_ LPCWSTR pTargetProfile, // Shader profile to compile. + _In_opt_count_(argCount) + LPCWSTR *pArguments, // Array of pointers to arguments. + _In_ UINT32 argCount, // Number of arguments. + _In_count_(defineCount) const DxcDefine *pDefines, // Array of defines. + _In_ UINT32 defineCount, // Number of defines. + _In_opt_ IDxcIncludeHandler + *pIncludeHandler, // User-provided interface to handle #include + // directives (optional). + _COM_Outptr_ IDxcOperationResult * + *ppResult // Compiler output status, buffer, and errors. + ) = 0; + + /// \brief Preprocess source text. + /// + /// \deprecated Please use IDxcCompiler3::Compile() with the "-P" argument + /// instead. + virtual HRESULT STDMETHODCALLTYPE Preprocess( + _In_ IDxcBlob *pSource, // Source text to preprocess. + _In_opt_z_ LPCWSTR pSourceName, // Optional file name for pSource. Used in + // errors and include handlers. + _In_opt_count_(argCount) + LPCWSTR *pArguments, // Array of pointers to arguments. + _In_ UINT32 argCount, // Number of arguments. + _In_count_(defineCount) const DxcDefine *pDefines, // Array of defines. + _In_ UINT32 defineCount, // Number of defines. + _In_opt_ IDxcIncludeHandler + *pIncludeHandler, // user-provided interface to handle #include + // directives (optional). + _COM_Outptr_ IDxcOperationResult * + *ppResult // Preprocessor output status, buffer, and errors. + ) = 0; + + /// \brief Disassemble a program. + /// + /// \deprecated Please use IDxcCompiler3::Disassemble() instead. + virtual HRESULT STDMETHODCALLTYPE Disassemble( + _In_ IDxcBlob *pSource, // Program to disassemble. + _COM_Outptr_ IDxcBlobEncoding **ppDisassembly // Disassembly text. + ) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcCompiler2, "A005A9D9-B8BB-4594-B5C9-0E633BEC4D37") +/// \deprecated Please use IDxcCompiler3 instead. +struct IDxcCompiler2 : public IDxcCompiler { + /// \brief Compile a single entry point to the target shader model with debug + /// information. + /// + /// \deprecated Please use IDxcCompiler3::Compile() instead. + virtual HRESULT STDMETHODCALLTYPE CompileWithDebug( + _In_ IDxcBlob *pSource, // Source text to compile. + _In_opt_z_ LPCWSTR pSourceName, // Optional file name for pSource. Used in + // errors and include handlers. + _In_opt_z_ LPCWSTR pEntryPoint, // Entry point name. + _In_z_ LPCWSTR pTargetProfile, // Shader profile to compile. + _In_opt_count_(argCount) + LPCWSTR *pArguments, // Array of pointers to arguments. + _In_ UINT32 argCount, // Number of arguments. + _In_count_(defineCount) const DxcDefine *pDefines, // Array of defines. + _In_ UINT32 defineCount, // Number of defines. + _In_opt_ IDxcIncludeHandler + *pIncludeHandler, // user-provided interface to handle #include + // directives (optional). + _COM_Outptr_ IDxcOperationResult * + *ppResult, // Compiler output status, buffer, and errors. + _Outptr_opt_result_z_ LPWSTR + *ppDebugBlobName, // Suggested file name for debug blob. Must be + // CoTaskMemFree()'d. + _COM_Outptr_opt_ IDxcBlob **ppDebugBlob // Debug blob. + ) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcLinker, "F1B5BE2A-62DD-4327-A1C2-42AC1E1E78E6") +/// \brief DXC linker interface. +/// +/// Use DxcCreateInstance with CLSID_DxcLinker to obtain an instance of this +/// interface. +struct IDxcLinker : public IUnknown { +public: + /// \brief Register a library with name to reference it later. + virtual HRESULT + RegisterLibrary(_In_opt_ LPCWSTR pLibName, ///< Name of the library. + _In_ IDxcBlob *pLib ///< Library blob. + ) = 0; + + /// \brief Links the shader and produces a shader blob that the Direct3D + /// runtime can use. + virtual HRESULT STDMETHODCALLTYPE Link( + _In_opt_ LPCWSTR pEntryName, ///< Entry point name. + _In_ LPCWSTR pTargetProfile, ///< shader profile to link. + _In_count_(libCount) + const LPCWSTR *pLibNames, ///< Array of library names to link. + _In_ UINT32 libCount, ///< Number of libraries to link. + _In_opt_count_(argCount) + const LPCWSTR *pArguments, ///< Array of pointers to arguments. + _In_ UINT32 argCount, ///< Number of arguments. + _COM_Outptr_ IDxcOperationResult * + *ppResult ///< Linker output status, buffer, and errors. + ) = 0; +}; + +///////////////////////// +// Latest interfaces. Please use these. +//////////////////////// + +CROSS_PLATFORM_UUIDOF(IDxcUtils, "4605C4CB-2019-492A-ADA4-65F20BB7D67F") +/// \brief Various utility functions for DXC. +/// +/// Use DxcCreateInstance with CLSID_DxcUtils to obtain an instance of this +/// interface. +/// +/// IDxcUtils replaces IDxcLibrary. +struct IDxcUtils : public IUnknown { + /// \brief Create a sub-blob that holds a reference to the outer blob and + /// points to its memory. + /// + /// \param pBlob The outer blob. + /// + /// \param offset The offset inside the outer blob. + /// + /// \param length The size, in bytes, of the buffer to reference from the + /// output blob. + /// + /// \param ppResult Address of the pointer that receives a pointer to the + /// newly created blob. + virtual HRESULT STDMETHODCALLTYPE + CreateBlobFromBlob(_In_ IDxcBlob *pBlob, UINT32 offset, UINT32 length, + _COM_Outptr_ IDxcBlob **ppResult) = 0; + + // For codePage, use 0 (or DXC_CP_ACP) for raw binary or ANSI code page. + + /// \brief Create a blob referencing existing memory, with no copy. + /// + /// \param pData Pointer to buffer containing the contents of the new blob. + /// + /// \param size The size of the pData buffer, in bytes. + /// + /// \param codePage The code page to use if the blob contains text. Use + /// DXC_CP_ACP for binary or ANSI code page. + /// + /// \param ppBlobEncoding Address of the pointer that receives a pointer to + /// the newly created blob. + /// + /// The user must manage the memory lifetime separately. + /// + /// This replaces IDxcLibrary::CreateBlobWithEncodingFromPinned. + virtual HRESULT STDMETHODCALLTYPE CreateBlobFromPinned( + _In_bytecount_(size) LPCVOID pData, UINT32 size, UINT32 codePage, + _COM_Outptr_ IDxcBlobEncoding **ppBlobEncoding) = 0; + + /// \brief Create a blob, taking ownership of memory allocated with the + /// supplied allocator. + /// + /// \param pData Pointer to buffer containing the contents of the new blob. + /// + /// \param pIMalloc The memory allocator to use. + /// + /// \param size The size of thee pData buffer, in bytes. + /// + /// \param codePage The code page to use if the blob contains text. Use + /// DXC_CP_ACP for binary or ANSI code page. + /// + /// \param ppBlobEncoding Address of the pointer that receives a pointer to + /// the newly created blob. + /// + /// This replaces IDxcLibrary::CreateBlobWithEncodingOnMalloc. + virtual HRESULT STDMETHODCALLTYPE MoveToBlob( + _In_bytecount_(size) LPCVOID pData, IMalloc *pIMalloc, UINT32 size, + UINT32 codePage, _COM_Outptr_ IDxcBlobEncoding **ppBlobEncoding) = 0; + + /// \brief Create a blob containing a copy of the existing data. + /// + /// \param pData Pointer to buffer containing the contents of the new blob. + /// + /// \param size The size of thee pData buffer, in bytes. + /// + /// \param codePage The code page to use if the blob contains text. Use + /// DXC_CP_ACP for binary or ANSI code page. + /// + /// \param ppBlobEncoding Address of the pointer that receives a pointer to + /// the newly created blob. + /// + /// The new blob and its contents are allocated with the current allocator. + /// This replaces IDxcLibrary::CreateBlobWithEncodingOnHeapCopy. + virtual HRESULT STDMETHODCALLTYPE + CreateBlob(_In_bytecount_(size) LPCVOID pData, UINT32 size, UINT32 codePage, + _COM_Outptr_ IDxcBlobEncoding **ppBlobEncoding) = 0; + + /// \brief Create a blob with data loaded from a file. + /// + /// \param pFileName The name of the file to load from. + /// + /// \param pCodePage Optional code page to use if the blob contains text. Pass + /// NULL for binary data. + /// + /// \param ppBlobEncoding Address of the pointer that receives a pointer to + /// the newly created blob. + /// + /// The new blob and its contents are allocated with the current allocator. + /// This replaces IDxcLibrary::CreateBlobFromFile. + virtual HRESULT STDMETHODCALLTYPE + LoadFile(_In_z_ LPCWSTR pFileName, _In_opt_ UINT32 *pCodePage, + _COM_Outptr_ IDxcBlobEncoding **ppBlobEncoding) = 0; + + /// \brief Create a stream that reads data from a blob. + /// + /// \param pBlob The blob to read from. + /// + /// \param ppStream Address of the pointer that receives a pointer to the + /// newly created stream. + virtual HRESULT STDMETHODCALLTYPE CreateReadOnlyStreamFromBlob( + _In_ IDxcBlob *pBlob, _COM_Outptr_ IStream **ppStream) = 0; + + /// \brief Create default file-based include handler. + /// + /// \param ppResult Address of the pointer that receives a pointer to the + /// newly created include handler. + virtual HRESULT STDMETHODCALLTYPE + CreateDefaultIncludeHandler(_COM_Outptr_ IDxcIncludeHandler **ppResult) = 0; + + /// \brief Convert or return matching encoded text blob as UTF-8. + /// + /// \param pBlob The blob to convert. + /// + /// \param ppBlobEncoding Address of the pointer that receives a pointer to + /// the newly created blob. + virtual HRESULT STDMETHODCALLTYPE GetBlobAsUtf8( + _In_ IDxcBlob *pBlob, _COM_Outptr_ IDxcBlobUtf8 **ppBlobEncoding) = 0; + + /// \brief Convert or return matching encoded text blob as UTF-16. + /// + /// \param pBlob The blob to convert. + /// + /// \param ppBlobEncoding Address of the pointer that receives a pointer to + /// the newly created blob. + virtual HRESULT STDMETHODCALLTYPE GetBlobAsWide( + _In_ IDxcBlob *pBlob, _COM_Outptr_ IDxcBlobWide **ppBlobEncoding) = 0; + +#ifdef _WIN32 + /// \brief Convert or return matching encoded text blob as UTF-16. + /// + /// \param pBlob The blob to convert. + /// + /// \param ppBlobEncoding Address of the pointer that receives a pointer to + /// the newly created blob. + /// + /// Alias to GetBlobAsWide on Win32. + inline HRESULT GetBlobAsUtf16(_In_ IDxcBlob *pBlob, + _COM_Outptr_ IDxcBlobWide **ppBlobEncoding) { + return this->GetBlobAsWide(pBlob, ppBlobEncoding); + } +#endif + + /// \brief Retrieve a single part from a DXIL container. + /// + /// \param pShader The shader to retrieve the part from. + /// + /// \param DxcPart The part to retrieve (eg DXC_PART_ROOT_SIGNATURE). + /// + /// \param ppPartData Address of the pointer that receives a pointer to the + /// part. + /// + /// \param pPartSizeInBytes Address of the pointer that receives the size of + /// the part. + /// + /// The returned pointer points inside the buffer passed in pShader. + virtual HRESULT STDMETHODCALLTYPE + GetDxilContainerPart(_In_ const DxcBuffer *pShader, _In_ UINT32 DxcPart, + _Outptr_result_nullonfailure_ void **ppPartData, + _Out_ UINT32 *pPartSizeInBytes) = 0; + + /// \brief Create reflection interface from serialized DXIL container or the + /// DXC_OUT_REFLECTION blob contents. + /// + /// \param pData The source data. + /// + /// \param iid The interface ID of the reflection interface to create. + /// + /// \param ppvReflection Address of the pointer that receives a pointer to the + /// newly created reflection interface. + /// + /// Use this with interfaces such as ID3D12ShaderReflection. + virtual HRESULT STDMETHODCALLTYPE CreateReflection( + _In_ const DxcBuffer *pData, REFIID iid, void **ppvReflection) = 0; + + /// \brief Build arguments that can be passed to the Compile method. + virtual HRESULT STDMETHODCALLTYPE BuildArguments( + _In_opt_z_ LPCWSTR pSourceName, ///< Optional file name for pSource. Used + ///< in errors and include handlers. + _In_opt_z_ LPCWSTR pEntryPoint, ///< Entry point name (-E). + _In_z_ LPCWSTR pTargetProfile, ///< Shader profile to compile (-T). + _In_opt_count_(argCount) + LPCWSTR *pArguments, ///< Array of pointers to arguments. + _In_ UINT32 argCount, ///< Number of arguments. + _In_count_(defineCount) const DxcDefine *pDefines, ///< Array of defines. + _In_ UINT32 defineCount, ///< Number of defines. + _COM_Outptr_ IDxcCompilerArgs * + *ppArgs ///< Arguments you can use with Compile() method. + ) = 0; + + /// \brief Retrieve the hash and contents of a shader PDB. + /// + /// \param pPDBBlob The blob containing the PDB. + /// + /// \param ppHash Address of the pointer that receives a pointer to the hash + /// blob. + /// + /// \param ppContainer Address of the pointer that receives a pointer to the + /// bloc containing the contents of the PDB. + /// + virtual HRESULT STDMETHODCALLTYPE + GetPDBContents(_In_ IDxcBlob *pPDBBlob, _COM_Outptr_ IDxcBlob **ppHash, + _COM_Outptr_ IDxcBlob **ppContainer) = 0; +}; + +/// \brief Specifies the kind of output to retrieve from a IDxcResult. +/// +/// Note: text outputs returned from version 2 APIs are UTF-8 or UTF-16 based on +/// the -encoding option passed to the compiler. +typedef enum DXC_OUT_KIND { + DXC_OUT_NONE = 0, ///< No output. + DXC_OUT_OBJECT = 1, ///< IDxcBlob - Shader or library object. + DXC_OUT_ERRORS = 2, ///< IDxcBlobUtf8 or IDxcBlobWide. + DXC_OUT_PDB = 3, ///< IDxcBlob. + DXC_OUT_SHADER_HASH = 4, ///< IDxcBlob - DxcShaderHash of shader or shader + ///< with source info (-Zsb/-Zss). + DXC_OUT_DISASSEMBLY = 5, ///< IDxcBlobUtf8 or IDxcBlobWide - from Disassemble. + DXC_OUT_HLSL = + 6, ///< IDxcBlobUtf8 or IDxcBlobWide - from Preprocessor or Rewriter. + DXC_OUT_TEXT = 7, ///< IDxcBlobUtf8 or IDxcBlobWide - other text, such as + ///< -ast-dump or -Odump. + DXC_OUT_REFLECTION = 8, ///< IDxcBlob - RDAT part with reflection data. + DXC_OUT_ROOT_SIGNATURE = 9, ///< IDxcBlob - Serialized root signature output. + DXC_OUT_EXTRA_OUTPUTS = 10, ///< IDxcExtraOutputs - Extra outputs. + DXC_OUT_REMARKS = + 11, ///< IDxcBlobUtf8 or IDxcBlobWide - text directed at stdout. + DXC_OUT_TIME_REPORT = + 12, ///< IDxcBlobUtf8 or IDxcBlobWide - text directed at stdout. + DXC_OUT_TIME_TRACE = + 13, ///< IDxcBlobUtf8 or IDxcBlobWide - text directed at stdout. + + DXC_OUT_LAST = DXC_OUT_TIME_TRACE, ///< Last value for a counter. + + DXC_OUT_NUM_ENUMS, + DXC_OUT_FORCE_DWORD = 0xFFFFFFFF +} DXC_OUT_KIND; + +static_assert(DXC_OUT_NUM_ENUMS == DXC_OUT_LAST + 1, + "DXC_OUT_* Enum added and last value not updated."); + +CROSS_PLATFORM_UUIDOF(IDxcResult, "58346CDA-DDE7-4497-9461-6F87AF5E0659") +/// \brief Result of a DXC operation. +/// +/// DXC operations may have multiple outputs, such as a shader object and +/// errors. This interface provides access to the outputs. +struct IDxcResult : public IDxcOperationResult { + /// \brief Determines whether or not this result has the specified output. + /// + /// \param dxcOutKind The kind of output to check for. + virtual BOOL STDMETHODCALLTYPE HasOutput(_In_ DXC_OUT_KIND dxcOutKind) = 0; + + /// \brief Retrieves the specified output. + /// + /// \param dxcOutKind The kind of output to retrieve. + /// + /// \param iid The interface ID of the output interface. + /// + /// \param ppvObject Address of the pointer that receives a pointer to the + /// output. + /// + /// \param ppOutputName Optional address of a pointer to receive the name + /// blob, if there is one. + virtual HRESULT STDMETHODCALLTYPE + GetOutput(_In_ DXC_OUT_KIND dxcOutKind, _In_ REFIID iid, + _COM_Outptr_opt_result_maybenull_ void **ppvObject, + _COM_Outptr_opt_result_maybenull_ IDxcBlobWide **ppOutputName) = 0; + + /// \brief Retrieves the number of outputs available in this result. + virtual UINT32 GetNumOutputs() = 0; + + /// \brief Retrieves the output kind at the specified index. + virtual DXC_OUT_KIND GetOutputByIndex(UINT32 Index) = 0; + + /// \brief Retrieves the primary output kind for this result. + /// + /// See IDxcOperationResult::GetResult() for more information on the primary + /// output kinds. + virtual DXC_OUT_KIND PrimaryOutput() = 0; +}; + +// Special names for extra output that should get written to specific streams. +#define DXC_EXTRA_OUTPUT_NAME_STDOUT L"*stdout*" +#define DXC_EXTRA_OUTPUT_NAME_STDERR L"*stderr*" + +CROSS_PLATFORM_UUIDOF(IDxcExtraOutputs, "319b37a2-a5c2-494a-a5de-4801b2faf989") +/// \brief Additional outputs from a DXC operation. +/// +/// This can be used to obtain outputs that don't have an explicit DXC_OUT_KIND. +/// Use DXC_OUT_EXTRA_OUTPUTS to obtain instances of this. +struct IDxcExtraOutputs : public IUnknown { + /// \brief Retrieves the number of outputs available + virtual UINT32 STDMETHODCALLTYPE GetOutputCount() = 0; + + /// \brief Retrieves the specified output. + /// + /// \param uIndex The index of the output to retrieve. + /// + /// \param iid The interface ID of the output interface. + /// + /// \param ppvObject Optional address of the pointer that receives a pointer + /// to the output if there is one. + /// + /// \param ppOutputType Optional address of the pointer that receives the + /// output type name blob if there is one. + /// + /// \param ppOutputName Optional address of the pointer that receives the + /// output name blob if there is one. + virtual HRESULT STDMETHODCALLTYPE + GetOutput(_In_ UINT32 uIndex, _In_ REFIID iid, + _COM_Outptr_opt_result_maybenull_ void **ppvObject, + _COM_Outptr_opt_result_maybenull_ IDxcBlobWide **ppOutputType, + _COM_Outptr_opt_result_maybenull_ IDxcBlobWide **ppOutputName) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcCompiler3, "228B4687-5A6A-4730-900C-9702B2203F54") +/// \brief Interface to the DirectX Shader Compiler. +/// +/// Use DxcCreateInstance with CLSID_DxcCompiler to obtain an instance of this +/// interface. +struct IDxcCompiler3 : public IUnknown { + /// \brief Compile a shader. + /// + /// IDxcUtils::BuildArguments can be used to assist building the pArguments + /// and argCount parameters. + /// + /// Depending on the arguments, this method can be used to: + /// + /// * Compile a single entry point to the target shader model, + /// * Compile a library to a library target (-T lib_*) + /// * Compile a root signature (-T rootsig_*), + /// * Preprocess HLSL source (-P). + virtual HRESULT STDMETHODCALLTYPE Compile( + _In_ const DxcBuffer *pSource, ///< Source text to compile. + _In_opt_count_(argCount) + LPCWSTR *pArguments, ///< Array of pointers to arguments. + _In_ UINT32 argCount, ///< Number of arguments. + _In_opt_ IDxcIncludeHandler + *pIncludeHandler, ///< user-provided interface to handle include + ///< directives (optional). + _In_ REFIID riid, ///< Interface ID for the result. + _Out_ LPVOID *ppResult ///< IDxcResult: status, buffer, and errors. + ) = 0; + + /// \brief Disassemble a program. + virtual HRESULT STDMETHODCALLTYPE Disassemble( + _In_ const DxcBuffer + *pObject, ///< Program to disassemble: dxil container or bitcode. + _In_ REFIID riid, ///< Interface ID for the result. + _Out_ LPVOID + *ppResult ///< IDxcResult: status, disassembly text, and errors. + ) = 0; +}; + +static const UINT32 DxcValidatorFlags_Default = 0; +static const UINT32 DxcValidatorFlags_InPlaceEdit = + 1; // Validator is allowed to update shader blob in-place. +static const UINT32 DxcValidatorFlags_RootSignatureOnly = 2; +static const UINT32 DxcValidatorFlags_ModuleOnly = 4; +static const UINT32 DxcValidatorFlags_ValidMask = 0x7; + +CROSS_PLATFORM_UUIDOF(IDxcValidator, "A6E82BD2-1FD7-4826-9811-2857E797F49A") +/// \brief Interface to DXC shader validator. +/// +/// Use DxcCreateInstance with CLSID_DxcValidator to obtain an instance of this. +struct IDxcValidator : public IUnknown { + /// \brief Validate a shader. + virtual HRESULT STDMETHODCALLTYPE Validate( + _In_ IDxcBlob *pShader, ///< Shader to validate. + _In_ UINT32 Flags, ///< Validation flags. + _COM_Outptr_ IDxcOperationResult * + *ppResult ///< Validation output status, buffer, and errors. + ) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcValidator2, "458e1fd1-b1b2-4750-a6e1-9c10f03bed92") +/// \brief Interface to DXC shader validator. +/// +/// Use DxcCreateInstance with CLSID_DxcValidator to obtain an instance of this. +struct IDxcValidator2 : public IDxcValidator { + /// \brief Validate a shader with optional debug bitcode. + virtual HRESULT STDMETHODCALLTYPE ValidateWithDebug( + _In_ IDxcBlob *pShader, ///< Shader to validate. + _In_ UINT32 Flags, ///< Validation flags. + _In_opt_ DxcBuffer *pOptDebugBitcode, ///< Optional debug module bitcode + ///< to provide line numbers. + _COM_Outptr_ IDxcOperationResult * + *ppResult ///< Validation output status, buffer, and errors. + ) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcContainerBuilder, + "334b1f50-2292-4b35-99a1-25588d8c17fe") +/// \brief Interface to DXC container builder. +/// +/// Use DxcCreateInstance with CLSID_DxcContainerBuilder to obtain an instance +/// of this. +struct IDxcContainerBuilder : public IUnknown { + /// \brief Load a DxilContainer to the builder. + virtual HRESULT STDMETHODCALLTYPE + Load(_In_ IDxcBlob *pDxilContainerHeader) = 0; + + /// \brief Add a part to the container. + /// + /// \param fourCC The part identifier (eg DXC_PART_PDB). + /// + /// \param pSource The source blob. + virtual HRESULT STDMETHODCALLTYPE AddPart(_In_ UINT32 fourCC, + _In_ IDxcBlob *pSource) = 0; + + /// \brief Remove a part from the container. + /// + /// \param fourCC The part identifier (eg DXC_PART_PDB). + /// + /// \return S_OK on success, DXC_E_MISSING_PART if the part was not found, or + /// other standard HRESULT error code. + virtual HRESULT STDMETHODCALLTYPE RemovePart(_In_ UINT32 fourCC) = 0; + + /// \brief Build the container. + /// + /// \param ppResult Pointer to variable to receive the result. + virtual HRESULT STDMETHODCALLTYPE + SerializeContainer(_Out_ IDxcOperationResult **ppResult) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcAssembler, "091f7a26-1c1f-4948-904b-e6e3a8a771d5") +/// \brief Interface to DxcAssembler. +/// +/// Use DxcCreateInstance with CLSID_DxcAssembler to obtain an instance of this. +struct IDxcAssembler : public IUnknown { + /// \brief Assemble DXIL in LL or LLVM bitcode to DXIL container. + virtual HRESULT STDMETHODCALLTYPE AssembleToContainer( + _In_ IDxcBlob *pShader, ///< Shader to assemble. + _COM_Outptr_ IDxcOperationResult * + *ppResult ///< Assembly output status, buffer, and errors. + ) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcContainerReflection, + "d2c21b26-8350-4bdc-976a-331ce6f4c54c") +/// \brief Interface to DxcContainerReflection. +/// +/// Use DxcCreateInstance with CLSID_DxcContainerReflection to obtain an +/// instance of this. +struct IDxcContainerReflection : public IUnknown { + /// \brief Choose the container to perform reflection on + /// + /// \param pContainer The container to load. If null is passed then this + /// instance will release any held resources. + virtual HRESULT STDMETHODCALLTYPE Load(_In_ IDxcBlob *pContainer) = 0; + + /// \brief Retrieves the number of parts in the container. + /// + /// \param pResult Pointer to variable to receive the result. + /// + /// \return S_OK on success, E_NOT_VALID_STATE if a container has not been + /// loaded using Load(), or other standard HRESULT error codes. + virtual HRESULT STDMETHODCALLTYPE GetPartCount(_Out_ UINT32 *pResult) = 0; + + /// \brief Retrieve the kind of a specified part. + /// + /// \param idx The index of the part to retrieve the kind of. + /// + /// \param pResult Pointer to variable to receive the result. + /// + /// \return S_OK on success, E_NOT_VALID_STATE if a container has not been + /// loaded using Load(), E_BOUND if idx is out of bounds, or other standard + /// HRESULT error codes. + virtual HRESULT STDMETHODCALLTYPE GetPartKind(UINT32 idx, + _Out_ UINT32 *pResult) = 0; + + /// \brief Retrieve the content of a specified part. + /// + /// \param idx The index of the part to retrieve. + /// + /// \param ppResult Pointer to variable to receive the result. + /// + /// \return S_OK on success, E_NOT_VALID_STATE if a container has not been + /// loaded using Load(), E_BOUND if idx is out of bounds, or other standard + /// HRESULT error codes. + virtual HRESULT STDMETHODCALLTYPE + GetPartContent(UINT32 idx, _COM_Outptr_ IDxcBlob **ppResult) = 0; + + /// \brief Retrieve the index of the first part with the specified kind. + /// + /// \param kind The kind to search for. + /// + /// \param pResult Pointer to variable to receive the index of the matching + /// part. + /// + /// \return S_OK on success, E_NOT_VALID_STATE if a container has not been + /// loaded using Load(), HRESULT_FROM_WIN32(ERROR_NOT_FOUND) if there is no + /// part with the specified kind, or other standard HRESULT error codes. + virtual HRESULT STDMETHODCALLTYPE + FindFirstPartKind(UINT32 kind, _Out_ UINT32 *pResult) = 0; + + /// \brief Retrieve the reflection interface for a specified part. + /// + /// \param idx The index of the part to retrieve the reflection interface of. + /// + /// \param iid The IID of the interface to retrieve. + /// + /// \param ppvObject Pointer to variable to receive the result. + /// + /// Use this with interfaces such as ID3D12ShaderReflection. + /// + /// \return S_OK on success, E_NOT_VALID_STATE if a container has not been + /// loaded using Load(), E_BOUND if idx is out of bounds, or other standard + /// HRESULT error codes. + virtual HRESULT STDMETHODCALLTYPE GetPartReflection(UINT32 idx, REFIID iid, + void **ppvObject) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcOptimizerPass, "AE2CD79F-CC22-453F-9B6B-B124E7A5204C") +/// \brief An optimizer pass. +/// +/// Instances of this can be obtained via IDxcOptimizer::GetAvailablePass. +struct IDxcOptimizerPass : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE + GetOptionName(_COM_Outptr_ LPWSTR *ppResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetDescription(_COM_Outptr_ LPWSTR *ppResult) = 0; + virtual HRESULT STDMETHODCALLTYPE GetOptionArgCount(_Out_ UINT32 *pCount) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetOptionArgName(UINT32 argIndex, _COM_Outptr_ LPWSTR *ppResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetOptionArgDescription(UINT32 argIndex, _COM_Outptr_ LPWSTR *ppResult) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcOptimizer, "25740E2E-9CBA-401B-9119-4FB42F39F270") +/// \brief Interface to DxcOptimizer. +/// +/// Use DxcCreateInstance with CLSID_DxcOptimizer to obtain an instance of this. +struct IDxcOptimizer : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE + GetAvailablePassCount(_Out_ UINT32 *pCount) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetAvailablePass(UINT32 index, _COM_Outptr_ IDxcOptimizerPass **ppResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + RunOptimizer(IDxcBlob *pBlob, _In_count_(optionCount) LPCWSTR *ppOptions, + UINT32 optionCount, _COM_Outptr_ IDxcBlob **pOutputModule, + _COM_Outptr_opt_ IDxcBlobEncoding **ppOutputText) = 0; +}; + +static const UINT32 DxcVersionInfoFlags_None = 0; +static const UINT32 DxcVersionInfoFlags_Debug = 1; // Matches VS_FF_DEBUG +static const UINT32 DxcVersionInfoFlags_Internal = + 2; // Internal Validator (non-signing) + +CROSS_PLATFORM_UUIDOF(IDxcVersionInfo, "b04f5b50-2059-4f12-a8ff-a1e0cde1cc7e") +/// \brief PDB Version information. +/// +/// Use IDxcPdbUtils2::GetVersionInfo to obtain an instance of this. +struct IDxcVersionInfo : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE GetVersion(_Out_ UINT32 *pMajor, + _Out_ UINT32 *pMinor) = 0; + virtual HRESULT STDMETHODCALLTYPE GetFlags(_Out_ UINT32 *pFlags) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcVersionInfo2, "fb6904c4-42f0-4b62-9c46-983af7da7c83") +/// \brief PDB Version Information. +/// +/// Use IDxcPdbUtils2::GetVersionInfo to obtain a IDxcVersionInfo interface, and +/// then use QueryInterface to obtain an instance of this interface from it. +struct IDxcVersionInfo2 : public IDxcVersionInfo { + virtual HRESULT STDMETHODCALLTYPE GetCommitInfo( + _Out_ UINT32 *pCommitCount, ///< The total number commits. + _Outptr_result_z_ char **pCommitHash ///< The SHA of the latest commit. + ///< Must be CoTaskMemFree()'d. + ) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcVersionInfo3, "5e13e843-9d25-473c-9ad2-03b2d0b44b1e") +/// \brief PDB Version Information. +/// +/// Use IDxcPdbUtils2::GetVersionInfo to obtain a IDxcVersionInfo interface, and +/// then use QueryInterface to obtain an instance of this interface from it. +struct IDxcVersionInfo3 : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE GetCustomVersionString( + _Outptr_result_z_ char * + *pVersionString ///< Custom version string for compiler. Must be + ///< CoTaskMemFree()'d. + ) = 0; +}; + +struct DxcArgPair { + const WCHAR *pName; + const WCHAR *pValue; +}; + +CROSS_PLATFORM_UUIDOF(IDxcPdbUtils, "E6C9647E-9D6A-4C3B-B94C-524B5A6C343D") +/// \deprecated Please use IDxcPdbUtils2 instead. +struct IDxcPdbUtils : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE Load(_In_ IDxcBlob *pPdbOrDxil) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetSourceCount(_Out_ UINT32 *pCount) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetSource(_In_ UINT32 uIndex, _COM_Outptr_ IDxcBlobEncoding **ppResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetSourceName(_In_ UINT32 uIndex, _Outptr_result_z_ BSTR *pResult) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetFlagCount(_Out_ UINT32 *pCount) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetFlag(_In_ UINT32 uIndex, _Outptr_result_z_ BSTR *pResult) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetArgCount(_Out_ UINT32 *pCount) = 0; + virtual HRESULT STDMETHODCALLTYPE GetArg(_In_ UINT32 uIndex, + _Outptr_result_z_ BSTR *pResult) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetArgPairCount(_Out_ UINT32 *pCount) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetArgPair(_In_ UINT32 uIndex, _Outptr_result_z_ BSTR *pName, + _Outptr_result_z_ BSTR *pValue) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetDefineCount(_Out_ UINT32 *pCount) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetDefine(_In_ UINT32 uIndex, _Outptr_result_z_ BSTR *pResult) = 0; + + virtual HRESULT STDMETHODCALLTYPE + GetTargetProfile(_Outptr_result_z_ BSTR *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetEntryPoint(_Outptr_result_z_ BSTR *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetMainFileName(_Outptr_result_z_ BSTR *pResult) = 0; + + virtual HRESULT STDMETHODCALLTYPE + GetHash(_COM_Outptr_ IDxcBlob **ppResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetName(_Outptr_result_z_ BSTR *pResult) = 0; + + virtual BOOL STDMETHODCALLTYPE IsFullPDB() = 0; + virtual HRESULT STDMETHODCALLTYPE + GetFullPDB(_COM_Outptr_ IDxcBlob **ppFullPDB) = 0; + + virtual HRESULT STDMETHODCALLTYPE + GetVersionInfo(_COM_Outptr_ IDxcVersionInfo **ppVersionInfo) = 0; + + virtual HRESULT STDMETHODCALLTYPE + SetCompiler(_In_ IDxcCompiler3 *pCompiler) = 0; + virtual HRESULT STDMETHODCALLTYPE + CompileForFullPDB(_COM_Outptr_ IDxcResult **ppResult) = 0; + virtual HRESULT STDMETHODCALLTYPE OverrideArgs(_In_ DxcArgPair *pArgPairs, + UINT32 uNumArgPairs) = 0; + virtual HRESULT STDMETHODCALLTYPE + OverrideRootSignature(_In_ const WCHAR *pRootSignature) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcPdbUtils2, "4315D938-F369-4F93-95A2-252017CC3807") +/// \brief DxcPdbUtils interface. +/// +/// Use DxcCreateInstance with CLSID_DxcPdbUtils to create an instance of this. +struct IDxcPdbUtils2 : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE Load(_In_ IDxcBlob *pPdbOrDxil) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetSourceCount(_Out_ UINT32 *pCount) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetSource(_In_ UINT32 uIndex, _COM_Outptr_ IDxcBlobEncoding **ppResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetSourceName(_In_ UINT32 uIndex, _COM_Outptr_ IDxcBlobWide **ppResult) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetLibraryPDBCount(UINT32 *pCount) = 0; + virtual HRESULT STDMETHODCALLTYPE GetLibraryPDB( + _In_ UINT32 uIndex, _COM_Outptr_ IDxcPdbUtils2 **ppOutPdbUtils, + _COM_Outptr_opt_result_maybenull_ IDxcBlobWide **ppLibraryName) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetFlagCount(_Out_ UINT32 *pCount) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetFlag(_In_ UINT32 uIndex, _COM_Outptr_ IDxcBlobWide **ppResult) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetArgCount(_Out_ UINT32 *pCount) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetArg(_In_ UINT32 uIndex, _COM_Outptr_ IDxcBlobWide **ppResult) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetArgPairCount(_Out_ UINT32 *pCount) = 0; + virtual HRESULT STDMETHODCALLTYPE GetArgPair( + _In_ UINT32 uIndex, _COM_Outptr_result_maybenull_ IDxcBlobWide **ppName, + _COM_Outptr_result_maybenull_ IDxcBlobWide **ppValue) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetDefineCount(_Out_ UINT32 *pCount) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetDefine(_In_ UINT32 uIndex, _COM_Outptr_ IDxcBlobWide **ppResult) = 0; + + virtual HRESULT STDMETHODCALLTYPE + GetTargetProfile(_COM_Outptr_result_maybenull_ IDxcBlobWide **ppResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetEntryPoint(_COM_Outptr_result_maybenull_ IDxcBlobWide **ppResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetMainFileName(_COM_Outptr_result_maybenull_ IDxcBlobWide **ppResult) = 0; + + virtual HRESULT STDMETHODCALLTYPE + GetHash(_COM_Outptr_result_maybenull_ IDxcBlob **ppResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetName(_COM_Outptr_result_maybenull_ IDxcBlobWide **ppResult) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetVersionInfo( + _COM_Outptr_result_maybenull_ IDxcVersionInfo **ppVersionInfo) = 0; + + virtual HRESULT STDMETHODCALLTYPE GetCustomToolchainID(_Out_ UINT32 *pID) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetCustomToolchainData(_COM_Outptr_result_maybenull_ IDxcBlob **ppBlob) = 0; + + virtual HRESULT STDMETHODCALLTYPE + GetWholeDxil(_COM_Outptr_result_maybenull_ IDxcBlob **ppResult) = 0; + + virtual BOOL STDMETHODCALLTYPE IsFullPDB() = 0; + virtual BOOL STDMETHODCALLTYPE IsPDBRef() = 0; +}; + +// Note: __declspec(selectany) requires 'extern' +// On Linux __declspec(selectany) is removed and using 'extern' results in link +// error. +#ifdef _MSC_VER +#define CLSID_SCOPE __declspec(selectany) extern +#else +#define CLSID_SCOPE +#endif + +CLSID_SCOPE const CLSID CLSID_DxcCompiler = { + 0x73e22d93, + 0xe6ce, + 0x47f3, + {0xb5, 0xbf, 0xf0, 0x66, 0x4f, 0x39, 0xc1, 0xb0}}; + +// {EF6A8087-B0EA-4D56-9E45-D07E1A8B7806} +CLSID_SCOPE const GUID CLSID_DxcLinker = { + 0xef6a8087, + 0xb0ea, + 0x4d56, + {0x9e, 0x45, 0xd0, 0x7e, 0x1a, 0x8b, 0x78, 0x6}}; + +// {CD1F6B73-2AB0-484D-8EDC-EBE7A43CA09F} +CLSID_SCOPE const CLSID CLSID_DxcDiaDataSource = { + 0xcd1f6b73, + 0x2ab0, + 0x484d, + {0x8e, 0xdc, 0xeb, 0xe7, 0xa4, 0x3c, 0xa0, 0x9f}}; + +// {3E56AE82-224D-470F-A1A1-FE3016EE9F9D} +CLSID_SCOPE const CLSID CLSID_DxcCompilerArgs = { + 0x3e56ae82, + 0x224d, + 0x470f, + {0xa1, 0xa1, 0xfe, 0x30, 0x16, 0xee, 0x9f, 0x9d}}; + +// {6245D6AF-66E0-48FD-80B4-4D271796748C} +CLSID_SCOPE const GUID CLSID_DxcLibrary = { + 0x6245d6af, + 0x66e0, + 0x48fd, + {0x80, 0xb4, 0x4d, 0x27, 0x17, 0x96, 0x74, 0x8c}}; + +CLSID_SCOPE const GUID CLSID_DxcUtils = CLSID_DxcLibrary; + +// {8CA3E215-F728-4CF3-8CDD-88AF917587A1} +CLSID_SCOPE const GUID CLSID_DxcValidator = { + 0x8ca3e215, + 0xf728, + 0x4cf3, + {0x8c, 0xdd, 0x88, 0xaf, 0x91, 0x75, 0x87, 0xa1}}; + +// {D728DB68-F903-4F80-94CD-DCCF76EC7151} +CLSID_SCOPE const GUID CLSID_DxcAssembler = { + 0xd728db68, + 0xf903, + 0x4f80, + {0x94, 0xcd, 0xdc, 0xcf, 0x76, 0xec, 0x71, 0x51}}; + +// {b9f54489-55b8-400c-ba3a-1675e4728b91} +CLSID_SCOPE const GUID CLSID_DxcContainerReflection = { + 0xb9f54489, + 0x55b8, + 0x400c, + {0xba, 0x3a, 0x16, 0x75, 0xe4, 0x72, 0x8b, 0x91}}; + +// {AE2CD79F-CC22-453F-9B6B-B124E7A5204C} +CLSID_SCOPE const GUID CLSID_DxcOptimizer = { + 0xae2cd79f, + 0xcc22, + 0x453f, + {0x9b, 0x6b, 0xb1, 0x24, 0xe7, 0xa5, 0x20, 0x4c}}; + +// {94134294-411f-4574-b4d0-8741e25240d2} +CLSID_SCOPE const GUID CLSID_DxcContainerBuilder = { + 0x94134294, + 0x411f, + 0x4574, + {0xb4, 0xd0, 0x87, 0x41, 0xe2, 0x52, 0x40, 0xd2}}; + +// {54621dfb-f2ce-457e-ae8c-ec355faeec7c} +CLSID_SCOPE const GUID CLSID_DxcPdbUtils = { + 0x54621dfb, + 0xf2ce, + 0x457e, + {0xae, 0x8c, 0xec, 0x35, 0x5f, 0xae, 0xec, 0x7c}}; + +#endif diff --git a/Engine/cpp/ThirdParty/DXC/include/dxc/dxcerrors.h b/Engine/cpp/ThirdParty/DXC/include/dxc/dxcerrors.h new file mode 100644 index 00000000..ce50b83e --- /dev/null +++ b/Engine/cpp/ThirdParty/DXC/include/dxc/dxcerrors.h @@ -0,0 +1,30 @@ +/////////////////////////////////////////////////////////////////////////////// +// // +// dxcerror.h // +// Copyright (C) Microsoft Corporation. All rights reserved. // +// This file is distributed under the University of Illinois Open Source // +// License. See LICENSE.TXT for details. // +// // +// Provides definition of error codes. // +// // +/////////////////////////////////////////////////////////////////////////////// + +#ifndef __DXC_ERRORS__ +#define __DXC_ERRORS__ + +#ifndef FACILITY_GRAPHICS +#define FACILITY_GRAPHICS 36 +#endif + +#define DXC_EXCEPTION_CODE(name, status) \ + static constexpr DWORD EXCEPTION_##name = \ + (0xc0000000u | (FACILITY_GRAPHICS << 16) | \ + (0xff00u | (status & 0xffu))); + +DXC_EXCEPTION_CODE(LOAD_LIBRARY_FAILED, 0x00u) +DXC_EXCEPTION_CODE(NO_HMODULE, 0x01u) +DXC_EXCEPTION_CODE(GET_PROC_FAILED, 0x02u) + +#undef DXC_EXCEPTION_CODE + +#endif diff --git a/Engine/cpp/ThirdParty/DXC/include/dxc/dxcisense.h b/Engine/cpp/ThirdParty/DXC/include/dxc/dxcisense.h new file mode 100644 index 00000000..661de168 --- /dev/null +++ b/Engine/cpp/ThirdParty/DXC/include/dxc/dxcisense.h @@ -0,0 +1,959 @@ +/////////////////////////////////////////////////////////////////////////////// +// // +// dxcisense.h // +// Copyright (C) Microsoft Corporation. All rights reserved. // +// This file is distributed under the University of Illinois Open Source // +// License. See LICENSE.TXT for details. // +// // +// Provides declarations for the DirectX Compiler IntelliSense component. // +// // +/////////////////////////////////////////////////////////////////////////////// + +#ifndef __DXC_ISENSE__ +#define __DXC_ISENSE__ + +#include "dxcapi.h" +#ifndef _WIN32 +#include "WinAdapter.h" +#endif + +typedef enum DxcGlobalOptions { + DxcGlobalOpt_None = 0x0, + DxcGlobalOpt_ThreadBackgroundPriorityForIndexing = 0x1, + DxcGlobalOpt_ThreadBackgroundPriorityForEditing = 0x2, + DxcGlobalOpt_ThreadBackgroundPriorityForAll = + DxcGlobalOpt_ThreadBackgroundPriorityForIndexing | + DxcGlobalOpt_ThreadBackgroundPriorityForEditing +} DxcGlobalOptions; + +typedef enum DxcTokenKind { + DxcTokenKind_Punctuation = + 0, // A token that contains some kind of punctuation. + DxcTokenKind_Keyword = 1, // A language keyword. + DxcTokenKind_Identifier = 2, // An identifier (that is not a keyword). + DxcTokenKind_Literal = 3, // A numeric, string, or character literal. + DxcTokenKind_Comment = 4, // A comment. + DxcTokenKind_Unknown = + 5, // An unknown token (possibly known to a future version). + DxcTokenKind_BuiltInType = 6, // A built-in type like int, void or float3. +} DxcTokenKind; + +typedef enum DxcTypeKind { + DxcTypeKind_Invalid = + 0, // Reprents an invalid type (e.g., where no type is available). + DxcTypeKind_Unexposed = + 1, // A type whose specific kind is not exposed via this interface. + // Builtin types + DxcTypeKind_Void = 2, + DxcTypeKind_Bool = 3, + DxcTypeKind_Char_U = 4, + DxcTypeKind_UChar = 5, + DxcTypeKind_Char16 = 6, + DxcTypeKind_Char32 = 7, + DxcTypeKind_UShort = 8, + DxcTypeKind_UInt = 9, + DxcTypeKind_ULong = 10, + DxcTypeKind_ULongLong = 11, + DxcTypeKind_UInt128 = 12, + DxcTypeKind_Char_S = 13, + DxcTypeKind_SChar = 14, + DxcTypeKind_WChar = 15, + DxcTypeKind_Short = 16, + DxcTypeKind_Int = 17, + DxcTypeKind_Long = 18, + DxcTypeKind_LongLong = 19, + DxcTypeKind_Int128 = 20, + DxcTypeKind_Float = 21, + DxcTypeKind_Double = 22, + DxcTypeKind_LongDouble = 23, + DxcTypeKind_NullPtr = 24, + DxcTypeKind_Overload = 25, + DxcTypeKind_Dependent = 26, + DxcTypeKind_ObjCId = 27, + DxcTypeKind_ObjCClass = 28, + DxcTypeKind_ObjCSel = 29, + DxcTypeKind_FirstBuiltin = DxcTypeKind_Void, + DxcTypeKind_LastBuiltin = DxcTypeKind_ObjCSel, + + DxcTypeKind_Complex = 100, + DxcTypeKind_Pointer = 101, + DxcTypeKind_BlockPointer = 102, + DxcTypeKind_LValueReference = 103, + DxcTypeKind_RValueReference = 104, + DxcTypeKind_Record = 105, + DxcTypeKind_Enum = 106, + DxcTypeKind_Typedef = 107, + DxcTypeKind_ObjCInterface = 108, + DxcTypeKind_ObjCObjectPointer = 109, + DxcTypeKind_FunctionNoProto = 110, + DxcTypeKind_FunctionProto = 111, + DxcTypeKind_ConstantArray = 112, + DxcTypeKind_Vector = 113, + DxcTypeKind_IncompleteArray = 114, + DxcTypeKind_VariableArray = 115, + DxcTypeKind_DependentSizedArray = 116, + DxcTypeKind_MemberPointer = 117 +} DxcTypeKind; + +// Describes the severity of a particular diagnostic. +typedef enum DxcDiagnosticSeverity { + // A diagnostic that has been suppressed, e.g., by a command-line option. + DxcDiagnostic_Ignored = 0, + + // This diagnostic is a note that should be attached to the previous + // (non-note) diagnostic. + DxcDiagnostic_Note = 1, + + // This diagnostic indicates suspicious code that may not be wrong. + DxcDiagnostic_Warning = 2, + + // This diagnostic indicates that the code is ill-formed. + DxcDiagnostic_Error = 3, + + // This diagnostic indicates that the code is ill-formed such that future + // parser rec unlikely to produce useful results. + DxcDiagnostic_Fatal = 4 + +} DxcDiagnosticSeverity; + +// Options to control the display of diagnostics. +typedef enum DxcDiagnosticDisplayOptions { + // Display the source-location information where the diagnostic was located. + DxcDiagnostic_DisplaySourceLocation = 0x01, + + // If displaying the source-location information of the diagnostic, + // also include the column number. + DxcDiagnostic_DisplayColumn = 0x02, + + // If displaying the source-location information of the diagnostic, + // also include information about source ranges in a machine-parsable format. + DxcDiagnostic_DisplaySourceRanges = 0x04, + + // Display the option name associated with this diagnostic, if any. + DxcDiagnostic_DisplayOption = 0x08, + + // Display the category number associated with this diagnostic, if any. + DxcDiagnostic_DisplayCategoryId = 0x10, + + // Display the category name associated with this diagnostic, if any. + DxcDiagnostic_DisplayCategoryName = 0x20, + + // Display the severity of the diagnostic message. + DxcDiagnostic_DisplaySeverity = 0x200 +} DxcDiagnosticDisplayOptions; + +typedef enum DxcTranslationUnitFlags { + // Used to indicate that no special translation-unit options are needed. + DxcTranslationUnitFlags_None = 0x0, + + // Used to indicate that the parser should construct a "detailed" + // preprocessing record, including all macro definitions and instantiations. + DxcTranslationUnitFlags_DetailedPreprocessingRecord = 0x01, + + // Used to indicate that the translation unit is incomplete. + DxcTranslationUnitFlags_Incomplete = 0x02, + + // Used to indicate that the translation unit should be built with an + // implicit precompiled header for the preamble. + DxcTranslationUnitFlags_PrecompiledPreamble = 0x04, + + // Used to indicate that the translation unit should cache some + // code-completion results with each reparse of the source file. + DxcTranslationUnitFlags_CacheCompletionResults = 0x08, + + // Used to indicate that the translation unit will be serialized with + // SaveTranslationUnit. + DxcTranslationUnitFlags_ForSerialization = 0x10, + + // DEPRECATED + DxcTranslationUnitFlags_CXXChainedPCH = 0x20, + + // Used to indicate that function/method bodies should be skipped while + // parsing. + DxcTranslationUnitFlags_SkipFunctionBodies = 0x40, + + // Used to indicate that brief documentation comments should be + // included into the set of code completions returned from this translation + // unit. + DxcTranslationUnitFlags_IncludeBriefCommentsInCodeCompletion = 0x80, + + // Used to indicate that compilation should occur on the caller's thread. + DxcTranslationUnitFlags_UseCallerThread = 0x800 +} DxcTranslationUnitFlags; + +typedef enum DxcCursorFormatting { + DxcCursorFormatting_Default = + 0x0, // Default rules, language-insensitive formatting. + DxcCursorFormatting_UseLanguageOptions = + 0x1, // Language-sensitive formatting. + DxcCursorFormatting_SuppressSpecifiers = 0x2, // Supresses type specifiers. + DxcCursorFormatting_SuppressTagKeyword = + 0x4, // Suppressed tag keyword (eg, 'class'). + DxcCursorFormatting_IncludeNamespaceKeyword = + 0x8, // Include namespace keyword. +} DxcCursorFormatting; + +enum DxcCursorKind { + /* Declarations */ + DxcCursor_UnexposedDecl = + 1, // A declaration whose specific kind is not exposed via this interface. + DxcCursor_StructDecl = 2, // A C or C++ struct. + DxcCursor_UnionDecl = 3, // A C or C++ union. + DxcCursor_ClassDecl = 4, // A C++ class. + DxcCursor_EnumDecl = 5, // An enumeration. + DxcCursor_FieldDecl = 6, // A field (in C) or non-static data member (in C++) + // in a struct, union, or C++ class. + DxcCursor_EnumConstantDecl = 7, // An enumerator constant. + DxcCursor_FunctionDecl = 8, // A function. + DxcCursor_VarDecl = 9, // A variable. + DxcCursor_ParmDecl = 10, // A function or method parameter. + DxcCursor_ObjCInterfaceDecl = 11, // An Objective-C interface. + DxcCursor_ObjCCategoryDecl = 12, // An Objective-C interface for a category. + DxcCursor_ObjCProtocolDecl = 13, // An Objective-C protocol declaration. + DxcCursor_ObjCPropertyDecl = 14, // An Objective-C property declaration. + DxcCursor_ObjCIvarDecl = 15, // An Objective-C instance variable. + DxcCursor_ObjCInstanceMethodDecl = 16, // An Objective-C instance method. + DxcCursor_ObjCClassMethodDecl = 17, // An Objective-C class method. + DxcCursor_ObjCImplementationDecl = 18, // An Objective-C \@implementation. + DxcCursor_ObjCCategoryImplDecl = + 19, // An Objective-C \@implementation for a category. + DxcCursor_TypedefDecl = 20, // A typedef + DxcCursor_CXXMethod = 21, // A C++ class method. + DxcCursor_Namespace = 22, // A C++ namespace. + DxcCursor_LinkageSpec = 23, // A linkage specification, e.g. 'extern "C"'. + DxcCursor_Constructor = 24, // A C++ constructor. + DxcCursor_Destructor = 25, // A C++ destructor. + DxcCursor_ConversionFunction = 26, // A C++ conversion function. + DxcCursor_TemplateTypeParameter = 27, // A C++ template type parameter. + DxcCursor_NonTypeTemplateParameter = 28, // A C++ non-type template parameter. + DxcCursor_TemplateTemplateParameter = + 29, // A C++ template template parameter. + DxcCursor_FunctionTemplate = 30, // A C++ function template. + DxcCursor_ClassTemplate = 31, // A C++ class template. + DxcCursor_ClassTemplatePartialSpecialization = + 32, // A C++ class template partial specialization. + DxcCursor_NamespaceAlias = 33, // A C++ namespace alias declaration. + DxcCursor_UsingDirective = 34, // A C++ using directive. + DxcCursor_UsingDeclaration = 35, // A C++ using declaration. + DxcCursor_TypeAliasDecl = 36, // A C++ alias declaration + DxcCursor_ObjCSynthesizeDecl = 37, // An Objective-C \@synthesize definition. + DxcCursor_ObjCDynamicDecl = 38, // An Objective-C \@dynamic definition. + DxcCursor_CXXAccessSpecifier = 39, // An access specifier. + + DxcCursor_FirstDecl = DxcCursor_UnexposedDecl, + DxcCursor_LastDecl = DxcCursor_CXXAccessSpecifier, + + /* References */ + DxcCursor_FirstRef = 40, /* Decl references */ + DxcCursor_ObjCSuperClassRef = 40, + DxcCursor_ObjCProtocolRef = 41, + DxcCursor_ObjCClassRef = 42, + /** + * \brief A reference to a type declaration. + * + * A type reference occurs anywhere where a type is named but not + * declared. For example, given: + * + * \code + * typedef unsigned size_type; + * size_type size; + * \endcode + * + * The typedef is a declaration of size_type (DxcCursor_TypedefDecl), + * while the type of the variable "size" is referenced. The cursor + * referenced by the type of size is the typedef for size_type. + */ + DxcCursor_TypeRef = 43, // A reference to a type declaration. + DxcCursor_CXXBaseSpecifier = 44, + DxcCursor_TemplateRef = + 45, // A reference to a class template, function template, template + // template parameter, or class template partial specialization. + DxcCursor_NamespaceRef = 46, // A reference to a namespace or namespace alias. + DxcCursor_MemberRef = + 47, // A reference to a member of a struct, union, or class that occurs in + // some non-expression context, e.g., a designated initializer. + /** + * \brief A reference to a labeled statement. + * + * This cursor kind is used to describe the jump to "start_over" in the + * goto statement in the following example: + * + * \code + * start_over: + * ++counter; + * + * goto start_over; + * \endcode + * + * A label reference cursor refers to a label statement. + */ + DxcCursor_LabelRef = 48, // A reference to a labeled statement. + + // A reference to a set of overloaded functions or function templates + // that has not yet been resolved to a specific function or function template. + // + // An overloaded declaration reference cursor occurs in C++ templates where + // a dependent name refers to a function. + DxcCursor_OverloadedDeclRef = 49, + DxcCursor_VariableRef = + 50, // A reference to a variable that occurs in some non-expression + // context, e.g., a C++ lambda capture list. + + DxcCursor_LastRef = DxcCursor_VariableRef, + + /* Error conditions */ + DxcCursor_FirstInvalid = 70, + DxcCursor_InvalidFile = 70, + DxcCursor_NoDeclFound = 71, + DxcCursor_NotImplemented = 72, + DxcCursor_InvalidCode = 73, + DxcCursor_LastInvalid = DxcCursor_InvalidCode, + + /* Expressions */ + DxcCursor_FirstExpr = 100, + + /** + * \brief An expression whose specific kind is not exposed via this + * interface. + * + * Unexposed expressions have the same operations as any other kind + * of expression; one can extract their location information, + * spelling, children, etc. However, the specific kind of the + * expression is not reported. + */ + DxcCursor_UnexposedExpr = 100, // An expression whose specific kind is not + // exposed via this interface. + DxcCursor_DeclRefExpr = + 101, // An expression that refers to some value declaration, such as a + // function, varible, or enumerator. + DxcCursor_MemberRefExpr = + 102, // An expression that refers to a member of a struct, union, class, + // Objective-C class, etc. + DxcCursor_CallExpr = 103, // An expression that calls a function. + DxcCursor_ObjCMessageExpr = 104, // An expression that sends a message to an + // Objective-C object or class. + DxcCursor_BlockExpr = 105, // An expression that represents a block literal. + DxcCursor_IntegerLiteral = 106, // An integer literal. + DxcCursor_FloatingLiteral = 107, // A floating point number literal. + DxcCursor_ImaginaryLiteral = 108, // An imaginary number literal. + DxcCursor_StringLiteral = 109, // A string literal. + DxcCursor_CharacterLiteral = 110, // A character literal. + DxcCursor_ParenExpr = + 111, // A parenthesized expression, e.g. "(1)". This AST node is only + // formed if full location information is requested. + DxcCursor_UnaryOperator = 112, // This represents the unary-expression's + // (except sizeof and alignof). + DxcCursor_ArraySubscriptExpr = 113, // [C99 6.5.2.1] Array Subscripting. + DxcCursor_BinaryOperator = + 114, // A builtin binary operation expression such as "x + y" or "x <= y". + DxcCursor_CompoundAssignOperator = 115, // Compound assignment such as "+=". + DxcCursor_ConditionalOperator = 116, // The ?: ternary operator. + DxcCursor_CStyleCastExpr = + 117, // An explicit cast in C (C99 6.5.4) or a C-style cast in C++ (C++ + // [expr.cast]), which uses the syntax (Type)expr, eg: (int)f. + DxcCursor_CompoundLiteralExpr = 118, // [C99 6.5.2.5] + DxcCursor_InitListExpr = 119, // Describes an C or C++ initializer list. + DxcCursor_AddrLabelExpr = + 120, // The GNU address of label extension, representing &&label. + DxcCursor_StmtExpr = + 121, // This is the GNU Statement Expression extension: ({int X=4; X;}) + DxcCursor_GenericSelectionExpr = 122, // Represents a C11 generic selection. + + /** \brief Implements the GNU __null extension, which is a name for a null + * pointer constant that has integral type (e.g., int or long) and is the same + * size and alignment as a pointer. + * + * The __null extension is typically only used by system headers, which define + * NULL as __null in C++ rather than using 0 (which is an integer that may not + * match the size of a pointer). + */ + DxcCursor_GNUNullExpr = 123, + DxcCursor_CXXStaticCastExpr = 124, // C++'s static_cast<> expression. + DxcCursor_CXXDynamicCastExpr = 125, // C++'s dynamic_cast<> expression. + DxcCursor_CXXReinterpretCastExpr = + 126, // C++'s reinterpret_cast<> expression. + DxcCursor_CXXConstCastExpr = 127, // C++'s const_cast<> expression. + + /** \brief Represents an explicit C++ type conversion that uses "functional" + * notion (C++ [expr.type.conv]). + * + * Example: + * \code + * x = int(0.5); + * \endcode + */ + DxcCursor_CXXFunctionalCastExpr = 128, + DxcCursor_CXXTypeidExpr = 129, // A C++ typeid expression (C++ [expr.typeid]). + DxcCursor_CXXBoolLiteralExpr = 130, // [C++ 2.13.5] C++ Boolean Literal. + DxcCursor_CXXNullPtrLiteralExpr = 131, // [C++0x 2.14.7] C++ Pointer Literal. + DxcCursor_CXXThisExpr = 132, // Represents the "this" expression in C++ + DxcCursor_CXXThrowExpr = 133, // [C++ 15] C++ Throw Expression, both 'throw' + // and 'throw' assignment-expression. + DxcCursor_CXXNewExpr = 134, // A new expression for memory allocation and + // constructor calls, e.g: "new CXXNewExpr(foo)". + DxcCursor_CXXDeleteExpr = + 135, // A delete expression for memory deallocation and destructor calls, + // e.g. "delete[] pArray". + DxcCursor_UnaryExpr = 136, // A unary expression. + DxcCursor_ObjCStringLiteral = + 137, // An Objective-C string literal i.e. @"foo". + DxcCursor_ObjCEncodeExpr = 138, // An Objective-C \@encode expression. + DxcCursor_ObjCSelectorExpr = 139, // An Objective-C \@selector expression. + DxcCursor_ObjCProtocolExpr = 140, // An Objective-C \@protocol expression. + + /** \brief An Objective-C "bridged" cast expression, which casts between + * Objective-C pointers and C pointers, transferring ownership in the process. + * + * \code + * NSString *str = (__bridge_transfer NSString *)CFCreateString(); + * \endcode + */ + DxcCursor_ObjCBridgedCastExpr = 141, + + /** \brief Represents a C++0x pack expansion that produces a sequence of + * expressions. + * + * A pack expansion expression contains a pattern (which itself is an + * expression) followed by an ellipsis. For example: + * + * \code + * template + * void forward(F f, Types &&...args) { + * f(static_cast(args)...); + * } + * \endcode + */ + DxcCursor_PackExpansionExpr = 142, + + /** \brief Represents an expression that computes the length of a parameter + * pack. + * + * \code + * template + * struct count { + * static const unsigned value = sizeof...(Types); + * }; + * \endcode + */ + DxcCursor_SizeOfPackExpr = 143, + + /* \brief Represents a C++ lambda expression that produces a local function + * object. + * + * \code + * void abssort(float *x, unsigned N) { + * std::sort(x, x + N, + * [](float a, float b) { + * return std::abs(a) < std::abs(b); + * }); + * } + * \endcode + */ + DxcCursor_LambdaExpr = 144, + DxcCursor_ObjCBoolLiteralExpr = 145, // Objective-c Boolean Literal. + DxcCursor_ObjCSelfExpr = + 146, // Represents the "self" expression in a ObjC method. + DxcCursor_LastExpr = DxcCursor_ObjCSelfExpr, + + /* Statements */ + DxcCursor_FirstStmt = 200, + /** + * \brief A statement whose specific kind is not exposed via this + * interface. + * + * Unexposed statements have the same operations as any other kind of + * statement; one can extract their location information, spelling, + * children, etc. However, the specific kind of the statement is not + * reported. + */ + DxcCursor_UnexposedStmt = 200, + + /** \brief A labelled statement in a function. + * + * This cursor kind is used to describe the "start_over:" label statement in + * the following example: + * + * \code + * start_over: + * ++counter; + * \endcode + * + */ + DxcCursor_LabelStmt = 201, + DxcCursor_CompoundStmt = + 202, // A group of statements like { stmt stmt }. This cursor kind is used + // to describe compound statements, e.g. function bodies. + DxcCursor_CaseStmt = 203, // A case statement. + DxcCursor_DefaultStmt = 204, // A default statement. + DxcCursor_IfStmt = 205, // An if statement + DxcCursor_SwitchStmt = 206, // A switch statement. + DxcCursor_WhileStmt = 207, // A while statement. + DxcCursor_DoStmt = 208, // A do statement. + DxcCursor_ForStmt = 209, // A for statement. + DxcCursor_GotoStmt = 210, // A goto statement. + DxcCursor_IndirectGotoStmt = 211, // An indirect goto statement. + DxcCursor_ContinueStmt = 212, // A continue statement. + DxcCursor_BreakStmt = 213, // A break statement. + DxcCursor_ReturnStmt = 214, // A return statement. + DxcCursor_GCCAsmStmt = 215, // A GCC inline assembly statement extension. + DxcCursor_AsmStmt = DxcCursor_GCCAsmStmt, + + DxcCursor_ObjCAtTryStmt = + 216, // Objective-C's overall \@try-\@catch-\@finally statement. + DxcCursor_ObjCAtCatchStmt = 217, // Objective-C's \@catch statement. + DxcCursor_ObjCAtFinallyStmt = 218, // Objective-C's \@finally statement. + DxcCursor_ObjCAtThrowStmt = 219, // Objective-C's \@throw statement. + DxcCursor_ObjCAtSynchronizedStmt = + 220, // Objective-C's \@synchronized statement. + DxcCursor_ObjCAutoreleasePoolStmt = + 221, // Objective-C's autorelease pool statement. + DxcCursor_ObjCForCollectionStmt = 222, // Objective-C's collection statement. + + DxcCursor_CXXCatchStmt = 223, // C++'s catch statement. + DxcCursor_CXXTryStmt = 224, // C++'s try statement. + DxcCursor_CXXForRangeStmt = 225, // C++'s for (* : *) statement. + + DxcCursor_SEHTryStmt = + 226, // Windows Structured Exception Handling's try statement. + DxcCursor_SEHExceptStmt = + 227, // Windows Structured Exception Handling's except statement. + DxcCursor_SEHFinallyStmt = + 228, // Windows Structured Exception Handling's finally statement. + + DxcCursor_MSAsmStmt = 229, // A MS inline assembly statement extension. + DxcCursor_NullStmt = 230, // The null satement ";": C99 6.8.3p3. + DxcCursor_DeclStmt = 231, // Adaptor class for mixing declarations with + // statements and expressions. + DxcCursor_OMPParallelDirective = 232, // OpenMP parallel directive. + DxcCursor_OMPSimdDirective = 233, // OpenMP SIMD directive. + DxcCursor_OMPForDirective = 234, // OpenMP for directive. + DxcCursor_OMPSectionsDirective = 235, // OpenMP sections directive. + DxcCursor_OMPSectionDirective = 236, // OpenMP section directive. + DxcCursor_OMPSingleDirective = 237, // OpenMP single directive. + DxcCursor_OMPParallelForDirective = 238, // OpenMP parallel for directive. + DxcCursor_OMPParallelSectionsDirective = + 239, // OpenMP parallel sections directive. + DxcCursor_OMPTaskDirective = 240, // OpenMP task directive. + DxcCursor_OMPMasterDirective = 241, // OpenMP master directive. + DxcCursor_OMPCriticalDirective = 242, // OpenMP critical directive. + DxcCursor_OMPTaskyieldDirective = 243, // OpenMP taskyield directive. + DxcCursor_OMPBarrierDirective = 244, // OpenMP barrier directive. + DxcCursor_OMPTaskwaitDirective = 245, // OpenMP taskwait directive. + DxcCursor_OMPFlushDirective = 246, // OpenMP flush directive. + DxcCursor_SEHLeaveStmt = + 247, // Windows Structured Exception Handling's leave statement. + DxcCursor_OMPOrderedDirective = 248, // OpenMP ordered directive. + DxcCursor_OMPAtomicDirective = 249, // OpenMP atomic directive. + DxcCursor_OMPForSimdDirective = 250, // OpenMP for SIMD directive. + DxcCursor_OMPParallelForSimdDirective = + 251, // OpenMP parallel for SIMD directive. + DxcCursor_OMPTargetDirective = 252, // OpenMP target directive. + DxcCursor_OMPTeamsDirective = 253, // OpenMP teams directive. + DxcCursor_OMPTaskgroupDirective = 254, // OpenMP taskgroup directive. + DxcCursor_OMPCancellationPointDirective = + 255, // OpenMP cancellation point directive. + DxcCursor_OMPCancelDirective = 256, // OpenMP cancel directive. + DxcCursor_LastStmt = DxcCursor_OMPCancelDirective, + + DxcCursor_TranslationUnit = + 300, // Cursor that represents the translation unit itself. + + /* Attributes */ + DxcCursor_FirstAttr = 400, + /** + * \brief An attribute whose specific kind is not exposed via this + * interface. + */ + DxcCursor_UnexposedAttr = 400, + + DxcCursor_IBActionAttr = 401, + DxcCursor_IBOutletAttr = 402, + DxcCursor_IBOutletCollectionAttr = 403, + DxcCursor_CXXFinalAttr = 404, + DxcCursor_CXXOverrideAttr = 405, + DxcCursor_AnnotateAttr = 406, + DxcCursor_AsmLabelAttr = 407, + DxcCursor_PackedAttr = 408, + DxcCursor_PureAttr = 409, + DxcCursor_ConstAttr = 410, + DxcCursor_NoDuplicateAttr = 411, + DxcCursor_CUDAConstantAttr = 412, + DxcCursor_CUDADeviceAttr = 413, + DxcCursor_CUDAGlobalAttr = 414, + DxcCursor_CUDAHostAttr = 415, + DxcCursor_CUDASharedAttr = 416, + DxcCursor_LastAttr = DxcCursor_CUDASharedAttr, + + /* Preprocessing */ + DxcCursor_PreprocessingDirective = 500, + DxcCursor_MacroDefinition = 501, + DxcCursor_MacroExpansion = 502, + DxcCursor_MacroInstantiation = DxcCursor_MacroExpansion, + DxcCursor_InclusionDirective = 503, + DxcCursor_FirstPreprocessing = DxcCursor_PreprocessingDirective, + DxcCursor_LastPreprocessing = DxcCursor_InclusionDirective, + + /* Extra Declarations */ + /** + * \brief A module import declaration. + */ + DxcCursor_ModuleImportDecl = 600, + DxcCursor_FirstExtraDecl = DxcCursor_ModuleImportDecl, + DxcCursor_LastExtraDecl = DxcCursor_ModuleImportDecl +}; + +enum DxcCursorKindFlags { + DxcCursorKind_None = 0, + DxcCursorKind_Declaration = 0x1, + DxcCursorKind_Reference = 0x2, + DxcCursorKind_Expression = 0x4, + DxcCursorKind_Statement = 0x8, + DxcCursorKind_Attribute = 0x10, + DxcCursorKind_Invalid = 0x20, + DxcCursorKind_TranslationUnit = 0x40, + DxcCursorKind_Preprocessing = 0x80, + DxcCursorKind_Unexposed = 0x100, +}; + +enum DxcCodeCompleteFlags { + DxcCodeCompleteFlags_None = 0, + DxcCodeCompleteFlags_IncludeMacros = 0x1, + DxcCodeCompleteFlags_IncludeCodePatterns = 0x2, + DxcCodeCompleteFlags_IncludeBriefComments = 0x4, +}; + +enum DxcCompletionChunkKind { + DxcCompletionChunk_Optional = 0, + DxcCompletionChunk_TypedText = 1, + DxcCompletionChunk_Text = 2, + DxcCompletionChunk_Placeholder = 3, + DxcCompletionChunk_Informative = 4, + DxcCompletionChunk_CurrentParameter = 5, + DxcCompletionChunk_LeftParen = 6, + DxcCompletionChunk_RightParen = 7, + DxcCompletionChunk_LeftBracket = 8, + DxcCompletionChunk_RightBracket = 9, + DxcCompletionChunk_LeftBrace = 10, + DxcCompletionChunk_RightBrace = 11, + DxcCompletionChunk_LeftAngle = 12, + DxcCompletionChunk_RightAngle = 13, + DxcCompletionChunk_Comma = 14, + DxcCompletionChunk_ResultType = 15, + DxcCompletionChunk_Colon = 16, + DxcCompletionChunk_SemiColon = 17, + DxcCompletionChunk_Equal = 18, + DxcCompletionChunk_HorizontalSpace = 19, + DxcCompletionChunk_VerticalSpace = 20, +}; + +struct IDxcCursor; +struct IDxcDiagnostic; +struct IDxcFile; +struct IDxcInclusion; +struct IDxcIntelliSense; +struct IDxcIndex; +struct IDxcSourceLocation; +struct IDxcSourceRange; +struct IDxcToken; +struct IDxcTranslationUnit; +struct IDxcType; +struct IDxcUnsavedFile; +struct IDxcCodeCompleteResults; +struct IDxcCompletionResult; +struct IDxcCompletionString; + +CROSS_PLATFORM_UUIDOF(IDxcCursor, "1467b985-288d-4d2a-80c1-ef89c42c40bc") +struct IDxcCursor : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE + GetExtent(_Outptr_result_nullonfailure_ IDxcSourceRange **pRange) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetLocation(_Outptr_result_nullonfailure_ IDxcSourceLocation **pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE GetKind(_Out_ DxcCursorKind *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetKindFlags(_Out_ DxcCursorKindFlags *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetSemanticParent(_Outptr_result_nullonfailure_ IDxcCursor **pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetLexicalParent(_Outptr_result_nullonfailure_ IDxcCursor **pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetCursorType(_Outptr_result_nullonfailure_ IDxcType **pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE GetNumArguments(_Out_ int *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE GetArgumentAt( + int index, _Outptr_result_nullonfailure_ IDxcCursor **pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetReferencedCursor(_Outptr_result_nullonfailure_ IDxcCursor **pResult) = 0; + /// For a cursor that is either a reference to or a declaration of + /// some entity, retrieve a cursor that describes the definition of that + /// entity. Some entities can be declared multiple times + /// within a translation unit, but only one of those declarations can also be + /// a definition. A cursor to the definition of this + /// entity; nullptr if there is no definition in this translation + /// unit. + virtual HRESULT STDMETHODCALLTYPE + GetDefinitionCursor(_Outptr_result_nullonfailure_ IDxcCursor **pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + FindReferencesInFile(_In_ IDxcFile *file, unsigned skip, unsigned top, + _Out_ unsigned *pResultLength, + _Outptr_result_buffer_maybenull_(*pResultLength) + IDxcCursor ***pResult) = 0; + /// Gets the name for the entity references by the cursor, e.g. foo + /// for an 'int foo' variable. + virtual HRESULT STDMETHODCALLTYPE + GetSpelling(_Outptr_result_maybenull_ LPSTR *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE IsEqualTo(_In_ IDxcCursor *other, + _Out_ BOOL *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE IsNull(_Out_ BOOL *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE IsDefinition(_Out_ BOOL *pResult) = 0; + /// Gets the display name for the cursor, including e.g. parameter + /// types for a function. + virtual HRESULT STDMETHODCALLTYPE GetDisplayName(_Out_ BSTR *pResult) = 0; + /// Gets the qualified name for the symbol the cursor refers + /// to. + virtual HRESULT STDMETHODCALLTYPE GetQualifiedName( + BOOL includeTemplateArgs, _Outptr_result_maybenull_ BSTR *pResult) = 0; + /// Gets a name for the cursor, applying the specified formatting + /// flags. + virtual HRESULT STDMETHODCALLTYPE + GetFormattedName(DxcCursorFormatting formatting, + _Outptr_result_maybenull_ BSTR *pResult) = 0; + /// Gets children in pResult up to top elements. + virtual HRESULT STDMETHODCALLTYPE + GetChildren(unsigned skip, unsigned top, _Out_ unsigned *pResultLength, + _Outptr_result_buffer_maybenull_(*pResultLength) + IDxcCursor ***pResult) = 0; + /// Gets the cursor following a location within a compound + /// cursor. + virtual HRESULT STDMETHODCALLTYPE + GetSnappedChild(_In_ IDxcSourceLocation *location, + _Outptr_result_maybenull_ IDxcCursor **pResult) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcDiagnostic, "4f76b234-3659-4d33-99b0-3b0db994b564") +struct IDxcDiagnostic : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE + FormatDiagnostic(DxcDiagnosticDisplayOptions options, + _Outptr_result_maybenull_ LPSTR *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetSeverity(_Out_ DxcDiagnosticSeverity *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetLocation(_Outptr_result_nullonfailure_ IDxcSourceLocation **pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetSpelling(_Outptr_result_maybenull_ LPSTR *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetCategoryText(_Outptr_result_maybenull_ LPSTR *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE GetNumRanges(_Out_ unsigned *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetRangeAt(unsigned index, + _Outptr_result_nullonfailure_ IDxcSourceRange **pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE GetNumFixIts(_Out_ unsigned *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetFixItAt(unsigned index, + _Outptr_result_nullonfailure_ IDxcSourceRange **pReplacementRange, + _Outptr_result_maybenull_ LPSTR *pText) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcFile, "bb2fca9e-1478-47ba-b08c-2c502ada4895") +struct IDxcFile : public IUnknown { + /// Gets the file name for this file. + virtual HRESULT STDMETHODCALLTYPE + GetName(_Outptr_result_maybenull_ LPSTR *pResult) = 0; + /// Checks whether this file is equal to the other specified + /// file. + virtual HRESULT STDMETHODCALLTYPE IsEqualTo(_In_ IDxcFile *other, + _Out_ BOOL *pResult) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcInclusion, "0c364d65-df44-4412-888e-4e552fc5e3d6") +struct IDxcInclusion : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE + GetIncludedFile(_Outptr_result_nullonfailure_ IDxcFile **pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE GetStackLength(_Out_ unsigned *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetStackItem(unsigned index, + _Outptr_result_nullonfailure_ IDxcSourceLocation **pResult) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcIntelliSense, "b1f99513-46d6-4112-8169-dd0d6053f17d") +struct IDxcIntelliSense : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE + CreateIndex(_Outptr_result_nullonfailure_ IDxcIndex **index) = 0; + virtual HRESULT STDMETHODCALLTYPE GetNullLocation( + _Outptr_result_nullonfailure_ IDxcSourceLocation **location) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetNullRange(_Outptr_result_nullonfailure_ IDxcSourceRange **location) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetRange(_In_ IDxcSourceLocation *start, _In_ IDxcSourceLocation *end, + _Outptr_result_nullonfailure_ IDxcSourceRange **location) = 0; + virtual HRESULT STDMETHODCALLTYPE GetDefaultDiagnosticDisplayOptions( + _Out_ DxcDiagnosticDisplayOptions *pValue) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetDefaultEditingTUOptions(_Out_ DxcTranslationUnitFlags *pValue) = 0; + virtual HRESULT STDMETHODCALLTYPE CreateUnsavedFile( + _In_ LPCSTR fileName, _In_ LPCSTR contents, unsigned contentLength, + _Outptr_result_nullonfailure_ IDxcUnsavedFile **pResult) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcIndex, "937824a0-7f5a-4815-9ba7-7fc0424f4173") +struct IDxcIndex : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE + SetGlobalOptions(DxcGlobalOptions options) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetGlobalOptions(_Out_ DxcGlobalOptions *options) = 0; + virtual HRESULT STDMETHODCALLTYPE ParseTranslationUnit( + _In_z_ const char *source_filename, + _In_count_(num_command_line_args) const char *const *command_line_args, + int num_command_line_args, + _In_count_(num_unsaved_files) IDxcUnsavedFile **unsaved_files, + unsigned num_unsaved_files, DxcTranslationUnitFlags options, + _Out_ IDxcTranslationUnit **pTranslationUnit) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcSourceLocation, + "8e7ddf1c-d7d3-4d69-b286-85fccba1e0cf") +struct IDxcSourceLocation : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE IsEqualTo(_In_ IDxcSourceLocation *other, + _Out_ BOOL *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE GetSpellingLocation( + _Outptr_opt_ IDxcFile **pFile, _Out_opt_ unsigned *pLine, + _Out_opt_ unsigned *pCol, _Out_opt_ unsigned *pOffset) = 0; + virtual HRESULT STDMETHODCALLTYPE IsNull(_Out_ BOOL *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetPresumedLocation(_Outptr_opt_ LPSTR *pFilename, _Out_opt_ unsigned *pLine, + _Out_opt_ unsigned *pCol) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcSourceRange, "f1359b36-a53f-4e81-b514-b6b84122a13f") +struct IDxcSourceRange : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE IsNull(_Out_ BOOL *pValue) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetStart(_Out_ IDxcSourceLocation **pValue) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetEnd(_Out_ IDxcSourceLocation **pValue) = 0; + virtual HRESULT STDMETHODCALLTYPE GetOffsets(_Out_ unsigned *startOffset, + _Out_ unsigned *endOffset) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcToken, "7f90b9ff-a275-4932-97d8-3cfd234482a2") +struct IDxcToken : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE GetKind(_Out_ DxcTokenKind *pValue) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetLocation(_Out_ IDxcSourceLocation **pValue) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetExtent(_Out_ IDxcSourceRange **pValue) = 0; + virtual HRESULT STDMETHODCALLTYPE GetSpelling(_Out_ LPSTR *pValue) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcTranslationUnit, + "9677dee0-c0e5-46a1-8b40-3db3168be63d") +struct IDxcTranslationUnit : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE GetCursor(_Out_ IDxcCursor **pCursor) = 0; + virtual HRESULT STDMETHODCALLTYPE + Tokenize(_In_ IDxcSourceRange *range, + _Outptr_result_buffer_maybenull_(*pTokenCount) IDxcToken ***pTokens, + _Out_ unsigned *pTokenCount) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetLocation(_In_ IDxcFile *file, unsigned line, unsigned column, + _Outptr_result_nullonfailure_ IDxcSourceLocation **pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetNumDiagnostics(_Out_ unsigned *pValue) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetDiagnostic(unsigned index, + _Outptr_result_nullonfailure_ IDxcDiagnostic **pValue) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetFile(_In_ const char *name, + _Outptr_result_nullonfailure_ IDxcFile **pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetFileName(_Outptr_result_maybenull_ LPSTR *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE Reparse(_In_count_(num_unsaved_files) + IDxcUnsavedFile **unsaved_files, + unsigned num_unsaved_files) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetCursorForLocation(_In_ IDxcSourceLocation *location, + _Outptr_result_nullonfailure_ IDxcCursor **pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE GetLocationForOffset( + _In_ IDxcFile *file, unsigned offset, + _Outptr_result_nullonfailure_ IDxcSourceLocation **pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE GetSkippedRanges( + _In_ IDxcFile *file, _Out_ unsigned *pResultCount, + _Outptr_result_buffer_(*pResultCount) IDxcSourceRange ***pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetDiagnosticDetails(unsigned index, DxcDiagnosticDisplayOptions options, + _Out_ unsigned *errorCode, _Out_ unsigned *errorLine, + _Out_ unsigned *errorColumn, _Out_ BSTR *errorFile, + _Out_ unsigned *errorOffset, _Out_ unsigned *errorLength, + _Out_ BSTR *errorMessage) = 0; + virtual HRESULT STDMETHODCALLTYPE GetInclusionList( + _Out_ unsigned *pResultCount, + _Outptr_result_buffer_(*pResultCount) IDxcInclusion ***pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE CodeCompleteAt( + _In_ const char *fileName, unsigned line, unsigned column, + _In_ IDxcUnsavedFile **pUnsavedFiles, unsigned numUnsavedFiles, + _In_ DxcCodeCompleteFlags options, + _Outptr_result_nullonfailure_ IDxcCodeCompleteResults **pResult) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcType, "2ec912fd-b144-4a15-ad0d-1c5439c81e46") +struct IDxcType : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE + GetSpelling(_Outptr_result_z_ LPSTR *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE IsEqualTo(_In_ IDxcType *other, + _Out_ BOOL *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE GetKind(_Out_ DxcTypeKind *pResult) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcUnsavedFile, "8ec00f98-07d0-4e60-9d7c-5a50b5b0017f") +struct IDxcUnsavedFile : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE + GetFileName(_Outptr_result_z_ LPSTR *pFileName) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetContents(_Outptr_result_z_ LPSTR *pContents) = 0; + virtual HRESULT STDMETHODCALLTYPE GetLength(_Out_ unsigned *pLength) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcCodeCompleteResults, + "1E06466A-FD8B-45F3-A78F-8A3F76EBB552") +struct IDxcCodeCompleteResults : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE GetNumResults(_Out_ unsigned *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetResultAt(unsigned index, + _Outptr_result_nullonfailure_ IDxcCompletionResult **pResult) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcCompletionResult, + "943C0588-22D0-4784-86FC-701F802AC2B6") +struct IDxcCompletionResult : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE + GetCursorKind(_Out_ DxcCursorKind *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE GetCompletionString( + _Outptr_result_nullonfailure_ IDxcCompletionString **pResult) = 0; +}; + +CROSS_PLATFORM_UUIDOF(IDxcCompletionString, + "06B51E0F-A605-4C69-A110-CD6E14B58EEC") +struct IDxcCompletionString : public IUnknown { + virtual HRESULT STDMETHODCALLTYPE + GetNumCompletionChunks(_Out_ unsigned *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE GetCompletionChunkKind( + unsigned chunkNumber, _Out_ DxcCompletionChunkKind *pResult) = 0; + virtual HRESULT STDMETHODCALLTYPE + GetCompletionChunkText(unsigned chunkNumber, _Out_ LPSTR *pResult) = 0; +}; + +// Fun fact: 'extern' is required because const is by default static in C++, so +// CLSID_DxcIntelliSense is not visible externally (this is OK in C, since const +// is not by default static in C) + +#ifdef _MSC_VER +#define CLSID_SCOPE __declspec(selectany) extern +#else +#define CLSID_SCOPE +#endif + +CLSID_SCOPE const CLSID + CLSID_DxcIntelliSense = {/* 3047833c-d1c0-4b8e-9d40-102878605985 */ + 0x3047833c, + 0xd1c0, + 0x4b8e, + {0x9d, 0x40, 0x10, 0x28, 0x78, 0x60, 0x59, 0x85}}; + +#endif diff --git a/Engine/cpp/ThirdParty/DXC/include/dxc/dxcpix.h b/Engine/cpp/ThirdParty/DXC/include/dxc/dxcpix.h new file mode 100644 index 00000000..f1ab1447 --- /dev/null +++ b/Engine/cpp/ThirdParty/DXC/include/dxc/dxcpix.h @@ -0,0 +1,206 @@ +/////////////////////////////////////////////////////////////////////////////// +// // +// dxcpix.h // +// Copyright (C) Microsoft Corporation. All rights reserved. // +// This file is distributed under the University of Illinois Open Source // +// License. See LICENSE.TXT for details. // +// // +// Provides declarations for the DirectX Compiler API with pix debugging. // +// // +/////////////////////////////////////////////////////////////////////////////// + +#ifndef __DXC_PIX__ +#define __DXC_PIX__ + +#include "dxc/dxcapi.h" +#ifdef _WIN32 +#include "objidl.h" +#endif + +struct __declspec(uuid("199d8c13-d312-4197-a2c1-07a532999727")) IDxcPixType + : public IUnknown { + virtual STDMETHODIMP GetName(_Outptr_result_z_ BSTR *Name) = 0; + + virtual STDMETHODIMP GetSizeInBits(_Out_ DWORD *GetSizeInBits) = 0; + + virtual STDMETHODIMP UnAlias(_COM_Outptr_ IDxcPixType **ppBaseType) = 0; +}; + +struct __declspec(uuid("d9df2c8b-2773-466d-9bc2-d848d8496bf6")) IDxcPixConstType + : public IDxcPixType {}; + +struct __declspec(uuid("7bfca9c0-1ed0-429c-9dc2-c75597d821d2")) + IDxcPixTypedefType : public IDxcPixType {}; + +struct __declspec(uuid("246e1652-ed2a-4ffc-a949-43bf63750ee5")) + IDxcPixScalarType : public IDxcPixType {}; + +struct __declspec(uuid("9ba0d9d3-457b-426f-8019-9f3849982aa2")) IDxcPixArrayType + : public IDxcPixType { + virtual STDMETHODIMP GetNumElements(_Out_ DWORD *ppNumElements) = 0; + + virtual STDMETHODIMP + GetIndexedType(_COM_Outptr_ IDxcPixType **ppElementType) = 0; + + virtual STDMETHODIMP + GetElementType(_COM_Outptr_ IDxcPixType **ppElementType) = 0; +}; + +struct __declspec(uuid("6c707d08-7995-4a84-bae5-e6d8291f3b78")) + IDxcPixStructField0 : public IUnknown { + virtual STDMETHODIMP GetName(_Outptr_result_z_ BSTR *Name) = 0; + + virtual STDMETHODIMP GetType(_COM_Outptr_ IDxcPixType **ppType) = 0; + + virtual STDMETHODIMP GetOffsetInBits(_Out_ DWORD *pOffsetInBits) = 0; +}; + +struct __declspec(uuid("de45597c-5869-4f97-a77b-d6650b9a16cf")) + IDxcPixStructField : public IUnknown { + virtual STDMETHODIMP GetName(_Outptr_result_z_ BSTR *Name) = 0; + + virtual STDMETHODIMP GetType(_COM_Outptr_ IDxcPixType **ppType) = 0; + + virtual STDMETHODIMP GetOffsetInBits(_Out_ DWORD *pOffsetInBits) = 0; + + virtual STDMETHODIMP GetFieldSizeInBits(_Out_ DWORD *pFieldSizeInBits) = 0; +}; + +struct __declspec(uuid("24c08c44-684b-4b1c-b41b-f8772383d074")) + IDxcPixStructType : public IDxcPixType { + virtual STDMETHODIMP GetNumFields(_Out_ DWORD *ppNumFields) = 0; + + virtual STDMETHODIMP + GetFieldByIndex(DWORD dwIndex, _COM_Outptr_ IDxcPixStructField **ppField) = 0; + + virtual STDMETHODIMP + GetFieldByName(_In_ LPCWSTR lpName, + _COM_Outptr_ IDxcPixStructField **ppField) = 0; +}; + +struct __declspec(uuid("7409f40c-dccb-41aa-bb42-1c95bbf7562f")) + IDxcPixStructType2 : public IDxcPixStructType { + virtual STDMETHODIMP GetBaseType(_COM_Outptr_ IDxcPixType **ppType) = 0; +}; + +struct __declspec(uuid("74d522f5-16c4-40cb-867b-4b4149e3db0e")) + IDxcPixDxilStorage : public IUnknown { + virtual STDMETHODIMP + AccessField(_In_ LPCWSTR Name, + _COM_Outptr_ IDxcPixDxilStorage **ppResult) = 0; + + virtual STDMETHODIMP Index(_In_ DWORD Index, + _COM_Outptr_ IDxcPixDxilStorage **ppResult) = 0; + + virtual STDMETHODIMP GetRegisterNumber(_Out_ DWORD *pRegNum) = 0; + + virtual STDMETHODIMP GetIsAlive() = 0; + + virtual STDMETHODIMP GetType(_COM_Outptr_ IDxcPixType **ppType) = 0; +}; + +struct __declspec(uuid("2f954b30-61a7-4348-95b1-2db356a75cde")) IDxcPixVariable + : public IUnknown { + virtual STDMETHODIMP GetName(_Outptr_result_z_ BSTR *Name) = 0; + + virtual STDMETHODIMP GetType(_COM_Outptr_ IDxcPixType **ppType) = 0; + + virtual STDMETHODIMP + GetStorage(_COM_Outptr_ IDxcPixDxilStorage **ppStorage) = 0; +}; + +struct __declspec(uuid("c59d302f-34a2-4fe5-9646-32ce7a52d03f")) + IDxcPixDxilLiveVariables : public IUnknown { + virtual STDMETHODIMP GetCount(_Out_ DWORD *dwSize) = 0; + + virtual STDMETHODIMP + GetVariableByIndex(_In_ DWORD Index, + _COM_Outptr_ IDxcPixVariable **ppVariable) = 0; + + virtual STDMETHODIMP + GetVariableByName(_In_ LPCWSTR Name, + _COM_Outptr_ IDxcPixVariable **ppVariable) = 0; +}; + +struct __declspec(uuid("eb71f85e-8542-44b5-87da-9d76045a1910")) + IDxcPixDxilInstructionOffsets : public IUnknown { + virtual STDMETHODIMP_(DWORD) GetCount() = 0; + + virtual STDMETHODIMP_(DWORD) GetOffsetByIndex(_In_ DWORD Index) = 0; +}; + +struct __declspec(uuid("761c833d-e7b8-4624-80f8-3a3fb4146342")) + IDxcPixDxilSourceLocations : public IUnknown { + virtual STDMETHODIMP_(DWORD) GetCount() = 0; + virtual STDMETHODIMP_(DWORD) GetLineNumberByIndex(_In_ DWORD Index) = 0; + virtual STDMETHODIMP_(DWORD) GetColumnByIndex(_In_ DWORD Index) = 0; + virtual STDMETHODIMP GetFileNameByIndex(_In_ DWORD Index, + _Outptr_result_z_ BSTR *Name) = 0; +}; + +struct __declspec(uuid("b875638e-108a-4d90-a53a-68d63773cb38")) + IDxcPixDxilDebugInfo : public IUnknown { + virtual STDMETHODIMP GetLiveVariablesAt( + _In_ DWORD InstructionOffset, + _COM_Outptr_ IDxcPixDxilLiveVariables **ppLiveVariables) = 0; + + virtual STDMETHODIMP + IsVariableInRegister(_In_ DWORD InstructionOffset, + _In_ const wchar_t *VariableName) = 0; + + virtual STDMETHODIMP + GetFunctionName(_In_ DWORD InstructionOffset, + _Outptr_result_z_ BSTR *ppFunctionName) = 0; + + virtual STDMETHODIMP GetStackDepth(_In_ DWORD InstructionOffset, + _Out_ DWORD *StackDepth) = 0; + + virtual STDMETHODIMP InstructionOffsetsFromSourceLocation( + _In_ const wchar_t *FileName, _In_ DWORD SourceLine, + _In_ DWORD SourceColumn, + _COM_Outptr_ IDxcPixDxilInstructionOffsets **ppOffsets) = 0; + + virtual STDMETHODIMP SourceLocationsFromInstructionOffset( + _In_ DWORD InstructionOffset, + _COM_Outptr_ IDxcPixDxilSourceLocations **ppSourceLocations) = 0; +}; + +struct __declspec(uuid("61b16c95-8799-4ed8-bdb0-3b6c08a141b4")) + IDxcPixCompilationInfo : public IUnknown { + virtual STDMETHODIMP + GetSourceFile(_In_ DWORD SourceFileOrdinal, + _Outptr_result_z_ BSTR *pSourceName, + _Outptr_result_z_ BSTR *pSourceContents) = 0; + virtual STDMETHODIMP GetArguments(_Outptr_result_z_ BSTR *pArguments) = 0; + virtual STDMETHODIMP + GetMacroDefinitions(_Outptr_result_z_ BSTR *pMacroDefinitions) = 0; + virtual STDMETHODIMP + GetEntryPointFile(_Outptr_result_z_ BSTR *pEntryPointFile) = 0; + virtual STDMETHODIMP GetHlslTarget(_Outptr_result_z_ BSTR *pHlslTarget) = 0; + virtual STDMETHODIMP GetEntryPoint(_Outptr_result_z_ BSTR *pEntryPoint) = 0; +}; + +struct __declspec(uuid("9c2a040d-8068-44ec-8c68-8bfef1b43789")) + IDxcPixDxilDebugInfoFactory : public IUnknown { + virtual STDMETHODIMP NewDxcPixDxilDebugInfo( + _COM_Outptr_ IDxcPixDxilDebugInfo **ppDxilDebugInfo) = 0; + virtual STDMETHODIMP NewDxcPixCompilationInfo( + _COM_Outptr_ IDxcPixCompilationInfo **ppCompilationInfo) = 0; +}; + +#ifndef CLSID_SCOPE +#ifdef _MSC_VER +#define CLSID_SCOPE __declspec(selectany) extern +#else +#define CLSID_SCOPE +#endif +#endif // !CLSID_SCOPE + +CLSID_SCOPE const CLSID CLSID_DxcPixDxilDebugger = + {/* a712b622-5af7-4c77-a965-c83ac1a5d8bc */ + 0xa712b622, + 0x5af7, + 0x4c77, + {0xa9, 0x65, 0xc8, 0x3a, 0xc1, 0xa5, 0xd8, 0xbc}}; + +#endif diff --git a/Engine/cpp/ThirdParty/DXC/lib/linux-x86_64/libdxcompiler.so b/Engine/cpp/ThirdParty/DXC/lib/linux-x86_64/libdxcompiler.so new file mode 100644 index 00000000..13b060b3 Binary files /dev/null and b/Engine/cpp/ThirdParty/DXC/lib/linux-x86_64/libdxcompiler.so differ diff --git a/Engine/cpp/ThirdParty/DXC/lib/linux-x86_64/libdxil.so b/Engine/cpp/ThirdParty/DXC/lib/linux-x86_64/libdxil.so new file mode 100644 index 00000000..a96b1987 Binary files /dev/null and b/Engine/cpp/ThirdParty/DXC/lib/linux-x86_64/libdxil.so differ diff --git a/Engine/cpp/ThirdParty/DXC/lib/win-x64/dxcompiler.dll b/Engine/cpp/ThirdParty/DXC/lib/win-x64/dxcompiler.dll new file mode 100644 index 00000000..748c08a7 Binary files /dev/null and b/Engine/cpp/ThirdParty/DXC/lib/win-x64/dxcompiler.dll differ diff --git a/Engine/cpp/ThirdParty/DXC/lib/win-x64/dxil.dll b/Engine/cpp/ThirdParty/DXC/lib/win-x64/dxil.dll new file mode 100644 index 00000000..b6889fc1 Binary files /dev/null and b/Engine/cpp/ThirdParty/DXC/lib/win-x64/dxil.dll differ diff --git a/Engine/cpp/ThirdParty/bgfx b/Engine/cpp/ThirdParty/bgfx deleted file mode 160000 index 8532b2c4..00000000 --- a/Engine/cpp/ThirdParty/bgfx +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8532b2c45d2f4332a9ac9734b85c2ea2253cb8d5 diff --git a/Engine/cpp/ThirdParty/bimg b/Engine/cpp/ThirdParty/bimg deleted file mode 160000 index 9114b47f..00000000 --- a/Engine/cpp/ThirdParty/bimg +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9114b47f532ce59cd0c6c9f8932df2c48888d4c1 diff --git a/Engine/cpp/ThirdParty/bx b/Engine/cpp/ThirdParty/bx deleted file mode 160000 index cac72f6c..00000000 --- a/Engine/cpp/ThirdParty/bx +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cac72f6cfa0893393ea12692ebfacb4495f8c826 diff --git a/Engine/cpp/ThirdParty/cmake/Config.cmake.in b/Engine/cpp/ThirdParty/cmake/Config.cmake.in deleted file mode 100644 index 33a3943a..00000000 --- a/Engine/cpp/ThirdParty/cmake/Config.cmake.in +++ /dev/null @@ -1,31 +0,0 @@ -@PACKAGE_INIT@ - -if(@BGFX_CMAKE_USER_SCRIPT_PRESENT@) - include("${CMAKE_CURRENT_LIST_DIR}/@BGFX_CMAKE_USER_SCRIPT_INSTALL_NAME@") -endif() -include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake") -get_target_property(BGFX_INCLUDE_PATH bgfx::bgfx INTERFACE_INCLUDE_DIRECTORIES) -list(GET BGFX_INCLUDE_PATH 0 BGFX_INCLUDE_PATH_1) # bgfx::bgfx exports include directory twice? -set(BGFX_SHADER_INCLUDE_PATH ${BGFX_INCLUDE_PATH_1}/bgfx) - -# If cross compiling, we need a host-compatible version of shaderc to compile shaders -macro(_bgfx_crosscompile_use_host_tool TOOL_NAME) - if(NOT TARGET bgfx::${TOOL_NAME}) - find_program( - ${TOOL_NAME}_EXECUTABLE - NAMES bgfx-${TOOL_NAME} ${TOOL_NAME} - PATHS @BGFX_ADDITIONAL_TOOL_PATHS@ /usr/bin - ) - add_executable(bgfx::${TOOL_NAME} IMPORTED) - set_target_properties(bgfx::${TOOL_NAME} PROPERTIES IMPORTED_LOCATION "${${TOOL_NAME}_EXECUTABLE}") - endif() -endmacro() - -_bgfx_crosscompile_use_host_tool(bin2c) -_bgfx_crosscompile_use_host_tool(texturec) -_bgfx_crosscompile_use_host_tool(shaderc) -_bgfx_crosscompile_use_host_tool(texturev) -_bgfx_crosscompile_use_host_tool(geometryv) - -include("${CMAKE_CURRENT_LIST_DIR}/bgfxToolUtils.cmake") -check_required_components("@PROJECT_NAME@") diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/dear-imgui.cmake b/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/dear-imgui.cmake deleted file mode 100644 index baaa9fdd..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/dear-imgui.cmake +++ /dev/null @@ -1,26 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BGFX_DIR}) - message(SEND_ERROR "Could not load bgfx, directory does not exist. ${BGFX_DIR}") - return() -endif() - -if(NOT DEAR_IMGUI_LIBRARIES) - file( - GLOB # - DEAR_IMGUI_SOURCES # - ${BGFX_DIR}/3rdparty/dear-imgui/*.cpp # - ${BGFX_DIR}/3rdparty/dear-imgui/*.h # - ${BGFX_DIR}/3rdparty/dear-imgui/*.inl # - ) - set(DEAR_IMGUI_INCLUDE_DIR ${BGFX_DIR}/3rdparty) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/fcpp.cmake b/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/fcpp.cmake deleted file mode 100644 index e62712cd..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/fcpp.cmake +++ /dev/null @@ -1,64 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BGFX_DIR}) - message(SEND_ERROR "Could not load bgfx, directory does not exist. ${BGFX_DIR}") - return() -endif() - -set(FCPP_DIR ${BGFX_DIR}/3rdparty/fcpp) - -file( - GLOB - FCPP_SOURCES - ${FCPP_DIR}/*.h - ${FCPP_DIR}/cpp1.c - ${FCPP_DIR}/cpp2.c - ${FCPP_DIR}/cpp3.c - ${FCPP_DIR}/cpp4.c - ${FCPP_DIR}/cpp5.c - ${FCPP_DIR}/cpp6.c - ${FCPP_DIR}/cpp6.c -) - -add_library(fcpp STATIC ${FCPP_SOURCES}) - -target_compile_definitions( - fcpp - PRIVATE "NINCLUDE=64" # - "NWORK=65536" # - "NBUFF=65536" # - "OLD_PREPROCESSOR=0" # - # "MSG_PREFIX=\"Preprocessor: \"" # -) - -# Put in a "bgfx" folder in Visual Studio -set_target_properties(fcpp PROPERTIES FOLDER "bgfx") - -target_include_directories(fcpp PUBLIC ${FCPP_DIR}) - -if(MSVC) - target_compile_options( - fcpp - PRIVATE - "/wd4055" # warning C4055: 'type cast': from data pointer 'void *' to function pointer 'void (__cdecl *)(char *,void *)' - "/wd4244" # warning C4244: '=': conversion from 'const flex_int32_t' to 'YY_CHAR', possible loss of data - "/wd4701" # warning C4701: potentially uninitialized local variable 'lower' used - "/wd4706" # warning C4706: assignment within conditional expression - ) -else() - target_compile_options( - fcpp - PRIVATE -Wno-implicit-fallthrough # - -Wno-incompatible-pointer-types # - -Wno-parentheses-equality # - ) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/glsl-optimizer.cmake b/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/glsl-optimizer.cmake deleted file mode 100644 index a209a723..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/glsl-optimizer.cmake +++ /dev/null @@ -1,246 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BGFX_DIR}) - message(SEND_ERROR "Could not load bgfx, directory does not exist. ${BGFX_DIR}") - return() -endif() - -set(GLSL_OPTIMIZER ${BGFX_DIR}/3rdparty/glsl-optimizer) - -file( - GLOB - GLSL_OPTIMIZER_SOURCES - ${GLSL_OPTIMIZER}/src/glsl/glcpp/glcpp.h - ${GLSL_OPTIMIZER}/src/glsl/glcpp/glcpp-lex.c - ${GLSL_OPTIMIZER}/src/glsl/glcpp/glcpp-parse.c - ${GLSL_OPTIMIZER}/src/glsl/glcpp/glcpp-parse.h - ${GLSL_OPTIMIZER}/src/glsl/glcpp/pp.c - ${GLSL_OPTIMIZER}/src/glsl/ast.h - ${GLSL_OPTIMIZER}/src/glsl/ast_array_index.cpp - ${GLSL_OPTIMIZER}/src/glsl/ast_expr.cpp - ${GLSL_OPTIMIZER}/src/glsl/ast_function.cpp - ${GLSL_OPTIMIZER}/src/glsl/ast_to_hir.cpp - ${GLSL_OPTIMIZER}/src/glsl/ast_type.cpp - ${GLSL_OPTIMIZER}/src/glsl/builtin_functions.cpp - ${GLSL_OPTIMIZER}/src/glsl/builtin_type_macros.h - ${GLSL_OPTIMIZER}/src/glsl/builtin_types.cpp - ${GLSL_OPTIMIZER}/src/glsl/builtin_variables.cpp - ${GLSL_OPTIMIZER}/src/glsl/glsl_lexer.cpp - ${GLSL_OPTIMIZER}/src/glsl/glsl_optimizer.cpp - ${GLSL_OPTIMIZER}/src/glsl/glsl_optimizer.h - ${GLSL_OPTIMIZER}/src/glsl/glsl_parser.cpp - ${GLSL_OPTIMIZER}/src/glsl/glsl_parser.h - ${GLSL_OPTIMIZER}/src/glsl/glsl_parser_extras.cpp - ${GLSL_OPTIMIZER}/src/glsl/glsl_parser_extras.h - ${GLSL_OPTIMIZER}/src/glsl/glsl_symbol_table.cpp - ${GLSL_OPTIMIZER}/src/glsl/glsl_symbol_table.h - ${GLSL_OPTIMIZER}/src/glsl/glsl_types.cpp - ${GLSL_OPTIMIZER}/src/glsl/glsl_types.h - ${GLSL_OPTIMIZER}/src/glsl/hir_field_selection.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir.h - ${GLSL_OPTIMIZER}/src/glsl/ir_basic_block.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_basic_block.h - ${GLSL_OPTIMIZER}/src/glsl/ir_builder.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_builder.h - ${GLSL_OPTIMIZER}/src/glsl/ir_clone.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_constant_expression.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_equals.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_expression_flattening.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_expression_flattening.h - ${GLSL_OPTIMIZER}/src/glsl/ir_function.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_function_can_inline.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_function_detect_recursion.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_function_inlining.h - ${GLSL_OPTIMIZER}/src/glsl/ir_hierarchical_visitor.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_hierarchical_visitor.h - ${GLSL_OPTIMIZER}/src/glsl/ir_hv_accept.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_import_prototypes.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_optimization.h - ${GLSL_OPTIMIZER}/src/glsl/ir_print_glsl_visitor.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_print_glsl_visitor.h - ${GLSL_OPTIMIZER}/src/glsl/ir_print_metal_visitor.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_print_metal_visitor.h - ${GLSL_OPTIMIZER}/src/glsl/ir_print_visitor.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_print_visitor.h - ${GLSL_OPTIMIZER}/src/glsl/ir_rvalue_visitor.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_rvalue_visitor.h - ${GLSL_OPTIMIZER}/src/glsl/ir_stats.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_stats.h - ${GLSL_OPTIMIZER}/src/glsl/ir_uniform.h - ${GLSL_OPTIMIZER}/src/glsl/ir_unused_structs.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_unused_structs.h - ${GLSL_OPTIMIZER}/src/glsl/ir_validate.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_variable_refcount.cpp - ${GLSL_OPTIMIZER}/src/glsl/ir_variable_refcount.h - ${GLSL_OPTIMIZER}/src/glsl/ir_visitor.h - ${GLSL_OPTIMIZER}/src/glsl/link_atomics.cpp - ${GLSL_OPTIMIZER}/src/glsl/link_functions.cpp - ${GLSL_OPTIMIZER}/src/glsl/link_interface_blocks.cpp - ${GLSL_OPTIMIZER}/src/glsl/link_uniform_block_active_visitor.cpp - ${GLSL_OPTIMIZER}/src/glsl/link_uniform_block_active_visitor.h - ${GLSL_OPTIMIZER}/src/glsl/link_uniform_blocks.cpp - ${GLSL_OPTIMIZER}/src/glsl/link_uniform_initializers.cpp - ${GLSL_OPTIMIZER}/src/glsl/link_uniforms.cpp - ${GLSL_OPTIMIZER}/src/glsl/link_varyings.cpp - ${GLSL_OPTIMIZER}/src/glsl/link_varyings.h - ${GLSL_OPTIMIZER}/src/glsl/linker.cpp - ${GLSL_OPTIMIZER}/src/glsl/linker.h - ${GLSL_OPTIMIZER}/src/glsl/list.h - ${GLSL_OPTIMIZER}/src/glsl/loop_analysis.cpp - ${GLSL_OPTIMIZER}/src/glsl/loop_analysis.h - ${GLSL_OPTIMIZER}/src/glsl/loop_controls.cpp - ${GLSL_OPTIMIZER}/src/glsl/loop_unroll.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_clip_distance.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_discard.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_discard_flow.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_if_to_cond_assign.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_instructions.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_jumps.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_mat_op_to_vec.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_named_interface_blocks.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_noise.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_offset_array.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_output_reads.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_packed_varyings.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_packing_builtins.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_ubo_reference.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_variable_index_to_cond_assign.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_vec_index_to_cond_assign.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_vec_index_to_swizzle.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_vector.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_vector_insert.cpp - ${GLSL_OPTIMIZER}/src/glsl/lower_vertex_id.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_algebraic.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_array_splitting.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_constant_folding.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_constant_propagation.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_constant_variable.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_copy_propagation.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_copy_propagation_elements.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_cse.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_dead_builtin_variables.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_dead_builtin_varyings.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_dead_code.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_dead_code_local.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_dead_functions.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_flatten_nested_if_blocks.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_flip_matrices.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_function_inlining.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_if_simplification.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_minmax.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_noop_swizzle.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_rebalance_tree.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_redundant_jumps.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_structure_splitting.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_swizzle_swizzle.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_tree_grafting.cpp - ${GLSL_OPTIMIZER}/src/glsl/opt_vectorize.cpp - ${GLSL_OPTIMIZER}/src/glsl/program.h - ${GLSL_OPTIMIZER}/src/glsl/s_expression.cpp - ${GLSL_OPTIMIZER}/src/glsl/s_expression.h - ${GLSL_OPTIMIZER}/src/glsl/standalone_scaffolding.cpp - ${GLSL_OPTIMIZER}/src/glsl/standalone_scaffolding.h - ${GLSL_OPTIMIZER}/src/glsl/strtod.c - ${GLSL_OPTIMIZER}/src/glsl/strtod.h - ${GLSL_OPTIMIZER}/src/mesa/main/compiler.h - ${GLSL_OPTIMIZER}/src/mesa/main/config.h - ${GLSL_OPTIMIZER}/src/mesa/main/context.h - ${GLSL_OPTIMIZER}/src/mesa/main/core.h - ${GLSL_OPTIMIZER}/src/mesa/main/dd.h - ${GLSL_OPTIMIZER}/src/mesa/main/errors.h - ${GLSL_OPTIMIZER}/src/mesa/main/glheader.h - ${GLSL_OPTIMIZER}/src/mesa/main/glminimal.h - ${GLSL_OPTIMIZER}/src/mesa/main/imports.c - ${GLSL_OPTIMIZER}/src/mesa/main/imports.h - ${GLSL_OPTIMIZER}/src/mesa/main/macros.h - ${GLSL_OPTIMIZER}/src/mesa/main/mtypes.h - ${GLSL_OPTIMIZER}/src/mesa/main/simple_list.h - ${GLSL_OPTIMIZER}/src/mesa/program/hash_table.h - ${GLSL_OPTIMIZER}/src/mesa/program/prog_hash_table.c - ${GLSL_OPTIMIZER}/src/mesa/program/prog_instruction.h - ${GLSL_OPTIMIZER}/src/mesa/program/prog_parameter.h - ${GLSL_OPTIMIZER}/src/mesa/program/prog_statevars.h - ${GLSL_OPTIMIZER}/src/mesa/program/symbol_table.c - ${GLSL_OPTIMIZER}/src/mesa/program/symbol_table.h - ${GLSL_OPTIMIZER}/src/util/hash_table.c - ${GLSL_OPTIMIZER}/src/util/hash_table.h - ${GLSL_OPTIMIZER}/src/util/macros.h - ${GLSL_OPTIMIZER}/src/util/ralloc.c - ${GLSL_OPTIMIZER}/src/util/ralloc.h -) - -add_library(glsl-optimizer STATIC ${GLSL_OPTIMIZER_SOURCES}) - -# Put in a "bgfx" folder in Visual Studio -set_target_properties(glsl-optimizer PROPERTIES FOLDER "bgfx") - -target_include_directories( - glsl-optimizer - PUBLIC ${GLSL_OPTIMIZER}/include # - ${GLSL_OPTIMIZER}/src/glsl # - PRIVATE ${GLSL_OPTIMIZER}/src # - ${GLSL_OPTIMIZER}/src/mesa # - ${GLSL_OPTIMIZER}/src/mapi # -) - -if(MSVC) - target_compile_definitions( - glsl-optimizer - PRIVATE "__STDC__" # - "__STDC_VERSION__=199901L" # - "strdup=_strdup" # - "alloca=_alloca" # - "isascii=__isascii" # - ) - target_compile_options( - glsl-optimizer - PRIVATE - "/wd4100" # error C4100: '' : unreferenced formal parameter - "/wd4127" # warning C4127: conditional expression is constant - "/wd4132" # warning C4132: 'deleted_key_value': const object should be initialized - "/wd4189" # warning C4189: 'interface_type': local variable is initialized but not referenced - "/wd4204" # warning C4204: nonstandard extension used: non-constant aggregate initializer - "/wd4244" # warning C4244: '=': conversion from 'const flex_int32_t' to 'YY_CHAR', possible loss of data - "/wd4389" # warning C4389: '!=': signed/unsigned mismatch - "/wd4245" # warning C4245: 'return': conversion from 'int' to 'unsigned int', signed/unsigned mismatch - "/wd4701" # warning C4701: potentially uninitialized local variable 'lower' used - "/wd4702" # warning C4702: unreachable code - "/wd4706" # warning C4706: assignment within conditional expression - "/wd4996" # warning C4996: 'strdup': The POSIX name for this item is deprecated. Instead, use the ISO C++ conformant name: _strdup. - ) -else() - target_compile_options( - glsl-optimizer - PRIVATE "-fno-strict-aliasing" # glsl-optimizer has bugs if strict aliasing is used. - # - "-Wno-implicit-fallthrough" # - "-Wno-parentheses" # - "-Wno-sign-compare" # - "-Wno-unused-function" # - "-Wno-unused-parameter" # - ) -endif() - -if(XCODE) - target_compile_options( - glsl-optimizer PRIVATE # - "-Wno-deprecated-register" # - ) -endif() - -if(MINGW) - target_compile_options( - glsl-optimizer PRIVATE # - "-Wno-misleading-indentation" # - ) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/glslang.cmake b/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/glslang.cmake deleted file mode 100644 index 048cc877..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/glslang.cmake +++ /dev/null @@ -1,64 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BGFX_DIR}) - message(SEND_ERROR "Could not load bgfx, directory does not exist. ${BGFX_DIR}") - return() -endif() - -set(GLSLANG ${BGFX_DIR}/3rdparty/glslang) -set(SPIRV_TOOLS ${BGFX_DIR}/3rdparty/spirv-tools) - -file( - GLOB_RECURSE - GLSLANG_SOURCES - ${GLSLANG}/glslang/*.cpp - ${GLSLANG}/glslang/*.h - # - ${GLSLANG}/hlsl/*.cpp - ${GLSLANG}/hlsl/*.h - # - ${GLSLANG}/SPIRV/*.cpp - ${GLSLANG}/SPIRV/*.h - # - ${GLSLANG}/OGLCompilersDLL/*.cpp - ${GLSLANG}/OGLCompilersDLL/*.h -) - -if(WIN32) - list(FILTER GLSLANG_SOURCES EXCLUDE REGEX "glslang/OSDependent/Unix/.*.cpp") - list(FILTER GLSLANG_SOURCES EXCLUDE REGEX "glslang/OSDependent/Unix/.*.h") -else() - list(FILTER GLSLANG_SOURCES EXCLUDE REGEX "glslang/OSDependent/Windows/.*.cpp") - list(FILTER GLSLANG_SOURCES EXCLUDE REGEX "glslang/OSDependent/Windows/.*.h") -endif() - -add_library(glslang STATIC ${GLSLANG_SOURCES}) - -target_compile_definitions( - glslang - PRIVATE # - ENABLE_OPT=1 # spriv-tools - ENABLE_HLSL=1 # -) - -# Put in a "bgfx" folder in Visual Studio -set_target_properties(glslang PROPERTIES FOLDER "bgfx") - -target_include_directories( - glslang - PUBLIC ${GLSLANG} - ${GLSLANG}/glslang/Public - ${GLSLANG}/glslang/Include - PRIVATE ${GLSLANG}/.. - ${SPIRV_TOOLS}/include - ${SPIRV_TOOLS}/source -) diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/meshoptimizer.cmake b/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/meshoptimizer.cmake deleted file mode 100644 index d6283afb..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/meshoptimizer.cmake +++ /dev/null @@ -1,25 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BGFX_DIR}) - message(SEND_ERROR "Could not load bgfx, directory does not exist. ${BGFX_DIR}") - return() -endif() - -if(NOT MESHOPTIMIZER_LIBRARIES) - file( - GLOB # - MESHOPTIMIZER_SOURCES # - ${BGFX_DIR}/3rdparty/meshoptimizer/src/*.cpp # - ${BGFX_DIR}/3rdparty/meshoptimizer/src/*.h # - ) - set(MESHOPTIMIZER_INCLUDE_DIR ${BGFX_DIR}/3rdparty) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/spirv-cross.cmake b/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/spirv-cross.cmake deleted file mode 100644 index 7737e445..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/spirv-cross.cmake +++ /dev/null @@ -1,60 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BGFX_DIR}) - message(SEND_ERROR "Could not load bgfx, directory does not exist. ${BGFX_DIR}") - return() -endif() - -set(SPIRV_CROSS ${BGFX_DIR}/3rdparty/spirv-cross) - -file( - GLOB - SPIRV_CROSS_SOURCES - # - ${SPIRV_CROSS}/spirv.hpp - ${SPIRV_CROSS}/spirv_cfg.cpp - ${SPIRV_CROSS}/spirv_cfg.hpp - ${SPIRV_CROSS}/spirv_common.hpp - ${SPIRV_CROSS}/spirv_cpp.cpp - ${SPIRV_CROSS}/spirv_cpp.hpp - ${SPIRV_CROSS}/spirv_cross.cpp - ${SPIRV_CROSS}/spirv_cross.hpp - ${SPIRV_CROSS}/spirv_cross_parsed_ir.cpp - ${SPIRV_CROSS}/spirv_cross_parsed_ir.hpp - ${SPIRV_CROSS}/spirv_cross_util.cpp - ${SPIRV_CROSS}/spirv_cross_util.hpp - ${SPIRV_CROSS}/spirv_glsl.cpp - ${SPIRV_CROSS}/spirv_glsl.hpp - ${SPIRV_CROSS}/spirv_hlsl.cpp - ${SPIRV_CROSS}/spirv_hlsl.hpp - ${SPIRV_CROSS}/spirv_msl.cpp - ${SPIRV_CROSS}/spirv_msl.hpp - ${SPIRV_CROSS}/spirv_parser.cpp - ${SPIRV_CROSS}/spirv_parser.hpp - ${SPIRV_CROSS}/spirv_reflect.cpp - ${SPIRV_CROSS}/spirv_reflect.hpp -) - -add_library(spirv-cross STATIC ${SPIRV_CROSS_SOURCES}) - -target_compile_definitions(spirv-cross PRIVATE SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS) - -# Put in a "bgfx" folder in Visual Studio -set_target_properties(spirv-cross PROPERTIES FOLDER "bgfx") - -target_include_directories( - spirv-cross # - PUBLIC # - ${SPIRV_CROSS} # - PRIVATE # - ${SPIRV_CROSS}/include # -) diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/spirv-opt.cmake b/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/spirv-opt.cmake deleted file mode 100644 index b1d90b91..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/spirv-opt.cmake +++ /dev/null @@ -1,159 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BGFX_DIR}) - message(SEND_ERROR "Could not load bgfx, directory does not exist. ${BGFX_DIR}") - return() -endif() - -set(SPIRV_HEADERS ${BGFX_DIR}/3rdparty/spirv-headers) -set(SPIRV_TOOLS ${BGFX_DIR}/3rdparty/spirv-tools) - -file( - GLOB - SPIRV_OPT_SOURCES - # libspirv - ${SPIRV_TOOLS}/source/opt/*.cpp - ${SPIRV_TOOLS}/source/opt/*.h - ${SPIRV_TOOLS}/source/reduce/*.cpp - ${SPIRV_TOOLS}/source/reduce/*.h - ${SPIRV_TOOLS}/source/assembly_grammar.cpp - ${SPIRV_TOOLS}/source/assembly_grammar.h - ${SPIRV_TOOLS}/source/binary.cpp - ${SPIRV_TOOLS}/source/binary.h - ${SPIRV_TOOLS}/source/cfa.h - ${SPIRV_TOOLS}/source/diagnostic.cpp - ${SPIRV_TOOLS}/source/diagnostic.h - ${SPIRV_TOOLS}/source/disassemble.cpp - ${SPIRV_TOOLS}/source/disassemble.h - ${SPIRV_TOOLS}/source/enum_set.h - ${SPIRV_TOOLS}/source/enum_string_mapping.cpp - ${SPIRV_TOOLS}/source/enum_string_mapping.h - ${SPIRV_TOOLS}/source/ext_inst.cpp - ${SPIRV_TOOLS}/source/ext_inst.h - ${SPIRV_TOOLS}/source/extensions.cpp - ${SPIRV_TOOLS}/source/extensions.h - ${SPIRV_TOOLS}/source/instruction.h - ${SPIRV_TOOLS}/source/latest_version_glsl_std_450_header.h - ${SPIRV_TOOLS}/source/latest_version_opencl_std_header.h - ${SPIRV_TOOLS}/source/latest_version_spirv_header.h - ${SPIRV_TOOLS}/source/libspirv.cpp - ${SPIRV_TOOLS}/source/macro.h - ${SPIRV_TOOLS}/source/name_mapper.cpp - ${SPIRV_TOOLS}/source/name_mapper.h - ${SPIRV_TOOLS}/source/opcode.cpp - ${SPIRV_TOOLS}/source/opcode.h - ${SPIRV_TOOLS}/source/operand.cpp - ${SPIRV_TOOLS}/source/operand.h - ${SPIRV_TOOLS}/source/parsed_operand.cpp - ${SPIRV_TOOLS}/source/parsed_operand.h - ${SPIRV_TOOLS}/source/print.cpp - ${SPIRV_TOOLS}/source/print.h - ${SPIRV_TOOLS}/source/software_version.cpp - ${SPIRV_TOOLS}/source/spirv_constant.h - ${SPIRV_TOOLS}/source/spirv_definition.h - ${SPIRV_TOOLS}/source/spirv_endian.cpp - ${SPIRV_TOOLS}/source/spirv_endian.h - ${SPIRV_TOOLS}/source/spirv_optimizer_options.cpp - ${SPIRV_TOOLS}/source/spirv_reducer_options.cpp - ${SPIRV_TOOLS}/source/spirv_target_env.cpp - ${SPIRV_TOOLS}/source/spirv_target_env.h - ${SPIRV_TOOLS}/source/spirv_validator_options.cpp - ${SPIRV_TOOLS}/source/spirv_validator_options.h - ${SPIRV_TOOLS}/source/table.cpp - ${SPIRV_TOOLS}/source/table.h - ${SPIRV_TOOLS}/source/table2.cpp - ${SPIRV_TOOLS}/source/table2.h - ${SPIRV_TOOLS}/source/text.cpp - ${SPIRV_TOOLS}/source/text.h - ${SPIRV_TOOLS}/source/text_handler.cpp - ${SPIRV_TOOLS}/source/text_handler.h - ${SPIRV_TOOLS}/source/to_string.cpp - ${SPIRV_TOOLS}/source/to_string.h - ${SPIRV_TOOLS}/source/util/bit_vector.cpp - ${SPIRV_TOOLS}/source/util/bit_vector.h - ${SPIRV_TOOLS}/source/util/bitutils.h - ${SPIRV_TOOLS}/source/util/hex_float.h - ${SPIRV_TOOLS}/source/util/parse_number.cpp - ${SPIRV_TOOLS}/source/util/parse_number.h - ${SPIRV_TOOLS}/source/util/status.h - ${SPIRV_TOOLS}/source/util/string_utils.cpp - ${SPIRV_TOOLS}/source/util/string_utils.h - ${SPIRV_TOOLS}/source/util/timer.h - ${SPIRV_TOOLS}/source/val/basic_block.cpp - ${SPIRV_TOOLS}/source/val/construct.cpp - ${SPIRV_TOOLS}/source/val/decoration.h - ${SPIRV_TOOLS}/source/val/function.cpp - ${SPIRV_TOOLS}/source/val/instruction.cpp - ${SPIRV_TOOLS}/source/val/validate.cpp - ${SPIRV_TOOLS}/source/val/validate.h - ${SPIRV_TOOLS}/source/val/validate_adjacency.cpp - ${SPIRV_TOOLS}/source/val/validate_annotation.cpp - ${SPIRV_TOOLS}/source/val/validate_arithmetics.cpp - ${SPIRV_TOOLS}/source/val/validate_atomics.cpp - ${SPIRV_TOOLS}/source/val/validate_barriers.cpp - ${SPIRV_TOOLS}/source/val/validate_bitwise.cpp - ${SPIRV_TOOLS}/source/val/validate_builtins.cpp - ${SPIRV_TOOLS}/source/val/validate_capability.cpp - ${SPIRV_TOOLS}/source/val/validate_cfg.cpp - ${SPIRV_TOOLS}/source/val/validate_composites.cpp - ${SPIRV_TOOLS}/source/val/validate_constants.cpp - ${SPIRV_TOOLS}/source/val/validate_conversion.cpp - ${SPIRV_TOOLS}/source/val/validate_debug.cpp - ${SPIRV_TOOLS}/source/val/validate_decorations.cpp - ${SPIRV_TOOLS}/source/val/validate_derivatives.cpp - ${SPIRV_TOOLS}/source/val/validate_dot_product.cpp - ${SPIRV_TOOLS}/source/val/validate_execution_limitations.cpp - ${SPIRV_TOOLS}/source/val/validate_extensions.cpp - ${SPIRV_TOOLS}/source/val/validate_function.cpp - ${SPIRV_TOOLS}/source/val/validate_graph.cpp - ${SPIRV_TOOLS}/source/val/validate_group.cpp - ${SPIRV_TOOLS}/source/val/validate_id.cpp - ${SPIRV_TOOLS}/source/val/validate_image.cpp - ${SPIRV_TOOLS}/source/val/validate_instruction.cpp - ${SPIRV_TOOLS}/source/val/validate_interfaces.cpp - ${SPIRV_TOOLS}/source/val/validate_invalid_type.cpp - ${SPIRV_TOOLS}/source/val/validate_layout.cpp - ${SPIRV_TOOLS}/source/val/validate_literals.cpp - ${SPIRV_TOOLS}/source/val/validate_logical_pointers.cpp - ${SPIRV_TOOLS}/source/val/validate_logicals.cpp - ${SPIRV_TOOLS}/source/val/validate_memory.cpp - ${SPIRV_TOOLS}/source/val/validate_memory_semantics.cpp - ${SPIRV_TOOLS}/source/val/validate_mesh_shading.cpp - ${SPIRV_TOOLS}/source/val/validate_misc.cpp - ${SPIRV_TOOLS}/source/val/validate_mode_setting.cpp - ${SPIRV_TOOLS}/source/val/validate_non_uniform.cpp - ${SPIRV_TOOLS}/source/val/validate_pipe.cpp - ${SPIRV_TOOLS}/source/val/validate_primitives.cpp - ${SPIRV_TOOLS}/source/val/validate_ray_query.cpp - ${SPIRV_TOOLS}/source/val/validate_ray_tracing.cpp - ${SPIRV_TOOLS}/source/val/validate_ray_tracing_reorder.cpp - ${SPIRV_TOOLS}/source/val/validate_scopes.cpp - ${SPIRV_TOOLS}/source/val/validate_small_type_uses.cpp - ${SPIRV_TOOLS}/source/val/validate_tensor.cpp - ${SPIRV_TOOLS}/source/val/validate_tensor_layout.cpp - ${SPIRV_TOOLS}/source/val/validate_type.cpp - ${SPIRV_TOOLS}/source/val/validation_state.cpp -) - -add_library(spirv-opt STATIC ${SPIRV_OPT_SOURCES}) - -# Put in a "bgfx" folder in Visual Studio -set_target_properties(spirv-opt PROPERTIES FOLDER "bgfx") - -target_include_directories( - spirv-opt - PUBLIC ${SPIRV_TOOLS}/include # - PRIVATE ${SPIRV_TOOLS} # - ${SPIRV_TOOLS}/include/generated # - ${SPIRV_TOOLS}/source # - ${SPIRV_HEADERS}/include # -) diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/tint.cmake b/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/tint.cmake deleted file mode 100644 index eb86f57c..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/tint.cmake +++ /dev/null @@ -1,98 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -if(TARGET tint) - return() -endif() - -if(NOT IS_DIRECTORY ${BGFX_DIR}) - message(SEND_ERROR "Could not load bgfx, directory does not exist. ${BGFX_DIR}") - return() -endif() - -set(TINT_DIR ${BGFX_DIR}/3rdparty/dawn) -set(SPIRV_HEADERS ${BGFX_DIR}/3rdparty/spirv-headers) -set(SPIRV_TOOLS ${BGFX_DIR}/3rdparty/spirv-tools) - -file( - GLOB_RECURSE - TINT_SOURCES - ${TINT_DIR}/src/tint/utils/*.cc - ${TINT_DIR}/src/tint/utils/*.h - ${TINT_DIR}/src/tint/lang/core/*.cc - ${TINT_DIR}/src/tint/lang/core/*.h - ${TINT_DIR}/src/tint/lang/null/*.cc - ${TINT_DIR}/src/tint/lang/null/*.h - ${TINT_DIR}/src/tint/lang/spirv/*.cc - ${TINT_DIR}/src/tint/lang/spirv/*.h - ${TINT_DIR}/src/tint/lang/wgsl/*.cc - ${TINT_DIR}/src/tint/lang/wgsl/*.h - ${TINT_DIR}/src/tint/api/*.cc - ${TINT_DIR}/src/tint/api/*.h -) - -add_library(tint STATIC ${TINT_SOURCES}) - -set_target_properties(tint PROPERTIES FOLDER "bgfx") - -target_include_directories( - tint - PUBLIC ${TINT_DIR} - ${TINT_DIR}/src/tint - PRIVATE ${TINT_DIR}/third_party/protobuf/src - ${TINT_DIR}/third_party/abseil-cpp - ${SPIRV_TOOLS} - ${SPIRV_TOOLS}/include - ${SPIRV_TOOLS}/include/generated - ${SPIRV_HEADERS}/include -) - -target_compile_definitions( - tint - PRIVATE TINT_BUILD_GLSL_WRITER=0 - TINT_BUILD_HLSL_WRITER=0 - TINT_BUILD_MSL_WRITER=0 - TINT_BUILD_NULL_WRITER=0 - TINT_BUILD_SPV_READER=1 - TINT_BUILD_SPV_WRITER=0 - TINT_BUILD_WGSL_READER=0 - TINT_BUILD_WGSL_WRITER=1 - TINT_ENABLE_IR_VALIDATION=0 -) - -if(WIN32) - target_compile_definitions( - tint - PRIVATE TINT_BUILD_IS_LINUX=0 - TINT_BUILD_IS_MAC=0 - TINT_BUILD_IS_WIN=1 - ) -elseif(APPLE) - target_compile_definitions( - tint - PRIVATE TINT_BUILD_IS_LINUX=0 - TINT_BUILD_IS_MAC=1 - TINT_BUILD_IS_WIN=0 - ) -else() - target_compile_definitions( - tint - PRIVATE TINT_BUILD_IS_LINUX=1 - TINT_BUILD_IS_MAC=0 - TINT_BUILD_IS_WIN=0 - ) -endif() - -if(MSVC) - target_compile_options( - tint - PRIVATE "/Zc:preprocessor" - ) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/webgpu.cmake b/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/webgpu.cmake deleted file mode 100644 index 1de320a1..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/3rdparty/webgpu.cmake +++ /dev/null @@ -1,40 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover - -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. - -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -if(TARGET webgpu) - return() -endif() - -file(GLOB WEBGPU_SOURCES ${BGFX_DIR}/3rdparty/webgpu/include/webgpu/*.h - # ${BGFX_DIR}/3rdparty/webgpu/webgpu_cpp.cpp # requires dawn to be installed -) - -# Library without sources is interface -#add_library( webgpu STATIC ${WEBGPU_SOURCES} ) -add_library(webgpu INTERFACE) -target_include_directories( - webgpu # PUBLIC - INTERFACE $ -) - -# These properties are not allowed on interface -# set_target_properties(webgpu PROPERTIES FOLDER "bgfx/3rdparty" PREFIX "${CMAKE_STATIC_LIBRARY_PREFIX}bgfx-") - -if(BGFX_INSTALL AND BGFX_CONFIG_RENDERER_WEBGPU) - install( - TARGETS webgpu - EXPORT "${TARGETS_EXPORT_NAME}" - LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" - ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" - RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" - INCLUDES - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" - ) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/CMakeLists.txt b/Engine/cpp/ThirdParty/cmake/bgfx/CMakeLists.txt deleted file mode 100644 index 4537e95f..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -include(bgfx.cmake) -include(3rdparty/meshoptimizer.cmake) -include(3rdparty/dear-imgui.cmake) - -if(BGFX_BUILD_TOOLS_TEXTURE) - include(texturev.cmake) -endif() -if(BGFX_BUILD_TOOLS_GEOMETRY) - include(geometryc.cmake) - include(geometryv.cmake) -endif() -if(BGFX_BUILD_TOOLS_SHADER) - include(3rdparty/spirv-opt.cmake) - include(3rdparty/spirv-cross.cmake) - include(3rdparty/glslang.cmake) - include(3rdparty/glsl-optimizer.cmake) - include(3rdparty/fcpp.cmake) - include(3rdparty/webgpu.cmake) - include(3rdparty/tint.cmake) - include(shaderc.cmake) -endif() - -include(shared.cmake) -include(examples.cmake) diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/bgfx.cmake b/Engine/cpp/ThirdParty/cmake/bgfx/bgfx.cmake deleted file mode 100644 index 06ca401f..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/bgfx.cmake +++ /dev/null @@ -1,223 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# To prevent this warning: https://cmake.org/cmake/help/git-stage/policy/CMP0072.html -if(POLICY CMP0072) - cmake_policy(SET CMP0072 NEW) -endif() - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BGFX_DIR}) - message(SEND_ERROR "Could not load bgfx, directory does not exist. ${BGFX_DIR}") - return() -endif() - -# Grab the bgfx source files -file( - GLOB - BGFX_SOURCES - ${BGFX_DIR}/src/*.cpp - ${BGFX_DIR}/src/*.h - ${BGFX_DIR}/include/bgfx/*.h - ${BGFX_DIR}/include/bgfx/c99/*.h -) - -set(BGFX_AMALGAMATED_SOURCE ${BGFX_DIR}/src/amalgamated.cpp) - -if(BGFX_AMALGAMATED) - set(BGFX_NOBUILD ${BGFX_SOURCES}) - list(REMOVE_ITEM BGFX_NOBUILD ${BGFX_AMALGAMATED_SOURCE}) - foreach(BGFX_SRC ${BGFX_NOBUILD}) - set_source_files_properties(${BGFX_SRC} PROPERTIES HEADER_FILE_ONLY ON) - endforeach() -else() - # Do not build using amalgamated sources - set_source_files_properties(${BGFX_AMALGAMATED_SOURCE} PROPERTIES HEADER_FILE_ONLY ON) -endif() - -# Create the bgfx target -if(BGFX_LIBRARY_TYPE STREQUAL STATIC) - add_library(bgfx STATIC ${BGFX_SOURCES}) -else() - add_library(bgfx SHARED ${BGFX_SOURCES}) - target_compile_definitions(bgfx PUBLIC BGFX_SHARED_LIB_BUILD=1) -endif() - -if(BGFX_CONFIG_RENDERER_WEBGPU) - include(${CMAKE_CURRENT_LIST_DIR}/3rdparty/webgpu.cmake) - if(EMSCRIPTEN) - target_link_options(bgfx PRIVATE "-s USE_WEBGPU=1") - else() - target_link_libraries(bgfx PRIVATE webgpu) - endif() -endif() - -if(EMSCRIPTEN) - target_link_options(bgfx PUBLIC "-sMAX_WEBGL_VERSION=2") -endif() - -if(NOT ${BGFX_OPENGL_VERSION} STREQUAL "") - target_compile_definitions(bgfx PRIVATE BGFX_CONFIG_RENDERER_OPENGL_MIN_VERSION=${BGFX_OPENGL_VERSION}) -endif() - -if(NOT ${BGFX_OPENGLES_VERSION} STREQUAL "") - target_compile_definitions(bgfx PRIVATE BGFX_CONFIG_RENDERER_OPENGLES_MIN_VERSION=${BGFX_OPENGLES_VERSION}) -endif() - -if(NOT ${BGFX_CONFIG_DEFAULT_MAX_ENCODERS} STREQUAL "") - target_compile_definitions( - bgfx - PUBLIC - "BGFX_CONFIG_DEFAULT_MAX_ENCODERS=$,${BGFX_CONFIG_DEFAULT_MAX_ENCODERS},1>" - ) -endif() - -if(BGFX_WITH_WAYLAND) - target_compile_definitions(bgfx PRIVATE "WL_EGL_PLATFORM=1") - target_link_libraries(bgfx PRIVATE wayland-egl) -endif() - -set(BGFX_CONFIG_OPTIONS "") -list( - APPEND - BGFX_CONFIG_OPTIONS - "BGFX_CONFIG_MAX_DRAW_CALLS" - "BGFX_CONFIG_MAX_VIEWS" - "BGFX_CONFIG_MAX_FRAME_BUFFERS" - "BGFX_CONFIG_MAX_VERTEX_LAYOUTS" - "BGFX_CONFIG_MAX_VERTEX_BUFFERS" - "BGFX_CONFIG_MAX_DYNAMIC_VERTEX_BUFFERS" - "BGFX_CONFIG_MAX_INDEX_BUFFERS" - "BGFX_CONFIG_MAX_DYNAMIC_INDEX_BUFFERS" - "BGFX_CONFIG_MAX_TEXTURES" - "BGFX_CONFIG_MAX_TEXTURE_SAMPLERS" - "BGFX_CONFIG_MAX_SHADERS" - "BGFX_CONFIG_SORT_KEY_NUM_BITS_PROGRAM" -) -foreach(BGFX_CONFIG_OPTION IN LISTS BGFX_CONFIG_OPTIONS) - if(NOT ${${BGFX_CONFIG_OPTION}} STREQUAL "") - target_compile_definitions(bgfx PUBLIC "${BGFX_CONFIG_OPTION}=${${BGFX_CONFIG_OPTION}}") - endif() -endforeach() - -# Special Visual Studio Flags -if(MSVC) - target_compile_definitions(bgfx PRIVATE "_CRT_SECURE_NO_WARNINGS") -endif() - -# Add debug config required in bx headers since bx is private -target_compile_definitions( - bgfx - PUBLIC - "BX_CONFIG_DEBUG=$,$>" - "BGFX_CONFIG_DEBUG_ANNOTATION=$>,$,$>>" - "BGFX_CONFIG_MULTITHREADED=$" -) - -# directx-headers -set(DIRECTX_HEADERS) -if(UNIX - AND NOT APPLE - AND NOT EMSCRIPTEN - AND NOT ANDROID -) # Only Linux - set(DIRECTX_HEADERS - ${BGFX_DIR}/3rdparty/directx-headers/include/directx ${BGFX_DIR}/3rdparty/directx-headers/include - ${BGFX_DIR}/3rdparty/directx-headers/include/wsl/stubs - ) -elseif(WIN32) # Only Windows - set(DIRECTX_HEADERS ${BGFX_DIR}/3rdparty/directx-headers/include/directx - ${BGFX_DIR}/3rdparty/directx-headers/include - ) -endif() - -# Includes -target_include_directories( - bgfx PRIVATE ${DIRECTX_HEADERS} ${BGFX_DIR}/3rdparty ${BGFX_DIR}/3rdparty/khronos - PUBLIC $ $ -) - -# bgfx depends on bx and bimg -target_link_libraries(bgfx PRIVATE bx bimg) - -# Frameworks required on iOS, tvOS and macOS -if(${CMAKE_SYSTEM_NAME} MATCHES iOS|tvOS) - target_link_libraries( - bgfx - PUBLIC - "-framework OpenGLES -framework Metal -framework UIKit -framework CoreGraphics -framework QuartzCore -framework IOKit -framework CoreFoundation" - ) -elseif(APPLE) - find_library(COCOA_LIBRARY Cocoa) - find_library(METAL_LIBRARY Metal) - find_library(QUARTZCORE_LIBRARY QuartzCore) - find_library(IOKIT_LIBRARY IOKit) - find_library(COREFOUNDATION_LIBRARY CoreFoundation) - mark_as_advanced(COCOA_LIBRARY) - mark_as_advanced(METAL_LIBRARY) - mark_as_advanced(QUARTZCORE_LIBRARY) - mark_as_advanced(IOKIT_LIBRARY) - mark_as_advanced(COREFOUNDATION_LIBRARY) - target_link_libraries( - bgfx PUBLIC ${COCOA_LIBRARY} ${METAL_LIBRARY} ${QUARTZCORE_LIBRARY} ${IOKIT_LIBRARY} ${COREFOUNDATION_LIBRARY} - ) -endif() - -if(UNIX - AND NOT APPLE - AND NOT EMSCRIPTEN - AND NOT ANDROID -) - find_package(X11 REQUIRED) - find_package(OpenGL REQUIRED) - #The following commented libraries are linked by bx - #find_package(Threads REQUIRED) - #find_library(LIBRT_LIBRARIES rt) - #find_library(LIBDL_LIBRARIES dl) - target_link_libraries(bgfx PUBLIC ${X11_LIBRARIES} ${OPENGL_LIBRARIES}) -endif() - -# Exclude glx context on non-unix -if(NOT UNIX OR APPLE) - set_source_files_properties(${BGFX_DIR}/src/glcontext_glx.cpp PROPERTIES HEADER_FILE_ONLY ON) -endif() - -# Put in a "bgfx" folder in Visual Studio -set_target_properties(bgfx PROPERTIES FOLDER "bgfx") - -# in Xcode we need to specify these files as objective-c++ (instead of renaming to .mm) -if(XCODE) - set_source_files_properties( - ${BGFX_DIR}/src/renderer_vk.cpp - ${BGFX_DIR}/src/renderer_webgpu.cpp - PROPERTIES - LANGUAGE OBJCXX - XCODE_EXPLICIT_FILE_TYPE sourcecode.cpp.objcpp - ) -endif() - -if(BGFX_INSTALL) - install( - TARGETS bgfx - EXPORT "${TARGETS_EXPORT_NAME}" - LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" - ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" - RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" - INCLUDES - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" - ) - - install(DIRECTORY ${BGFX_DIR}/include/bgfx DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") - - # header required for shader compilation - install(FILES ${BGFX_DIR}/src/bgfx_shader.sh ${BGFX_DIR}/src/bgfx_compute.sh - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/bgfx" - ) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/examples.cmake b/Engine/cpp/ThirdParty/cmake/bgfx/examples.cmake deleted file mode 100644 index a4f4f3cf..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/examples.cmake +++ /dev/null @@ -1,365 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -include(CMakeParseArguments) - -include(${CMAKE_CURRENT_LIST_DIR}/util/ConfigureDebugging.cmake) - -include(${CMAKE_CURRENT_LIST_DIR}/../bgfxToolUtils.cmake) - -function(add_bgfx_shader FILE FOLDER) - get_filename_component(FILENAME "${FILE}" NAME_WE) - string(SUBSTRING "${FILENAME}" 0 2 TYPE) - if("${TYPE}" STREQUAL "fs") - set(TYPE "FRAGMENT") - elseif("${TYPE}" STREQUAL "vs") - set(TYPE "VERTEX") - elseif("${TYPE}" STREQUAL "cs") - set(TYPE "COMPUTE") - else() - set(TYPE "") - endif() - - if(NOT "${TYPE}" STREQUAL "") - set(COMMON FILE ${FILE} ${TYPE} INCLUDES ${BGFX_DIR}/src) - set(OUTPUTS "") - set(OUTPUTS_PRETTY "") - set(OUTPUT_FILES "") - set(COMMANDS "") - - if(WIN32) - # dx11 - set(DX11_OUTPUT ${BGFX_DIR}/examples/runtime/shaders/dx11/${FILENAME}.bin) - if(NOT "${TYPE}" STREQUAL "COMPUTE") - _bgfx_shaderc_parse( - DX11 ${COMMON} WINDOWS - PROFILE s_5_0 - O 3 - OUTPUT ${DX11_OUTPUT} - ) - else() - _bgfx_shaderc_parse( - DX11 ${COMMON} WINDOWS - PROFILE s_5_0 - O 1 - OUTPUT ${DX11_OUTPUT} - ) - endif() - list(APPEND OUTPUTS "DX11") - set(OUTPUTS_PRETTY "${OUTPUTS_PRETTY}DX11, ") - endif() - - if(APPLE) - # metal - set(METAL_OUTPUT ${BGFX_DIR}/examples/runtime/shaders/metal/${FILENAME}.bin) - _bgfx_shaderc_parse(METAL ${COMMON} OSX PROFILE metal OUTPUT ${METAL_OUTPUT}) - list(APPEND OUTPUTS "METAL") - set(OUTPUTS_PRETTY "${OUTPUTS_PRETTY}Metal, ") - endif() - - # essl - if(NOT "${TYPE}" STREQUAL "COMPUTE") - set(ESSL_OUTPUT ${BGFX_DIR}/examples/runtime/shaders/essl/${FILENAME}.bin) - _bgfx_shaderc_parse(ESSL ${COMMON} ANDROID PROFILE 100_es OUTPUT ${ESSL_OUTPUT}) - list(APPEND OUTPUTS "ESSL") - set(OUTPUTS_PRETTY "${OUTPUTS_PRETTY}ESSL, ") - endif() - - # glsl - set(GLSL_OUTPUT ${BGFX_DIR}/examples/runtime/shaders/glsl/${FILENAME}.bin) - if(NOT "${TYPE}" STREQUAL "COMPUTE") - _bgfx_shaderc_parse(GLSL ${COMMON} LINUX PROFILE 140 OUTPUT ${GLSL_OUTPUT}) - else() - _bgfx_shaderc_parse(GLSL ${COMMON} LINUX PROFILE 430 OUTPUT ${GLSL_OUTPUT}) - endif() - list(APPEND OUTPUTS "GLSL") - set(OUTPUTS_PRETTY "${OUTPUTS_PRETTY}GLSL, ") - - # spirv - if(NOT "${TYPE}" STREQUAL "COMPUTE") - set(SPIRV_OUTPUT ${BGFX_DIR}/examples/runtime/shaders/spirv/${FILENAME}.bin) - _bgfx_shaderc_parse(SPIRV ${COMMON} LINUX PROFILE spirv OUTPUT ${SPIRV_OUTPUT}) - list(APPEND OUTPUTS "SPIRV") - set(OUTPUTS_PRETTY "${OUTPUTS_PRETTY}SPIRV, ") - endif() - - # wgsl - set(WGSL_OUTPUT ${BGFX_DIR}/examples/runtime/shaders/wgsl/${FILENAME}.bin) - _bgfx_shaderc_parse(WGSL ${COMMON} LINUX PROFILE wgsl OUTPUT ${WGSL_OUTPUT}) - list(APPEND OUTPUTS "WGSL") - set(OUTPUTS_PRETTY "${OUTPUTS_PRETTY}WGSL") - - foreach(OUT ${OUTPUTS}) - list(APPEND OUTPUT_FILES ${${OUT}_OUTPUT}) - list(APPEND COMMANDS COMMAND "bgfx::shaderc" ${${OUT}}) - get_filename_component(OUT_DIR ${${OUT}_OUTPUT} DIRECTORY) - file(MAKE_DIRECTORY ${OUT_DIR}) - endforeach() - - file(RELATIVE_PATH PRINT_NAME ${BGFX_DIR}/examples ${FILE}) - add_custom_command( - MAIN_DEPENDENCY ${FILE} OUTPUT ${OUTPUT_FILES} ${COMMANDS} - COMMENT "Compiling shader ${PRINT_NAME} for ${OUTPUTS_PRETTY}" - ) - endif() -endfunction() - -function(add_example ARG_NAME) - # Parse arguments - cmake_parse_arguments(ARG "COMMON" "" "DIRECTORIES;SOURCES" ${ARGN}) - - # Get all source files - list(APPEND ARG_DIRECTORIES "${BGFX_DIR}/examples/${ARG_NAME}") - set(SOURCES "") - set(SHADERS "") - foreach(DIR ${ARG_DIRECTORIES}) - if(APPLE) - file(GLOB GLOB_SOURCES ${DIR}/*.mm) - list(APPEND SOURCES ${GLOB_SOURCES}) - endif() - file(GLOB GLOB_SOURCES ${DIR}/*.c ${DIR}/*.cpp ${DIR}/*.h ${DIR}/*.sc) - list(APPEND SOURCES ${GLOB_SOURCES}) - file(GLOB GLOB_SHADERS ${DIR}/*.sc) - list(APPEND SHADERS ${GLOB_SHADERS}) - endforeach() - - # Add target - if(ARG_COMMON) - add_library( - example-${ARG_NAME} STATIC EXCLUDE_FROM_ALL ${SOURCES} ${DEAR_IMGUI_SOURCES} ${MESHOPTIMIZER_SOURCES} - ) - target_include_directories( - example-${ARG_NAME} PUBLIC ${BGFX_DIR}/examples/common ${DEAR_IMGUI_INCLUDE_DIR} - ${MESHOPTIMIZER_INCLUDE_DIR} - ) - target_link_libraries( - example-${ARG_NAME} PUBLIC bgfx bx bimg bimg_decode ${DEAR_IMGUI_LIBRARIES} ${MESHOPTIMIZER_LIBRARIES} - ) - - if(BGFX_WITH_WAYLAND) - target_compile_definitions(example-${ARG_NAME} PUBLIC ENTRY_CONFIG_USE_WAYLAND=1) - endif() - - if(BGFX_WITH_GLFW) - find_package(glfw3 REQUIRED) - target_link_libraries(example-${ARG_NAME} PUBLIC glfw) - target_compile_definitions(example-${ARG_NAME} PUBLIC ENTRY_CONFIG_USE_GLFW=1) - elseif(BGFX_WITH_SDL) - find_package(SDL2 REQUIRED) - target_link_libraries(example-${ARG_NAME} PUBLIC ${SDL2_LIBRARIES}) - target_compile_definitions(example-${ARG_NAME} PUBLIC ENTRY_CONFIG_USE_SDL=1) - elseif(UNIX AND NOT APPLE AND NOT ANDROID) - target_link_libraries(example-${ARG_NAME} PUBLIC X11) - endif() - - if(ANDROID) - target_include_directories(example-${ARG_NAME} PRIVATE ${BGFX_DIR}/3rdparty/native_app_glue) - target_link_libraries(example-${ARG_NAME} INTERFACE android EGL GLESv2) - endif() - - if(BGFX_BUILD_EXAMPLES) - if(IOS OR WIN32) - # on iOS we need to build a bundle so have to copy the data rather than symlink - # and on windows we can't create symlinks - add_custom_command( - TARGET example-${ARG_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${BGFX_DIR}/examples/runtime/ - $ - ) - else() - # For everything else symlink some folders into our output directory - add_custom_command( - TARGET example-${ARG_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink ${BGFX_DIR}/examples/runtime/font - $/font - ) - add_custom_command( - TARGET example-${ARG_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink ${BGFX_DIR}/examples/runtime/images - $/images - ) - add_custom_command( - TARGET example-${ARG_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink ${BGFX_DIR}/examples/runtime/meshes - $/meshes - ) - add_custom_command( - TARGET example-${ARG_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink ${BGFX_DIR}/examples/runtime/shaders - $/shaders - ) - add_custom_command( - TARGET example-${ARG_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink ${BGFX_DIR}/examples/runtime/text - $/text - ) - add_custom_command( - TARGET example-${ARG_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E create_symlink ${BGFX_DIR}/examples/runtime/textures - $/textures - ) - endif() - endif() - - else() - if(ANDROID) - add_library(example-${ARG_NAME} SHARED ${SOURCES}) - else() - add_executable(example-${ARG_NAME} WIN32 ${SOURCES}) - endif() - if(NOT BGFX_INSTALL_EXAMPLES) - set_property(TARGET example-${ARG_NAME} PROPERTY EXCLUDE_FROM_ALL ON) - endif() - target_link_libraries(example-${ARG_NAME} PUBLIC example-common) - configure_debugging(example-${ARG_NAME} WORKING_DIR ${BGFX_DIR}/examples/runtime) - if(MSVC) - set_target_properties(example-${ARG_NAME} PROPERTIES LINK_FLAGS "/ENTRY:\"mainCRTStartup\"") - endif() - if(BGFX_CUSTOM_TARGETS) - add_dependencies(examples example-${ARG_NAME}) - endif() - if(IOS) - set_target_properties( - example-${ARG_NAME} - PROPERTIES MACOSX_BUNDLE ON - MACOSX_BUNDLE_GUI_IDENTIFIER example-${ARG_NAME} - MACOSX_BUNDLE_BUNDLE_VERSION 0 - MACOSX_BUNDLE_SHORT_VERSION_STRING 0 - XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "iPhone Developer" - ) - endif() - endif() - target_compile_definitions( - example-${ARG_NAME} - PRIVATE "-D_CRT_SECURE_NO_WARNINGS" # - "-D__STDC_FORMAT_MACROS" # - "-DENTRY_CONFIG_IMPLEMENT_MAIN=1" # - ) - - # Configure shaders - if(NOT ARG_COMMON - AND NOT IOS - AND NOT EMSCRIPTEN - AND NOT ANDROID - ) - foreach(SHADER ${SHADERS}) - add_bgfx_shader(${SHADER} ${ARG_NAME}) - endforeach() - source_group("Shader Files" FILES ${SHADERS}) - endif() - - if(NOT ARG_COMMON AND EMSCRIPTEN) - set_target_properties( - example-${ARG_NAME} - PROPERTIES LINK_FLAGS - "-s PRECISE_F32=1 -s TOTAL_MEMORY=268435456 -s ENVIRONMENT=web --memory-init-file 1 --emrun" - SUFFIX ".html" - ) - endif() - - # Directory name - set_target_properties(example-${ARG_NAME} PROPERTIES FOLDER "bgfx/examples") -endfunction() - -# Build all examples target -if(BGFX_CUSTOM_TARGETS) - add_custom_target(examples) - set_target_properties(examples PROPERTIES FOLDER "bgfx/examples") -endif() - -# Add common library for examples -if(BGFX_BUILD_EXAMPLE_COMMON) - add_example( - common - COMMON - DIRECTORIES - ${BGFX_DIR}/examples/common/debugdraw - ${BGFX_DIR}/examples/common/entry - ${BGFX_DIR}/examples/common/font - ${BGFX_DIR}/examples/common/imgui - ${BGFX_DIR}/examples/common/nanovg - ${BGFX_DIR}/examples/common/ps - ) -endif() - -# Only add examples if set, otherwise we still need exmaples common for tools -if(BGFX_BUILD_EXAMPLES) - # Add examples - set(BGFX_EXAMPLES - 00-helloworld - 01-cubes - 02-metaballs - 03-raymarch - 04-mesh - 05-instancing - 06-bump - 07-callback - 08-update - 09-hdr - 10-font - 11-fontsdf - 12-lod - 13-stencil - 14-shadowvolumes - 15-shadowmaps-simple - 16-shadowmaps - 17-drawstress - 18-ibl - 19-oit - 20-nanovg - # 21-deferred - 22-windows - 23-vectordisplay - 24-nbody - 25-c99 - 26-occlusion - 27-terrain - 28-wireframe - 29-debugdraw - 30-picking - 31-rsm - 32-particles - 33-pom - 34-mvs - 35-dynamic - 36-sky - # 37-gpudrivenrendering - 38-bloom - 39-assao - 40-svt - # 41-tess - 42-bunnylod - 43-denoise - 44-sss - 45-bokeh - 46-fsr - 47-pixelformats - 48-drawindirect - 49-hextile - 50-headless - 51-gpufont - ) - - foreach(EXAMPLE ${BGFX_EXAMPLES}) - add_example(${EXAMPLE}) - endforeach() - - if(BGFX_INSTALL_EXAMPLES) - install(DIRECTORY ${BGFX_DIR}/examples/runtime/ DESTINATION examples) - foreach(EXAMPLE ${BGFX_EXAMPLES}) - install(TARGETS example-${EXAMPLE} DESTINATION examples) - endforeach() - endif() -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/generated/bounds.cpp.in b/Engine/cpp/ThirdParty/cmake/bgfx/generated/bounds.cpp.in deleted file mode 100644 index 1e3029e7..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/generated/bounds.cpp.in +++ /dev/null @@ -1 +0,0 @@ -#include "@BGFX_DIR@/examples/common/bounds.cpp" diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/generated/shader.cpp.in b/Engine/cpp/ThirdParty/cmake/bgfx/generated/shader.cpp.in deleted file mode 100644 index bbfce0d1..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/generated/shader.cpp.in +++ /dev/null @@ -1,3 +0,0 @@ -#include "@BGFX_DIR@/src/shader.cpp" -#include "@BGFX_DIR@/src/shader_dxbc.cpp" -#include "@BGFX_DIR@/src/shader_spirv.cpp" diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/generated/vertexlayout.cpp.in b/Engine/cpp/ThirdParty/cmake/bgfx/generated/vertexlayout.cpp.in deleted file mode 100644 index a007bb59..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/generated/vertexlayout.cpp.in +++ /dev/null @@ -1 +0,0 @@ -#include "@BGFX_DIR@/src/vertexlayout.cpp" diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/geometryc.cmake b/Engine/cpp/ThirdParty/cmake/bgfx/geometryc.cmake deleted file mode 100644 index e6efcd58..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/geometryc.cmake +++ /dev/null @@ -1,43 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Grab the geometryc source files -file( - GLOB_RECURSE - GEOMETRYC_SOURCES # - ${BGFX_DIR}/tools/geometryc/*.cpp # - ${BGFX_DIR}/tools/geometryc/*.h # - # - ${MESHOPTIMIZER_SOURCES} -) -add_executable(geometryc ${GEOMETRYC_SOURCES}) - -target_include_directories(geometryc PRIVATE ${MESHOPTIMIZER_INCLUDE_DIR}) -target_link_libraries(geometryc PRIVATE bx bgfx-vertexlayout ${MESHOPTIMIZER_LIBRARIES}) -target_compile_definitions(geometryc PRIVATE "-D_CRT_SECURE_NO_WARNINGS") -set_target_properties( - geometryc PROPERTIES FOLDER "bgfx/tools" # - OUTPUT_NAME ${BGFX_TOOLS_PREFIX}geometryc # -) - -if(BGFX_BUILD_TOOLS_GEOMETRY) - add_executable(bgfx::geometryc ALIAS geometryc) - if(BGFX_CUSTOM_TARGETS) - add_dependencies(tools geometryc) - endif() -endif() - -if(IOS) - set_target_properties(geometryc PROPERTIES MACOSX_BUNDLE ON MACOSX_BUNDLE_GUI_IDENTIFIER geometryc) -endif() - -if(BGFX_INSTALL) - install(TARGETS geometryc EXPORT "${TARGETS_EXPORT_NAME}" DESTINATION "${CMAKE_INSTALL_BINDIR}") -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/geometryv.cmake b/Engine/cpp/ThirdParty/cmake/bgfx/geometryv.cmake deleted file mode 100644 index 764ff7c4..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/geometryv.cmake +++ /dev/null @@ -1,42 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Grab the geometryv source files -file(GLOB_RECURSE GEOMETRYV_SOURCES # - ${BGFX_DIR}/tools/geometryv/* -) - -if(ANDROID) - add_library(geometryv SHARED ${GEOMETRYV_SOURCES}) -else() - add_executable(geometryv ${GEOMETRYV_SOURCES}) -endif() - -target_link_libraries(geometryv PRIVATE example-common) -set_target_properties( - geometryv PROPERTIES FOLDER "bgfx/tools" # - OUTPUT_NAME ${BGFX_TOOLS_PREFIX}geometryv # -) - -if(BGFX_BUILD_TOOLS_GEOMETRY AND BGFX_CUSTOM_TARGETS) - add_dependencies(tools geometryv) -endif() - -if(ANDROID) - set_property(TARGET geometryv PROPERTY PREFIX "") -elseif(EMSCRIPTEN) - target_link_options(geometryv PRIVATE -sMAX_WEBGL_VERSION=2) -elseif(IOS) - set_target_properties(geometryv PROPERTIES MACOSX_BUNDLE ON MACOSX_BUNDLE_GUI_IDENTIFIER geometryv) -endif() - -if(BGFX_INSTALL) - install(TARGETS geometryv EXPORT "${TARGETS_EXPORT_NAME}" DESTINATION "${CMAKE_INSTALL_BINDIR}") -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/shaderc.cmake b/Engine/cpp/ThirdParty/cmake/bgfx/shaderc.cmake deleted file mode 100644 index 7ecf472f..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/shaderc.cmake +++ /dev/null @@ -1,95 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Grab the shaderc source files -file( - GLOB - SHADERC_SOURCES # - ${BGFX_DIR}/tools/shaderc/*.cpp # - ${BGFX_DIR}/tools/shaderc/*.h # - ${BGFX_DIR}/src/shader* # -) - -add_executable(shaderc ${SHADERC_SOURCES}) - -target_link_libraries( - shaderc - PRIVATE bx - bimg - bgfx-vertexlayout - fcpp - glslang - glsl-optimizer - spirv-opt - spirv-cross - webgpu - tint -) - -target_include_directories( - shaderc - PRIVATE ${BGFX_DIR}/3rdparty/dawn - ${BGFX_DIR}/3rdparty/dawn/src -) - -set(DXCOMPILER_RUNTIME) -if(UNIX - AND NOT APPLE - AND NOT EMSCRIPTEN - AND NOT ANDROID -) - target_include_directories( - shaderc - PRIVATE ${BGFX_DIR}/3rdparty/directx-headers/include/directx - ${BGFX_DIR}/3rdparty/directx-headers/include - ${BGFX_DIR}/3rdparty/directx-headers/include/wsl/stubs - ) - set(DXCOMPILER_RUNTIME ${BGFX_DIR}/tools/bin/linux/libdxcompiler.so) -elseif(WIN32) - set(DXCOMPILER_RUNTIME ${BGFX_DIR}/tools/bin/windows/dxcompiler.dll) -endif() - -if(BGFX_AMALGAMATED) - target_link_libraries(shaderc PRIVATE bgfx-shader) -endif() - -set_target_properties( - shaderc PROPERTIES FOLDER "bgfx/tools" # - OUTPUT_NAME ${BGFX_TOOLS_PREFIX}shaderc # -) - -if(BGFX_BUILD_TOOLS_SHADER) - add_executable(bgfx::shaderc ALIAS shaderc) - if(BGFX_CUSTOM_TARGETS) - add_dependencies(tools shaderc) - endif() -endif() - -if(ANDROID) - target_link_libraries(shaderc PRIVATE log) -elseif(IOS) - set_target_properties(shaderc PROPERTIES MACOSX_BUNDLE ON MACOSX_BUNDLE_GUI_IDENTIFIER shaderc) -endif() - -if(BGFX_INSTALL) - install(TARGETS shaderc EXPORT "${TARGETS_EXPORT_NAME}" DESTINATION "${CMAKE_INSTALL_BINDIR}") -endif() - -# DXIL compiler will be dynamically loaded at runtime - no need -# to link, just install the needed binaries alongside shaderc.exe -if(DXCOMPILER_RUNTIME) - add_custom_command( - TARGET shaderc POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DXCOMPILER_RUNTIME} $ - ) - if(BGFX_INSTALL) - install(FILES ${DXCOMPILER_RUNTIME} DESTINATION "${CMAKE_INSTALL_BINDIR}") - endif() -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/shared.cmake b/Engine/cpp/ThirdParty/cmake/bgfx/shared.cmake deleted file mode 100644 index b8decafb..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/shared.cmake +++ /dev/null @@ -1,29 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -add_library(bgfx-vertexlayout INTERFACE) -configure_file( - ${CMAKE_CURRENT_SOURCE_DIR}/generated/vertexlayout.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/generated/vertexlayout.cpp -) -target_sources(bgfx-vertexlayout INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/generated/vertexlayout.cpp) -target_include_directories(bgfx-vertexlayout INTERFACE ${BGFX_DIR}/include) - -add_library(bgfx-shader INTERFACE) - -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/generated/shader.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/generated/shader.cpp) -target_sources(bgfx-shader INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/generated/shader.cpp) -target_include_directories(bgfx-shader INTERFACE ${BGFX_DIR}/include) - -# Frameworks required on OS X -if(${CMAKE_SYSTEM_NAME} MATCHES Darwin) - find_library(COCOA_LIBRARY Cocoa) - mark_as_advanced(COCOA_LIBRARY) - target_link_libraries(bgfx-vertexlayout INTERFACE ${COCOA_LIBRARY}) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/texturev.cmake b/Engine/cpp/ThirdParty/cmake/bgfx/texturev.cmake deleted file mode 100644 index a45447bd..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/texturev.cmake +++ /dev/null @@ -1,42 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Grab the texturev source files -file(GLOB_RECURSE TEXTUREV_SOURCES # - ${BGFX_DIR}/tools/texturev/* -) - -if(ANDROID) - add_library(texturev SHARED ${TEXTUREV_SOURCES}) -else() - add_executable(texturev ${TEXTUREV_SOURCES}) -endif() - -target_link_libraries(texturev PRIVATE example-common) -set_target_properties( - texturev PROPERTIES FOLDER "bgfx/tools" # - OUTPUT_NAME ${BGFX_TOOLS_PREFIX}texturev # -) - -if(BGFX_BUILD_TOOLS_TEXTURE AND BGFX_CUSTOM_TARGETS) - add_dependencies(tools texturev) -endif() - -if(ANDROID) - set_property(TARGET texturev PROPERTY PREFIX "") -elseif(EMSCRIPTEN) - target_link_options(texturev PRIVATE -sMAX_WEBGL_VERSION=2) -elseif(IOS) - set_target_properties(texturev PROPERTIES MACOSX_BUNDLE ON MACOSX_BUNDLE_GUI_IDENTIFIER texturev) -endif() - -if(BGFX_INSTALL) - install(TARGETS texturev EXPORT "${TARGETS_EXPORT_NAME}" DESTINATION "${CMAKE_INSTALL_BINDIR}") -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bgfx/util/ConfigureDebugging.cmake b/Engine/cpp/ThirdParty/cmake/bgfx/util/ConfigureDebugging.cmake deleted file mode 100644 index 2df4fef7..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfx/util/ConfigureDebugging.cmake +++ /dev/null @@ -1,157 +0,0 @@ -# ConfigureDebugging.cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# configure_debugging( TARGET [OPTIONS...] ) -# -# Configures the debugging settings in visual studio. -# Results in a no-op on non visual studio builds. -# Must be called in the same cmake file as the add_executable command. -# -# See OPTIONS variable in the function for supported user settings. -# See CONFIGS variable in the function for supported cmake configurations. -# See PROCESSORS variable in the function for supported architecture configurations. -# -# All variables can be set with one of the following formats: -# -# (OPTION) -# (OPTION)_(CONFIG) -# (OPTION)_(CONFIG)_(ARCH) -# (OPTION)_(ARCH) -# -# So, some examples (variables should be all caps): -# -# WORKING_DIR -# WORKING_DIR_X64 -# WORKING_DIR_RELEASE_WIN32 -# WORKING_DIR_X64 -# -# An example of a full command: -# -# configure_debugging(target COMMAND "node.exe" COMMAND_X64 "node64.exe" WORKING_DIR ${CMAKE_SOURCE_DIR} DEBUGGER_ENV "PATH=%PATH%\\;$(ProjectDir)") - -include(CMakeParseArguments) - -function(configure_debugging ARG_TARGET) - if(MSVC) - # Visual Studio Options - set(OPTIONS - WORKING_DIR - LocalDebuggerWorkingDirectory - DEBUGGER_ENV - LocalDebuggerEnvironment - COMMAND - LocalDebuggerCommand - COMMAND_ARGS - LocalDebuggerCommandArguments - ) - - # Valid Configurations - set(CONFIGS Debug Release MinSizeRel RelWithDebInfo) - - # Processors - set(PROCESSORS Win32 x64) - - # Begin hackery - if(${CMAKE_SIZEOF_VOID_P} EQUAL 8) - set(ACTIVE_PROCESSOR "x64") - else() - set(ACTIVE_PROCESSOR "Win32") - endif() - # Fix issues with semicolons, thx cmake - foreach(ARG ${ARGN}) - string(REPLACE ";" "\\\\\\\\\\\\\\;" RES "${ARG}") - list(APPEND ARGS "${RES}") - endforeach() - # Build options for cmake_parse_arguments, result is ONE_ARG variable - set(ODD ON) - foreach(OPTION ${OPTIONS}) - if(ODD) - set(ARG ${OPTION}) - list(APPEND ONE_ARG ${ARG}) - foreach(CONFIG ${CONFIGS}) - string(TOUPPER ${CONFIG} CONFIG) - list(APPEND ONE_ARG ${ARG}_${CONFIG}) - foreach(PROCESSOR ${PROCESSORS}) - string(TOUPPER ${PROCESSOR} PROCESSOR) - list(APPEND ONE_ARG ${ARG}_${CONFIG}_${PROCESSOR}) - endforeach() - endforeach() - foreach(PROCESSOR ${PROCESSORS}) - string(TOUPPER ${PROCESSOR} PROCESSOR) - list(APPEND ONE_ARG ${ARG}_${PROCESSOR}) - endforeach() - set(ODD OFF) - else() - set(ODD ON) - endif() - endforeach() - cmake_parse_arguments(ARG "" "${ONE_ARG}" "" ${ARGS}) - # Parse options, fills in all variables of format ARG_(ARG)_(CONFIG)_(PROCESSOR), for example ARG_WORKING_DIR_DEBUG_X64 - set(ODD ON) - foreach(OPTION ${OPTIONS}) - if(ODD) - set(ARG ${OPTION}) - foreach(CONFIG ${CONFIGS}) - string(TOUPPER ${CONFIG} CONFIG_CAP) - if("${ARG_${ARG}_${CONFIG_CAP}}" STREQUAL "") - set(ARG_${ARG}_${CONFIG_CAP} ${ARG_${ARG}}) - endif() - foreach(PROCESSOR ${PROCESSORS}) - string(TOUPPER ${PROCESSOR} PROCESSOR_CAP) - if("${ARG_${ARG}_${CONFIG_CAP}_${PROCESSOR_CAP}}" STREQUAL "") - if("${ARG_${ARG}_${PROCESSOR_CAP}}" STREQUAL "") - set(ARG_${ARG}_${CONFIG_CAP}_${PROCESSOR_CAP} ${ARG_${ARG}_${CONFIG_CAP}}) - else() - set(ARG_${ARG}_${CONFIG_CAP}_${PROCESSOR_CAP} ${ARG_${ARG}_${PROCESSOR_CAP}}) - endif() - endif() - if(NOT "${ARG_${ARG}_${CONFIG_CAP}_${PROCESSOR_CAP}}" STREQUAL "") - - endif() - endforeach() - endforeach() - set(ODD OFF) - else() - set(ODD ON) - endif() - endforeach() - # Create string to put in proj.vcxproj.user file - set(RESULT - "\n" - ) - foreach(CONFIG ${CONFIGS}) - string(TOUPPER ${CONFIG} CONFIG_CAPS) - foreach(PROCESSOR ${PROCESSORS}) - if("${PROCESSOR}" STREQUAL "${ACTIVE_PROCESSOR}") - string(TOUPPER ${PROCESSOR} PROCESSOR_CAPS) - set(RESULT - "${RESULT}\n " - ) - set(ODD ON) - foreach(OPTION ${OPTIONS}) - if(ODD) - set(ARG ${OPTION}) - set(ODD OFF) - else() - set(VALUE ${ARG_${ARG}_${CONFIG_CAPS}_${PROCESSOR_CAPS}}) - if(NOT "${VALUE}" STREQUAL "") - set(RESULT "${RESULT}\n <${OPTION}>${VALUE}") - endif() - set(ODD ON) - endif() - endforeach() - set(RESULT "${RESULT}\n ") - endif() - endforeach() - endforeach() - set(RESULT "${RESULT}\n") - file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/${ARG_TARGET}.vcxproj.user "${RESULT}") - endif() -endfunction() diff --git a/Engine/cpp/ThirdParty/cmake/bgfxToolUtils.cmake b/Engine/cpp/ThirdParty/cmake/bgfxToolUtils.cmake deleted file mode 100644 index 7f504451..00000000 --- a/Engine/cpp/ThirdParty/cmake/bgfxToolUtils.cmake +++ /dev/null @@ -1,677 +0,0 @@ -# If bgfx.cmake was compiled without tools or cross compiled without host having tools, -# then don't provide helper functions -if(TARGET bgfx::bin2c) - # _bgfx_bin2c_parse( - # INPUT_FILE filename - # OUTPUT_FILE filename - # ARRAY_NAME name - # ) - # Usage: bin2c -f -o -n - function(_bgfx_bin2c_parse ARG_OUT) - set(options "") - set(oneValueArgs INPUT_FILE;OUTPUT_FILE;ARRAY_NAME) - set(multiValueArgs "") - cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}") - set(CLI "") - - # -f - if(ARG_INPUT_FILE) - list(APPEND CLI "-f" "${ARG_INPUT_FILE}") - else() - message(SEND_ERROR "Call to _bgfx_bin2c_parse() must have an INPUT_FILE") - endif() - - # -o - if(ARG_OUTPUT_FILE) - list(APPEND CLI "-o" "${ARG_OUTPUT_FILE}") - else() - message(SEND_ERROR "Call to _bgfx_bin2c_parse() must have an OUTPUT_FILE") - endif() - - # -n - if(ARG_ARRAY_NAME) - list(APPEND CLI "-n" "${ARG_ARRAY_NAME}") - else() - message(SEND_ERROR "Call to _bgfx_bin2c_parse() must have an ARRAY_NAME") - endif() - - set(${ARG_OUT} ${CLI} PARENT_SCOPE) - endfunction() - - # bgfx_compile_binary_to_header( - # INPUT_FILE filename - # OUTPUT_FILE filename - # ARRAY_NAME name - # ) - # - function(bgfx_compile_binary_to_header) - set(options "") - set(oneValueArgs INPUT_FILE;OUTPUT_FILE;ARRAY_NAME) - set(multiValueArgs "") - cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}") - _bgfx_bin2c_parse( - CLI - INPUT_FILE ${ARG_INPUT_FILE} - OUTPUT_FILE ${ARG_OUTPUT_FILE} - ARRAY_NAME ${ARG_ARRAY_NAME} - ) - add_custom_command( - OUTPUT ${ARG_OUTPUT_FILE} # - COMMAND bgfx::bin2c ${CLI} # - MAIN_DEPENDENCY ${ARG_INPUT_FILE} # - ) - endfunction() -endif() - -# If bgfx.cmake was compiled without tools or cross compiled without host having tools, -# then don't provide helper functions -if(TARGET bgfx::texturec) - # _bgfx_texturec_parse( - # FILE filename - # OUTPUT filename - # [FORMAT format] - # [QUALITY default|fastest|highest] - # [MIPS] - # [MIPSKIP N] - # [NORMALMAP] - # [EQUIRECT] - # [STRIP] - # [SDF] - # [REF alpha] - # [IQA] - # [PMA] - # [LINEAR] - # [MAX max size] - # [RADIANCE model] - # [AS extension] - # ) - function(_bgfx_texturec_parse ARG_OUT) - cmake_parse_arguments( - ARG # - "MIPS;NORMALMAP;EQUIRECT;STRIP;SDF;IQA;PMA;LINEAR" # - "FILE;OUTPUT;FORMAT;QUALITY;MIPSKIP;REF;MAX;RADIANCE;AS" # - "" # - ${ARGN} # - ) - set(CLI "") - - # -f - if(ARG_FILE) - list(APPEND CLI "-f" "${ARG_FILE}") - endif() - - # -o - if(ARG_OUTPUT) - list(APPEND CLI "-o" "${ARG_OUTPUT}") - endif() - - # -t - if(ARG_FORMAT) - list(APPEND CLI "-t" "${ARG_FORMAT}") - endif() - - # -q - if(ARG_QUALITY) - list(APPEND CLI "-q" "${ARG_QUALITY}") - endif() - - # --mips - if(ARG_MIPS) - list(APPEND CLI "--mips") - endif() - - # --mipskip - if(ARG_MIPSKIP) - list(APPEND CLI "--mipskip" "${ARG_MIPSKIP}") - endif() - - # --normalmap - if(ARG_NORMALMAP) - list(APPEND CLI "--normalmap") - endif() - - # --equirect - if(ARG_EQUIRECT) - list(APPEND CLI "--equirect") - endif() - - # --strip - if(ARG_STRIP) - list(APPEND CLI "--strip") - endif() - - # --sdf - if(ARG_SDF) - list(APPEND CLI "--sdf") - endif() - - # --ref - if(ARG_REF) - list(APPEND CLI "--ref" "${ARG_REF}") - endif() - - # --iqa - if(ARG_IQA) - list(APPEND CLI "--iqa") - endif() - - # --pma - if(ARG_PMA) - list(APPEND CLI "--pma") - endif() - - # --linear - if(ARG_LINEAR) - list(APPEND CLI "--linear") - endif() - - # --max - if(ARG_MAX) - list(APPEND CLI "--max" "${ARG_MAX}") - endif() - - # --radiance - if(ARG_RADIANCE) - list(APPEND CLI "--radiance" "${ARG_RADIANCE}") - endif() - - # --as - if(ARG_AS) - list(APPEND CLI "--as" "${ARG_AS}") - endif() - - set(${ARG_OUT} ${CLI} PARENT_SCOPE) - endfunction() - - # bgfx_compile_texture( - # FILE filename - # OUTPUT filename - # [FORMAT format] - # [QUALITY default|fastest|highest] - # [MIPS] - # [MIPSKIP N] - # [NORMALMAP] - # [EQUIRECT] - # [STRIP] - # [SDF] - # [REF alpha] - # [IQA] - # [PMA] - # [LINEAR] - # [MAX max size] - # [RADIANCE model] - # [AS extension] - # ) - # - function(bgfx_compile_texture) - cmake_parse_arguments( - ARG # - "MIPS;NORMALMAP;EQUIRECT;STRIP;SDF;IQA;PMA;LINEAR" # - "FILE;OUTPUT;FORMAT;QUALITY;MIPSKIP;REF;MAX;RADIANCE;AS" # - "" # - ${ARGN} # - ) - _bgfx_texturec_parse(CLI ${ARGV}) - add_custom_command( - OUTPUT ${ARG_OUTPUT} # - COMMAND bgfx::texturec ${CLI} # - MAIN_DEPENDENCY ${ARG_FILE} # - ) - endfunction() -endif() - -# If bgfx.cmake was compiled without tools or cross compiled without host having tools, -# then don't provide helper functions -if(TARGET bgfx::geometryc) - # _bgfx_geometryc_parse( - # FILE filename - # OUTPUT filename - # [SCALE scale] - # [CCW] - # [FLIPV] - # [OBB num steps] - # [PACKNORMAL 0|1] - # [PACKUV 0|1] - # [TANGENT] - # [BARYCENTRIC] - # [COMPRESS] - # [LH_UP_Y|LH_UP_Z|RH_UP_Y|RH_UP_Z] - # ) - function(_bgfx_geometryc_parse ARG_OUT) - cmake_parse_arguments( - ARG # - "CCW;FLIPV;TANGENT;BARYCENTRIC;COMPRESS;LH_UP_Y;LH_UP_Z;RH_UP_Y;RH_UP_Z" # - "FILE;OUTPUT;SCALE;OBB;PACKNORMAL;PACKUV" # - "" # - ${ARGN} # - ) - set(CLI "") - - # -f - if(ARG_FILE) - list(APPEND CLI "-f" "${ARG_FILE}") - endif() - - # -o - if(ARG_OUTPUT) - list(APPEND CLI "-o" "${ARG_OUTPUT}") - endif() - - # -s - if(ARG_SCALE) - list(APPEND CLI "-s" "${ARG_SCALE}") - endif() - - # --cw - if(ARG_QUALITY) - list(APPEND CLI "--cw") - endif() - - # --flipv - if(ARG_FLIPV) - list(APPEND CLI "--flipv") - endif() - - # --obb - if(ARG_OBB) - list(APPEND CLI "--mipskip" "${ARG_OBB}") - endif() - - # --packnormal - if(ARG_PACKNORMAL) - list(APPEND CLI "--packnormal" "${ARG_PACKNORMAL}") - endif() - - # --packuv - if(ARG_PACKUV) - list(APPEND CLI "--packuv" "${ARG_PACKUV}") - endif() - - # --tangent - if(ARG_TANGENT) - list(APPEND CLI "--tangent") - endif() - - # --barycentric - if(ARG_BARYCENTRIC) - list(APPEND CLI "--barycentric") - endif() - - # --compress - if(ARG_REF) - list(APPEND CLI "--compress" "${ARG_COMPRESS}") - endif() - - # --lh-up+y - if(ARG_LH_UP_Y) - list(APPEND CLI "--lh-up+y") - endif() - - # --lh-up+z - if(ARG_LH_UP_Z) - list(APPEND CLI "--lh-up+z") - endif() - - # --rh-up+y - if(ARG_RH_UP_Y) - list(APPEND CLI "--rh-up+y") - endif() - - # --rh-up+z - if(ARG_RH_UP_Z) - list(APPEND CLI "--rh-up+z") - endif() - - set(${ARG_OUT} ${CLI} PARENT_SCOPE) - endfunction() - - # bgfx_compile_geometry( - # FILE filename - # OUTPUT filename - # [SCALE scale] - # [CCW] - # [FLIPV] - # [OBB num steps] - # [PACKNORMAL 0|1] - # [PACKUV 0|1] - # [TANGENT] - # [BARYCENTRIC] - # [COMPRESS] - # [LH_UP_Y|LH_UP_Z|RH_UP_Y|RH_UP_Z] - # ) - # - function(bgfx_compile_geometry) - cmake_parse_arguments( - ARG # - "CCW;FLIPV;TANGENT;BARYCENTRIC;COMPRESS;LH_UP_Y;LH_UP_Z;RH_UP_Y;RH_UP_Z" # - "FILE;OUTPUT;SCALE;OBB;PACKNORMAL;PACKUV" # - "" # - ${ARGN} # - ) - _bgfx_geometryc_parse(CLI ${ARGV}) - add_custom_command( - OUTPUT ${ARG_OUTPUT} # - COMMAND bgfx::geometryc ${CLI} # - MAIN_DEPENDENCY ${ARG_FILE} # - ) - endfunction() -endif() - -# If bgfx.cmake was compiled without tools or cross compiled without host having tools, -# then don't provide helper functions -if(TARGET bgfx::shaderc) - # _bgfx_shaderc_parse( - # FILE filename - # OUTPUT filename - # FRAGMENT|VERTEX|COMPUTE - # ANDROID|ASM_JS|IOS|LINUX|OSX|WINDOWS|ORBIS - # PROFILE profile - # [O 0|1|2|3] - # [VARYINGDEF filename] - # [BIN2C filename] - # [INCLUDES include;include] - # [DEFINES include;include] - # [DEPENDS] - # [PREPROCESS] - # [RAW] - # [VERBOSE] - # [DEBUG] - # [DISASM] - # [WERROR] - # ) - function(_bgfx_shaderc_parse ARG_OUT) - cmake_parse_arguments( - ARG - "DEPENDS;ANDROID;ASM_JS;IOS;LINUX;OSX;WINDOWS;ORBIS;PREPROCESS;RAW;FRAGMENT;VERTEX;COMPUTE;VERBOSE;DEBUG;DISASM;WERROR" - "FILE;OUTPUT;VARYINGDEF;BIN2C;PROFILE;O" - "INCLUDES;DEFINES" - ${ARGN} - ) - set(CLI "") - - # -f - if(ARG_FILE) - list(APPEND CLI "-f" "${ARG_FILE}") - else() - message(SEND_ERROR "Call to _bgfx_shaderc_parse() must have an input file path specified.") - endif() - - # -i - if(ARG_INCLUDES) - foreach(INCLUDE ${ARG_INCLUDES}) - list(APPEND CLI "-i") - list(APPEND CLI "${INCLUDE}") - endforeach() - endif() - - # -o - if(ARG_OUTPUT) - list(APPEND CLI "-o" "${ARG_OUTPUT}") - else() - message(SEND_ERROR "Call to _bgfx_shaderc_parse() must have an output file path specified.") - endif() - - # --bin2c - if(ARG_BIN2C) - list(APPEND CLI "--bin2c" "${ARG_BIN2C}") - endif() - - # --depends - if(ARG_DEPENDS) - list(APPEND CLI "--depends") - endif() - - # --platform - set(PLATFORM "") - set(PLATFORMS "ANDROID;ASM_JS;IOS;LINUX;OSX;WINDOWS;ORBIS") - foreach(P ${PLATFORMS}) - if(ARG_${P}) - if(PLATFORM) - message(SEND_ERROR "Call to _bgfx_shaderc_parse() cannot have both flags ${PLATFORM} and ${P}.") - return() - endif() - set(PLATFORM "${P}") - endif() - endforeach() - if(PLATFORM STREQUAL "") - message(SEND_ERROR "Call to _bgfx_shaderc_parse() must have a platform flag: ${PLATFORMS}") - return() - elseif(PLATFORM STREQUAL "ANDROID") - list(APPEND CLI "--platform" "android") - elseif(PLATFORM STREQUAL "ASM_JS") - list(APPEND CLI "--platform" "asm.js") - elseif(PLATFORM STREQUAL "IOS") - list(APPEND CLI "--platform" "ios") - elseif(PLATFORM STREQUAL "OSX") - list(APPEND CLI "--platform" "osx") - elseif(PLATFORM STREQUAL "LINUX") - list(APPEND CLI "--platform" "linux") - elseif(PLATFORM STREQUAL "WINDOWS") - list(APPEND CLI "--platform" "windows") - elseif(PLATFORM STREQUAL "ORBIS") - list(APPEND CLI "--platform" "orbis") - endif() - - # --preprocess - if(ARG_PREPROCESS) - list(APPEND CLI "--preprocess") - endif() - - # --define - if(ARG_DEFINES) - # Add extra escapes or CMake will expand in the final CLI - string(REPLACE ";" "\\\\\\;" DEFINES "${ARG_DEFINES}") - # Also need to quote escape for Unix shells - list(APPEND CLI "--define" "\"${DEFINES}\"") - endif() - - # --raw - if(ARG_RAW) - list(APPEND CLI "--raw") - endif() - - # --type - set(TYPE "") - set(TYPES "FRAGMENT;VERTEX;COMPUTE") - foreach(T ${TYPES}) - if(ARG_${T}) - if(TYPE) - message(SEND_ERROR "Call to _bgfx_shaderc_parse() cannot have both flags ${TYPE} and ${T}.") - return() - endif() - set(TYPE "${T}") - endif() - endforeach() - if("${TYPE}" STREQUAL "") - message(SEND_ERROR "Call to _bgfx_shaderc_parse() must have a type flag: ${TYPES}") - return() - elseif("${TYPE}" STREQUAL "FRAGMENT") - list(APPEND CLI "--type" "fragment") - elseif("${TYPE}" STREQUAL "VERTEX") - list(APPEND CLI "--type" "vertex") - elseif("${TYPE}" STREQUAL "COMPUTE") - list(APPEND CLI "--type" "compute") - endif() - - # --varyingdef - if(ARG_VARYINGDEF) - list(APPEND CLI "--varyingdef" "${ARG_VARYINGDEF}") - endif() - - # --verbose - if(ARG_VERBOSE) - list(APPEND CLI "--verbose") - endif() - - # --debug - if(ARG_DEBUG) - list(APPEND CLI "--debug") - endif() - - # --disasm - if(ARG_DISASM) - list(APPEND CLI "--disasm") - endif() - - # --profile - if(ARG_PROFILE) - list(APPEND CLI "--profile" "${ARG_PROFILE}") - else() - message(SEND_ERROR "Call to _bgfx_shaderc_parse() must have a shader profile.") - endif() - - # -O - if(ARG_O) - list(APPEND CLI "-O" "${ARG_O}") - endif() - - # --Werror - if(ARG_WERROR) - list(APPEND CLI "--Werror") - endif() - - set(${ARG_OUT} ${CLI} PARENT_SCOPE) - endfunction() - - # extensions consistent with those listed under bgfx/runtime/shaders - function(_bgfx_get_profile_path_ext PROFILE PROFILE_PATH_EXT) - string(REPLACE 100_es essl PROFILE ${PROFILE}) - string(REPLACE 300_es essl PROFILE ${PROFILE}) - string(REPLACE 120 glsl PROFILE ${PROFILE}) - string(REPLACE 430 glsl PROFILE ${PROFILE}) - string(REPLACE s_5_0 dxbc PROFILE ${PROFILE}) - string(REPLACE s_6_0 dxil PROFILE ${PROFILE}) - set(${PROFILE_PATH_EXT} ${PROFILE} PARENT_SCOPE) - endfunction() - - # extensions consistent with embedded_shader.h - function(_bgfx_get_profile_ext PROFILE PROFILE_EXT) - string(REPLACE 100_es essl PROFILE ${PROFILE}) - string(REPLACE 300_es essl PROFILE ${PROFILE}) - string(REPLACE 120 glsl PROFILE ${PROFILE}) - string(REPLACE 430 glsl PROFILE ${PROFILE}) - string(REPLACE spirv spv PROFILE ${PROFILE}) - string(REPLACE metal mtl PROFILE ${PROFILE}) - string(REPLACE s_5_0 dxbc PROFILE ${PROFILE}) - string(REPLACE s_6_0 dxil PROFILE ${PROFILE}) - set(${PROFILE_EXT} ${PROFILE} PARENT_SCOPE) - endfunction() - - # bgfx_compile_shaders( - # TYPE VERTEX|FRAGMENT|COMPUTE - # SHADERS filenames - # VARYING_DEF filename - # OUTPUT_DIR directory - # OUT_FILES_VAR variable name - # INCLUDE_DIRS directories - # DEFINES defines - # [AS_HEADERS] - # ) - # - function(bgfx_compile_shaders) - set(options AS_HEADERS) - set(oneValueArgs TYPE VARYING_DEF OUTPUT_DIR OUT_FILES_VAR) - set(multiValueArgs SHADERS INCLUDE_DIRS DEFINES) - cmake_parse_arguments(ARGS "${options}" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}") - - set(PROFILES spirv) - if(ARGS_TYPE STREQUAL "COMPUTE") - list(APPEND PROFILES 430 300_es) - else() - list(APPEND PROFILES 120 100_es) - endif() - if(BGFX_CONFIG_RENDERER_WEBGPU) - list(APPEND PROFILES wgsl) - endif() - if(IOS) - set(PLATFORM IOS) - list(APPEND PROFILES metal) - elseif(ANDROID) - set(PLATFORM ANDROID) - elseif(UNIX AND NOT APPLE) - set(PLATFORM LINUX) - elseif(EMSCRIPTEN) - set(PLATFORM ASM_JS) - elseif(APPLE) - set(PLATFORM OSX) - list(APPEND PROFILES metal) - elseif( - WIN32 - OR MINGW - OR MSYS - OR CYGWIN - ) - set(PLATFORM WINDOWS) - list(APPEND PROFILES s_5_0) - list(APPEND PROFILES s_6_0) - elseif(ORBIS) # ORBIS should be defined by a PS4 CMake toolchain - set(PLATFORM ORBIS) - list(APPEND PROFILES pssl) - else() - # pssl for Agc and Gnm renderers - # nvn for Nvn renderer - message(error "shaderc: Unsupported platform") - endif() - - set(ALL_OUTPUTS "") - foreach(SHADER_FILE ${ARGS_SHADERS}) - source_group("Shaders" FILES "${SHADER}") - get_filename_component(SHADER_FILE_BASENAME ${SHADER_FILE} NAME) - get_filename_component(SHADER_FILE_NAME_WE ${SHADER_FILE} NAME_WE) - get_filename_component(SHADER_FILE_ABSOLUTE ${SHADER_FILE} ABSOLUTE) - - # Build output targets and their commands - set(OUTPUTS "") - set(COMMANDS "") - set(MKDIR_COMMANDS "") - foreach(PROFILE ${PROFILES}) - _bgfx_get_profile_path_ext(${PROFILE} PROFILE_PATH_EXT) - _bgfx_get_profile_ext(${PROFILE} PROFILE_EXT) - if(ARGS_AS_HEADERS) - set(HEADER_PREFIX .h) - endif() - set(OUTPUT ${ARGS_OUTPUT_DIR}/${PROFILE_PATH_EXT}/${SHADER_FILE_BASENAME}.bin${HEADER_PREFIX}) - set(PLATFORM_I ${PLATFORM}) - set(BIN2C_PART "") - if(ARGS_AS_HEADERS) - set(BIN2C_PART BIN2C ${SHADER_FILE_NAME_WE}_${PROFILE_EXT}) - endif() - _bgfx_shaderc_parse( - CLI # - ${BIN2C_PART} # - ${ARGS_TYPE} ${PLATFORM_I} WERROR "$<$:DEBUG>" - FILE ${SHADER_FILE_ABSOLUTE} - OUTPUT ${OUTPUT} - PROFILE ${PROFILE} - O "$:0,3>" - VARYINGDEF ${ARGS_VARYING_DEF} - INCLUDES ${BGFX_SHADER_INCLUDE_PATH} ${ARGS_INCLUDE_DIRS} - DEFINES ${ARGS_DEFINES} - ) - list(APPEND OUTPUTS ${OUTPUT}) - list(APPEND ALL_OUTPUTS ${OUTPUT}) - list( - APPEND - MKDIR_COMMANDS - COMMAND - ${CMAKE_COMMAND} - -E - make_directory - ${ARGS_OUTPUT_DIR}/${PROFILE_PATH_EXT} - ) - list(APPEND COMMANDS COMMAND bgfx::shaderc ${CLI}) - endforeach() - - add_custom_command( - OUTPUT ${OUTPUTS} - COMMAND ${MKDIR_COMMANDS} ${COMMANDS} - MAIN_DEPENDENCY ${SHADER_FILE_ABSOLUTE} - DEPENDS ${ARGS_VARYING_DEF} - ) - endforeach() - - if(DEFINED ARGS_OUT_FILES_VAR) - set(${ARGS_OUT_FILES_VAR} ${ALL_OUTPUTS} PARENT_SCOPE) - endif() - endfunction() -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/astc_encoder.cmake b/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/astc_encoder.cmake deleted file mode 100644 index a2155954..00000000 --- a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/astc_encoder.cmake +++ /dev/null @@ -1,25 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BIMG_DIR}) - message(SEND_ERROR "Could not load bimg, directory does not exist. ${BIMG_DIR}") - return() -endif() - -if(NOT ASTC_ENCODER_LIBRARIES) - file( - GLOB_RECURSE # - ASTC_ENCODER_SOURCES # - ${BIMG_DIR}/3rdparty/astc-encoder/source/*.cpp # - ${BIMG_DIR}/3rdparty/astc-encoder/source/*.h # - ) - set(ASTC_ENCODER_INCLUDE_DIR ${BIMG_DIR}/3rdparty/astc-encoder/include) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/edtaa3.cmake b/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/edtaa3.cmake deleted file mode 100644 index 8b243972..00000000 --- a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/edtaa3.cmake +++ /dev/null @@ -1,25 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BIMG_DIR}) - message(SEND_ERROR "Could not load bimg, directory does not exist. ${BIMG_DIR}") - return() -endif() - -if(NOT EDTAA3_LIBRARIES) - file( - GLOB_RECURSE # - EDTAA3_SOURCES # - ${BIMG_DIR}/3rdparty/edtaa3/**.cpp # - ${BIMG_DIR}/3rdparty/edtaa3/**.h # - ) - set(EDTAA3_INCLUDE_DIR ${BIMG_DIR}/3rdparty) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/etc1.cmake b/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/etc1.cmake deleted file mode 100644 index f16eba33..00000000 --- a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/etc1.cmake +++ /dev/null @@ -1,22 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BIMG_DIR}) - message(SEND_ERROR "Could not load bimg, directory does not exist. ${BIMG_DIR}") - return() -endif() - -if(NOT ETC1_LIBRARIES) - file(GLOB_RECURSE ETC1_SOURCES ${BIMG_DIR}/3rdparty/etc1/**.cpp # - ${BIMG_DIR}/3rdparty/etc1/**.hpp # - ) - set(ETC1_INCLUDE_DIR ${BIMG_DIR}/3rdparty) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/etc2.cmake b/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/etc2.cmake deleted file mode 100644 index 41bed011..00000000 --- a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/etc2.cmake +++ /dev/null @@ -1,25 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BIMG_DIR}) - message(SEND_ERROR "Could not load bimg, directory does not exist. ${BIMG_DIR}") - return() -endif() - -if(NOT ETC2_LIBRARIES) - file( - GLOB_RECURSE # - ETC2_SOURCES # - ${BIMG_DIR}/3rdparty/etc2/**.cpp # - ${BIMG_DIR}/3rdparty/etc2/**.hpp # - ) - set(ETC2_INCLUDE_DIR ${BIMG_DIR}/3rdparty) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/iqa.cmake b/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/iqa.cmake deleted file mode 100644 index 4e1c40a5..00000000 --- a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/iqa.cmake +++ /dev/null @@ -1,25 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BIMG_DIR}) - message(SEND_ERROR "Could not load bimg, directory does not exist. ${BIMG_DIR}") - return() -endif() - -if(NOT IQA_LIBRARIES) - file( - GLOB_RECURSE # - IQA_SOURCES # - ${BIMG_DIR}/3rdparty/iqa/include/**.h # - ${BIMG_DIR}/3rdparty/iqa/source/**.c # - ) - set(IQA_INCLUDE_DIR ${BIMG_DIR}/3rdparty/iqa/include) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/libsquish.cmake b/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/libsquish.cmake deleted file mode 100644 index 290df63c..00000000 --- a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/libsquish.cmake +++ /dev/null @@ -1,25 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BIMG_DIR}) - message(SEND_ERROR "Could not load bimg, directory does not exist. ${BIMG_DIR}") - return() -endif() - -if(NOT LIBSQUISH_LIBRARIES) - file( - GLOB_RECURSE # - LIBSQUISH_SOURCES # - ${BIMG_DIR}/3rdparty/libsquish/**.cpp # - ${BIMG_DIR}/3rdparty/libsquish/**.h # - ) - set(LIBSQUISH_INCLUDE_DIR ${BIMG_DIR}/3rdparty) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/loadpng.cmake b/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/loadpng.cmake deleted file mode 100644 index 5f99436c..00000000 --- a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/loadpng.cmake +++ /dev/null @@ -1,26 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BIMG_DIR}) - message(SEND_ERROR "Could not load bimg, directory does not exist. ${BIMG_DIR}") - return() -endif() - -if(NOT LOADPNG_LIBRARIES) - file( - GLOB_RECURSE # - LOADPNG_SOURCES # - ${BIMG_DIR}/3rdparty/lodepng/lodepng.cpp # - ${BIMG_DIR}/3rdparty/lodepng/lodepng.h # - ) - set_source_files_properties(${BIMG_DIR}/3rdparty/lodepng/lodepng.cpp PROPERTIES HEADER_FILE_ONLY ON) - set(LOADPNG_INCLUDE_DIR ${BIMG_DIR}/3rdparty) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/miniz.cmake b/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/miniz.cmake deleted file mode 100644 index 20d60f1f..00000000 --- a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/miniz.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BIMG_DIR}) - message(SEND_ERROR "Could not load bimg, directory does not exist. ${BIMG_DIR}") - return() -endif() - -if(NOT MINIZ_LIBRARIES) - file(GLOB_RECURSE # - MINIZ_SOURCES # - ${BIMG_DIR}/3rdparty/tinyexr/deps/miniz/miniz.* # - ) - set(MINIZ_INCLUDE_DIR ${BIMG_DIR}/3rdparty/tinyexr/deps) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/nvtt.cmake b/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/nvtt.cmake deleted file mode 100644 index 26a0b765..00000000 --- a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/nvtt.cmake +++ /dev/null @@ -1,25 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BIMG_DIR}) - message(SEND_ERROR "Could not load bimg, directory does not exist. ${BIMG_DIR}") - return() -endif() - -if(NOT NVTT_LIBRARIES) - file( - GLOB_RECURSE # - NVTT_SOURCES # - ${BIMG_DIR}/3rdparty/nvtt/**.cpp # - ${BIMG_DIR}/3rdparty/nvtt/**.h # - ) - set(NVTT_INCLUDE_DIR ${BIMG_DIR}/3rdparty/nvtt) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/pvrtc.cmake b/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/pvrtc.cmake deleted file mode 100644 index c8974ed3..00000000 --- a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/pvrtc.cmake +++ /dev/null @@ -1,25 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BIMG_DIR}) - message(SEND_ERROR "Could not load bimg, directory does not exist. ${BIMG_DIR}") - return() -endif() - -if(NOT PVRTC_LIBRARIES) - file( - GLOB_RECURSE # - PVRTC_SOURCES # - ${BIMG_DIR}/3rdparty/pvrtc/**.cpp # - ${BIMG_DIR}/3rdparty/pvrtc/**.h # - ) - set(PVRTC_INCLUDE_DIR ${BIMG_DIR}/3rdparty) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/tinyexr.cmake b/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/tinyexr.cmake deleted file mode 100644 index f3cbc55f..00000000 --- a/Engine/cpp/ThirdParty/cmake/bimg/3rdparty/tinyexr.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BIMG_DIR}) - message(SEND_ERROR "Could not load bimg, directory does not exist. ${BIMG_DIR}") - return() -endif() - -if(NOT TINYEXR_LIBRARIES) - file(GLOB_RECURSE # - TINYEXR_SOURCES # - ${BIMG_DIR}/3rdparty/tinyexr/**.h # - ) - set(TINYEXR_INCLUDE_DIR ${BIMG_DIR}/3rdparty) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bimg/CMakeLists.txt b/Engine/cpp/ThirdParty/cmake/bimg/CMakeLists.txt deleted file mode 100644 index 200b29bd..00000000 --- a/Engine/cpp/ThirdParty/cmake/bimg/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -include(3rdparty/loadpng.cmake) -include(3rdparty/libsquish.cmake) -include(3rdparty/astc_encoder.cmake) -include(3rdparty/edtaa3.cmake) -include(3rdparty/etc1.cmake) -include(3rdparty/etc2.cmake) -include(3rdparty/nvtt.cmake) -include(3rdparty/pvrtc.cmake) -include(3rdparty/tinyexr.cmake) -include(3rdparty/iqa.cmake) -include(3rdparty/miniz.cmake) -include(bimg.cmake) -include(bimg_decode.cmake) -include(bimg_encode.cmake) - -if(BGFX_BUILD_TOOLS_TEXTURE) - include(texturec.cmake) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bimg/bimg.cmake b/Engine/cpp/ThirdParty/cmake/bimg/bimg.cmake deleted file mode 100644 index 6f52fe18..00000000 --- a/Engine/cpp/ThirdParty/cmake/bimg/bimg.cmake +++ /dev/null @@ -1,57 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BIMG_DIR}) - message(SEND_ERROR "Could not load bimg, directory does not exist. ${BIMG_DIR}") - return() -endif() - -file( - GLOB_RECURSE - BIMG_SOURCES - ${BIMG_DIR}/include/* # - ${BIMG_DIR}/src/image.* # - ${BIMG_DIR}/src/image_gnf.cpp # - # - ${ASTC_ENCODER_SOURCES} - ${MINIZ_SOURCES} -) - -add_library(bimg STATIC ${BIMG_SOURCES}) - -# Put in a "bgfx" folder in Visual Studio -set_target_properties(bimg PROPERTIES FOLDER "bgfx") - -target_include_directories( - bimg PUBLIC $$ - PRIVATE ${ASTC_ENCODER_INCLUDE_DIR} # - ${MINIZ_INCLUDE_DIR} # -) - -target_link_libraries( - bimg - PUBLIC bx # - ${ASTC_ENCODER_LIBRARIES} # - ${MINIZ_LIBRARIES} # -) - -if(BGFX_INSTALL) - install( - TARGETS bimg - EXPORT "${TARGETS_EXPORT_NAME}" - LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" - ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" - RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" - INCLUDES - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" - ) - install(DIRECTORY ${BIMG_DIR}/include/bimg DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bimg/bimg_decode.cmake b/Engine/cpp/ThirdParty/cmake/bimg/bimg_decode.cmake deleted file mode 100644 index a511e8f0..00000000 --- a/Engine/cpp/ThirdParty/cmake/bimg/bimg_decode.cmake +++ /dev/null @@ -1,57 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BIMG_DIR}) - message(SEND_ERROR "Could not load bimg_decode, directory does not exist. ${BIMG_DIR}") - return() -endif() - -file( - GLOB_RECURSE - BIMG_DECODE_SOURCES # - ${BIMG_DIR}/include/* # - ${BIMG_DIR}/src/image_decode.* # - # - ${LOADPNG_SOURCES} # - ${MINIZ_SOURCES} # -) - -add_library(bimg_decode STATIC ${BIMG_DECODE_SOURCES}) - -# Put in a "bgfx" folder in Visual Studio -set_target_properties(bimg_decode PROPERTIES FOLDER "bgfx") -target_include_directories( - bimg_decode - PUBLIC $ $ - PRIVATE ${LOADPNG_INCLUDE_DIR} # - ${MINIZ_INCLUDE_DIR} # - ${TINYEXR_INCLUDE_DIR} # -) - -target_link_libraries( - bimg_decode - PUBLIC bx # - ${LOADPNG_LIBRARIES} # - ${MINIZ_LIBRARIES} # - ${TINYEXR_LIBRARIES} # -) - -if(BGFX_INSTALL AND NOT BGFX_LIBRARY_TYPE MATCHES "SHARED") - install( - TARGETS bimg_decode - EXPORT "${TARGETS_EXPORT_NAME}" - LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" - ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" - RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" - INCLUDES - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" - ) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bimg/bimg_encode.cmake b/Engine/cpp/ThirdParty/cmake/bimg/bimg_encode.cmake deleted file mode 100644 index 82d9fe09..00000000 --- a/Engine/cpp/ThirdParty/cmake/bimg/bimg_encode.cmake +++ /dev/null @@ -1,99 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BIMG_DIR}) - message(SEND_ERROR "Could not load bimg_encode, directory does not exist. ${BIMG_DIR}") - return() -endif() - -add_library(bimg_encode STATIC) - -# Put in a "bgfx" folder in Visual Studio -set_target_properties(bimg_encode PROPERTIES FOLDER "bgfx") - -target_include_directories( - bimg_encode - PUBLIC $ $ - PRIVATE ${LIBSQUISH_INCLUDE_DIR} # - ${ASTC_ENCODER_INCLUDE_DIR} # - ${EDTAA3_INCLUDE_DIR} # - ${ETC1_INCLUDE_DIR} # - ${ETC2_INCLUDE_DIR} # - ${NVTT_INCLUDE_DIR} # - ${PVRTC_INCLUDE_DIR} # - ${TINYEXR_INCLUDE_DIR} # - ${IQA_INCLUDE_DIR} # - ${MINIZ_INCLUDE_DIR} # -) - -file( - GLOB_RECURSE - BIMG_ENCODE_SOURCES - ${BIMG_DIR}/include/* # - ${BIMG_DIR}/src/image_encode.* # - ${BIMG_DIR}/src/image_cubemap_filter.* # - ${LIBSQUISH_SOURCES} # - ${EDTAA3_SOURCES} # - ${ETC1_SOURCES} # - ${ETC2_SOURCES} # - ${NVTT_SOURCES} # - ${PVRTC_SOURCES} # - ${TINYEXR_SOURCES} - ${IQA_SOURCES} # -) - -target_sources(bimg_encode PRIVATE ${BIMG_ENCODE_SOURCES}) - -target_link_libraries( - bimg_encode - PUBLIC bx # - ${LIBSQUISH_LIBRARIES} # - ${ASTC_ENCODER_LIBRARIES} # - ${EDTAA3_LIBRARIES} # - ${ETC1_LIBRARIES} # - ${ETC2_LIBRARIES} # - ${NVTT_LIBRARIES} # - ${PVRTC_LIBRARIES} # - ${TINYEXR_LIBRARIES} # - ${IQA_LIBRARIES} # -) - -include(CheckCXXCompilerFlag) -foreach(flag "-Wno-implicit-fallthrough" "-Wno-shadow" "-Wno-shift-negative-value" "-Wno-undef") - check_cxx_compiler_flag(${flag} flag_supported) - if(flag_supported) - target_compile_options(bimg_encode PRIVATE ${flag}) - endif() -endforeach() - -foreach(flag "-Wno-class-memaccess" "-Wno-deprecated-copy") - check_cxx_compiler_flag(${flag} flag_supported) - if(flag_supported) - foreach(file ${BIMG_ENCODE_SOURCES}) - get_source_file_property(lang ${file} LANGUAGE) - if(lang STREQUAL "CXX") - set_source_files_properties(${file} PROPERTIES COMPILE_OPTIONS ${flag}) - endif() - endforeach() - endif() -endforeach() - -if(BGFX_INSTALL AND NOT BGFX_LIBRARY_TYPE MATCHES "SHARED") - install( - TARGETS bimg_encode - EXPORT "${TARGETS_EXPORT_NAME}" - LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" - ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" - RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" - INCLUDES - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" - ) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bimg/texturec.cmake b/Engine/cpp/ThirdParty/cmake/bimg/texturec.cmake deleted file mode 100644 index b6f7cac0..00000000 --- a/Engine/cpp/ThirdParty/cmake/bimg/texturec.cmake +++ /dev/null @@ -1,40 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Grab the texturec source files -file(GLOB_RECURSE TEXTUREC_SOURCES # - ${BIMG_DIR}/tools/texturec/*.cpp # - ${BIMG_DIR}/tools/texturec/*.h # -) - -add_executable(texturec ${TEXTUREC_SOURCES}) - -target_link_libraries(texturec PRIVATE bimg_decode bimg_encode bimg) -set_target_properties( - texturec PROPERTIES FOLDER "bgfx/tools" # - OUTPUT_NAME ${BGFX_TOOLS_PREFIX}texturec # -) - -if(BGFX_BUILD_TOOLS_TEXTURE) - add_executable(bgfx::texturec ALIAS texturec) - if(BGFX_CUSTOM_TARGETS) - add_dependencies(tools texturec) - endif() -endif() - -if(ANDROID) - target_link_libraries(texturec PRIVATE log) -elseif(IOS) - set_target_properties(texturec PROPERTIES MACOSX_BUNDLE ON MACOSX_BUNDLE_GUI_IDENTIFIER texturec) -endif() - -if(BGFX_INSTALL) - install(TARGETS texturec EXPORT "${TARGETS_EXPORT_NAME}" DESTINATION "${CMAKE_INSTALL_BINDIR}") -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bx/CMakeLists.txt b/Engine/cpp/ThirdParty/cmake/bx/CMakeLists.txt deleted file mode 100644 index 2ed7e282..00000000 --- a/Engine/cpp/ThirdParty/cmake/bx/CMakeLists.txt +++ /dev/null @@ -1,41 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -include(bx.cmake) - -if(BGFX_BUILD_TOOLS_BIN2C) - include(bin2c.cmake) -endif() - -if(BGFX_BUILD_TESTS) - file( - GLOB - BX_TEST_SOURCES # - ${BX_DIR}/3rdparty/catch/catch_amalgamated.cpp - ${BX_DIR}/tests/*_test.cpp # - ${BX_DIR}/tests/*.h # - ${BX_DIR}/tests/dbg.* # - ) - add_executable(bx_test ${BX_TEST_SOURCES}) - target_compile_definitions(bx_test PRIVATE CATCH_AMALGAMATED_CUSTOM_MAIN) - target_link_libraries(bx_test PRIVATE bx) - add_test(NAME bx.test COMMAND bx_test) - - file( - GLOB - BX_BENCH_SOURCES # - ${BX_DIR}/tests/*_bench.cpp # - ${BX_DIR}/tests/*_bench.h # - ${BX_DIR}/tests/dbg.* # - ) - add_executable(bx_bench ${BX_BENCH_SOURCES}) - target_link_libraries(bx_bench PRIVATE bx) - add_test(NAME bx.bench COMMAND bx_bench) -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bx/bin2c.cmake b/Engine/cpp/ThirdParty/cmake/bx/bin2c.cmake deleted file mode 100644 index 3c361594..00000000 --- a/Engine/cpp/ThirdParty/cmake/bx/bin2c.cmake +++ /dev/null @@ -1,40 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Grab the bin2c source files -file(GLOB_RECURSE BIN2C_SOURCES # - ${BX_DIR}/tools/bin2c/*.cpp # - ${BX_DIR}/tools/bin2c/*.h # -) - -add_executable(bin2c ${BIN2C_SOURCES}) - -target_link_libraries(bin2c PRIVATE bx) -set_target_properties( - bin2c PROPERTIES FOLDER "bgfx/tools" # - OUTPUT_NAME ${BGFX_TOOLS_PREFIX}bin2c # -) - -if(BGFX_BUILD_TOOLS_BIN2C) - add_executable(bgfx::bin2c ALIAS bin2c) - if(BGFX_CUSTOM_TARGETS) - add_dependencies(tools bin2c) - endif() -endif() - -if(ANDROID) - target_link_libraries(bin2c PRIVATE log) -elseif(IOS) - set_target_properties(bin2c PROPERTIES MACOSX_BUNDLE ON MACOSX_BUNDLE_GUI_IDENTIFIER bin2c) -endif() - -if(BGFX_INSTALL) - install(TARGETS bin2c EXPORT "${TARGETS_EXPORT_NAME}" DESTINATION "${CMAKE_INSTALL_BINDIR}") -endif() diff --git a/Engine/cpp/ThirdParty/cmake/bx/bx.cmake b/Engine/cpp/ThirdParty/cmake/bx/bx.cmake deleted file mode 100644 index bbca9e51..00000000 --- a/Engine/cpp/ThirdParty/cmake/bx/bx.cmake +++ /dev/null @@ -1,143 +0,0 @@ -# bgfx.cmake - bgfx building in cmake -# Written in 2017 by Joshua Brookover -# -# To the extent possible under law, the author(s) have dedicated all copyright -# and related and neighboring rights to this software to the public domain -# worldwide. This software is distributed without any warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication along with -# this software. If not, see . - -# Ensure the directory exists -if(NOT IS_DIRECTORY ${BX_DIR}) - message(SEND_ERROR "Could not load bx, directory does not exist. ${BX_DIR}") - return() -endif() - -include(GNUInstallDirs) - -# Grab the bx source files -file( - GLOB_RECURSE - BX_SOURCES - ${BX_DIR}/include/*.h # - ${BX_DIR}/include/**.inl # - ${BX_DIR}/src/*.cpp # - ${BX_DIR}/scripts/*.natvis # -) - -if(BX_AMALGAMATED) - list(APPEND BX_NOBUILD "${BX_DIR}/src/allocator.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/bounds.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/bx.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/commandline.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/crtnone.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/debug.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/dtoa.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/easing.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/file.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/filepath.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/hash.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/math.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/mutex.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/os.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/process.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/semaphore.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/settings.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/sort.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/string.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/thread.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/timer.cpp") - list(APPEND BX_NOBUILD "${BX_DIR}/src/url.cpp") -else() - file(GLOB_RECURSE BX_NOBUILD "${BX_DIR}/src/amalgamated.*") -endif() - -# Exclude files from the build but keep them in project -foreach(BX_SRC ${BX_NOBUILD}) - set_source_files_properties(${BX_SRC} PROPERTIES HEADER_FILE_ONLY ON) -endforeach() - -add_library(bx STATIC ${BX_SOURCES}) - -if(MSVC) - target_compile_options(bx PRIVATE /EHs-c-) - target_compile_definitions(bx PRIVATE _HAS_EXCEPTIONS=0) -endif() - -# Put in a "bgfx" folder in Visual Studio -set_target_properties(bx PROPERTIES FOLDER "bgfx") - -# Build system specific configurations -if(MINGW) - set(BX_COMPAT_PLATFORM mingw) -elseif(WIN32) - set(BX_COMPAT_PLATFORM msvc) -elseif(APPLE) # APPLE is technically UNIX... ORDERING MATTERS! - set(BX_COMPAT_PLATFORM osx) -elseif(UNIX) - set(BX_COMPAT_PLATFORM linux) -endif() - -# Add include directory of bx -target_include_directories( - bx - PUBLIC $ # - $ # - $ # - $ # - $ # -) - -# All configurations -target_compile_definitions(bx PUBLIC "BX_CONFIG_DEBUG=$,1,$>") -target_compile_definitions(bx PUBLIC "__STDC_LIMIT_MACROS") -target_compile_definitions(bx PUBLIC "__STDC_FORMAT_MACROS") -target_compile_definitions(bx PUBLIC "__STDC_CONSTANT_MACROS") - -target_compile_features(bx PUBLIC cxx_std_14) -# (note: see bx\scripts\toolchain.lua for equivalent compiler flag) -target_compile_options(bx PUBLIC $<$:/Zc:__cplusplus /Zc:preprocessor>) - -# Link against psapi on Windows -if(WIN32) - target_link_libraries(bx PUBLIC psapi) -endif() - -# Additional dependencies on Unix -if(ANDROID) - # For __android_log_write - find_library(LOG_LIBRARY log) - mark_as_advanced(LOG_LIBRARY) - target_link_libraries(bx PUBLIC ${LOG_LIBRARY}) -elseif(APPLE) - find_library(FOUNDATION_LIBRARY Foundation) - mark_as_advanced(FOUNDATION_LIBRARY) - target_link_libraries(bx PUBLIC ${FOUNDATION_LIBRARY}) -elseif(UNIX) - # Threads - find_package(Threads) - target_link_libraries(bx ${CMAKE_THREAD_LIBS_INIT} dl) - - # Real time (for clock_gettime) - target_link_libraries(bx rt) -endif() - -# Put in a "bgfx" folder in Visual Studio -set_target_properties(bx PROPERTIES FOLDER "bgfx") - -if(BGFX_INSTALL) - install( - TARGETS bx - EXPORT "${TARGETS_EXPORT_NAME}" - LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" - ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" - RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" - ) - # We will make sure tinystl and compat are not installed in /usr/include - install(DIRECTORY "${BX_DIR}/include/bx" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") - install(DIRECTORY "${BX_DIR}/include/compat/${BX_COMPAT_PLATFORM}" - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/bx/compat" - ) - install(DIRECTORY "${BX_DIR}/include/tinystl" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/bx") -endif() diff --git a/Engine/cpp/ThirdParty/cmake/version.cmake b/Engine/cpp/ThirdParty/cmake/version.cmake deleted file mode 100644 index 43d1573e..00000000 --- a/Engine/cpp/ThirdParty/cmake/version.cmake +++ /dev/null @@ -1,40 +0,0 @@ -# bgfx versioning scheme: -# bgfx 1.104.7082 -# ^ ^^^ ^^^^ -# | | +--- Commit number (https://github.com/bkaradzic/bgfx / git rev-list --count HEAD) -# | +------- API version (from https://github.com/bkaradzic/bgfx/blob/master/scripts/bgfx.idl#L4) -# +--------- Major revision (always 1) -# -# BGFX_API_VERSION generated from https://github.com/bkaradzic/bgfx/blob/master/scripts/bgfx.idl#L4 -# bgfx/src/version.h: -# BGFX_REV_NUMBER -# BGFX_REV_SHA1 - -find_package(Git QUIET) - -execute_process( - COMMAND "${GIT_EXECUTABLE}" -C bgfx log --pretty=format:'%h' -n 1 - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - OUTPUT_VARIABLE GIT_REV - ERROR_QUIET -) - -execute_process( - COMMAND "${GIT_EXECUTABLE}" -C bgfx rev-list --count HEAD - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - OUTPUT_VARIABLE GIT_REV_COUNT - OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET -) - -# read version(100) from bgfx.idl -file(READ "${BGFX_DIR}/scripts/bgfx.idl" BGFX_IDL) -string(REGEX MATCH "version\\(([^\)]+)\\)" BGFX_API_VERSION ${BGFX_IDL}) -set(BGFX_API_VERSION ${CMAKE_MATCH_1}) -set(BGFX_REV_NUMBER ${GIT_REV_COUNT}) -set(BGFX_REV ${GIT_REV}) - -# set project specific versions -set(PROJECT_VERSION 1.${BGFX_API_VERSION}.${BGFX_REV_NUMBER}) -set(PROJECT_VERSION_MAJOR 1) -set(PROJECT_VERSION_MINOR ${BGFX_API_VERSION}) -set(PROJECT_VERSION_PATCH ${BGFX_REV_NUMBER}) diff --git a/Samples/cpp/CMakeLists.txt b/Samples/cpp/CMakeLists.txt index 5dd136e2..1391f271 100644 --- a/Samples/cpp/CMakeLists.txt +++ b/Samples/cpp/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(Render) +add_subdirectory(RHI) diff --git a/Samples/cpp/RHI/CMakeLists.txt b/Samples/cpp/RHI/CMakeLists.txt new file mode 100644 index 00000000..8b1c7b0c --- /dev/null +++ b/Samples/cpp/RHI/CMakeLists.txt @@ -0,0 +1,68 @@ +# Draconic RHI samples + their shared framework (GPU/display dependent - built, +# not run in CI). + +# Shared sample framework: window bring-up (SDL3 shell), backend/device/swapchain, +# main loop, shader-compile helpers. Links every RHI backend + the shader compiler. +# DX12 is Windows-only (DRACO_HAS_DX12). +add_modules_library(Framework) +target_link_libraries(Framework PUBLIC + Core + Rendering_RHI + Rendering_RHI_Vulkan + Rendering_RHI_Validation + Rendering_Shaders + Shell) +if(WIN32) + target_link_libraries(Framework PUBLIC Rendering_RHI_DX12) + target_compile_definitions(Framework PUBLIC DRACO_HAS_DX12) +endif() + +# Each sample is one Main.cpp linking the framework. SDL3 is static, so its +# constructor-registered video/input drivers must be force-loaded (whole archive) +# at the executable link or they get stripped. +function(draco_add_rhi_sample name) + add_executable(${name} Main.cpp) + target_link_libraries(${name} PRIVATE Framework) + target_compile_options(${name} PRIVATE + "$<$:-Wno-error=unused-parameter;-Wno-error=unused-variable;-Wno-error=missing-field-initializers;-Wno-error=misleading-indentation>") + if(UNIX AND NOT APPLE) + target_link_libraries(${name} PRIVATE Threads::Threads dl) + target_link_options(${name} PRIVATE -Wl,--whole-archive $ -Wl,--no-whole-archive) + elseif(APPLE) + target_link_options(${name} PRIVATE -Wl,-force_load,$) + elseif(MSVC) + target_link_options(${name} PRIVATE /WHOLEARCHIVE:$) + endif() +endfunction() + +add_subdirectory(Sample001_Triangle) +add_subdirectory(Sample002_Textures) +add_subdirectory(Sample003_UniformBuffers) +add_subdirectory(Sample004_Compute) +add_subdirectory(Sample005_BindGroups) +add_subdirectory(Sample006_Blending) +add_subdirectory(Sample007_Instancing) +add_subdirectory(Sample008_DepthBuffer) +add_subdirectory(Sample009_Mipmaps) +add_subdirectory(Sample010_MSAA) +add_subdirectory(Sample011_MRT) +add_subdirectory(Sample012_Wireframe) +add_subdirectory(Sample013_BorderSampler) +add_subdirectory(Sample014_Blit) +add_subdirectory(Sample015_Queries) +add_subdirectory(Sample016_Readback) +add_subdirectory(Sample017_MultiQueue) +add_subdirectory(Sample018_Bindless) +add_subdirectory(Sample019_BatchUpload) +add_subdirectory(Sample020_MeshShaders) +add_subdirectory(Sample021_RayTracing) +add_subdirectory(Sample022_StencilOutline) +add_subdirectory(Sample023_CubeMap) +add_subdirectory(Sample024_OcclusionQuery) +add_subdirectory(Sample025_MultiDrawIndirect) +add_subdirectory(Sample026_DynamicOffsets) +add_subdirectory(Sample027_3DTexture) +add_subdirectory(Sample028_ProceduralRT) +add_subdirectory(Sample029_ResolveTexture) +add_subdirectory(Sample030_RenderBundles) +add_subdirectory(Smoketest) diff --git a/Samples/cpp/RHI/Framework/DepthBuffer.cppm b/Samples/cpp/RHI/Framework/DepthBuffer.cppm new file mode 100644 index 00000000..e0391a5f --- /dev/null +++ b/Samples/cpp/RHI/Framework/DepthBuffer.cppm @@ -0,0 +1,44 @@ +/// Reusable depth buffer helper for samples. + +export module samples.rhi.framework:depth_buffer; + +import core.stdtypes; +import core.status; +import rhi; + +using namespace draco; + +export namespace draco::samples::framework { + +struct DepthBuffer { + rhi::Texture* texture = nullptr; + rhi::TextureView* view = nullptr; + rhi::TextureFormat format = rhi::TextureFormat::Depth24PlusStencil8; + + Status recreate(rhi::Device* device, u32 width, u32 height, u32 sampleCount = 1) { + destroy(device); + auto desc = rhi::TextureDesc::depthBuffer(format, width, height, sampleCount, u8"Depth"); + if (device->createTexture(desc, texture) != ErrorCode::Ok) return ErrorCode::Unknown; + + rhi::TextureViewDesc vd{}; + vd.format = format; + vd.dimension = rhi::TextureViewDimension::Texture2D; + vd.baseMipLevel = 0; + vd.mipLevelCount = 1; + vd.baseArrayLayer = 0; + vd.arrayLayerCount = 1; + vd.label = u8"DepthView"; + if (device->createTextureView(texture, vd, view) != ErrorCode::Ok) { + device->destroyTexture(texture); + return ErrorCode::Unknown; + } + return ErrorCode::Ok; + } + + void destroy(rhi::Device* device) { + if (view) { device->destroyTextureView(view); view = nullptr; } + if (texture) { device->destroyTexture(texture); texture = nullptr; } + } +}; + +} // namespace draco::samples::framework diff --git a/Samples/cpp/RHI/Framework/Framework.cppm b/Samples/cpp/RHI/Framework/Framework.cppm new file mode 100644 index 00000000..b71bfd54 --- /dev/null +++ b/Samples/cpp/RHI/Framework/Framework.cppm @@ -0,0 +1,8 @@ +export module samples.rhi.framework; + +export import rhi; +export import shell; + +export import :sample_app; +export import :depth_buffer; +export import :shader_helpers; diff --git a/Samples/cpp/RHI/Framework/SampleApp.cppm b/Samples/cpp/RHI/Framework/SampleApp.cppm new file mode 100644 index 00000000..2dc025e5 --- /dev/null +++ b/Samples/cpp/RHI/Framework/SampleApp.cppm @@ -0,0 +1,233 @@ +// SampleApp - abstract base for RHI samples. Brings up a window (shell), a Vulkan +// backend (validation-wrapped), device, queue, and swap chain; pumps events, tracks +// timing, and calls onRender(). Resize is detected by polling the window size; the +// loop skips rendering while minimized. + +module; + +#include +#include +#include +#include + +export module samples.rhi.framework:sample_app; + +import core.stdtypes; +import core.status; +import rhi; +import rhi.vk; +#ifdef DRACO_HAS_DX12 +import rhi.dx12; +#endif +import rhi.validation; +import shell; +import shell.desktop; + +using namespace draco; + +export namespace draco::samples::framework { + +enum class BackendType { Vulkan, DX12 }; + +class SampleApp { +public: + explicit SampleApp(BackendType backend = BackendType::Vulkan, bool validation = true) + : m_backendType(backend), m_validationEnabled(validation) {} + virtual ~SampleApp() = default; + + SampleApp(const SampleApp&) = delete; + SampleApp& operator=(const SampleApp&) = delete; + + int run(int argc = 0, char** argv = nullptr); + +protected: + virtual std::u8string_view title() const { return u8"Draconic Sample"; } + virtual rhi::DeviceFeatures requiredFeatures() const { return {}; } + virtual rhi::TextureFormat swapChainFormat() const { return rhi::TextureFormat::RGBA8UnormSrgb; } + virtual rhi::PresentMode presentMode() const { return rhi::PresentMode::Fifo; } + virtual u32 bufferCount() const { return 2; } + + virtual Status onInit() = 0; + virtual void onRender() = 0; + virtual void onResize(u32, u32) {} + virtual void onShutdown() = 0; + + shell::IShell* m_shell = nullptr; // owns the shell (created in init(), freed in shutdown()) + shell::IWindow* m_window = nullptr; + rhi::Backend* m_backend = nullptr; + rhi::Device* m_device = nullptr; + rhi::Queue* m_graphicsQueue = nullptr; + rhi::Surface* m_surface = nullptr; + rhi::SwapChain* m_swapChain = nullptr; + + u32 m_width = 1280; + u32 m_height = 720; + bool m_running = false; + f32 m_deltaTime = 0.0f; + f32 m_totalTime = 0.0f; + + void exit() { m_running = false; } + +private: + BackendType m_backendType; + bool m_validationEnabled; + + Status init(); + void mainLoop(); + void shutdown(); + Status createBackend(); + Status createSwapChain(); + void checkAndResize(); +}; + +inline int SampleApp::run(int argc, char** argv) { + for (int i = 1; i < argc; ++i) { + if (std::strcmp(argv[i], "--dx12") == 0 || std::strcmp(argv[i], "--d3d12") == 0) { + m_backendType = BackendType::DX12; + } else if (std::strcmp(argv[i], "--vk") == 0 || std::strcmp(argv[i], "--vulkan") == 0) { + m_backendType = BackendType::Vulkan; + } else if (std::strcmp(argv[i], "--novalidation") == 0) { + m_validationEnabled = false; + } + } + + if (!init().isOk()) { shutdown(); return 1; } + mainLoop(); + shutdown(); + return 0; +} + +inline Status SampleApp::init() { + shell::WindowSettings ws{}; + ws.title = title(); + ws.width = m_width; + ws.height = m_height; + + m_shell = shell::createShell(ws); + if (m_shell == nullptr || m_shell->mainWindow() == nullptr) { + rhi::logError("SampleApp: shell/window init failed"); return ErrorCode::Unknown; + } + m_window = m_shell->mainWindow(); + m_width = m_window->width(); + m_height = m_window->height(); + + if (!createBackend().isOk()) return ErrorCode::Unknown; + + const shell::NativeWindow nw = m_window->native(); + if (!m_backend->createSurface(nw.window, nw.display, m_surface).isOk()) { + rhi::logError("SampleApp: createSurface failed"); return ErrorCode::Unknown; + } + + auto adapters = m_backend->enumerateAdapters(); + if (adapters.size() == 0) { rhi::logError("SampleApp: no adapters"); return ErrorCode::Unknown; } + rhi::Adapter* adapter = adapters[0]; // best GPU first + + { + rhi::AdapterInfo ai = adapter->info(); + const char* backendName = (m_backendType == BackendType::DX12) ? "DX12" : "Vulkan"; + const std::u8string name8 = std::u8string(ai.name); + std::printf("SampleApp: backend=%s adapter=%s\n", + backendName, reinterpret_cast(name8.c_str())); + } + + rhi::DeviceDesc dd{}; + dd.graphicsQueueCount = 1; + dd.requiredFeatures = requiredFeatures(); + if (!adapter->createDevice(dd, m_device).isOk()) { + rhi::logError("SampleApp: createDevice failed"); return ErrorCode::Unknown; + } + + m_graphicsQueue = m_device->getQueue(rhi::QueueType::Graphics); + if (m_graphicsQueue == nullptr) { rhi::logError("SampleApp: no graphics queue"); return ErrorCode::Unknown; } + + if (!createSwapChain().isOk()) return ErrorCode::Unknown; + return onInit(); +} + +inline Status SampleApp::createBackend() { + rhi::Backend* raw = nullptr; + switch (m_backendType) { + case BackendType::Vulkan: { + rhi::vk::VkBackendDesc desc{}; + desc.enableValidation = m_validationEnabled; + if (!rhi::vk::createBackend(desc, raw).isOk()) { + rhi::logError("SampleApp: vk::createBackend failed"); return ErrorCode::Unknown; + } + break; + } + case BackendType::DX12: { +#ifdef DRACO_HAS_DX12 + rhi::dx12::DxBackendDesc desc{}; + desc.enableValidation = m_validationEnabled; + if (!rhi::dx12::createDxBackend(desc, raw).isOk()) { + rhi::logError("SampleApp: dx12::createDxBackend failed"); return ErrorCode::Unknown; + } +#else + rhi::logError("SampleApp: DX12 backend not available on this platform"); + return ErrorCode::Unknown; +#endif + break; + } + } + m_backend = m_validationEnabled ? rhi::validation::createValidatedBackend(raw) : raw; + return m_backend != nullptr ? Status{} : Status{ ErrorCode::Unknown }; +} + +inline Status SampleApp::createSwapChain() { + rhi::SwapChainDesc desc{}; + desc.width = m_width; + desc.height = m_height; + desc.format = swapChainFormat(); + desc.presentMode = presentMode(); + desc.bufferCount = bufferCount(); + desc.label = u8"main"; + if (!m_device->createSwapChain(m_surface, desc, m_swapChain).isOk()) { + rhi::logError("SampleApp: createSwapChain failed"); return ErrorCode::Unknown; + } + return Status{}; +} + +inline void SampleApp::mainLoop() { + using clock = std::chrono::steady_clock; + using dur = std::chrono::duration; + + m_running = true; + auto lastTime = clock::now(); + + while (m_running && m_shell->isRunning()) { + m_shell->processEvents(); + + const auto now = clock::now(); + m_deltaTime = dur(now - lastTime).count(); + lastTime = now; + m_totalTime += m_deltaTime; + + if (!m_window->isMinimized()) { + checkAndResize(); + onRender(); + } + } +} + +inline void SampleApp::checkAndResize() { + const u32 nw = m_window->width(); + const u32 nh = m_window->height(); + if (nw == 0 || nh == 0) return; + if (nw == m_width && nh == m_height) return; + m_width = nw; m_height = nh; + m_device->waitIdle(); + m_swapChain->resize(m_width, m_height); + onResize(m_width, m_height); +} + +inline void SampleApp::shutdown() { + if (m_device) m_device->waitIdle(); + onShutdown(); + if (m_swapChain) { m_device->destroySwapChain(m_swapChain); m_swapChain = nullptr; } + if (m_surface) { m_device->destroySurface(m_surface); m_surface = nullptr; } + if (m_device) { m_device->destroy(); m_device = nullptr; } + if (m_backend) { m_backend->destroy(); m_backend = nullptr; } + if (m_shell != nullptr) { shell::destroyShell(m_shell); m_shell = nullptr; } // destroys the window + shell +} + +} // namespace draco::samples::framework diff --git a/Samples/cpp/RHI/Framework/ShaderHelpers.cppm b/Samples/cpp/RHI/Framework/ShaderHelpers.cppm new file mode 100644 index 00000000..f096d678 --- /dev/null +++ b/Samples/cpp/RHI/Framework/ShaderHelpers.cppm @@ -0,0 +1,78 @@ +/// Shader compilation helpers - wraps DXC compile + createShaderModule into one call. +/// Automatically selects SPIR-V (Vulkan) or DXIL (DX12) based on device type. +/// Applies Vulkan binding shifts when targeting SPIR-V. + +module; + +#include +#include +#include + +export module samples.rhi.framework:shader_helpers; + +import core.stdtypes; +import core.status; +import rhi; +import shaders; + +using namespace draco; + +export namespace draco::samples::framework { + +/// Compile HLSL source to a ShaderModule with a specific shader model. +inline Status compileToModule(shaders::Compiler* compiler, rhi::Device* device, + std::u8string_view hlslSource, shaders::ShaderStage stage, + std::u8string_view entryPoint, std::u8string_view label, + std::u8string_view shaderModel, + rhi::ShaderModule*& out) { + out = nullptr; + + bool isDX12 = (device->type == rhi::DeviceType::DX12); + auto target = isDX12 ? shaders::ShaderTarget::DXIL : shaders::ShaderTarget::SPIRV; + + shaders::CompileOptions opts{}; + opts.shaderModel = shaderModel; + opts.optimizationLevel = 3; + + // Vulkan needs binding shifts; DX12 uses register spaces natively. + if (!isDX12) { + opts.bindingShifts.constantBufferShift = 0; + opts.bindingShifts.textureShift = 1000; + opts.bindingShifts.uavShift = 2000; + opts.bindingShifts.samplerShift = 3000; + opts.bindingShiftSets = 4; + } + + shaders::CompileResult cr{}; + Status r = compiler->compile( + reinterpret_cast(hlslSource.data()), hlslSource.size(), + stage, entryPoint, target, opts, cr); + + if (r != ErrorCode::Ok) { + if (cr.messages) { + const std::u8string lbl = std::u8string(label); + rhi::logErrorf("Shader compile failed (%s): %s", + reinterpret_cast(lbl.c_str()), cr.messages); + } + compiler->freeResult(cr); + return ErrorCode::Unknown; + } + + rhi::ShaderModuleDesc desc{}; + desc.code = std::span(cr.bytecode, cr.bytecodeSize); + desc.label = label; + r = device->createShaderModule(desc, out); + + compiler->freeResult(cr); + return r; +} + +/// Compile HLSL source to a ShaderModule. Default shader model 6.0. +inline Status compileToModule(shaders::Compiler* compiler, rhi::Device* device, + std::u8string_view hlslSource, shaders::ShaderStage stage, + std::u8string_view entryPoint, std::u8string_view label, + rhi::ShaderModule*& out) { + return compileToModule(compiler, device, hlslSource, stage, entryPoint, label, u8"6_0", out); +} + +} // namespace draco::samples::framework diff --git a/Samples/cpp/RHI/Sample001_Triangle/CMakeLists.txt b/Samples/cpp/RHI/Sample001_Triangle/CMakeLists.txt new file mode 100644 index 00000000..36b506c4 --- /dev/null +++ b/Samples/cpp/RHI/Sample001_Triangle/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample001_Triangle) diff --git a/Samples/cpp/RHI/Sample001_Triangle/Main.cpp b/Samples/cpp/RHI/Sample001_Triangle/Main.cpp new file mode 100644 index 00000000..e3e6e933 --- /dev/null +++ b/Samples/cpp/RHI/Sample001_Triangle/Main.cpp @@ -0,0 +1,175 @@ +#include +/// Renders a colored triangle using vertex buffer + render pipeline. + +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class TriangleSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample001 - Triangle"; } + +protected: + draco::Status onInit() override; + void onRender() override; + void onShutdown() override; + +private: + static constexpr const char8_t kShaderSource[] = u8R"( + struct VSInput { + float3 Position : TEXCOORD0; + float3 Color : TEXCOORD1; + }; + struct PSInput { + float4 Position : SV_POSITION; + float3 Color : TEXCOORD0; + }; + PSInput VSMain(VSInput input) { + PSInput output; + output.Position = float4(input.Position, 1.0); + output.Color = input.Color; + return output; + } + float4 PSMain(PSInput input) : SV_TARGET { + return float4(input.Color, 1.0); + } + )"; + + static constexpr float kVertexData[] = { + 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, + 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, + -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, + }; + + shaders::Compiler* m_compiler = nullptr; + rhi::Buffer* m_vertexBuf = nullptr; + rhi::ShaderModule* m_vs = nullptr; + rhi::ShaderModule* m_ps = nullptr; + rhi::BindGroupLayout* m_bgl = nullptr; + rhi::PipelineLayout* m_pl = nullptr; + rhi::RenderPipeline* m_pipeline = nullptr; + rhi::CommandPool* m_pool = nullptr; + rhi::Fence* m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +draco::Status TriangleSample::onInit() { + using draco::Status; + + // Shader compiler. + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (sf::compileToModule(m_compiler, m_device, kShaderSource, shaders::ShaderStage::Vertex, u8"VSMain", u8"TriangleVS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShaderSource, shaders::ShaderStage::Fragment, u8"PSMain", u8"TrianglePS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Vertex buffer. + rhi::BufferDesc bd{}; bd.size = sizeof(kVertexData); bd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; + bd.memory = rhi::MemoryLocation::GpuOnly; bd.label = u8"TriangleVB"; + if (m_device->createBuffer(bd, m_vertexBuf) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Upload. + rhi::TransferBatch* batch = nullptr; + m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vertexBuf, 0, std::span(reinterpret_cast(kVertexData), sizeof(kVertexData))); + batch->submit(); + m_graphicsQueue->destroyTransferBatch(batch); + + // Pipeline layout (empty). + rhi::BindGroupLayoutDesc bglDesc{}; bglDesc.label = u8"EmptyBGL"; + if (m_device->createBindGroupLayout(bglDesc, m_bgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::PipelineLayoutDesc pld{}; + rhi::BindGroupLayout* sets[1] = { m_bgl }; + pld.bindGroupLayouts = std::span(sets, 1); + pld.label = u8"TrianglePL"; + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Render pipeline. + rhi::VertexAttribute attrs[2] = { + { rhi::VertexFormat::Float32x3, 0, 0 }, + { rhi::VertexFormat::Float32x3, 12, 1 }, + }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 24; vbl.attributes = std::span(attrs, 2); + + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); ct.writeMask = rhi::ColorWriteMask::All; + + rhi::RenderPipelineDesc rpd{}; + rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; + rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.primitive.topology = rhi::PrimitiveTopology::TriangleList; + rpd.label = u8"TrianglePipeline"; + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Command pool + fence. + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + return draco::ErrorCode::Ok; +} + +void TriangleSample::onRender() { + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + + rhi::ColorAttachment ca{}; + ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.1f, 0.1f, 0.15f, 1.0f); + + rhi::RenderPassDesc rpd{}; + rpd.colorAttachments.push_back(ca); + + auto* rp = enc->beginRenderPass(rpd); + rp->setPipeline(m_pipeline); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0, 1); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vertexBuf, 0); + rp->draw(3); + rp->end(); + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + + rhi::CommandBuffer* cb = enc->finish(); + m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void TriangleSample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); + if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); + if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_bgl) m_device->destroyBindGroupLayout(m_bgl); + if (m_ps) m_device->destroyShaderModule(m_ps); + if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_vertexBuf) m_device->destroyBuffer(m_vertexBuf); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { TriangleSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample002_Textures/CMakeLists.txt b/Samples/cpp/RHI/Sample002_Textures/CMakeLists.txt new file mode 100644 index 00000000..2c98b126 --- /dev/null +++ b/Samples/cpp/RHI/Sample002_Textures/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample002_Textures) diff --git a/Samples/cpp/RHI/Sample002_Textures/Main.cpp b/Samples/cpp/RHI/Sample002_Textures/Main.cpp new file mode 100644 index 00000000..0d3f89f1 --- /dev/null +++ b/Samples/cpp/RHI/Sample002_Textures/Main.cpp @@ -0,0 +1,206 @@ +#include +/// Renders a checkerboard-textured quad using texture, sampler, bind group. + +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class TextureSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample002 - Textured Quad"; } + +protected: + draco::Status onInit() override; + void onRender() override; + void onShutdown() override; + +private: + static constexpr const char8_t kShaderSource[] = u8R"( + Texture2D gTexture : register(t0, space0); + SamplerState gSampler : register(s0, space0); + struct VSInput { float3 Position : TEXCOORD0; float2 TexCoord : TEXCOORD1; }; + struct PSInput { float4 Position : SV_POSITION; float2 TexCoord : TEXCOORD0; }; + PSInput VSMain(VSInput input) { + PSInput output; + output.Position = float4(input.Position, 1.0); + output.TexCoord = input.TexCoord; + return output; + } + float4 PSMain(PSInput input) : SV_TARGET { + return gTexture.Sample(gSampler, input.TexCoord); + } + )"; + + static constexpr float kVertexData[] = { + -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, + 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, + 0.5f, -0.5f, 0.0f, 1.0f, 1.0f, + -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, + }; + static constexpr draco::u16 kIndexData[] = { 0, 1, 2, 0, 2, 3 }; + + shaders::Compiler* m_compiler = nullptr; + rhi::Buffer* m_vb = nullptr; + rhi::Buffer* m_ib = nullptr; + rhi::ShaderModule* m_vs = nullptr; + rhi::ShaderModule* m_ps = nullptr; + rhi::Texture* m_tex = nullptr; + rhi::TextureView* m_texView = nullptr; + rhi::Sampler* m_sampler = nullptr; + rhi::BindGroupLayout* m_bgl = nullptr; + rhi::BindGroup* m_bg = nullptr; + rhi::PipelineLayout* m_pl = nullptr; + rhi::RenderPipeline* m_pipeline = nullptr; + rhi::CommandPool* m_pool = nullptr; + rhi::Fence* m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +draco::Status TextureSample::onInit() { + using draco::Status, std::span, draco::u8, draco::u32; + + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShaderSource, shaders::ShaderStage::Vertex, u8"VSMain", u8"QuadVS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShaderSource, shaders::ShaderStage::Fragment, u8"PSMain", u8"QuadPS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Vertex + index buffers. + rhi::BufferDesc vbd{}; vbd.size = sizeof(kVertexData); vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ibd{}; ibd.size = sizeof(kIndexData); ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; ibd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Checkerboard texture 64x64 RGBA8. + constexpr u32 tw = 64, th = 64; + u8 texPixels[tw * th * 4]; + for (u32 y = 0; y < th; ++y) + for (u32 x = 0; x < tw; ++x) { + bool checker = ((x / 8) + (y / 8)) % 2 == 0; + u32 i = (y * tw + x) * 4; + texPixels[i] = checker ? 255 : 50; texPixels[i+1] = checker ? 255 : 50; + texPixels[i+2] = checker ? 255 : 200; texPixels[i+3] = 255; + } + + rhi::TextureDesc td{}; td.format = rhi::TextureFormat::RGBA8Unorm; td.width = tw; td.height = th; + td.usage = rhi::TextureUsage::Sampled | rhi::TextureUsage::CopyDst; + if (m_device->createTexture(td, m_tex) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Upload. + rhi::TransferBatch* batch = nullptr; + m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(kVertexData), sizeof(kVertexData))); + batch->writeBuffer(m_ib, 0, std::span(reinterpret_cast(kIndexData), sizeof(kIndexData))); + rhi::TextureDataLayout layout{}; layout.bytesPerRow = tw * 4; layout.rowsPerImage = th; + batch->writeTexture(m_tex, std::span(texPixels, sizeof(texPixels)), layout, rhi::Extent3D{tw, th, 1}); + batch->submit(); + m_graphicsQueue->destroyTransferBatch(batch); + + // Texture view + sampler. + rhi::TextureViewDesc tvd{}; tvd.format = rhi::TextureFormat::RGBA8Unorm; tvd.mipLevelCount = 1; tvd.arrayLayerCount = 1; + if (m_device->createTextureView(m_tex, tvd, m_texView) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::SamplerDesc sd{}; sd.minFilter = rhi::FilterMode::Nearest; sd.magFilter = rhi::FilterMode::Nearest; + if (m_device->createSampler(sd, m_sampler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Bind group layout + bind group. + rhi::BindGroupLayoutEntry bglEntries[2] = { + rhi::BindGroupLayoutEntry::sampledTexture(0, rhi::ShaderStage::Fragment), + rhi::BindGroupLayoutEntry::sampler(0, rhi::ShaderStage::Fragment), + }; + rhi::BindGroupLayoutDesc bgld{}; bgld.entries = std::span(bglEntries, 2); + if (m_device->createBindGroupLayout(bgld, m_bgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::BindGroupEntry bgEntries[2] = { + rhi::BindGroupEntry::textureEntry(m_texView), + rhi::BindGroupEntry::samplerEntry(m_sampler), + }; + rhi::BindGroupDesc bgd{}; bgd.layout = m_bgl; bgd.entries = std::span(bgEntries, 2); + if (m_device->createBindGroup(bgd, m_bg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Pipeline layout. + rhi::BindGroupLayout* sets[1] = { m_bgl }; + rhi::PipelineLayoutDesc pld{}; pld.bindGroupLayouts = std::span(sets, 1); + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Render pipeline. + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x2, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 20; vbl.attributes = std::span(attrs, 2); + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); ct.writeMask = rhi::ColorWriteMask::All; + + rhi::RenderPipelineDesc rpd{}; + rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + return draco::ErrorCode::Ok; +} + +void TextureSample::onRender() { + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + m_pool->reset(); + + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.2f, 0.2f, 0.25f, 1.0f); + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); + + auto* rp = enc->beginRenderPass(rpd); + rp->setPipeline(m_pipeline); + rp->setBindGroup(0, m_bg); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0, 1); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vb, 0); + rp->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + rp->drawIndexed(6); + rp->end(); + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + + rhi::CommandBuffer* cb = enc->finish(); + m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void TextureSample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); + if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); + if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_bg) m_device->destroyBindGroup(m_bg); + if (m_bgl) m_device->destroyBindGroupLayout(m_bgl); + if (m_sampler) m_device->destroySampler(m_sampler); + if (m_texView) m_device->destroyTextureView(m_texView); + if (m_tex) m_device->destroyTexture(m_tex); + if (m_ps) m_device->destroyShaderModule(m_ps); + if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_ib) m_device->destroyBuffer(m_ib); + if (m_vb) m_device->destroyBuffer(m_vb); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { TextureSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample003_UniformBuffers/CMakeLists.txt b/Samples/cpp/RHI/Sample003_UniformBuffers/CMakeLists.txt new file mode 100644 index 00000000..80c36c47 --- /dev/null +++ b/Samples/cpp/RHI/Sample003_UniformBuffers/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample003_UniformBuffers) diff --git a/Samples/cpp/RHI/Sample003_UniformBuffers/Main.cpp b/Samples/cpp/RHI/Sample003_UniformBuffers/Main.cpp new file mode 100644 index 00000000..5255eb95 --- /dev/null +++ b/Samples/cpp/RHI/Sample003_UniformBuffers/Main.cpp @@ -0,0 +1,195 @@ +#include +/// Sample003 - Rotating Cube with Uniform Buffers + Push Constants. + +#include +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; +using draco::math::Matrix4; + +class UniformBufferSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample003 - Rotating Cube (Uniform Buffers)"; } + +protected: + draco::Status onInit() override; + void onRender() override; + void onResize(draco::u32 w, draco::u32 h) override { m_depthBuf.recreate(m_device, w, h); } + void onShutdown() override; + +private: + static constexpr const char8_t kShaderSource[] = u8R"( + cbuffer UBO : register(b0, space0) { row_major float4x4 MVP; }; + struct PushData { float4 Tint; }; + [[vk::push_constant]] ConstantBuffer gPush : register(b0, space1); + struct VSInput { float3 Position : TEXCOORD0; float3 Color : TEXCOORD1; }; + struct PSInput { float4 Position : SV_POSITION; float3 Color : COLOR0; }; + PSInput VSMain(VSInput input) { + PSInput o; o.Position = mul(float4(input.Position, 1.0), MVP); o.Color = input.Color; return o; + } + float4 PSMain(PSInput input) : SV_TARGET { return float4(input.Color * gPush.Tint.rgb, 1.0); } + )"; + + static constexpr float kCubeVerts[] = { + -0.5f,-0.5f,-0.5f, 1,0,0, 0.5f,-0.5f,-0.5f, 0,1,0, 0.5f, 0.5f,-0.5f, 0,0,1, -0.5f, 0.5f,-0.5f, 1,1,0, + -0.5f,-0.5f, 0.5f, 1,0,1, 0.5f,-0.5f, 0.5f, 0,1,1, 0.5f, 0.5f, 0.5f, 1,1,1, -0.5f, 0.5f, 0.5f, .5f,.5f,.5f, + }; + static constexpr draco::u16 kCubeIdx[] = { + 0,2,1, 0,3,2, 4,5,6, 4,6,7, 4,7,3, 4,3,0, 1,2,6, 1,6,5, 3,7,6, 3,6,2, 4,0,1, 4,1,5, + }; + + shaders::Compiler* m_compiler = nullptr; + rhi::Buffer *m_vb = nullptr, *m_ib = nullptr, *m_ub = nullptr; + rhi::ShaderModule *m_vs = nullptr, *m_ps = nullptr; + rhi::BindGroupLayout* m_bgl = nullptr; + rhi::BindGroup* m_bg = nullptr; + rhi::PipelineLayout* m_pl = nullptr; + rhi::RenderPipeline* m_pipeline = nullptr; + rhi::CommandPool* m_pool = nullptr; + rhi::Fence* m_fence = nullptr; + draco::u64 m_fenceVal = 0; + void* m_ubMapped = nullptr; + sf::DepthBuffer m_depthBuf; +}; + +draco::Status UniformBufferSample::onInit() { + using draco::Status, std::span, draco::u8; + + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShaderSource, shaders::ShaderStage::Vertex, u8"VSMain", u8"CubeVS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShaderSource, shaders::ShaderStage::Fragment, u8"PSMain", u8"CubePS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Buffers. + rhi::BufferDesc vbd{}; vbd.size = sizeof(kCubeVerts); vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ibd{}; ibd.size = sizeof(kCubeIdx); ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; ibd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ubd{}; ubd.size = 64; ubd.usage = rhi::BufferUsage::Uniform; ubd.memory = rhi::MemoryLocation::CpuToGpu; + if (m_device->createBuffer(ubd, m_ub) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + m_ubMapped = m_ub->map(); + + // Upload. + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(kCubeVerts), sizeof(kCubeVerts))); + batch->writeBuffer(m_ib, 0, std::span(reinterpret_cast(kCubeIdx), sizeof(kCubeIdx))); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + // Bind group layout + group. + rhi::BindGroupLayoutEntry bglE[1] = { rhi::BindGroupLayoutEntry::uniformBuffer(0, rhi::ShaderStage::Vertex | rhi::ShaderStage::Fragment) }; + rhi::BindGroupLayoutDesc bgld{}; bgld.entries = std::span(bglE, 1); + if (m_device->createBindGroupLayout(bgld, m_bgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::BindGroupEntry bgE[1] = { rhi::BindGroupEntry::bufferEntry(m_ub, 0, 64) }; + rhi::BindGroupDesc bgd{}; bgd.layout = m_bgl; bgd.entries = std::span(bgE, 1); + if (m_device->createBindGroup(bgd, m_bg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Pipeline layout with push constants. + rhi::BindGroupLayout* sets[1] = { m_bgl }; + rhi::PushConstantRange pc{ rhi::ShaderStage::Vertex | rhi::ShaderStage::Fragment, 0, 16 }; + rhi::PipelineLayoutDesc pld{}; + pld.bindGroupLayouts = std::span(sets, 1); + pld.pushConstantRanges = std::span(&pc, 1); + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Depth buffer. + m_depthBuf.recreate(m_device, m_width, m_height); + + // Render pipeline. + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x3, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 24; vbl.attributes = std::span(attrs, 2); + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + + rhi::RenderPipelineDesc rpd{}; + rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.primitive = { rhi::PrimitiveTopology::TriangleList, rhi::FrontFace::CW, rhi::CullMode::Back }; + rpd.depthStencil = rhi::DepthStencilState{}; rpd.depthStencil->format = rhi::TextureFormat::Depth24PlusStencil8; + rpd.depthStencil->depthCompare = rhi::CompareFunction::Less; + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void UniformBufferSample::onRender() { + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + // Update MVP. + draco::f32 aspect = static_cast(m_width) / static_cast(m_height); + draco::f32 angle = m_totalTime * 1.2f; + Matrix4 model = (Matrix4::rotationY(angle) * Matrix4::rotationX( angle * 0.7f)); + Matrix4 view = Matrix4::lookAtRH(draco::math::Vector3{0, 1.5f, -3}, draco::math::Vector3{ 0, 0, 0}, draco::math::Vector3{ 0, 1, 0}); + Matrix4 proj = Matrix4::perspectiveFovRH(draco::math::degToRad(45.0f), aspect, 0.1f, 100.0f); + Matrix4 mvp = model * view * proj; + std::memcpy(m_ubMapped, mvp.data(), 64); + + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + enc->transitionTexture(m_depthBuf.texture, rhi::ResourceState::Undefined, rhi::ResourceState::DepthStencilWrite); + + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.1f, 0.1f, 0.15f, 1.0f); + rhi::DepthStencilAttachment dsa{}; dsa.view = m_depthBuf.view; + dsa.depthLoadOp = rhi::LoadOp::Clear; dsa.depthStoreOp = rhi::StoreOp::Store; dsa.depthClearValue = 1.0f; + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); rpd.depthStencilAttachment = dsa; + + auto* rp = enc->beginRenderPass(rpd); + rp->setPipeline(m_pipeline); + rp->setBindGroup(0, m_bg); + draco::f32 pulse = (std::sin(m_totalTime * 2.0f) * 0.3f + 0.7f); + draco::f32 tint[4] = { pulse, pulse, pulse, 1.0f }; + rp->setPushConstants(rhi::ShaderStage::Vertex | rhi::ShaderStage::Fragment, 0, 16, tint); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0, 1); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vb, 0); + rp->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + rp->drawIndexed(36); + rp->end(); + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void UniformBufferSample::onShutdown() { + m_depthBuf.destroy(m_device); + if (m_fence) m_device->destroyFence(m_fence); + if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); + if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_bg) m_device->destroyBindGroup(m_bg); + if (m_bgl) m_device->destroyBindGroupLayout(m_bgl); + if (m_ub) m_device->destroyBuffer(m_ub); + if (m_ib) m_device->destroyBuffer(m_ib); + if (m_vb) m_device->destroyBuffer(m_vb); + if (m_ps) m_device->destroyShaderModule(m_ps); + if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { UniformBufferSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample004_Compute/CMakeLists.txt b/Samples/cpp/RHI/Sample004_Compute/CMakeLists.txt new file mode 100644 index 00000000..8fe81e81 --- /dev/null +++ b/Samples/cpp/RHI/Sample004_Compute/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample004_Compute) diff --git a/Samples/cpp/RHI/Sample004_Compute/Main.cpp b/Samples/cpp/RHI/Sample004_Compute/Main.cpp new file mode 100644 index 00000000..59b437b3 --- /dev/null +++ b/Samples/cpp/RHI/Sample004_Compute/Main.cpp @@ -0,0 +1,214 @@ +#include +/// Sample004 - Compute Shader (Animated Point Grid). + +#include +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; +using draco::math::Matrix4; + +class ComputeSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample004 - Compute (Animated Point Grid)"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onResize(draco::u32 w, draco::u32 h) override { m_depthBuf.recreate(m_device, w, h); } + void onShutdown() override; +private: + static constexpr const char8_t kComputeSrc[] = u8R"( + cbuffer Params : register(b0, space0) { float Time; uint NumPoints; float Spacing; float Padding; }; + struct Vertex { float PosX, PosY, PosZ, ColR, ColG, ColB; }; + RWStructuredBuffer gVertices : register(u0, space0); + [numthreads(64, 1, 1)] + void CSMain(uint3 dtid : SV_DispatchThreadID) { + uint idx = dtid.x; if (idx >= NumPoints) return; + uint gridSize = (uint)sqrt((float)NumPoints); + uint row = idx / gridSize, col = idx % gridSize; + float fx = ((float)col / (float)(gridSize-1))*2.0 - 1.0; + float fz = ((float)row / (float)(gridSize-1))*2.0 - 1.0; + float dist = sqrt(fx*fx + fz*fz); + float fy = sin(dist*6.0 - Time*2.0) * 0.15; + gVertices[idx].PosX = fx; gVertices[idx].PosY = fy; gVertices[idx].PosZ = fz; + gVertices[idx].ColR = fx*0.5+0.5; gVertices[idx].ColG = fy*2.0+0.5; gVertices[idx].ColB = fz*0.5+0.5; + } + )"; + static constexpr const char8_t kRenderSrc[] = u8R"( + cbuffer ViewProj : register(b0, space0) { row_major float4x4 VP; }; + struct VSInput { float3 Position : TEXCOORD0; float3 Color : TEXCOORD1; }; + struct PSInput { float4 Position : SV_POSITION; float3 Color : COLOR0; + [[vk::builtin("PointSize")]] float PointSize : PSIZE; }; + PSInput VSMain(VSInput i) { PSInput o; o.Position = mul(float4(i.Position,1), VP); o.Color = i.Color; o.PointSize = 1.0; return o; } + float4 PSMain(PSInput i) : SV_TARGET { return float4(i.Color, 1.0); } + )"; + + static constexpr draco::u32 kGrid = 64, kNumPts = kGrid*kGrid, kVertSz = 24, kBufSz = kNumPts*kVertSz; + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_cs = nullptr, *m_vs = nullptr, *m_ps = nullptr; + rhi::Buffer *m_vtxBuf = nullptr, *m_paramsBuf = nullptr, *m_vpBuf = nullptr; + void *m_paramsMapped = nullptr, *m_vpMapped = nullptr; + rhi::BindGroupLayout *m_compBgl = nullptr, *m_renBgl = nullptr; + rhi::BindGroup *m_compBg = nullptr, *m_renBg = nullptr; + rhi::PipelineLayout *m_compPl = nullptr, *m_renPl = nullptr; + rhi::ComputePipeline *m_compPipe = nullptr; + rhi::RenderPipeline *m_renPipe = nullptr; + rhi::CommandPool *m_pool = nullptr; rhi::Fence *m_fence = nullptr; + draco::u64 m_fenceVal = 0; + sf::DepthBuffer m_depthBuf; +}; + +draco::Status ComputeSample::onInit() { + using draco::Status, std::span, draco::u8, draco::u32; + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kComputeSrc, shaders::ShaderStage::Compute, u8"CSMain", u8"CS", m_cs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kRenderSrc, shaders::ShaderStage::Vertex, u8"VSMain", u8"VS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kRenderSrc, shaders::ShaderStage::Fragment,u8"PSMain", u8"PS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Buffers. + rhi::BufferDesc vbd{}; vbd.size = kBufSz; vbd.usage = rhi::BufferUsage::Storage | rhi::BufferUsage::Vertex; vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vtxBuf) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc pbd{}; pbd.size = 16; pbd.usage = rhi::BufferUsage::Uniform; pbd.memory = rhi::MemoryLocation::CpuToGpu; + if (m_device->createBuffer(pbd, m_paramsBuf) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + m_paramsMapped = m_paramsBuf->map(); + rhi::BufferDesc vpd{}; vpd.size = 64; vpd.usage = rhi::BufferUsage::Uniform; vpd.memory = rhi::MemoryLocation::CpuToGpu; + if (m_device->createBuffer(vpd, m_vpBuf) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + m_vpMapped = m_vpBuf->map(); + + // Compute BGL + BG + PL + pipeline. + rhi::BindGroupLayoutEntry cE[2] = { rhi::BindGroupLayoutEntry::uniformBuffer(0, rhi::ShaderStage::Compute), + rhi::BindGroupLayoutEntry::storageBuffer(0, rhi::ShaderStage::Compute, false) }; + rhi::BindGroupLayoutDesc cBgld{}; cBgld.entries = std::span(cE, 2); + if (m_device->createBindGroupLayout(cBgld, m_compBgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BindGroupEntry cBgE[2] = { rhi::BindGroupEntry::bufferEntry(m_paramsBuf, 0, 16), + rhi::BindGroupEntry::bufferEntry(m_vtxBuf, 0, kBufSz) }; + rhi::BindGroupDesc cBgd{}; cBgd.layout = m_compBgl; cBgd.entries = std::span(cBgE, 2); + if (m_device->createBindGroup(cBgd, m_compBg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BindGroupLayout* cSets[1] = { m_compBgl }; + rhi::PipelineLayoutDesc cPld{}; cPld.bindGroupLayouts = std::span(cSets, 1); + if (m_device->createPipelineLayout(cPld, m_compPl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::ComputePipelineDesc cpd{}; cpd.layout = m_compPl; cpd.compute = { m_cs, u8"CSMain", rhi::ShaderStage::Compute }; + if (m_device->createComputePipeline(cpd, m_compPipe) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Render BGL + BG + PL + pipeline. + rhi::BindGroupLayoutEntry rE[1] = { rhi::BindGroupLayoutEntry::uniformBuffer(0, rhi::ShaderStage::Vertex) }; + rhi::BindGroupLayoutDesc rBgld{}; rBgld.entries = std::span(rE, 1); + if (m_device->createBindGroupLayout(rBgld, m_renBgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BindGroupEntry rBgE[1] = { rhi::BindGroupEntry::bufferEntry(m_vpBuf, 0, 64) }; + rhi::BindGroupDesc rBgd{}; rBgd.layout = m_renBgl; rBgd.entries = std::span(rBgE, 1); + if (m_device->createBindGroup(rBgd, m_renBg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BindGroupLayout* rSets[1] = { m_renBgl }; + rhi::PipelineLayoutDesc rPld{}; rPld.bindGroupLayouts = std::span(rSets, 1); + if (m_device->createPipelineLayout(rPld, m_renPl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + m_depthBuf.recreate(m_device, m_width, m_height); + + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x3, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = kVertSz; vbl.attributes = std::span(attrs, 2); + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_renPl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.primitive.topology = rhi::PrimitiveTopology::PointList; + rpd.depthStencil = rhi::DepthStencilState{}; rpd.depthStencil->format = rhi::TextureFormat::Depth24PlusStencil8; + rpd.depthStencil->depthCompare = rhi::CompareFunction::Less; + if (m_device->createRenderPipeline(rpd, m_renPipe) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void ComputeSample::onRender() { + using draco::u32, draco::f32, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + // Update params. + u32 numPts = kNumPts; + f32 params[4] = { m_totalTime, 0, 1.0f, 0 }; + std::memcpy(¶ms[1], &numPts, 4); + std::memcpy(m_paramsMapped, params, 16); + + // Update VP. + f32 aspect = static_cast(m_width) / static_cast(m_height); + f32 camAngle = m_totalTime * 0.3f, camDist = 2.5f; + Matrix4 view = Matrix4::lookAtRH(draco::math::Vector3{std::sin(camAngle)*camDist, 1.2f, std::cos(camAngle)*camDist}, draco::math::Vector3{ 0,0,0}, draco::math::Vector3{0,1,0}); + Matrix4 proj = Matrix4::perspectiveFovRH(draco::math::degToRad(45.0f), aspect, 0.1f, 100.0f); + Matrix4 vp = view * proj; + std::memcpy(m_vpMapped, vp.data(), 64); + + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + // Compute pass. + rhi::BufferBarrier bb{}; bb.buffer = m_vtxBuf; bb.oldState = rhi::ResourceState::VertexBuffer; bb.newState = rhi::ResourceState::ShaderWrite; + rhi::BarrierGroup bg1{}; bg1.bufferBarriers = std::span(&bb, 1); + enc->barrier(bg1); + auto* cp = enc->beginComputePass(u8"GenerateVertices"); + cp->setPipeline(m_compPipe); cp->setBindGroup(0, m_compBg); + cp->dispatch((kNumPts + 63) / 64); cp->end(); + bb.oldState = rhi::ResourceState::ShaderWrite; bb.newState = rhi::ResourceState::VertexBuffer; + enc->barrier(bg1); + + // Render pass. + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + enc->transitionTexture(m_depthBuf.texture, rhi::ResourceState::Undefined, rhi::ResourceState::DepthStencilWrite); + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.05f, 0.05f, 0.08f, 1.0f); + rhi::DepthStencilAttachment dsa{}; dsa.view = m_depthBuf.view; + dsa.depthLoadOp = rhi::LoadOp::Clear; dsa.depthStoreOp = rhi::StoreOp::Store; dsa.depthClearValue = 1.0f; + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); rpd.depthStencilAttachment = dsa; + auto* rp = enc->beginRenderPass(rpd); + rp->setPipeline(m_renPipe); rp->setBindGroup(0, m_renBg); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0, 1); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vtxBuf, 0); + rp->draw(kNumPts); rp->end(); + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void ComputeSample::onShutdown() { + m_depthBuf.destroy(m_device); + if (m_fence) m_device->destroyFence(m_fence); + if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_renPipe) m_device->destroyRenderPipeline(m_renPipe); + if (m_renPl) m_device->destroyPipelineLayout(m_renPl); + if (m_renBg) m_device->destroyBindGroup(m_renBg); + if (m_renBgl) m_device->destroyBindGroupLayout(m_renBgl); + if (m_compPipe) m_device->destroyComputePipeline(m_compPipe); + if (m_compPl) m_device->destroyPipelineLayout(m_compPl); + if (m_compBg) m_device->destroyBindGroup(m_compBg); + if (m_compBgl) m_device->destroyBindGroupLayout(m_compBgl); + if (m_vpBuf) m_device->destroyBuffer(m_vpBuf); + if (m_paramsBuf) m_device->destroyBuffer(m_paramsBuf); + if (m_vtxBuf) m_device->destroyBuffer(m_vtxBuf); + if (m_ps) m_device->destroyShaderModule(m_ps); + if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_cs) m_device->destroyShaderModule(m_cs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { ComputeSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample005_BindGroups/CMakeLists.txt b/Samples/cpp/RHI/Sample005_BindGroups/CMakeLists.txt new file mode 100644 index 00000000..53510f82 --- /dev/null +++ b/Samples/cpp/RHI/Sample005_BindGroups/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample005_BindGroups) diff --git a/Samples/cpp/RHI/Sample005_BindGroups/Main.cpp b/Samples/cpp/RHI/Sample005_BindGroups/Main.cpp new file mode 100644 index 00000000..0bdcf943 --- /dev/null +++ b/Samples/cpp/RHI/Sample005_BindGroups/Main.cpp @@ -0,0 +1,220 @@ +#include +/// Sample005 - Multiple Bind Groups with Dynamic Offsets. +/// 4x4 grid of lit cubes, each with unique color via dynamic offset. + +#include +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; +using draco::math::Matrix4; + +class BindGroupSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample005 - Multiple Bind Groups"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onResize(draco::u32 w, draco::u32 h) override { m_depthBuf.recreate(m_device, w, h); } + void onShutdown() override; +private: + static constexpr const char8_t kShader[] = u8R"( + cbuffer GlobalUBO : register(b0, space0) { row_major float4x4 VP; }; + cbuffer ObjectUBO : register(b0, space1) { row_major float4x4 Model; float4 ObjColor; }; + struct VSInput { float3 Position : TEXCOORD0; float3 Normal : TEXCOORD1; }; + struct PSInput { float4 Position : SV_POSITION; float3 Normal : NORMAL; float4 Color : COLOR; }; + PSInput VSMain(VSInput i) { + PSInput o; + float4 wp = mul(float4(i.Position, 1.0), Model); + o.Position = mul(wp, VP); + o.Normal = mul(i.Normal, (float3x3)Model); + o.Color = ObjColor; + return o; + } + float4 PSMain(PSInput i) : SV_TARGET { + float3 ld = normalize(float3(0.5, 1.0, -0.7)); + float ndotl = max(dot(normalize(i.Normal), ld), 0.0); + return float4(i.Color.rgb * (0.2 + 0.8 * ndotl), 1.0); + } + )"; + + static constexpr int kGrid = 4, kObjCount = kGrid * kGrid; + static constexpr draco::u32 kObjStride = 256; // DX12 CBV alignment + + // Cube with face normals (24 verts, 36 indices). + struct Vert { float px,py,pz, nx,ny,nz; }; + static constexpr Vert kCubeV[24] = { + {-.5f,-.5f,-.5f, 0,0,-1},{.5f,-.5f,-.5f, 0,0,-1},{.5f,.5f,-.5f, 0,0,-1},{-.5f,.5f,-.5f, 0,0,-1}, + {.5f,-.5f,.5f, 0,0,1},{-.5f,-.5f,.5f, 0,0,1},{-.5f,.5f,.5f, 0,0,1},{.5f,.5f,.5f, 0,0,1}, + {-.5f,-.5f,.5f,-1,0,0},{-.5f,-.5f,-.5f,-1,0,0},{-.5f,.5f,-.5f,-1,0,0},{-.5f,.5f,.5f,-1,0,0}, + {.5f,-.5f,-.5f,1,0,0},{.5f,-.5f,.5f,1,0,0},{.5f,.5f,.5f,1,0,0},{.5f,.5f,-.5f,1,0,0}, + {-.5f,.5f,-.5f,0,1,0},{.5f,.5f,-.5f,0,1,0},{.5f,.5f,.5f,0,1,0},{-.5f,.5f,.5f,0,1,0}, + {-.5f,-.5f,.5f,0,-1,0},{.5f,-.5f,.5f,0,-1,0},{.5f,-.5f,-.5f,0,-1,0},{-.5f,-.5f,-.5f,0,-1,0}, + }; + static constexpr draco::u16 kCubeI[36] = { + 0,1,2,0,2,3, 4,5,6,4,6,7, 8,9,10,8,10,11, 12,13,14,12,14,15, 16,17,18,16,18,19, 20,21,22,20,22,23 + }; + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_vs = nullptr, *m_ps = nullptr; + rhi::Buffer *m_vb = nullptr, *m_ib = nullptr, *m_globalUbo = nullptr, *m_objUbo = nullptr; + void *m_globalMapped = nullptr, *m_objMapped = nullptr; + rhi::BindGroupLayout *m_globalBgl = nullptr, *m_objBgl = nullptr; + rhi::BindGroup *m_globalBg = nullptr, *m_objBg = nullptr; + rhi::PipelineLayout *m_pl = nullptr; + rhi::RenderPipeline *m_pipeline = nullptr; + rhi::CommandPool *m_pool = nullptr; rhi::Fence *m_fence = nullptr; + draco::u64 m_fenceVal = 0; + sf::DepthBuffer m_depthBuf; +}; + +draco::Status BindGroupSample::onInit() { + using draco::Status, std::span, draco::u8, draco::u32; + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"VS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"PS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Buffers. + rhi::BufferDesc vbd{}; vbd.size = sizeof(kCubeV); vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ibd{}; ibd.size = sizeof(kCubeI); ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; ibd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(kCubeV), sizeof(kCubeV))); + batch->writeBuffer(m_ib, 0, std::span(reinterpret_cast(kCubeI), sizeof(kCubeI))); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + rhi::BufferDesc gbd{}; gbd.size = 256; gbd.usage = rhi::BufferUsage::Uniform; gbd.memory = rhi::MemoryLocation::CpuToGpu; + if (m_device->createBuffer(gbd, m_globalUbo) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + m_globalMapped = m_globalUbo->map(); + rhi::BufferDesc obd{}; obd.size = kObjCount * kObjStride; obd.usage = rhi::BufferUsage::Uniform; obd.memory = rhi::MemoryLocation::CpuToGpu; + if (m_device->createBuffer(obd, m_objUbo) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + m_objMapped = m_objUbo->map(); + + // Set 0: global VP. + rhi::BindGroupLayoutEntry gE[1] = { rhi::BindGroupLayoutEntry::uniformBuffer(0, rhi::ShaderStage::Vertex) }; + rhi::BindGroupLayoutDesc gBgld{}; gBgld.entries = std::span(gE, 1); + if (m_device->createBindGroupLayout(gBgld, m_globalBgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BindGroupEntry gBgE[1] = { rhi::BindGroupEntry::bufferEntry(m_globalUbo, 0, 64) }; + rhi::BindGroupDesc gBgd{}; gBgd.layout = m_globalBgl; gBgd.entries = std::span(gBgE, 1); + if (m_device->createBindGroup(gBgd, m_globalBg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Set 1: per-object with dynamic offset. + rhi::BindGroupLayoutEntry oE[1] = { rhi::BindGroupLayoutEntry::uniformBuffer(0, rhi::ShaderStage::Vertex | rhi::ShaderStage::Fragment) }; + oE[0].hasDynamicOffset = true; + rhi::BindGroupLayoutDesc oBgld{}; oBgld.entries = std::span(oE, 1); + if (m_device->createBindGroupLayout(oBgld, m_objBgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BindGroupEntry oBgE[1] = { rhi::BindGroupEntry::bufferEntry(m_objUbo, 0, kObjStride) }; + rhi::BindGroupDesc oBgd{}; oBgd.layout = m_objBgl; oBgd.entries = std::span(oBgE, 1); + if (m_device->createBindGroup(oBgd, m_objBg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Pipeline layout with 2 sets. + rhi::BindGroupLayout* sets[2] = { m_globalBgl, m_objBgl }; + rhi::PipelineLayoutDesc pld{}; pld.bindGroupLayouts = std::span(sets, 2); + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + m_depthBuf.recreate(m_device, m_width, m_height); + + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x3, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 24; vbl.attributes = std::span(attrs, 2); + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.primitive = { rhi::PrimitiveTopology::TriangleList, rhi::FrontFace::CW, rhi::CullMode::Back }; + rpd.depthStencil = rhi::DepthStencilState{}; rpd.depthStencil->format = rhi::TextureFormat::Depth24PlusStencil8; + rpd.depthStencil->depthCompare = rhi::CompareFunction::Less; + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void BindGroupSample::onRender() { + using draco::f32, draco::u32, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + // Update VP. + f32 aspect = static_cast(m_width) / static_cast(m_height); + f32 camAngle = m_totalTime * 0.3f, camDist = 8.0f; + Matrix4 view = Matrix4::lookAtRH(draco::math::Vector3{std::sin(camAngle)*camDist, 5.0f, -std::cos(camAngle)*camDist}, draco::math::Vector3{ 0,0,0}, draco::math::Vector3{0,1,0}); + Matrix4 proj = Matrix4::perspectiveFovRH(draco::math::degToRad(45.0f), aspect, 0.1f, 100.0f); + Matrix4 vp = view * proj; + std::memcpy(m_globalMapped, vp.data(), 64); + + // Update per-object. + static constexpr f32 kColors[kObjCount * 4] = { + 1,.3f,.3f,1, .3f,1,.3f,1, .3f,.3f,1,1, 1,1,.3f,1, 1,.3f,1,1, .3f,1,1,1, 1,.6f,.2f,1, .6f,.2f,1,1, + .2f,.8f,.6f,1, .8f,.8f,.8f,1, .5f,.3f,.1f,1, .9f,.5f,.7f,1, .4f,.7f,.2f,1, .2f,.4f,.8f,1, .8f,.4f,.4f,1, .6f,.6f,.3f,1 + }; + f32 spacing = 2.0f, half = (kGrid - 1) * spacing * 0.5f; + for (int r = 0; r < kGrid; ++r) for (int c = 0; c < kGrid; ++c) { + int idx = r * kGrid + c; + f32 angle = m_totalTime * (0.5f + idx * 0.1f); + Matrix4 model = Matrix4::rotationY(angle); + model.m[3][0] = c * spacing - half; model.m[3][1] = 0; model.m[3][2] = r * spacing - half; + auto* dest = static_cast(m_objMapped) + idx * kObjStride; + std::memcpy(dest, model.data(), 64); + std::memcpy(dest + 64, &kColors[idx * 4], 16); + } + + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + enc->transitionTexture(m_depthBuf.texture, rhi::ResourceState::Undefined, rhi::ResourceState::DepthStencilWrite); + + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; ca.clearValue = rhi::ClearColor(0.08f, 0.08f, 0.12f, 1); + rhi::DepthStencilAttachment dsa{}; dsa.view = m_depthBuf.view; + dsa.depthLoadOp = rhi::LoadOp::Clear; dsa.depthStoreOp = rhi::StoreOp::Store; dsa.depthClearValue = 1.0f; + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); rpd.depthStencilAttachment = dsa; + auto* rp = enc->beginRenderPass(rpd); + rp->setPipeline(m_pipeline); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0, 1); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vb, 0); + rp->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + rp->setBindGroup(0, m_globalBg); + for (int i = 0; i < kObjCount; ++i) { + u32 dynOff = static_cast(i * kObjStride); + rp->setBindGroup(1, m_objBg, std::span(&dynOff, 1)); + rp->drawIndexed(36); + } + rp->end(); + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); m_pool->destroyEncoder(enc); +} + +void BindGroupSample::onShutdown() { + m_depthBuf.destroy(m_device); + if (m_fence) m_device->destroyFence(m_fence); if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_objBg) m_device->destroyBindGroup(m_objBg); if (m_objBgl) m_device->destroyBindGroupLayout(m_objBgl); + if (m_globalBg) m_device->destroyBindGroup(m_globalBg); if (m_globalBgl) m_device->destroyBindGroupLayout(m_globalBgl); + if (m_objUbo) m_device->destroyBuffer(m_objUbo); if (m_globalUbo) m_device->destroyBuffer(m_globalUbo); + if (m_ib) m_device->destroyBuffer(m_ib); if (m_vb) m_device->destroyBuffer(m_vb); + if (m_ps) m_device->destroyShaderModule(m_ps); if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { BindGroupSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample006_Blending/CMakeLists.txt b/Samples/cpp/RHI/Sample006_Blending/CMakeLists.txt new file mode 100644 index 00000000..24532e83 --- /dev/null +++ b/Samples/cpp/RHI/Sample006_Blending/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample006_Blending) diff --git a/Samples/cpp/RHI/Sample006_Blending/Main.cpp b/Samples/cpp/RHI/Sample006_Blending/Main.cpp new file mode 100644 index 00000000..06cd5dde --- /dev/null +++ b/Samples/cpp/RHI/Sample006_Blending/Main.cpp @@ -0,0 +1,133 @@ +#include +/// Renders overlapping semi-transparent colored quads. + +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class BlendingSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample006 - Alpha Blending"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onShutdown() override; +private: + static constexpr const char8_t kShader[] = u8R"( + struct VSInput { float3 Position : TEXCOORD0; float4 Color : TEXCOORD1; }; + struct PSInput { float4 Position : SV_POSITION; float4 Color : COLOR0; }; + PSInput VSMain(VSInput i) { PSInput o; o.Position = float4(i.Position, 1.0); o.Color = i.Color; return o; } + float4 PSMain(PSInput i) : SV_TARGET { return i.Color; } + )"; + // 4 quads: background opaque + 3 overlapping translucent. + static constexpr float kVerts[] = { + -.9f,-.9f,.5f, .15f,.15f,.2f,1, .9f,-.9f,.5f, .15f,.15f,.2f,1, .9f,.9f,.5f, .15f,.15f,.2f,1, -.9f,.9f,.5f, .15f,.15f,.2f,1, + -.6f,-.4f,.3f, 1,.2f,.2f,.5f, .1f,-.4f,.3f, 1,.2f,.2f,.5f, .1f,.4f,.3f, 1,.2f,.2f,.5f, -.6f,.4f,.3f, 1,.2f,.2f,.5f, + -.3f,-.5f,.2f, .2f,1,.2f,.5f, .4f,-.5f,.2f, .2f,1,.2f,.5f, .4f,.3f,.2f, .2f,1,.2f,.5f, -.3f,.3f,.2f, .2f,1,.2f,.5f, + -.1f,-.3f,.1f, .2f,.3f,1,.5f, .6f,-.3f,.1f, .2f,.3f,1,.5f, .6f,.5f,.1f, .2f,.3f,1,.5f, -.1f,.5f,.1f, .2f,.3f,1,.5f, + }; + static constexpr draco::u16 kIdx[] = { 0,1,2,0,2,3, 4,5,6,4,6,7, 8,9,10,8,10,11, 12,13,14,12,14,15 }; + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_vs = nullptr, *m_ps = nullptr; + rhi::Buffer *m_vb = nullptr, *m_ib = nullptr; + rhi::PipelineLayout *m_pl = nullptr; + rhi::RenderPipeline *m_opaquePipe = nullptr, *m_blendPipe = nullptr; + rhi::CommandPool *m_pool = nullptr; rhi::Fence *m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +draco::Status BlendingSample::onInit() { + using draco::Status, std::span, draco::u8; + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"VS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"PS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::BufferDesc vbd{}; vbd.size = sizeof(kVerts); vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ibd{}; ibd.size = sizeof(kIdx); ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; ibd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(kVerts), sizeof(kVerts))); + batch->writeBuffer(m_ib, 0, std::span(reinterpret_cast(kIdx), sizeof(kIdx))); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + // Empty pipeline layout. + rhi::PipelineLayoutDesc pld{}; + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x4, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 28; vbl.attributes = std::span(attrs, 2); + rhi::ColorTargetState ctOpaque{}; ctOpaque.format = m_swapChain->format(); + rhi::ColorTargetState ctBlend{}; ctBlend.format = m_swapChain->format(); + ctBlend.blend = rhi::BlendState::alphaBlend(); + + // Opaque pipeline (for background quad). + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ctOpaque, 1); + if (m_device->createRenderPipeline(rpd, m_opaquePipe) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Blend pipeline (for translucent quads). + rpd.fragment->targets = std::span(&ctBlend, 1); + if (m_device->createRenderPipeline(rpd, m_blendPipe) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void BlendingSample::onRender() { + using draco::f32, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; ca.clearValue = rhi::ClearColor(0.05f, 0.05f, 0.08f, 1); + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); + auto* rp = enc->beginRenderPass(rpd); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0, 1); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vb, 0); + rp->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + // Draw background opaque. + rp->setPipeline(m_opaquePipe); rp->drawIndexed(6, 1, 0, 0, 0); + // Draw 3 translucent quads. + rp->setPipeline(m_blendPipe); rp->drawIndexed(18, 1, 6, 0, 0); + rp->end(); + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); m_pool->destroyEncoder(enc); +} + +void BlendingSample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_blendPipe) m_device->destroyRenderPipeline(m_blendPipe); + if (m_opaquePipe) m_device->destroyRenderPipeline(m_opaquePipe); + if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_ib) m_device->destroyBuffer(m_ib); if (m_vb) m_device->destroyBuffer(m_vb); + if (m_ps) m_device->destroyShaderModule(m_ps); if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { BlendingSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample007_Instancing/CMakeLists.txt b/Samples/cpp/RHI/Sample007_Instancing/CMakeLists.txt new file mode 100644 index 00000000..d0123af6 --- /dev/null +++ b/Samples/cpp/RHI/Sample007_Instancing/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample007_Instancing) diff --git a/Samples/cpp/RHI/Sample007_Instancing/Main.cpp b/Samples/cpp/RHI/Sample007_Instancing/Main.cpp new file mode 100644 index 00000000..4cb355fd --- /dev/null +++ b/Samples/cpp/RHI/Sample007_Instancing/Main.cpp @@ -0,0 +1,208 @@ +#include +/// Renders 64 small quads in a grid using instanced draw with per-instance offset + color. +/// Instance buffer is CpuToGpu with per-frame wobble animation. + +#include +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +struct InstanceData { + float offset[2]; + float color[4]; +}; + +class InstancingSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample007 - Instanced Rendering"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onShutdown() override; +private: + static constexpr const char8_t kShader[] = u8R"( + struct VSInput + { + float3 Position : TEXCOORD0; + float2 Offset : TEXCOORD1; + float4 InstColor : TEXCOORD2; + }; + struct PSInput + { + float4 Position : SV_POSITION; + float4 Color : COLOR0; + }; + PSInput VSMain(VSInput input) + { + PSInput output; + output.Position = float4(input.Position.xy + input.Offset, input.Position.z, 1.0); + output.Color = input.InstColor; + return output; + } + float4 PSMain(PSInput input) : SV_TARGET + { + return input.Color; + } + )"; + static constexpr int kInstanceCount = 64; + + // Unit quad vertices (pos only). + static constexpr float kQuadVerts[] = { + -0.04f, -0.04f, 0.0f, + 0.04f, -0.04f, 0.0f, + 0.04f, 0.04f, 0.0f, + -0.04f, 0.04f, 0.0f, + }; + static constexpr draco::u16 kQuadIdx[] = { 0, 1, 2, 0, 2, 3 }; + + void updateInstances(); + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_vs = nullptr, *m_ps = nullptr; + rhi::Buffer *m_vb = nullptr, *m_ib = nullptr, *m_instBuf = nullptr; + void* m_instMapped = nullptr; + rhi::PipelineLayout *m_pl = nullptr; + rhi::RenderPipeline *m_pipeline = nullptr; + rhi::CommandPool *m_pool = nullptr; rhi::Fence *m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +draco::Status InstancingSample::onInit() { + using draco::Status, std::span, draco::u8, draco::f32, draco::u32; + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"VS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"PS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Vertex + index buffers (GpuOnly, static quad geometry). + rhi::BufferDesc vbd{}; vbd.size = sizeof(kQuadVerts); vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ibd{}; ibd.size = sizeof(kQuadIdx); ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; ibd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(kQuadVerts), sizeof(kQuadVerts))); + batch->writeBuffer(m_ib, 0, std::span(reinterpret_cast(kQuadIdx), sizeof(kQuadIdx))); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + // Instance buffer (CpuToGpu for per-frame updates). + rhi::BufferDesc instBd{}; instBd.size = kInstanceCount * sizeof(InstanceData); instBd.usage = rhi::BufferUsage::Vertex; instBd.memory = rhi::MemoryLocation::CpuToGpu; + if (m_device->createBuffer(instBd, m_instBuf) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + m_instMapped = m_instBuf->map(); + if (!m_instMapped) return draco::ErrorCode::Unknown; + + // Pipeline layout (empty - no bind groups needed). + rhi::PipelineLayoutDesc pld{}; + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Two vertex buffer layouts: slot 0 = per-vertex, slot 1 = per-instance. + rhi::VertexAttribute vtxAttrs[1] = { {rhi::VertexFormat::Float32x3, 0, 0} }; + rhi::VertexBufferLayout vtxLayout{}; vtxLayout.stride = 12; vtxLayout.stepMode = rhi::VertexStepMode::Vertex; + vtxLayout.attributes = std::span(vtxAttrs, 1); + + rhi::VertexAttribute instAttrs[2] = { {rhi::VertexFormat::Float32x2, 0, 1}, {rhi::VertexFormat::Float32x4, 8, 2} }; + rhi::VertexBufferLayout instLayout{}; instLayout.stride = static_cast(sizeof(InstanceData)); instLayout.stepMode = rhi::VertexStepMode::Instance; + instLayout.attributes = std::span(instAttrs, 2); + + rhi::VertexBufferLayout layouts[2] = { vtxLayout, instLayout }; + + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(layouts, 2); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void InstancingSample::updateInstances() { + auto* data = static_cast(m_instMapped); + int gridSize = static_cast(std::sqrt(static_cast(kInstanceCount))); + + for (int i = 0; i < kInstanceCount; ++i) { + int row = i / gridSize; + int col = i % gridSize; + + float spacing = 2.0f / static_cast(gridSize); + float baseX = -1.0f + spacing * 0.5f + col * spacing; + float baseY = -1.0f + spacing * 0.5f + row * spacing; + + // Animate: wobble in a circle. + float phase = m_totalTime * 2.0f + i * 0.3f; + float wobbleX = std::sin(phase) * 0.02f; + float wobbleY = std::cos(phase * 1.3f) * 0.02f; + + data[i].offset[0] = baseX + wobbleX; + data[i].offset[1] = baseY + wobbleY; + + // Color: hue based on index. + float t = static_cast(i) / static_cast(kInstanceCount); + constexpr float pi2 = 3.14159265f * 2.0f; + data[i].color[0] = std::abs(std::sin(t * pi2)); + data[i].color[1] = std::abs(std::sin(t * pi2 + 2.094f)); + data[i].color[2] = std::abs(std::sin(t * pi2 + 4.189f)); + data[i].color[3] = 1.0f; + } +} + +void InstancingSample::onRender() { + using draco::f32, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + updateInstances(); + + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.05f, 0.05f, 0.08f, 1.0f); + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); + auto* rp = enc->beginRenderPass(rpd); + + rp->setPipeline(m_pipeline); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0, 1); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vb, 0); + rp->setVertexBuffer(1, m_instBuf, 0); + rp->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + rp->drawIndexed(6, kInstanceCount); + rp->end(); + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void InstancingSample::onShutdown() { + if (m_instBuf && m_instMapped) m_instBuf->unmap(); + if (m_fence) m_device->destroyFence(m_fence); if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_instBuf) m_device->destroyBuffer(m_instBuf); + if (m_ib) m_device->destroyBuffer(m_ib); if (m_vb) m_device->destroyBuffer(m_vb); + if (m_ps) m_device->destroyShaderModule(m_ps); if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { InstancingSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample008_DepthBuffer/CMakeLists.txt b/Samples/cpp/RHI/Sample008_DepthBuffer/CMakeLists.txt new file mode 100644 index 00000000..74f7a5f9 --- /dev/null +++ b/Samples/cpp/RHI/Sample008_DepthBuffer/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample008_DepthBuffer) diff --git a/Samples/cpp/RHI/Sample008_DepthBuffer/Main.cpp b/Samples/cpp/RHI/Sample008_DepthBuffer/Main.cpp new file mode 100644 index 00000000..f9510b18 --- /dev/null +++ b/Samples/cpp/RHI/Sample008_DepthBuffer/Main.cpp @@ -0,0 +1,190 @@ +#include +/// Three overlapping quads at different Z depths demonstrate depth testing. +/// Red (z=0.8, far), Green (z=0.5, mid), Blue (z=0.2, near) - drawn far-to-near, +/// depth buffer ensures correct visibility. + +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class DepthBufferSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample008 - Depth Buffer"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onResize(draco::u32 w, draco::u32 h) override { recreateDepth(w, h); } + void onShutdown() override; +private: + static constexpr const char8_t kShader[] = u8R"( + struct VSInput + { + float3 Position : TEXCOORD0; + float4 Color : TEXCOORD1; + }; + struct PSInput + { + float4 Position : SV_POSITION; + float4 Color : COLOR0; + }; + PSInput VSMain(VSInput input) + { + PSInput output; + output.Position = float4(input.Position, 1.0); + output.Color = input.Color; + return output; + } + float4 PSMain(PSInput input) : SV_TARGET + { + return input.Color; + } + )"; + + // Three overlapping quads drawn in order: red (far), green (middle), blue (near). + // Stride: 7 floats per vertex (pos xyz + color rgba). + static constexpr float kVerts[] = { + // Quad 0: Red - large, behind (z=0.8), drawn first. + -0.6f, -0.6f, 0.8f, 1.0f, 0.2f, 0.2f, 1.0f, + 0.4f, -0.6f, 0.8f, 1.0f, 0.2f, 0.2f, 1.0f, + 0.4f, 0.6f, 0.8f, 1.0f, 0.2f, 0.2f, 1.0f, + -0.6f, 0.6f, 0.8f, 1.0f, 0.2f, 0.2f, 1.0f, + // Quad 1: Green - overlaps red, closer (z=0.5), drawn second. + -0.2f, -0.4f, 0.5f, 0.2f, 1.0f, 0.2f, 1.0f, + 0.6f, -0.4f, 0.5f, 0.2f, 1.0f, 0.2f, 1.0f, + 0.6f, 0.4f, 0.5f, 0.2f, 1.0f, 0.2f, 1.0f, + -0.2f, 0.4f, 0.5f, 0.2f, 1.0f, 0.2f, 1.0f, + // Quad 2: Blue - overlaps both, nearest (z=0.2), drawn third. + -0.4f, -0.7f, 0.2f, 0.2f, 0.3f, 1.0f, 1.0f, + 0.2f, -0.7f, 0.2f, 0.2f, 0.3f, 1.0f, 1.0f, + 0.2f, 0.7f, 0.2f, 0.2f, 0.3f, 1.0f, 1.0f, + -0.4f, 0.7f, 0.2f, 0.2f, 0.3f, 1.0f, 1.0f, + }; + static constexpr draco::u16 kIdx[] = { + 0, 1, 2, 0, 2, 3, + 4, 5, 6, 4, 6, 7, + 8, 9, 10, 8, 10, 11, + }; + + void recreateDepth(draco::u32 w, draco::u32 h); + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_vs = nullptr, *m_ps = nullptr; + rhi::Buffer *m_vb = nullptr, *m_ib = nullptr; + rhi::PipelineLayout *m_pl = nullptr; + rhi::RenderPipeline *m_pipeline = nullptr; + rhi::Texture *m_depthTex = nullptr; + rhi::TextureView *m_depthView = nullptr; + rhi::CommandPool *m_pool = nullptr; rhi::Fence *m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +void DepthBufferSample::recreateDepth(draco::u32 w, draco::u32 h) { + if (m_depthView) { m_device->destroyTextureView(m_depthView); m_depthView = nullptr; } + if (m_depthTex) { m_device->destroyTexture(m_depthTex); m_depthTex = nullptr; } + + rhi::TextureDesc td = rhi::TextureDesc::depthBuffer(rhi::TextureFormat::Depth24PlusStencil8, w, h, 1, u8"DepthTex"); + m_device->createTexture(td, m_depthTex); + rhi::TextureViewDesc tvd{}; tvd.format = rhi::TextureFormat::Depth24PlusStencil8; tvd.dimension = rhi::TextureViewDimension::Texture2D; + tvd.mipLevelCount = 1; tvd.arrayLayerCount = 1; + m_device->createTextureView(m_depthTex, tvd, m_depthView); +} + +draco::Status DepthBufferSample::onInit() { + using draco::Status, std::span, draco::u8; + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"VS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"PS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::BufferDesc vbd{}; vbd.size = sizeof(kVerts); vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ibd{}; ibd.size = sizeof(kIdx); ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; ibd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(kVerts), sizeof(kVerts))); + batch->writeBuffer(m_ib, 0, std::span(reinterpret_cast(kIdx), sizeof(kIdx))); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + // Pipeline layout (empty - no bind groups needed). + rhi::PipelineLayoutDesc pld{}; + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + recreateDepth(m_width, m_height); + + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x4, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 28; vbl.attributes = std::span(attrs, 2); + + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.depthStencil = rhi::DepthStencilState{}; rpd.depthStencil->format = rhi::TextureFormat::Depth24PlusStencil8; + rpd.depthStencil->depthWriteEnabled = true; + rpd.depthStencil->depthCompare = rhi::CompareFunction::Less; + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void DepthBufferSample::onRender() { + using draco::f32, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + enc->transitionTexture(m_depthTex, rhi::ResourceState::Undefined, rhi::ResourceState::DepthStencilWrite); + + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.1f, 0.1f, 0.15f, 1.0f); + rhi::DepthStencilAttachment dsa{}; dsa.view = m_depthView; + dsa.depthLoadOp = rhi::LoadOp::Clear; dsa.depthStoreOp = rhi::StoreOp::Store; dsa.depthClearValue = 1.0f; + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); rpd.depthStencilAttachment = dsa; + auto* rp = enc->beginRenderPass(rpd); + + rp->setPipeline(m_pipeline); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0, 1); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vb, 0); + rp->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + // Draw all 3 quads - depth buffer determines visibility. + rp->drawIndexed(18); + rp->end(); + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void DepthBufferSample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_depthView) m_device->destroyTextureView(m_depthView); + if (m_depthTex) m_device->destroyTexture(m_depthTex); + if (m_ib) m_device->destroyBuffer(m_ib); if (m_vb) m_device->destroyBuffer(m_vb); + if (m_ps) m_device->destroyShaderModule(m_ps); if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { DepthBufferSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample009_Mipmaps/CMakeLists.txt b/Samples/cpp/RHI/Sample009_Mipmaps/CMakeLists.txt new file mode 100644 index 00000000..e840075c --- /dev/null +++ b/Samples/cpp/RHI/Sample009_Mipmaps/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample009_Mipmaps) diff --git a/Samples/cpp/RHI/Sample009_Mipmaps/Main.cpp b/Samples/cpp/RHI/Sample009_Mipmaps/Main.cpp new file mode 100644 index 00000000..c1869181 --- /dev/null +++ b/Samples/cpp/RHI/Sample009_Mipmaps/Main.cpp @@ -0,0 +1,205 @@ +#include +/// Textured quad that recedes into the distance showing mip level selection. +/// Uses generateMipmaps() to auto-generate mip chain from a base texture. + +#include +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; +using draco::math::Matrix4; + +class MipmapSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample009 - Mipmaps"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onResize(draco::u32 w, draco::u32 h) override { m_depthBuf.recreate(m_device, w, h); } + void onShutdown() override; +private: + static constexpr const char8_t kShader[] = u8R"( + Texture2D gTexture : register(t0, space0); + SamplerState gSampler : register(s0, space0); + cbuffer UBO : register(b0, space1) { row_major float4x4 MVP; }; + struct VSInput { float3 Position : TEXCOORD0; float2 TexCoord : TEXCOORD1; }; + struct PSInput { float4 Position : SV_POSITION; float2 TexCoord : TEXCOORD0; }; + PSInput VSMain(VSInput i) { PSInput o; o.Position = mul(float4(i.Position,1), MVP); o.TexCoord = i.TexCoord; return o; } + float4 PSMain(PSInput i) : SV_TARGET { return gTexture.Sample(gSampler, i.TexCoord); } + )"; + // Receding floor plane. + static constexpr float kVerts[] = { + -4, 0, 0, 0, 0, 4, 0, 0, 8, 0, 4, 0, -20, 8, 10, -4, 0, -20, 0, 10 + }; + static constexpr draco::u16 kIdx[] = { 0,1,2, 0,2,3 }; + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_vs = nullptr, *m_ps = nullptr; + rhi::Buffer *m_vb = nullptr, *m_ib = nullptr, *m_ub = nullptr; + void* m_ubMapped = nullptr; + rhi::Texture* m_tex = nullptr; rhi::TextureView* m_texView = nullptr; + rhi::Sampler* m_sampler = nullptr; + rhi::BindGroupLayout *m_texBgl = nullptr, *m_uboBgl = nullptr; + rhi::BindGroup *m_texBg = nullptr, *m_uboBg = nullptr; + rhi::PipelineLayout* m_pl = nullptr; rhi::RenderPipeline* m_pipeline = nullptr; + rhi::CommandPool* m_pool = nullptr; rhi::Fence* m_fence = nullptr; + draco::u64 m_fenceVal = 0; + sf::DepthBuffer m_depthBuf; +}; + +draco::Status MipmapSample::onInit() { + using draco::Status, std::span, draco::u8, draco::u32; + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"VS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"PS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Buffers. + rhi::BufferDesc vbd{}; vbd.size = sizeof(kVerts); vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ibd{}; ibd.size = sizeof(kIdx); ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; ibd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ubd{}; ubd.size = 64; ubd.usage = rhi::BufferUsage::Uniform; ubd.memory = rhi::MemoryLocation::CpuToGpu; + if (m_device->createBuffer(ubd, m_ub) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + m_ubMapped = m_ub->map(); + + // Checkerboard base texture 256x256 with 9 mip levels. + constexpr u32 tw = 256, th = 256, mipCount = 9; + u8 texPixels[tw * th * 4]; + for (u32 y = 0; y < th; ++y) for (u32 x = 0; x < tw; ++x) { + bool c = ((x/16) + (y/16)) % 2 == 0; + u32 i = (y*tw+x)*4; + texPixels[i]=c?255:30; texPixels[i+1]=c?255:30; texPixels[i+2]=c?255:200; texPixels[i+3]=255; + } + rhi::TextureDesc td{}; td.format = rhi::TextureFormat::RGBA8Unorm; td.width = tw; td.height = th; + td.mipLevelCount = mipCount; + td.usage = rhi::TextureUsage::Sampled | rhi::TextureUsage::CopySrc | rhi::TextureUsage::CopyDst | rhi::TextureUsage::RenderTarget; + if (m_device->createTexture(td, m_tex) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Upload base mip + generate mips. + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(kVerts), sizeof(kVerts))); + batch->writeBuffer(m_ib, 0, std::span(reinterpret_cast(kIdx), sizeof(kIdx))); + rhi::TextureDataLayout layout{}; layout.bytesPerRow = tw*4; layout.rowsPerImage = th; + batch->writeTexture(m_tex, std::span(texPixels, sizeof(texPixels)), layout, rhi::Extent3D{tw,th,1}); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + // Generate mipmaps then transition to shader-read. + rhi::CommandPool* tmpPool = nullptr; m_device->createCommandPool(rhi::QueueType::Graphics, tmpPool); + rhi::CommandEncoder* enc = nullptr; tmpPool->createEncoder(enc); + enc->generateMipmaps(m_tex); + // generateMipmaps leaves all mips in TRANSFER_SRC. + // Transition all mips to SHADER_READ for sampling. + rhi::TextureBarrier tb{}; tb.texture = m_tex; + tb.oldState = rhi::ResourceState::CopySrc; tb.newState = rhi::ResourceState::ShaderRead; + tb.baseMipLevel = 0; tb.mipLevelCount = mipCount; + tb.baseArrayLayer = 0; tb.arrayLayerCount = 1; + rhi::BarrierGroup bg{}; bg.textureBarriers = std::span(&tb, 1); + enc->barrier(bg); + rhi::CommandBuffer* cb = enc->finish(); + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1)); + m_graphicsQueue->waitIdle(); + tmpPool->destroyEncoder(enc); m_device->destroyCommandPool(tmpPool); + + rhi::TextureViewDesc tvd{}; tvd.format = rhi::TextureFormat::RGBA8Unorm; tvd.mipLevelCount = mipCount; tvd.arrayLayerCount = 1; + if (m_device->createTextureView(m_tex, tvd, m_texView) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::SamplerDesc sd{}; sd.minFilter = rhi::FilterMode::Linear; sd.magFilter = rhi::FilterMode::Linear; + sd.mipmapFilter = rhi::MipmapFilterMode::Linear; sd.maxLod = static_cast(mipCount); + if (m_device->createSampler(sd, m_sampler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Bind groups: set 0 = texture+sampler, set 1 = UBO. + rhi::BindGroupLayoutEntry tE[2] = { rhi::BindGroupLayoutEntry::sampledTexture(0, rhi::ShaderStage::Fragment), + rhi::BindGroupLayoutEntry::sampler(0, rhi::ShaderStage::Fragment) }; + rhi::BindGroupLayoutDesc tBgld{}; tBgld.entries = std::span(tE, 2); + if (m_device->createBindGroupLayout(tBgld, m_texBgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BindGroupEntry tBgE[2] = { rhi::BindGroupEntry::textureEntry(m_texView), rhi::BindGroupEntry::samplerEntry(m_sampler) }; + rhi::BindGroupDesc tBgd{}; tBgd.layout = m_texBgl; tBgd.entries = std::span(tBgE, 2); + if (m_device->createBindGroup(tBgd, m_texBg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::BindGroupLayoutEntry uE[1] = { rhi::BindGroupLayoutEntry::uniformBuffer(0, rhi::ShaderStage::Vertex) }; + rhi::BindGroupLayoutDesc uBgld{}; uBgld.entries = std::span(uE, 1); + if (m_device->createBindGroupLayout(uBgld, m_uboBgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BindGroupEntry uBgE[1] = { rhi::BindGroupEntry::bufferEntry(m_ub, 0, 64) }; + rhi::BindGroupDesc uBgd{}; uBgd.layout = m_uboBgl; uBgd.entries = std::span(uBgE, 1); + if (m_device->createBindGroup(uBgd, m_uboBg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::BindGroupLayout* sets[2] = { m_texBgl, m_uboBgl }; + rhi::PipelineLayoutDesc pld{}; pld.bindGroupLayouts = std::span(sets, 2); + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + m_depthBuf.recreate(m_device, m_width, m_height); + + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x2, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 20; vbl.attributes = std::span(attrs, 2); + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.depthStencil = rhi::DepthStencilState{}; rpd.depthStencil->format = rhi::TextureFormat::Depth24PlusStencil8; + rpd.depthStencil->depthCompare = rhi::CompareFunction::Less; + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void MipmapSample::onRender() { + using draco::f32, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + f32 aspect = static_cast(m_width) / static_cast(m_height); + Matrix4 view = Matrix4::lookAtRH(draco::math::Vector3{0, 2, 2}, draco::math::Vector3{ 0, 0, -5}, draco::math::Vector3{0,1,0}); + Matrix4 proj = Matrix4::perspectiveFovRH(draco::math::degToRad(60.0f), aspect, 0.1f, 100.0f); + Matrix4 mvp = view * proj; + std::memcpy(m_ubMapped, mvp.data(), 64); + + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + enc->transitionTexture(m_depthBuf.texture, rhi::ResourceState::Undefined, rhi::ResourceState::DepthStencilWrite); + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; ca.clearValue = rhi::ClearColor(0.1f,0.1f,0.15f,1); + rhi::DepthStencilAttachment dsa{}; dsa.view = m_depthBuf.view; + dsa.depthLoadOp = rhi::LoadOp::Clear; dsa.depthStoreOp = rhi::StoreOp::Store; dsa.depthClearValue = 1.0f; + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); rpd.depthStencilAttachment = dsa; + auto* rp = enc->beginRenderPass(rpd); + rp->setPipeline(m_pipeline); rp->setBindGroup(0, m_texBg); rp->setBindGroup(1, m_uboBg); + rp->setViewport(0,0,static_cast(m_width),static_cast(m_height),0,1); + rp->setScissor(0,0,m_width,m_height); + rp->setVertexBuffer(0, m_vb, 0); rp->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + rp->drawIndexed(6); rp->end(); + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); m_pool->destroyEncoder(enc); +} + +void MipmapSample::onShutdown() { + m_depthBuf.destroy(m_device); + if (m_fence) m_device->destroyFence(m_fence); if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_uboBg) m_device->destroyBindGroup(m_uboBg); if (m_uboBgl) m_device->destroyBindGroupLayout(m_uboBgl); + if (m_texBg) m_device->destroyBindGroup(m_texBg); if (m_texBgl) m_device->destroyBindGroupLayout(m_texBgl); + if (m_sampler) m_device->destroySampler(m_sampler); if (m_texView) m_device->destroyTextureView(m_texView); + if (m_tex) m_device->destroyTexture(m_tex); if (m_ub) m_device->destroyBuffer(m_ub); + if (m_ib) m_device->destroyBuffer(m_ib); if (m_vb) m_device->destroyBuffer(m_vb); + if (m_ps) m_device->destroyShaderModule(m_ps); if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { MipmapSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample010_MSAA/CMakeLists.txt b/Samples/cpp/RHI/Sample010_MSAA/CMakeLists.txt new file mode 100644 index 00000000..4cc485c6 --- /dev/null +++ b/Samples/cpp/RHI/Sample010_MSAA/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample010_MSAA) diff --git a/Samples/cpp/RHI/Sample010_MSAA/Main.cpp b/Samples/cpp/RHI/Sample010_MSAA/Main.cpp new file mode 100644 index 00000000..f9bc6c8e --- /dev/null +++ b/Samples/cpp/RHI/Sample010_MSAA/Main.cpp @@ -0,0 +1,132 @@ +#include +/// Renders a triangle with 4x MSAA, resolving to the swap chain. + +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class MSAASample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample010 - MSAA (4x)"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onResize(draco::u32 w, draco::u32 h) override { recreateMSAA(w, h); } + void onShutdown() override; +private: + static constexpr const char8_t kShader[] = u8R"( + struct VSInput { float3 Position : TEXCOORD0; float3 Color : TEXCOORD1; }; + struct PSInput { float4 Position : SV_POSITION; float3 Color : COLOR0; }; + PSInput VSMain(VSInput i) { PSInput o; o.Position = float4(i.Position,1); o.Color = i.Color; return o; } + float4 PSMain(PSInput i) : SV_TARGET { return float4(i.Color, 1.0); } + )"; + static constexpr float kVerts[] = { 0,.7f,0, 1,0,0, .7f,-.5f,0, 0,1,0, -.7f,-.5f,0, 0,0,1 }; + static constexpr draco::u16 kIdx[] = { 0, 1, 2 }; + static constexpr draco::u32 kSamples = 4; + + void recreateMSAA(draco::u32 w, draco::u32 h); + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_vs = nullptr, *m_ps = nullptr; + rhi::Buffer *m_vb = nullptr, *m_ib = nullptr; + rhi::PipelineLayout *m_pl = nullptr; rhi::RenderPipeline *m_pipeline = nullptr; + rhi::Texture *m_msaaTex = nullptr; rhi::TextureView *m_msaaView = nullptr; + rhi::CommandPool *m_pool = nullptr; rhi::Fence *m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +void MSAASample::recreateMSAA(draco::u32 w, draco::u32 h) { + if (m_msaaView) { m_device->destroyTextureView(m_msaaView); m_msaaView = nullptr; } + if (m_msaaTex) { m_device->destroyTexture(m_msaaTex); m_msaaTex = nullptr; } + rhi::TextureDesc td = rhi::TextureDesc::renderTarget(m_swapChain->format(), w, h, kSamples, u8"MSAATarget"); + m_device->createTexture(td, m_msaaTex); + rhi::TextureViewDesc tvd{}; tvd.format = m_swapChain->format(); tvd.mipLevelCount = 1; tvd.arrayLayerCount = 1; + m_device->createTextureView(m_msaaTex, tvd, m_msaaView); +} + +draco::Status MSAASample::onInit() { + using draco::Status, std::span, draco::u8; + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"VS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"PS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::BufferDesc vbd{}; vbd.size = sizeof(kVerts); vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ibd{}; ibd.size = sizeof(kIdx); ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; ibd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(kVerts), sizeof(kVerts))); + batch->writeBuffer(m_ib, 0, std::span(reinterpret_cast(kIdx), sizeof(kIdx))); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + rhi::PipelineLayoutDesc pld{}; if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x3, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 24; vbl.attributes = std::span(attrs, 2); + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.multisample.count = kSamples; + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + recreateMSAA(m_width, m_height); + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void MSAASample::onRender() { + using draco::f32, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + enc->transitionTexture(m_msaaTex, rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + + // Render into MSAA target, resolve to swap chain. + rhi::ColorAttachment ca{}; + ca.view = m_msaaView; + ca.resolveTarget = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.08f, 0.08f, 0.12f, 1); + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); + auto* rp = enc->beginRenderPass(rpd); + rp->setPipeline(m_pipeline); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0, 1); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vb, 0); rp->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + rp->drawIndexed(3); rp->end(); + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); m_pool->destroyEncoder(enc); +} + +void MSAASample::onShutdown() { + if (m_msaaView) m_device->destroyTextureView(m_msaaView); if (m_msaaTex) m_device->destroyTexture(m_msaaTex); + if (m_fence) m_device->destroyFence(m_fence); if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_ib) m_device->destroyBuffer(m_ib); if (m_vb) m_device->destroyBuffer(m_vb); + if (m_ps) m_device->destroyShaderModule(m_ps); if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { MSAASample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample011_MRT/CMakeLists.txt b/Samples/cpp/RHI/Sample011_MRT/CMakeLists.txt new file mode 100644 index 00000000..3739de5c --- /dev/null +++ b/Samples/cpp/RHI/Sample011_MRT/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample011_MRT) diff --git a/Samples/cpp/RHI/Sample011_MRT/Main.cpp b/Samples/cpp/RHI/Sample011_MRT/Main.cpp new file mode 100644 index 00000000..2de2b3be --- /dev/null +++ b/Samples/cpp/RHI/Sample011_MRT/Main.cpp @@ -0,0 +1,209 @@ +#include +/// Pass 1: Renders triangles to 2 render targets (color + brightness). +/// Pass 2: Composites both side-by-side via fullscreen triangle. + +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class MRTSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample011 - MRT"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onResize(draco::u32 /*w*/, draco::u32 /*h*/) override { createRenderTargets(); } + void onShutdown() override; +private: + static constexpr const char8_t kGBufShader[] = u8R"( + struct VSInput { float3 Position : TEXCOORD0; float4 Color : TEXCOORD1; }; + struct PSInput { float4 Position : SV_POSITION; float4 Color : COLOR0; }; + struct PSOutput { float4 Color : SV_TARGET0; float4 Brightness : SV_TARGET1; }; + PSInput VSMain(VSInput i) { PSInput o; o.Position = float4(i.Position,1); o.Color = i.Color; return o; } + PSOutput PSMain(PSInput i) { PSOutput o; o.Color = i.Color; + float lum = dot(i.Color.rgb, float3(0.299,0.587,0.114)); + o.Brightness = float4(lum,lum,lum,1); return o; } + )"; + static constexpr const char8_t kCompShader[] = u8R"( + Texture2D gColorTex : register(t0, space0); + Texture2D gBrightTex : register(t1, space0); + SamplerState gSampler : register(s0, space0); + struct PSInput { float4 Position : SV_POSITION; float2 TexCoord : TEXCOORD0; }; + PSInput VSMain(uint vid : SV_VertexID) { PSInput o; + float2 uv = float2((vid << 1) & 2, vid & 2); + o.Position = float4(uv * 2.0 - 1.0, 0, 1); o.TexCoord = float2(uv.x, 1.0 - uv.y); return o; } + float4 PSMain(PSInput i) : SV_TARGET { + float2 uv = i.TexCoord; + if (uv.x < 0.5) return gColorTex.Sample(gSampler, float2(uv.x*2, uv.y)); + else return gBrightTex.Sample(gSampler, float2((uv.x-0.5)*2, uv.y)); } + )"; + static constexpr float kVerts[] = { + -.5f,-.5f,0, 1,.2f,.2f,1, .5f,-.5f,0, 1,.2f,.2f,1, 0,.6f,0, 1,.8f,.2f,1, + -.3f,-.3f,0, .2f,.3f,1,1, .7f,-.1f,0, .2f,.3f,1,1, .2f,.5f,0, .2f,.8f,1,1, + }; + static constexpr draco::u16 kIdx[] = { 0,1,2, 3,4,5 }; + + void createRenderTargets(); + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_gbVs=nullptr, *m_gbPs=nullptr, *m_compVs=nullptr, *m_compPs=nullptr; + rhi::Buffer *m_vb=nullptr, *m_ib=nullptr; + rhi::Sampler* m_sampler = nullptr; + rhi::PipelineLayout *m_gbPl=nullptr, *m_compPl=nullptr; + rhi::RenderPipeline *m_gbPipe=nullptr, *m_compPipe=nullptr; + rhi::BindGroupLayout* m_compBgl = nullptr; + rhi::BindGroup* m_compBg = nullptr; + rhi::Texture *m_colorRT=nullptr, *m_brightRT=nullptr; + rhi::TextureView *m_colorRTView=nullptr, *m_brightRTView=nullptr; + rhi::CommandPool* m_pool=nullptr; rhi::Fence* m_fence=nullptr; + draco::u64 m_fenceVal = 0; +}; + +void MRTSample::createRenderTargets() { + if (m_compBg) { m_device->destroyBindGroup(m_compBg); m_compBg = nullptr; } + if (m_colorRTView) { m_device->destroyTextureView(m_colorRTView); m_colorRTView = nullptr; } + if (m_colorRT) { m_device->destroyTexture(m_colorRT); m_colorRT = nullptr; } + if (m_brightRTView) { m_device->destroyTextureView(m_brightRTView); m_brightRTView = nullptr; } + if (m_brightRT) { m_device->destroyTexture(m_brightRT); m_brightRT = nullptr; } + + rhi::TextureDesc td{}; td.format = rhi::TextureFormat::RGBA8Unorm; td.width = m_width; td.height = m_height; + td.usage = rhi::TextureUsage::RenderTarget | rhi::TextureUsage::Sampled; + m_device->createTexture(td, m_colorRT); + m_device->createTexture(td, m_brightRT); + rhi::TextureViewDesc tvd{}; tvd.format = rhi::TextureFormat::RGBA8Unorm; tvd.mipLevelCount = 1; tvd.arrayLayerCount = 1; + m_device->createTextureView(m_colorRT, tvd, m_colorRTView); + m_device->createTextureView(m_brightRT, tvd, m_brightRTView); + + rhi::BindGroupEntry bgE[3] = { rhi::BindGroupEntry::textureEntry(m_colorRTView), + rhi::BindGroupEntry::textureEntry(m_brightRTView), + rhi::BindGroupEntry::samplerEntry(m_sampler) }; + rhi::BindGroupDesc bgd{}; bgd.layout = m_compBgl; bgd.entries = std::span(bgE, 3); + m_device->createBindGroup(bgd, m_compBg); +} + +draco::Status MRTSample::onInit() { + using draco::Status, std::span, draco::u8; + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kGBufShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"GBufVS", m_gbVs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kGBufShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"GBufPS", m_gbPs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kCompShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"CompVS", m_compVs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kCompShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"CompPS", m_compPs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::BufferDesc vbd{}; vbd.size = sizeof(kVerts); vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ibd{}; ibd.size = sizeof(kIdx); ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; ibd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(kVerts), sizeof(kVerts))); + batch->writeBuffer(m_ib, 0, std::span(reinterpret_cast(kIdx), sizeof(kIdx))); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + rhi::SamplerDesc sd{}; sd.minFilter = rhi::FilterMode::Nearest; sd.magFilter = rhi::FilterMode::Nearest; + sd.addressU = rhi::AddressMode::ClampToEdge; sd.addressV = rhi::AddressMode::ClampToEdge; + if (m_device->createSampler(sd, m_sampler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // GBuffer pipeline (empty layout, 2 color targets). + rhi::PipelineLayoutDesc gpld{}; if (m_device->createPipelineLayout(gpld, m_gbPl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x4, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 28; vbl.attributes = std::span(attrs, 2); + rhi::ColorTargetState gbCt[2] = { {.format = rhi::TextureFormat::RGBA8Unorm, .blend = {}, .writeMask = rhi::ColorWriteMask::All}, + {.format = rhi::TextureFormat::RGBA8Unorm, .blend = {}, .writeMask = rhi::ColorWriteMask::All} }; + rhi::RenderPipelineDesc grpd{}; grpd.layout = m_gbPl; + grpd.vertex.shader = { m_gbVs, u8"VSMain", rhi::ShaderStage::Vertex }; + grpd.vertex.buffers = std::span(&vbl, 1); + grpd.fragment = rhi::FragmentState{}; grpd.fragment->shader = { m_gbPs, u8"PSMain", rhi::ShaderStage::Fragment }; + grpd.fragment->targets = std::span(gbCt, 2); + if (m_device->createRenderPipeline(grpd, m_gbPipe) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Composite BGL + pipeline (3 bindings: 2 textures + 1 sampler). + rhi::BindGroupLayoutEntry cE[3] = { + rhi::BindGroupLayoutEntry::sampledTexture(0, rhi::ShaderStage::Fragment), + rhi::BindGroupLayoutEntry::sampledTexture(1, rhi::ShaderStage::Fragment), + rhi::BindGroupLayoutEntry::sampler(0, rhi::ShaderStage::Fragment), + }; + rhi::BindGroupLayoutDesc cBgld{}; cBgld.entries = std::span(cE, 3); + if (m_device->createBindGroupLayout(cBgld, m_compBgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BindGroupLayout* cSets[1] = { m_compBgl }; + rhi::PipelineLayoutDesc cpld{}; cpld.bindGroupLayouts = std::span(cSets, 1); + if (m_device->createPipelineLayout(cpld, m_compPl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::ColorTargetState compCt{}; compCt.format = m_swapChain->format(); + rhi::RenderPipelineDesc crpd{}; crpd.layout = m_compPl; + crpd.vertex.shader = { m_compVs, u8"VSMain", rhi::ShaderStage::Vertex }; + crpd.fragment = rhi::FragmentState{}; crpd.fragment->shader = { m_compPs, u8"PSMain", rhi::ShaderStage::Fragment }; + crpd.fragment->targets = std::span(&compCt, 1); + if (m_device->createRenderPipeline(crpd, m_compPipe) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + createRenderTargets(); + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void MRTSample::onRender() { + using draco::f32, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + // Pass 1: render to 2 RTs. + enc->transitionTexture(m_colorRT, rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + enc->transitionTexture(m_brightRT, rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + rhi::ColorAttachment ca2[2]; + ca2[0].view = m_colorRTView; ca2[0].loadOp = rhi::LoadOp::Clear; ca2[0].storeOp = rhi::StoreOp::Store; ca2[0].clearValue = rhi::ClearColor(0.1f,0.1f,0.15f,1); + ca2[1].view = m_brightRTView; ca2[1].loadOp = rhi::LoadOp::Clear; ca2[1].storeOp = rhi::StoreOp::Store; ca2[1].clearValue = rhi::ClearColor::black(); + rhi::RenderPassDesc rpd1{}; rpd1.colorAttachments.push_back(ca2[0]); rpd1.colorAttachments.push_back(ca2[1]); + auto* rp1 = enc->beginRenderPass(rpd1); + rp1->setPipeline(m_gbPipe); + rp1->setViewport(0,0,static_cast(m_width),static_cast(m_height),0,1); + rp1->setScissor(0,0,m_width,m_height); + rp1->setVertexBuffer(0, m_vb, 0); rp1->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + rp1->drawIndexed(6); rp1->end(); + enc->transitionTexture(m_colorRT, rhi::ResourceState::RenderTarget, rhi::ResourceState::ShaderRead); + enc->transitionTexture(m_brightRT, rhi::ResourceState::RenderTarget, rhi::ResourceState::ShaderRead); + + // Pass 2: composite to swap chain. + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + rhi::ColorAttachment ca1{}; ca1.view = m_swapChain->currentTextureView(); + ca1.loadOp = rhi::LoadOp::Clear; ca1.storeOp = rhi::StoreOp::Store; ca1.clearValue = rhi::ClearColor::black(); + rhi::RenderPassDesc rpd2{}; rpd2.colorAttachments.push_back(ca1); + auto* rp2 = enc->beginRenderPass(rpd2); + rp2->setPipeline(m_compPipe); rp2->setBindGroup(0, m_compBg); + rp2->setViewport(0,0,static_cast(m_width),static_cast(m_height),0,1); + rp2->setScissor(0,0,m_width,m_height); + rp2->draw(3); rp2->end(); + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); m_pool->destroyEncoder(enc); +} + +void MRTSample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_compPipe) m_device->destroyRenderPipeline(m_compPipe); if (m_compPl) m_device->destroyPipelineLayout(m_compPl); + if (m_compBg) m_device->destroyBindGroup(m_compBg); if (m_compBgl) m_device->destroyBindGroupLayout(m_compBgl); + if (m_gbPipe) m_device->destroyRenderPipeline(m_gbPipe); if (m_gbPl) m_device->destroyPipelineLayout(m_gbPl); + if (m_brightRTView) m_device->destroyTextureView(m_brightRTView); if (m_brightRT) m_device->destroyTexture(m_brightRT); + if (m_colorRTView) m_device->destroyTextureView(m_colorRTView); if (m_colorRT) m_device->destroyTexture(m_colorRT); + if (m_sampler) m_device->destroySampler(m_sampler); + if (m_ib) m_device->destroyBuffer(m_ib); if (m_vb) m_device->destroyBuffer(m_vb); + if (m_compPs) m_device->destroyShaderModule(m_compPs); if (m_compVs) m_device->destroyShaderModule(m_compVs); + if (m_gbPs) m_device->destroyShaderModule(m_gbPs); if (m_gbVs) m_device->destroyShaderModule(m_gbVs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { MRTSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample012_Wireframe/CMakeLists.txt b/Samples/cpp/RHI/Sample012_Wireframe/CMakeLists.txt new file mode 100644 index 00000000..035235d4 --- /dev/null +++ b/Samples/cpp/RHI/Sample012_Wireframe/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample012_Wireframe) diff --git a/Samples/cpp/RHI/Sample012_Wireframe/Main.cpp b/Samples/cpp/RHI/Sample012_Wireframe/Main.cpp new file mode 100644 index 00000000..c6b80368 --- /dev/null +++ b/Samples/cpp/RHI/Sample012_Wireframe/Main.cpp @@ -0,0 +1,167 @@ +#include +/// Renders a rotating icosahedron in wireframe mode. + +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; +using draco::math::Matrix4; + +class WireframeSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample012 - Wireframe"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onResize(draco::u32 w, draco::u32 h) override { m_depthBuf.recreate(m_device, w, h); } + void onShutdown() override; +private: + static constexpr const char8_t kShader[] = u8R"( + cbuffer UBO : register(b0, space0) { row_major float4x4 MVP; }; + struct VSInput { float3 Position : TEXCOORD0; float4 Color : TEXCOORD1; }; + struct PSInput { float4 Position : SV_POSITION; float4 Color : COLOR0; }; + PSInput VSMain(VSInput i) { PSInput o; o.Position = mul(float4(i.Position,1), MVP); o.Color = i.Color; return o; } + float4 PSMain(PSInput i) : SV_TARGET { return i.Color; } + )"; + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_vs=nullptr, *m_ps=nullptr; + rhi::Buffer *m_vb=nullptr, *m_ib=nullptr, *m_ub=nullptr; + void* m_ubMapped = nullptr; + rhi::BindGroupLayout* m_bgl=nullptr; rhi::BindGroup* m_bg=nullptr; + rhi::PipelineLayout* m_pl=nullptr; + rhi::RenderPipeline *m_wirePipe=nullptr; + rhi::CommandPool* m_pool=nullptr; rhi::Fence* m_fence=nullptr; + draco::u64 m_fenceVal = 0; + draco::u32 m_indexCount = 0; + sf::DepthBuffer m_depthBuf; +}; + +draco::Status WireframeSample::onInit() { + using draco::Status, std::span, draco::u8, draco::f32; + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"VS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"PS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Icosahedron. + f32 t = (1.0f + std::sqrt(5.0f)) / 2.0f; + f32 s = 1.0f / std::sqrt(1.0f + t*t); + f32 a = s, b = t*s; + f32 vertData[84] = { + -a,b,0, 1,.3f,.3f,1, a,b,0, .3f,1,.3f,1, -a,-b,0, .3f,.3f,1,1, a,-b,0, 1,1,.3f,1, + 0,-a,b, 1,.3f,1,1, 0,a,b, .3f,1,1,1, 0,-a,-b, 1,.6f,.3f,1, 0,a,-b, .6f,.3f,1,1, + b,0,-a, .3f,1,.6f,1, b,0,a, 1,.6f,.6f,1, -b,0,-a, .6f,1,.3f,1, -b,0,a, .6f,.3f,.6f,1, + }; + draco::u16 idxData[60] = { + 0,11,5, 0,5,1, 0,1,7, 0,7,10, 0,10,11, + 1,5,9, 5,11,4, 11,10,2, 10,7,6, 7,1,8, + 3,9,4, 3,4,2, 3,2,6, 3,6,8, 3,8,9, + 4,9,5, 2,4,11, 6,2,10, 8,6,7, 9,8,1, + }; + m_indexCount = 60; + + rhi::BufferDesc vbd{}; vbd.size = sizeof(vertData); vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ibd{}; ibd.size = sizeof(idxData); ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; ibd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(vertData), sizeof(vertData))); + batch->writeBuffer(m_ib, 0, std::span(reinterpret_cast(idxData), sizeof(idxData))); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + rhi::BufferDesc ubd{}; ubd.size = 256; ubd.usage = rhi::BufferUsage::Uniform; ubd.memory = rhi::MemoryLocation::CpuToGpu; + if (m_device->createBuffer(ubd, m_ub) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + m_ubMapped = m_ub->map(); + + rhi::BindGroupLayoutEntry bglE[1] = { rhi::BindGroupLayoutEntry::uniformBuffer(0, rhi::ShaderStage::Vertex) }; + rhi::BindGroupLayoutDesc bgld{}; bgld.entries = std::span(bglE, 1); + if (m_device->createBindGroupLayout(bgld, m_bgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BindGroupEntry bgE[1] = { rhi::BindGroupEntry::bufferEntry(m_ub, 0, 64) }; + rhi::BindGroupDesc bgd{}; bgd.layout = m_bgl; bgd.entries = std::span(bgE, 1); + if (m_device->createBindGroup(bgd, m_bg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BindGroupLayout* sets[1] = { m_bgl }; + rhi::PipelineLayoutDesc pld{}; pld.bindGroupLayouts = std::span(sets, 1); + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + m_depthBuf.recreate(m_device, m_width, m_height); + + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x4, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 28; vbl.attributes = std::span(attrs, 2); + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.primitive = { rhi::PrimitiveTopology::TriangleList, rhi::FrontFace::CCW, rhi::CullMode::None, rhi::FillMode::Wireframe }; + rpd.depthStencil = rhi::DepthStencilState{}; rpd.depthStencil->format = rhi::TextureFormat::Depth24PlusStencil8; + rpd.depthStencil->depthCompare = rhi::CompareFunction::LessEqual; + rpd.depthStencil->depthWriteEnabled = false; + if (m_device->createRenderPipeline(rpd, m_wirePipe) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void WireframeSample::onRender() { + using draco::f32, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + f32 aspect = static_cast(m_width) / static_cast(m_height); + Matrix4 model = Matrix4::rotationY(m_totalTime * 0.8f); + // Row-vector view: identity rotation, camera 3 units along +Z (RH: looking toward -Z). + // The view is the inverse of the camera transform, so the translation is the NEGATED + // eye position: m[3][2] = -3 (puts the object at view-space z=-3, in front of the camera). + // PerspectiveFovRH gives clip.w = -viewZ, so geometry must have negative view z. + f32 view[16] = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,-3,1 }; + Matrix4 proj = Matrix4::perspectiveFovRH(draco::math::degToRad(45.0f), aspect, 0.1f, 100.0f); + Matrix4 vMat; std::memcpy(vMat.data(), view, 64); + Matrix4 mvp = model * vMat * proj; + std::memcpy(m_ubMapped, mvp.data(), 64); + + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + enc->transitionTexture(m_depthBuf.texture, rhi::ResourceState::Undefined, rhi::ResourceState::DepthStencilWrite); + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; ca.clearValue = rhi::ClearColor(0.06f,0.06f,0.1f,1); + rhi::DepthStencilAttachment dsa{}; dsa.view = m_depthBuf.view; + dsa.depthLoadOp = rhi::LoadOp::Clear; dsa.depthStoreOp = rhi::StoreOp::Store; dsa.depthClearValue = 1.0f; + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); rpd.depthStencilAttachment = dsa; + auto* rp = enc->beginRenderPass(rpd); + rp->setPipeline(m_wirePipe); rp->setBindGroup(0, m_bg); + rp->setViewport(0,0,static_cast(m_width),static_cast(m_height),0,1); + rp->setScissor(0,0,m_width,m_height); + rp->setVertexBuffer(0, m_vb, 0); rp->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + rp->drawIndexed(m_indexCount); rp->end(); + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); m_pool->destroyEncoder(enc); +} + +void WireframeSample::onShutdown() { + m_depthBuf.destroy(m_device); + if (m_fence) m_device->destroyFence(m_fence); if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_wirePipe) m_device->destroyRenderPipeline(m_wirePipe); if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_bg) m_device->destroyBindGroup(m_bg); if (m_bgl) m_device->destroyBindGroupLayout(m_bgl); + if (m_ub) m_device->destroyBuffer(m_ub); if (m_ib) m_device->destroyBuffer(m_ib); if (m_vb) m_device->destroyBuffer(m_vb); + if (m_ps) m_device->destroyShaderModule(m_ps); if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { WireframeSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample013_BorderSampler/CMakeLists.txt b/Samples/cpp/RHI/Sample013_BorderSampler/CMakeLists.txt new file mode 100644 index 00000000..79abfe55 --- /dev/null +++ b/Samples/cpp/RHI/Sample013_BorderSampler/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample013_BorderSampler) diff --git a/Samples/cpp/RHI/Sample013_BorderSampler/Main.cpp b/Samples/cpp/RHI/Sample013_BorderSampler/Main.cpp new file mode 100644 index 00000000..4211fef0 --- /dev/null +++ b/Samples/cpp/RHI/Sample013_BorderSampler/Main.cpp @@ -0,0 +1,232 @@ +#include +/// Demonstrates sampler border colors: TransparentBlack, OpaqueBlack, OpaqueWhite. +/// Three quads with UVs extending beyond [0,1] to show the border region. + +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class BorderSamplerSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample013 - Border Sampler"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onShutdown() override; +private: + static constexpr const char8_t kShader[] = u8R"( + Texture2D gTexture : register(t0, space0); + SamplerState gSampler : register(s0, space0); + cbuffer UBO : register(b0, space1) { float4 QuadOffset; }; + struct VSInput { float3 Position : TEXCOORD0; float2 TexCoord : TEXCOORD1; }; + struct PSInput { float4 Position : SV_POSITION; float2 TexCoord : TEXCOORD0; }; + PSInput VSMain(VSInput input) { + PSInput output; + output.Position = float4(input.Position.xy + QuadOffset.xy, input.Position.z, 1.0); + output.TexCoord = input.TexCoord; + return output; + } + float4 PSMain(PSInput input) : SV_TARGET { + return gTexture.Sample(gSampler, input.TexCoord); + } + )"; + + // Quad with UVs from -0.5 to 1.5 to show border region. + static constexpr float kQuadVerts[] = { + -0.25f, -0.25f, 0.0f, -0.5f, -0.5f, + 0.25f, -0.25f, 0.0f, 1.5f, -0.5f, + 0.25f, 0.25f, 0.0f, 1.5f, 1.5f, + -0.25f, 0.25f, 0.0f, -0.5f, 1.5f, + }; + static constexpr draco::u16 kQuadIdx[] = { 0, 1, 2, 0, 2, 3 }; + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_vs = nullptr, *m_ps = nullptr; + rhi::Buffer *m_vb = nullptr, *m_ib = nullptr, *m_ub = nullptr; + void* m_ubMapped = nullptr; + rhi::Texture* m_tex = nullptr; rhi::TextureView* m_texView = nullptr; + rhi::Sampler *m_sampTransparent = nullptr, *m_sampOpaqueBlack = nullptr, *m_sampOpaqueWhite = nullptr; + rhi::BindGroupLayout *m_texBgl = nullptr, *m_uboBgl = nullptr; + rhi::BindGroup *m_bgTransparent = nullptr, *m_bgOpaqueBlack = nullptr, *m_bgOpaqueWhite = nullptr; + rhi::BindGroup *m_uboBg = nullptr; + rhi::PipelineLayout *m_pl = nullptr; rhi::RenderPipeline *m_pipeline = nullptr; + rhi::CommandPool *m_pool = nullptr; rhi::Fence *m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +draco::Status BorderSamplerSample::onInit() { + using draco::Status, std::span, draco::u8, draco::u32; + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"VS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"PS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Buffers. + rhi::BufferDesc vbd{}; vbd.size = sizeof(kQuadVerts); vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ibd{}; ibd.size = sizeof(kQuadIdx); ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; ibd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Uniform buffer: 3 slots * 256 bytes (DX12 CBV alignment). + rhi::BufferDesc ubd{}; ubd.size = 768; ubd.usage = rhi::BufferUsage::Uniform; ubd.memory = rhi::MemoryLocation::CpuToGpu; + if (m_device->createBuffer(ubd, m_ub) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + m_ubMapped = m_ub->map(); + // Write all 3 offsets upfront. + float off0[4] = { -0.55f, 0.0f, 0.0f, 0.0f }; + float off1[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float off2[4] = { 0.55f, 0.0f, 0.0f, 0.0f }; + std::memcpy(static_cast(m_ubMapped), off0, 16); + std::memcpy(static_cast(m_ubMapped) + 256, off1, 16); + std::memcpy(static_cast(m_ubMapped) + 512, off2, 16); + + // 8x8 checkerboard texture (red/white). + constexpr u32 tw = 8, th = 8; + u8 texData[tw * th * 4]; + for (u32 y = 0; y < th; ++y) for (u32 x = 0; x < tw; ++x) { + u32 i = (y * tw + x) * 4; + bool white = ((x + y) % 2) == 0; + texData[i+0] = white ? 255 : 220; texData[i+1] = white ? 255 : 60; + texData[i+2] = white ? 255 : 60; texData[i+3] = 255; + } + rhi::TextureDesc td{}; td.format = rhi::TextureFormat::RGBA8Unorm; td.width = tw; td.height = th; + td.mipLevelCount = 1; td.usage = rhi::TextureUsage::Sampled | rhi::TextureUsage::CopyDst; + if (m_device->createTexture(td, m_tex) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(kQuadVerts), sizeof(kQuadVerts))); + batch->writeBuffer(m_ib, 0, std::span(reinterpret_cast(kQuadIdx), sizeof(kQuadIdx))); + rhi::TextureDataLayout layout{}; layout.bytesPerRow = tw * 4; layout.rowsPerImage = th; + batch->writeTexture(m_tex, std::span(texData, sizeof(texData)), layout, rhi::Extent3D{tw, th, 1}); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + rhi::TextureViewDesc tvd{}; tvd.format = rhi::TextureFormat::RGBA8Unorm; tvd.mipLevelCount = 1; tvd.arrayLayerCount = 1; + if (m_device->createTextureView(m_tex, tvd, m_texView) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Three samplers with ClampToBorder and different border colors. + auto makeSampler = [&](rhi::SamplerBorderColor bc, rhi::Sampler*& out) -> draco::Status { + rhi::SamplerDesc sd{}; sd.minFilter = rhi::FilterMode::Nearest; sd.magFilter = rhi::FilterMode::Nearest; + sd.addressU = rhi::AddressMode::ClampToBorder; sd.addressV = rhi::AddressMode::ClampToBorder; + sd.addressW = rhi::AddressMode::ClampToBorder; sd.borderColor = bc; + return m_device->createSampler(sd, out); + }; + if (makeSampler(rhi::SamplerBorderColor::TransparentBlack, m_sampTransparent) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (makeSampler(rhi::SamplerBorderColor::OpaqueBlack, m_sampOpaqueBlack) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (makeSampler(rhi::SamplerBorderColor::OpaqueWhite, m_sampOpaqueWhite) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Bind group layout: set 0 = texture + sampler. + rhi::BindGroupLayoutEntry tE[2] = { rhi::BindGroupLayoutEntry::sampledTexture(0, rhi::ShaderStage::Fragment), + rhi::BindGroupLayoutEntry::sampler(0, rhi::ShaderStage::Fragment) }; + rhi::BindGroupLayoutDesc tBgld{}; tBgld.entries = std::span(tE, 2); + if (m_device->createBindGroupLayout(tBgld, m_texBgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Three bind groups, one per sampler. + auto makeBG = [&](rhi::Sampler* s, rhi::BindGroup*& out) -> draco::Status { + rhi::BindGroupEntry e[2] = { rhi::BindGroupEntry::textureEntry(m_texView), rhi::BindGroupEntry::samplerEntry(s) }; + rhi::BindGroupDesc bgd{}; bgd.layout = m_texBgl; bgd.entries = std::span(e, 2); + return m_device->createBindGroup(bgd, out); + }; + if (makeBG(m_sampTransparent, m_bgTransparent) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (makeBG(m_sampOpaqueBlack, m_bgOpaqueBlack) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (makeBG(m_sampOpaqueWhite, m_bgOpaqueWhite) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Bind group layout: set 1 = uniform buffer with dynamic offset. + rhi::BindGroupLayoutEntry uEntry = rhi::BindGroupLayoutEntry::uniformBuffer(0, rhi::ShaderStage::Vertex); + uEntry.hasDynamicOffset = true; + rhi::BindGroupLayoutEntry uE[1] = { uEntry }; + rhi::BindGroupLayoutDesc uBgld{}; uBgld.entries = std::span(uE, 1); + if (m_device->createBindGroupLayout(uBgld, m_uboBgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BindGroupEntry uBgE[1] = { rhi::BindGroupEntry::bufferEntry(m_ub, 0, 16) }; + rhi::BindGroupDesc uBgd{}; uBgd.layout = m_uboBgl; uBgd.entries = std::span(uBgE, 1); + if (m_device->createBindGroup(uBgd, m_uboBg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Pipeline layout. + rhi::BindGroupLayout* sets[2] = { m_texBgl, m_uboBgl }; + rhi::PipelineLayoutDesc pld{}; pld.bindGroupLayouts = std::span(sets, 2); + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x2, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 20; vbl.attributes = std::span(attrs, 2); + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + ct.blend = rhi::BlendState::alphaBlend(); + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void BorderSamplerSample::onRender() { + using draco::f32, draco::u32, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.2f, 0.2f, 0.25f, 1.0f); + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); + auto* rp = enc->beginRenderPass(rpd); + + rp->setPipeline(m_pipeline); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0, 1); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vb, 0); + rp->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + + // Draw 3 quads side by side with different samplers and dynamic UBO offsets. + rhi::BindGroup* texBGs[3] = { m_bgTransparent, m_bgOpaqueBlack, m_bgOpaqueWhite }; + u32 dynOffsets[3] = { 0, 256, 512 }; + for (int i = 0; i < 3; ++i) { + rp->setBindGroup(0, texBGs[i]); + u32 off[1] = { dynOffsets[i] }; + rp->setBindGroup(1, m_uboBg, std::span(off, 1)); + rp->drawIndexed(6); + } + + rp->end(); + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void BorderSamplerSample::onShutdown() { + if (m_ub && m_ubMapped) m_ub->unmap(); + if (m_fence) m_device->destroyFence(m_fence); if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_uboBg) m_device->destroyBindGroup(m_uboBg); if (m_uboBgl) m_device->destroyBindGroupLayout(m_uboBgl); + if (m_bgOpaqueWhite) m_device->destroyBindGroup(m_bgOpaqueWhite); + if (m_bgOpaqueBlack) m_device->destroyBindGroup(m_bgOpaqueBlack); + if (m_bgTransparent) m_device->destroyBindGroup(m_bgTransparent); + if (m_texBgl) m_device->destroyBindGroupLayout(m_texBgl); + if (m_sampOpaqueWhite) m_device->destroySampler(m_sampOpaqueWhite); + if (m_sampOpaqueBlack) m_device->destroySampler(m_sampOpaqueBlack); + if (m_sampTransparent) m_device->destroySampler(m_sampTransparent); + if (m_texView) m_device->destroyTextureView(m_texView); if (m_tex) m_device->destroyTexture(m_tex); + if (m_ub) m_device->destroyBuffer(m_ub); if (m_ib) m_device->destroyBuffer(m_ib); if (m_vb) m_device->destroyBuffer(m_vb); + if (m_ps) m_device->destroyShaderModule(m_ps); if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { BorderSamplerSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample014_Blit/CMakeLists.txt b/Samples/cpp/RHI/Sample014_Blit/CMakeLists.txt new file mode 100644 index 00000000..0b60bb51 --- /dev/null +++ b/Samples/cpp/RHI/Sample014_Blit/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample014_Blit) diff --git a/Samples/cpp/RHI/Sample014_Blit/Main.cpp b/Samples/cpp/RHI/Sample014_Blit/Main.cpp new file mode 100644 index 00000000..caf42850 --- /dev/null +++ b/Samples/cpp/RHI/Sample014_Blit/Main.cpp @@ -0,0 +1,150 @@ +#include +/// Renders a spinning triangle to a small 128x128 offscreen texture, then blits +/// it to the full swapchain (scaled up with linear filtering). + +#include +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class BlitSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample014 - Blit (Scaled Copy)"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onShutdown() override; +private: + static constexpr const char8_t kShader[] = u8R"( + struct VSInput { float3 Position : TEXCOORD0; float4 Color : TEXCOORD1; }; + struct PSInput { float4 Position : SV_POSITION; float4 Color : COLOR0; }; + PSInput VSMain(VSInput i) { PSInput o; o.Position = float4(i.Position,1); o.Color = i.Color; return o; } + float4 PSMain(PSInput i) : SV_TARGET { return i.Color; } + )"; + static constexpr draco::u32 kOffscreenSize = 128; + + void updateTriangle(); + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_vs = nullptr, *m_ps = nullptr; + rhi::Buffer *m_vb = nullptr; + rhi::PipelineLayout *m_pl = nullptr; rhi::RenderPipeline *m_pipeline = nullptr; + rhi::Texture *m_offscreenTex = nullptr; rhi::TextureView *m_offscreenView = nullptr; + rhi::CommandPool *m_pool = nullptr; rhi::Fence *m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +draco::Status BlitSample::onInit() { + using draco::Status, std::span, draco::u8; + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"VS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"PS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Triangle VB (CpuToGpu for per-frame rotation updates). + rhi::BufferDesc vbd{}; vbd.size = 84; vbd.usage = rhi::BufferUsage::Vertex; vbd.memory = rhi::MemoryLocation::CpuToGpu; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::PipelineLayoutDesc pld{}; + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Offscreen render target. + rhi::TextureDesc td{}; td.format = m_swapChain->format(); td.width = kOffscreenSize; td.height = kOffscreenSize; + td.mipLevelCount = 1; td.usage = rhi::TextureUsage::RenderTarget | rhi::TextureUsage::CopySrc | rhi::TextureUsage::Sampled; + if (m_device->createTexture(td, m_offscreenTex) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::TextureViewDesc tvd{}; tvd.format = m_swapChain->format(); tvd.mipLevelCount = 1; tvd.arrayLayerCount = 1; + if (m_device->createTextureView(m_offscreenTex, tvd, m_offscreenView) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x4, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 28; vbl.attributes = std::span(attrs, 2); + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void BlitSample::updateTriangle() { + float angle = m_totalTime * 2.0f; + float c = std::cos(angle), s = std::sin(angle); + float basePos[6] = { 0.0f, 0.5f, 0.433f, -0.25f, -0.433f, -0.25f }; + float colors[12] = { 1,0.2f,0.2f,1, 0.2f,1,0.2f,1, 0.2f,0.4f,1,1 }; + float verts[21]; + for (int i = 0; i < 3; ++i) { + float x = basePos[i*2], y = basePos[i*2+1]; + verts[i*7+0] = x*c - y*s; verts[i*7+1] = x*s + y*c; verts[i*7+2] = 0.0f; + verts[i*7+3] = colors[i*4]; verts[i*7+4] = colors[i*4+1]; + verts[i*7+5] = colors[i*4+2]; verts[i*7+6] = colors[i*4+3]; + } + void* mapped = m_vb->map(); + if (mapped) { std::memcpy(mapped, verts, 84); m_vb->unmap(); } +} + +void BlitSample::onRender() { + using draco::f32, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + updateTriangle(); + + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + // Pass 1: Render spinning triangle to offscreen texture. + enc->transitionTexture(m_offscreenTex, rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + { + rhi::ColorAttachment ca{}; ca.view = m_offscreenView; + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.15f, 0.1f, 0.2f, 1.0f); + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); + auto* rp = enc->beginRenderPass(rpd); + rp->setPipeline(m_pipeline); + rp->setViewport(0, 0, static_cast(kOffscreenSize), static_cast(kOffscreenSize), 0, 1); + rp->setScissor(0, 0, kOffscreenSize, kOffscreenSize); + rp->setVertexBuffer(0, m_vb, 0); + rp->draw(3); + rp->end(); + } + enc->transitionTexture(m_offscreenTex, rhi::ResourceState::RenderTarget, rhi::ResourceState::CopySrc); + + // Pass 2: Blit offscreen (128x128) to full swapchain (scaled up with linear filtering). + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::CopyDst); + enc->blit(m_offscreenTex, m_swapChain->currentTexture()); + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::CopyDst, rhi::ResourceState::Present); + + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void BlitSample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_offscreenView) m_device->destroyTextureView(m_offscreenView); + if (m_offscreenTex) m_device->destroyTexture(m_offscreenTex); + if (m_vb) m_device->destroyBuffer(m_vb); + if (m_ps) m_device->destroyShaderModule(m_ps); if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { BlitSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample015_Queries/CMakeLists.txt b/Samples/cpp/RHI/Sample015_Queries/CMakeLists.txt new file mode 100644 index 00000000..9c870272 --- /dev/null +++ b/Samples/cpp/RHI/Sample015_Queries/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample015_Queries) diff --git a/Samples/cpp/RHI/Sample015_Queries/Main.cpp b/Samples/cpp/RHI/Sample015_Queries/Main.cpp new file mode 100644 index 00000000..2deb67a0 --- /dev/null +++ b/Samples/cpp/RHI/Sample015_Queries/Main.cpp @@ -0,0 +1,169 @@ +#include +/// Demonstrates GPU timestamp queries to measure render pass duration. +/// Prints render pass GPU time to console every 2 seconds. + +#include +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class QuerySample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample015 - GPU Queries"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onShutdown() override; +private: + static constexpr const char8_t kShader[] = u8R"( + struct VSInput { float3 Position : TEXCOORD0; float4 Color : TEXCOORD1; }; + struct PSInput { float4 Position : SV_POSITION; float4 Color : COLOR0; }; + PSInput VSMain(VSInput i) { PSInput o; o.Position = float4(i.Position,1); o.Color = i.Color; return o; } + float4 PSMain(PSInput i) : SV_TARGET { return i.Color; } + )"; + static constexpr float kVerts[] = { + 0.0f, 0.5f, 0.0f, 1.0f, 0.3f, 0.3f, 1.0f, + 0.5f, -0.5f, 0.0f, 0.3f, 1.0f, 0.3f, 1.0f, + -0.5f, -0.5f, 0.0f, 0.3f, 0.3f, 1.0f, 1.0f, + }; + static constexpr draco::u16 kIdx[] = { 0, 1, 2 }; + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_vs = nullptr, *m_ps = nullptr; + rhi::Buffer *m_vb = nullptr, *m_ib = nullptr; + rhi::PipelineLayout *m_pl = nullptr; rhi::RenderPipeline *m_pipeline = nullptr; + rhi::QuerySet *m_tsQuerySet = nullptr; + rhi::Buffer *m_queryResultBuf = nullptr; + rhi::CommandPool *m_pool = nullptr; rhi::Fence *m_fence = nullptr; + draco::u64 m_fenceVal = 0; + int m_frameCount = 0; + float m_lastReportTime = 0.0f; +}; + +draco::Status QuerySample::onInit() { + using draco::Status, std::span, draco::u8; + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"VS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"PS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::BufferDesc vbd{}; vbd.size = sizeof(kVerts); vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ibd{}; ibd.size = sizeof(kIdx); ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; ibd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(kVerts), sizeof(kVerts))); + batch->writeBuffer(m_ib, 0, std::span(reinterpret_cast(kIdx), sizeof(kIdx))); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + rhi::PipelineLayoutDesc pld{}; + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x4, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 28; vbl.attributes = std::span(attrs, 2); + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Timestamp query set: 2 queries (before + after render pass). + rhi::QuerySetDesc qsd{}; qsd.type = rhi::QueryType::Timestamp; qsd.count = 2; + if (m_device->createQuerySet(qsd, m_tsQuerySet) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Buffer to receive resolved query results (2 * uint64 = 16 bytes). + rhi::BufferDesc qbd{}; qbd.size = 16; qbd.usage = rhi::BufferUsage::CopyDst; qbd.memory = rhi::MemoryLocation::GpuToCpu; + if (m_device->createBuffer(qbd, m_queryResultBuf) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void QuerySample::onRender() { + using draco::f32, draco::u64, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + + // Read back previous frame's query results (after fence wait ensures GPU is done). + if (m_frameCount > 1) { + void* mapped = m_queryResultBuf->map(); + if (mapped) { + auto* timestamps = static_cast(mapped); + u64 begin = timestamps[0], end = timestamps[1], delta = end - begin; + f32 period = m_graphicsQueue->timestampPeriod(); + f32 gpuTimeUs = static_cast(delta) * period / 1000.0f; + if (m_totalTime - m_lastReportTime >= 2.0f) { + std::printf("GPU render pass time: %.2f us (%llu ticks, period=%.2f ns)\n", + gpuTimeUs, static_cast(delta), period); + m_lastReportTime = m_totalTime; + } + m_queryResultBuf->unmap(); + } + } + + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + // Reset queries for this frame. + enc->resetQuerySet(m_tsQuerySet, 0, 2); + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + + // Timestamp before render pass. + enc->writeTimestamp(m_tsQuerySet, 0); + + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.08f, 0.08f, 0.12f, 1.0f); + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); + auto* rp = enc->beginRenderPass(rpd); + rp->setPipeline(m_pipeline); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0, 1); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vb, 0); + rp->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + rp->drawIndexed(3); + rp->end(); + + // Timestamp after render pass. + enc->writeTimestamp(m_tsQuerySet, 1); + + // Resolve timestamps to buffer. + enc->resolveQuerySet(m_tsQuerySet, 0, 2, m_queryResultBuf, 0); + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); + m_frameCount++; +} + +void QuerySample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_queryResultBuf) m_device->destroyBuffer(m_queryResultBuf); + if (m_tsQuerySet) m_device->destroyQuerySet(m_tsQuerySet); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_ib) m_device->destroyBuffer(m_ib); if (m_vb) m_device->destroyBuffer(m_vb); + if (m_ps) m_device->destroyShaderModule(m_ps); if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { QuerySample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample016_Readback/CMakeLists.txt b/Samples/cpp/RHI/Sample016_Readback/CMakeLists.txt new file mode 100644 index 00000000..9f771a60 --- /dev/null +++ b/Samples/cpp/RHI/Sample016_Readback/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample016_Readback) diff --git a/Samples/cpp/RHI/Sample016_Readback/Main.cpp b/Samples/cpp/RHI/Sample016_Readback/Main.cpp new file mode 100644 index 00000000..501b0367 --- /dev/null +++ b/Samples/cpp/RHI/Sample016_Readback/Main.cpp @@ -0,0 +1,212 @@ +#include +/// Renders a colored triangle to a small offscreen texture, copies it to a +/// readback buffer, then reads pixel values on the CPU and prints them. + +#include +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class ReadbackSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample016 - GPU Readback"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onShutdown() override; +private: + static constexpr const char8_t kShader[] = u8R"( + struct VSInput { float3 Position : TEXCOORD0; float4 Color : TEXCOORD1; }; + struct PSInput { float4 Position : SV_POSITION; float4 Color : COLOR0; }; + PSInput VSMain(VSInput i) { PSInput o; o.Position = float4(i.Position,1); o.Color = i.Color; return o; } + float4 PSMain(PSInput i) : SV_TARGET { return i.Color; } + )"; + static constexpr draco::u32 kTexSize = 16; + static constexpr float kVerts[] = { + 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, + }; + + void readbackPixels(); + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_vs = nullptr, *m_ps = nullptr; + rhi::Buffer *m_vb = nullptr; + rhi::PipelineLayout *m_pl = nullptr; + rhi::RenderPipeline *m_offPipeline = nullptr, *m_swapPipeline = nullptr; + rhi::Texture *m_offTex = nullptr; rhi::TextureView *m_offView = nullptr; + rhi::Buffer *m_readbackBuf = nullptr; + rhi::CommandPool *m_pool = nullptr; rhi::Fence *m_fence = nullptr; + draco::u64 m_fenceVal = 0; + bool m_hasReadback = false; + float m_lastReportTime = 0.0f; +}; + +draco::Status ReadbackSample::onInit() { + using draco::Status, std::span, draco::u8, draco::u32; + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"VS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"PS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::BufferDesc vbd{}; vbd.size = sizeof(kVerts); vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(kVerts), sizeof(kVerts))); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + rhi::PipelineLayoutDesc pld{}; + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Small offscreen RGBA8 texture. + rhi::TextureDesc td{}; td.format = rhi::TextureFormat::RGBA8Unorm; td.width = kTexSize; td.height = kTexSize; + td.mipLevelCount = 1; td.usage = rhi::TextureUsage::RenderTarget | rhi::TextureUsage::CopySrc; + if (m_device->createTexture(td, m_offTex) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::TextureViewDesc tvd{}; tvd.format = rhi::TextureFormat::RGBA8Unorm; tvd.mipLevelCount = 1; tvd.arrayLayerCount = 1; + if (m_device->createTextureView(m_offTex, tvd, m_offView) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Readback buffer with row alignment (256 bytes for DX12 compat). + u32 bytesPerRow = ((kTexSize * 4 + 255) / 256) * 256; + rhi::BufferDesc rbd{}; rbd.size = bytesPerRow * kTexSize; rbd.usage = rhi::BufferUsage::CopyDst; rbd.memory = rhi::MemoryLocation::GpuToCpu; + if (m_device->createBuffer(rbd, m_readbackBuf) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x4, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 28; vbl.attributes = std::span(attrs, 2); + + // Pipeline for offscreen (RGBA8Unorm). + rhi::ColorTargetState ct{}; ct.format = rhi::TextureFormat::RGBA8Unorm; + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + if (m_device->createRenderPipeline(rpd, m_offPipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Pipeline for swapchain (different format). + ct.format = m_swapChain->format(); + rpd.fragment->targets = std::span(&ct, 1); + if (m_device->createRenderPipeline(rpd, m_swapPipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void ReadbackSample::readbackPixels() { + void* mapped = m_readbackBuf->map(); + if (!mapped) return; + draco::u32 bytesPerRow = ((kTexSize * 4 + 255) / 256) * 256; + auto* data = static_cast(mapped); + + std::printf("=== Readback: %ux%u RGBA8 texture ===\n", kTexSize, kTexSize); + auto printPixel = [&](draco::u32 x, draco::u32 y, const char* label) { + draco::u32 off = y * bytesPerRow + x * 4; + std::printf(" %s (%u,%u): R=%u G=%u B=%u A=%u\n", label, x, y, + data[off], data[off+1], data[off+2], data[off+3]); + }; + printPixel(0, 0, "Top-left"); + printPixel(kTexSize-1, 0, "Top-right"); + printPixel(kTexSize/2, kTexSize/2, "Center"); + printPixel(0, kTexSize-1, "Bottom-left"); + printPixel(kTexSize-1, kTexSize-1, "Bottom-right"); + + int nonBlack = 0; + for (draco::u32 y = 0; y < kTexSize; ++y) + for (draco::u32 x = 0; x < kTexSize; ++x) { + draco::u32 off = y * bytesPerRow + x * 4; + if (data[off] > 0 || data[off+1] > 0 || data[off+2] > 0) nonBlack++; + } + std::printf("Non-black pixels: %d / %u (%.0f%%)\n", nonBlack, kTexSize * kTexSize, + 100.0f * static_cast(nonBlack) / static_cast(kTexSize * kTexSize)); + m_readbackBuf->unmap(); +} + +void ReadbackSample::onRender() { + using draco::f32, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + + if (m_hasReadback && (m_totalTime - m_lastReportTime >= 3.0f)) { + readbackPixels(); + m_lastReportTime = m_totalTime; + } + + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + // Render triangle to offscreen texture. + enc->transitionTexture(m_offTex, rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + { + rhi::ColorAttachment ca{}; ca.view = m_offView; + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.0f, 0.0f, 0.0f, 1.0f); + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); + auto* rp = enc->beginRenderPass(rpd); + rp->setPipeline(m_offPipeline); + rp->setViewport(0, 0, static_cast(kTexSize), static_cast(kTexSize), 0, 1); + rp->setScissor(0, 0, kTexSize, kTexSize); + rp->setVertexBuffer(0, m_vb, 0); + rp->draw(3); + rp->end(); + } + enc->transitionTexture(m_offTex, rhi::ResourceState::RenderTarget, rhi::ResourceState::CopySrc); + + // Copy texture to readback buffer. + draco::u32 bytesPerRow = ((kTexSize * 4 + 255) / 256) * 256; + rhi::BufferTextureCopyRegion region{}; + region.bufferOffset = 0; region.bytesPerRow = bytesPerRow; region.rowsPerImage = kTexSize; + region.textureExtent = rhi::Extent3D{ kTexSize, kTexSize, 1 }; + enc->copyTextureToBuffer(m_offTex, m_readbackBuf, region); + + // Also render to swapchain so we see something. + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + { + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.08f, 0.08f, 0.12f, 1.0f); + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); + auto* rp = enc->beginRenderPass(rpd); + rp->setPipeline(m_swapPipeline); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0, 1); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vb, 0); + rp->draw(3); + rp->end(); + } + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); + m_hasReadback = true; +} + +void ReadbackSample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_swapPipeline) m_device->destroyRenderPipeline(m_swapPipeline); + if (m_offPipeline) m_device->destroyRenderPipeline(m_offPipeline); + if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_readbackBuf) m_device->destroyBuffer(m_readbackBuf); + if (m_offView) m_device->destroyTextureView(m_offView); + if (m_offTex) m_device->destroyTexture(m_offTex); + if (m_vb) m_device->destroyBuffer(m_vb); + if (m_ps) m_device->destroyShaderModule(m_ps); if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { ReadbackSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample017_MultiQueue/CMakeLists.txt b/Samples/cpp/RHI/Sample017_MultiQueue/CMakeLists.txt new file mode 100644 index 00000000..9b45c2bb --- /dev/null +++ b/Samples/cpp/RHI/Sample017_MultiQueue/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample017_MultiQueue) diff --git a/Samples/cpp/RHI/Sample017_MultiQueue/Main.cpp b/Samples/cpp/RHI/Sample017_MultiQueue/Main.cpp new file mode 100644 index 00000000..f6127229 --- /dev/null +++ b/Samples/cpp/RHI/Sample017_MultiQueue/Main.cpp @@ -0,0 +1,279 @@ +#include +/// A compute shader generates an animated vertex grid on the compute queue, +/// then the graphics queue waits on the compute fence and renders the result. + +#include +#include +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; +using draco::math::Matrix4; + +class MultiQueueSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample017 - MultiQueue (Async Compute)"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onResize(draco::u32 w, draco::u32 h) override { recreateDepth(w, h); } + void onShutdown() override; +private: + static constexpr const char8_t kComputeSrc[] = u8R"( + cbuffer Params : register(b0, space0) { float Time; uint NumPoints; float Spacing; float Padding; }; + struct Vertex { float PosX, PosY, PosZ, ColR, ColG, ColB; }; + RWStructuredBuffer gVertices : register(u0, space0); + [numthreads(64, 1, 1)] + void CSMain(uint3 dtid : SV_DispatchThreadID) { + uint idx = dtid.x; if (idx >= NumPoints) return; + uint gridSize = (uint)sqrt((float)NumPoints); + uint row = idx / gridSize, col = idx % gridSize; + float fx = ((float)col / (float)(gridSize-1))*2.0 - 1.0; + float fz = ((float)row / (float)(gridSize-1))*2.0 - 1.0; + float dist = sqrt(fx*fx + fz*fz); + float fy = sin(dist*8.0 - Time*3.0) * 0.2; + gVertices[idx].PosX = fx; gVertices[idx].PosY = fy; gVertices[idx].PosZ = fz; + gVertices[idx].ColR = 0.5 + 0.5*sin(Time + fx*3.0); + gVertices[idx].ColG = 0.5 + 0.5*cos(Time + fz*3.0); + gVertices[idx].ColB = 0.5 + 0.5*sin(Time*0.7 + dist*4.0); + } + )"; + static constexpr const char8_t kRenderSrc[] = u8R"( + cbuffer ViewProj : register(b0, space0) { row_major float4x4 VP; }; + struct VSInput { float3 Position : TEXCOORD0; float3 Color : TEXCOORD1; }; + struct PSInput { float4 Position : SV_POSITION; float3 Color : COLOR0; + [[vk::builtin("PointSize")]] float PointSize : PSIZE; }; + PSInput VSMain(VSInput i) { PSInput o; o.Position = mul(float4(i.Position,1), VP); o.Color = i.Color; o.PointSize = 1.0; return o; } + float4 PSMain(PSInput i) : SV_TARGET { return float4(i.Color, 1.0); } + )"; + + static constexpr draco::u32 kGrid = 64, kNumPts = kGrid*kGrid, kVertSz = 24, kBufSz = kNumPts*kVertSz; + + void recreateDepth(draco::u32 w, draco::u32 h); + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_cs = nullptr, *m_vs = nullptr, *m_ps = nullptr; + + // Compute resources. + rhi::Queue* m_computeQueue = nullptr; + rhi::CommandPool *m_computePool = nullptr; + rhi::BindGroupLayout *m_compBgl = nullptr; rhi::BindGroup *m_compBg = nullptr; + rhi::PipelineLayout *m_compPl = nullptr; rhi::ComputePipeline *m_compPipe = nullptr; + rhi::Buffer *m_paramsBuf = nullptr; void *m_paramsMapped = nullptr; + + // Graphics resources. + rhi::CommandPool *m_gfxPool = nullptr; + rhi::BindGroupLayout *m_renBgl = nullptr; rhi::BindGroup *m_renBg = nullptr; + rhi::PipelineLayout *m_renPl = nullptr; rhi::RenderPipeline *m_renPipe = nullptr; + rhi::Buffer *m_vpBuf = nullptr; void *m_vpMapped = nullptr; + + // Shared. + rhi::Buffer *m_vtxBuf = nullptr; + sf::DepthBuffer m_depthBuf; + + // Synchronization. + rhi::Fence *m_compFence = nullptr, *m_gfxFence = nullptr; + draco::u64 m_compFenceVal = 0, m_gfxFenceVal = 0; + bool m_hasDedicatedCompute = false; + float m_lastReportTime = 0.0f; +}; + +void MultiQueueSample::recreateDepth(draco::u32 w, draco::u32 h) { + m_depthBuf.recreate(m_device, w, h); +} + +draco::Status MultiQueueSample::onInit() { + using draco::Status, std::span, draco::u8, draco::u32; + + // Check for dedicated compute queue. + if (m_device->getQueueCount(rhi::QueueType::Compute) == 0) { + m_computeQueue = m_graphicsQueue; + m_hasDedicatedCompute = false; + std::printf("No dedicated compute queue - using graphics queue for both\n"); + } else { + m_computeQueue = m_device->getQueue(rhi::QueueType::Compute, 0); + m_hasDedicatedCompute = true; + std::printf("Using dedicated compute queue\n"); + } + + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kComputeSrc, shaders::ShaderStage::Compute, u8"CSMain", u8"CS", m_cs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kRenderSrc, shaders::ShaderStage::Vertex, u8"VSMain", u8"VS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kRenderSrc, shaders::ShaderStage::Fragment, u8"PSMain", u8"PS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Shared vertex/storage buffer. + rhi::BufferDesc vbd{}; vbd.size = kBufSz; vbd.usage = rhi::BufferUsage::Storage | rhi::BufferUsage::Vertex; vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vtxBuf) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Compute params UBO. + rhi::BufferDesc pbd{}; pbd.size = 16; pbd.usage = rhi::BufferUsage::Uniform; pbd.memory = rhi::MemoryLocation::CpuToGpu; + if (m_device->createBuffer(pbd, m_paramsBuf) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + m_paramsMapped = m_paramsBuf->map(); + + // View-projection UBO. + rhi::BufferDesc vpd{}; vpd.size = 64; vpd.usage = rhi::BufferUsage::Uniform; vpd.memory = rhi::MemoryLocation::CpuToGpu; + if (m_device->createBuffer(vpd, m_vpBuf) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + m_vpMapped = m_vpBuf->map(); + + // Compute pipeline. + rhi::BindGroupLayoutEntry cE[2] = { rhi::BindGroupLayoutEntry::uniformBuffer(0, rhi::ShaderStage::Compute), + rhi::BindGroupLayoutEntry::storageBuffer(0, rhi::ShaderStage::Compute, false) }; + rhi::BindGroupLayoutDesc cBgld{}; cBgld.entries = std::span(cE, 2); + if (m_device->createBindGroupLayout(cBgld, m_compBgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BindGroupEntry cBgE[2] = { rhi::BindGroupEntry::bufferEntry(m_paramsBuf, 0, 16), + rhi::BindGroupEntry::bufferEntry(m_vtxBuf, 0, kBufSz) }; + rhi::BindGroupDesc cBgd{}; cBgd.layout = m_compBgl; cBgd.entries = std::span(cBgE, 2); + if (m_device->createBindGroup(cBgd, m_compBg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BindGroupLayout* cSets[1] = { m_compBgl }; + rhi::PipelineLayoutDesc cPld{}; cPld.bindGroupLayouts = std::span(cSets, 1); + if (m_device->createPipelineLayout(cPld, m_compPl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::ComputePipelineDesc cpd{}; cpd.layout = m_compPl; cpd.compute = { m_cs, u8"CSMain", rhi::ShaderStage::Compute }; + if (m_device->createComputePipeline(cpd, m_compPipe) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Render pipeline. + rhi::BindGroupLayoutEntry rE[1] = { rhi::BindGroupLayoutEntry::uniformBuffer(0, rhi::ShaderStage::Vertex) }; + rhi::BindGroupLayoutDesc rBgld{}; rBgld.entries = std::span(rE, 1); + if (m_device->createBindGroupLayout(rBgld, m_renBgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BindGroupEntry rBgE[1] = { rhi::BindGroupEntry::bufferEntry(m_vpBuf, 0, 64) }; + rhi::BindGroupDesc rBgd{}; rBgd.layout = m_renBgl; rBgd.entries = std::span(rBgE, 1); + if (m_device->createBindGroup(rBgd, m_renBg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BindGroupLayout* rSets[1] = { m_renBgl }; + rhi::PipelineLayoutDesc rPld{}; rPld.bindGroupLayouts = std::span(rSets, 1); + if (m_device->createPipelineLayout(rPld, m_renPl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + m_depthBuf.recreate(m_device, m_width, m_height); + + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x3, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = kVertSz; vbl.attributes = std::span(attrs, 2); + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_renPl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.primitive.topology = rhi::PrimitiveTopology::PointList; + rpd.depthStencil = rhi::DepthStencilState{}; rpd.depthStencil->format = rhi::TextureFormat::Depth24PlusStencil8; + rpd.depthStencil->depthWriteEnabled = true; rpd.depthStencil->depthCompare = rhi::CompareFunction::Less; + if (m_device->createRenderPipeline(rpd, m_renPipe) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Command pools - one per queue type. + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_gfxPool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + auto compPoolType = m_hasDedicatedCompute ? rhi::QueueType::Compute : rhi::QueueType::Graphics; + if (m_device->createCommandPool(compPoolType, m_computePool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createFence(0, m_compFence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_gfxFence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void MultiQueueSample::onRender() { + using draco::u32, draco::f32, std::span; + if (m_gfxFenceVal > 0) m_gfxFence->wait(m_gfxFenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + // Update compute params. + u32 numPts = kNumPts; + f32 params[4] = { m_totalTime, 0, 1.0f, 0 }; + std::memcpy(¶ms[1], &numPts, 4); + std::memcpy(m_paramsMapped, params, 16); + + // Update VP. + f32 aspect = static_cast(m_width) / static_cast(m_height); + f32 camAngle = m_totalTime * 0.4f, camDist = 2.5f; + Matrix4 view = Matrix4::lookAtRH(draco::math::Vector3{std::sin(camAngle)*camDist, 1.2f, std::cos(camAngle)*camDist}, draco::math::Vector3{ 0,0,0}, draco::math::Vector3{0,1,0}); + Matrix4 proj = Matrix4::perspectiveFovRH(draco::math::degToRad(45.0f), aspect, 0.1f, 100.0f); + Matrix4 vp = view * proj; + std::memcpy(m_vpMapped, vp.data(), 64); + + // === Compute pass on compute queue === + m_computePool->reset(); + rhi::CommandEncoder* cEnc = nullptr; + if (m_computePool->createEncoder(cEnc) != draco::ErrorCode::Ok || !cEnc) return; + + rhi::BufferBarrier bb{}; bb.buffer = m_vtxBuf; bb.oldState = rhi::ResourceState::VertexBuffer; bb.newState = rhi::ResourceState::ShaderWrite; + rhi::BarrierGroup bg1{}; bg1.bufferBarriers = std::span(&bb, 1); + cEnc->barrier(bg1); + auto* cp = cEnc->beginComputePass(u8"AsyncCompute"); + cp->setPipeline(m_compPipe); cp->setBindGroup(0, m_compBg); + cp->dispatch((kNumPts + 63) / 64); cp->end(); + bb.oldState = rhi::ResourceState::ShaderWrite; bb.newState = rhi::ResourceState::VertexBuffer; + cEnc->barrier(bg1); + + rhi::CommandBuffer* cCb = cEnc->finish(); m_compFenceVal++; + rhi::CommandBuffer* cCbs[1] = { cCb }; + m_computeQueue->submit(std::span(cCbs, 1), m_compFence, m_compFenceVal); + m_computePool->destroyEncoder(cEnc); + + // === Graphics pass - waits on compute fence before executing === + m_gfxPool->reset(); + rhi::CommandEncoder* gEnc = nullptr; + if (m_gfxPool->createEncoder(gEnc) != draco::ErrorCode::Ok || !gEnc) return; + + gEnc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + gEnc->transitionTexture(m_depthBuf.texture, rhi::ResourceState::Undefined, rhi::ResourceState::DepthStencilWrite); + + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.03f, 0.03f, 0.06f, 1.0f); + rhi::DepthStencilAttachment dsa{}; dsa.view = m_depthBuf.view; + dsa.depthLoadOp = rhi::LoadOp::Clear; dsa.depthStoreOp = rhi::StoreOp::Store; dsa.depthClearValue = 1.0f; + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); rpd.depthStencilAttachment = dsa; + auto* rp = gEnc->beginRenderPass(rpd); + rp->setPipeline(m_renPipe); rp->setBindGroup(0, m_renBg); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0, 1); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vtxBuf, 0); + rp->draw(kNumPts); rp->end(); + + gEnc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + rhi::CommandBuffer* gCb = gEnc->finish(); m_gfxFenceVal++; + rhi::CommandBuffer* gCbs[1] = { gCb }; + + // Submit graphics - wait on compute fence, signal graphics fence. + rhi::Fence* waitFences[1] = { m_compFence }; + draco::u64 waitValues[1] = { m_compFenceVal }; + m_graphicsQueue->submit(std::span(gCbs, 1), + std::span(waitFences, 1), + std::span(waitValues, 1), + m_gfxFence, m_gfxFenceVal); + m_swapChain->present(m_graphicsQueue); + m_gfxPool->destroyEncoder(gEnc); + + if (m_totalTime - m_lastReportTime >= 3.0f) { + std::printf("MultiQueue: compute fence=%llu, graphics fence=%llu, dt=%.2fms\n", + static_cast(m_compFenceVal), + static_cast(m_gfxFenceVal), + m_deltaTime * 1000.0f); + m_lastReportTime = m_totalTime; + } +} + +void MultiQueueSample::onShutdown() { + if (m_paramsBuf && m_paramsMapped) m_paramsBuf->unmap(); + if (m_vpBuf && m_vpMapped) m_vpBuf->unmap(); + m_depthBuf.destroy(m_device); + if (m_gfxFence) m_device->destroyFence(m_gfxFence); if (m_compFence) m_device->destroyFence(m_compFence); + if (m_gfxPool) m_device->destroyCommandPool(m_gfxPool); if (m_computePool) m_device->destroyCommandPool(m_computePool); + if (m_renPipe) m_device->destroyRenderPipeline(m_renPipe); if (m_renPl) m_device->destroyPipelineLayout(m_renPl); + if (m_renBg) m_device->destroyBindGroup(m_renBg); if (m_renBgl) m_device->destroyBindGroupLayout(m_renBgl); + if (m_compPipe) m_device->destroyComputePipeline(m_compPipe); if (m_compPl) m_device->destroyPipelineLayout(m_compPl); + if (m_compBg) m_device->destroyBindGroup(m_compBg); if (m_compBgl) m_device->destroyBindGroupLayout(m_compBgl); + if (m_vpBuf) m_device->destroyBuffer(m_vpBuf); if (m_paramsBuf) m_device->destroyBuffer(m_paramsBuf); + if (m_vtxBuf) m_device->destroyBuffer(m_vtxBuf); + if (m_ps) m_device->destroyShaderModule(m_ps); if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_cs) m_device->destroyShaderModule(m_cs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { MultiQueueSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample018_Bindless/CMakeLists.txt b/Samples/cpp/RHI/Sample018_Bindless/CMakeLists.txt new file mode 100644 index 00000000..187c8951 --- /dev/null +++ b/Samples/cpp/RHI/Sample018_Bindless/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample018_Bindless) diff --git a/Samples/cpp/RHI/Sample018_Bindless/Main.cpp b/Samples/cpp/RHI/Sample018_Bindless/Main.cpp new file mode 100644 index 00000000..bc360e74 --- /dev/null +++ b/Samples/cpp/RHI/Sample018_Bindless/Main.cpp @@ -0,0 +1,339 @@ +#include +/// Demonstrates bindless texture arrays with material index via push constants. +/// Creates 4 procedural textures, binds them in a bindless array, and renders +/// 4 quads each selecting a different texture via push constant index. + +#include +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class BindlessSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample018 - Bindless Textures"; } + rhi::DeviceFeatures requiredFeatures() const override { + rhi::DeviceFeatures f{}; f.bindlessDescriptors = true; return f; + } +protected: + draco::Status onInit() override; + void onRender() override; + void onShutdown() override; +private: + draco::Status createTextures(); + void generatePixel(draco::u32 texIndex, draco::u32 x, draco::u32 y, draco::u8* rgba); + + static constexpr const char8_t kShader[] = u8R"( + Texture2D gTextures[] : register(t0, space0); + SamplerState gSampler : register(s0, space1); + + struct PushData + { + uint TextureIndex; + float OffsetX; + float OffsetY; + float Padding; + }; + + [[vk::push_constant]] ConstantBuffer gPush : register(b0, space2); + + struct PSInput + { + float4 Position : SV_POSITION; + float2 TexCoord : TEXCOORD0; + }; + + PSInput VSMain(uint vertexID : SV_VertexID) + { + // Fullscreen-quad-style: 4 vertices for a unit quad + float2 positions[4] = { + float2(-0.4, 0.4), + float2( 0.4, 0.4), + float2(-0.4,-0.4), + float2( 0.4,-0.4) + }; + float2 uvs[4] = { + float2(0, 0), float2(1, 0), + float2(0, 1), float2(1, 1) + }; + + PSInput output; + float2 pos = positions[vertexID]; + pos.x += gPush.OffsetX; + pos.y += gPush.OffsetY; + output.Position = float4(pos, 0.0, 1.0); + output.TexCoord = uvs[vertexID]; + return output; + } + + float4 PSMain(PSInput input) : SV_TARGET + { + return gTextures[gPush.TextureIndex].Sample(gSampler, input.TexCoord); + } + )"; + + static constexpr draco::u32 kTexSize = 64; + static constexpr draco::u32 kNumTextures = 4; + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule* m_vs = nullptr; + rhi::ShaderModule* m_ps = nullptr; + + // Textures + rhi::Texture* m_textures[kNumTextures] = {}; + rhi::TextureView* m_textureViews[kNumTextures] = {}; + rhi::Sampler* m_sampler = nullptr; + + // Bindless bind group (space0: bindless textures) + rhi::BindGroupLayout* m_bindlessBgl = nullptr; + rhi::BindGroup* m_bindlessBg = nullptr; + + // Sampler bind group (space1: sampler) + rhi::BindGroupLayout* m_samplerBgl = nullptr; + rhi::BindGroup* m_samplerBg = nullptr; + + rhi::PipelineLayout* m_pl = nullptr; + rhi::RenderPipeline* m_pipeline = nullptr; + rhi::CommandPool* m_pool = nullptr; + rhi::Fence* m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +draco::Status BindlessSample::onInit() { + using draco::Status, std::span, draco::u8, draco::u32; + + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"BindlessVS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"BindlessPS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Create 4 procedural textures with different patterns + if (createTextures() != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Sampler + rhi::SamplerDesc sd{}; sd.minFilter = rhi::FilterMode::Linear; sd.magFilter = rhi::FilterMode::Linear; + sd.addressU = rhi::AddressMode::Repeat; sd.addressV = rhi::AddressMode::Repeat; + sd.label = u8"BindlessSampler"; + if (m_device->createSampler(sd, m_sampler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Bindless BGL (space0): unbounded texture array + rhi::BindGroupLayoutEntry bindlessEntry{}; + bindlessEntry.binding = 0; + bindlessEntry.visibility = rhi::ShaderStage::Fragment; + bindlessEntry.type = rhi::BindingType::BindlessTextures; + bindlessEntry.textureDimension = rhi::TextureViewDimension::Texture2D; + bindlessEntry.count = 0xFFFFFFFF; + rhi::BindGroupLayoutEntry blEntries[1] = { bindlessEntry }; + rhi::BindGroupLayoutDesc blBgld{}; blBgld.entries = std::span(blEntries, 1); + blBgld.label = u8"BindlessBGL"; + if (m_device->createBindGroupLayout(blBgld, m_bindlessBgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Create bindless bind group (no entries at creation - populated via updateBindless) + rhi::BindGroupDesc blBgd{}; blBgd.layout = m_bindlessBgl; blBgd.label = u8"BindlessBG"; + if (m_device->createBindGroup(blBgd, m_bindlessBg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Populate bindless slots + rhi::BindlessUpdateEntry bindlessUpdates[kNumTextures]; + for (u32 i = 0; i < kNumTextures; ++i) { + bindlessUpdates[i] = {}; + bindlessUpdates[i].layoutIndex = 0; + bindlessUpdates[i].arrayIndex = i; + bindlessUpdates[i].textureView = m_textureViews[i]; + } + m_bindlessBg->updateBindless(std::span(bindlessUpdates, kNumTextures)); + + // Sampler BGL (space1) + rhi::BindGroupLayoutEntry samplerEntry = rhi::BindGroupLayoutEntry::sampler(0, rhi::ShaderStage::Fragment); + rhi::BindGroupLayoutEntry sEntries[1] = { samplerEntry }; + rhi::BindGroupLayoutDesc sBgld{}; sBgld.entries = std::span(sEntries, 1); + sBgld.label = u8"SamplerBGL"; + if (m_device->createBindGroupLayout(sBgld, m_samplerBgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::BindGroupEntry sBgEntries[1] = { rhi::BindGroupEntry::samplerEntry(m_sampler) }; + rhi::BindGroupDesc sBgd{}; sBgd.layout = m_samplerBgl; + sBgd.entries = std::span(sBgEntries, 1); sBgd.label = u8"SamplerBG"; + if (m_device->createBindGroup(sBgd, m_samplerBg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Pipeline layout: group 0 = bindless textures, group 1 = sampler, push constants + rhi::BindGroupLayout* sets[2] = { m_bindlessBgl, m_samplerBgl }; + rhi::PushConstantRange pcr{}; pcr.stages = rhi::ShaderStage::Vertex | rhi::ShaderStage::Fragment; + pcr.offset = 0; pcr.size = 16; + rhi::PushConstantRange pushRanges[1] = { pcr }; + rhi::PipelineLayoutDesc pld{}; pld.bindGroupLayouts = std::span(sets, 2); + pld.pushConstantRanges = std::span(pushRanges, 1); + pld.label = u8"BindlessPL"; + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Render pipeline (no vertex buffers - SV_VertexID driven) + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.primitive.topology = rhi::PrimitiveTopology::TriangleStrip; + rpd.label = u8"BindlessPipeline"; + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +draco::Status BindlessSample::createTextures() { + using draco::Status, std::span, draco::u8, draco::u32; + + constexpr u32 rowBytes = kTexSize * 4; + constexpr u32 texBytes = rowBytes * kTexSize; + u8 pixels[texBytes]; + + rhi::TransferBatch* batch = nullptr; + m_graphicsQueue->createTransferBatch(batch); + + for (u32 t = 0; t < kNumTextures; ++t) { + // Generate pattern + for (u32 y = 0; y < kTexSize; ++y) { + for (u32 x = 0; x < kTexSize; ++x) { + u32 offset = (y * kTexSize + x) * 4; + generatePixel(t, x, y, &pixels[offset]); + } + } + + rhi::TextureDesc td{}; td.format = rhi::TextureFormat::RGBA8Unorm; + td.width = kTexSize; td.height = kTexSize; + td.mipLevelCount = 1; td.usage = rhi::TextureUsage::Sampled | rhi::TextureUsage::CopyDst; + td.label = u8"BindlessTex"; + if (m_device->createTexture(td, m_textures[t]) != draco::ErrorCode::Ok) { + m_graphicsQueue->destroyTransferBatch(batch); return draco::ErrorCode::Unknown; + } + + rhi::TextureDataLayout layout{}; layout.bytesPerRow = rowBytes; layout.rowsPerImage = kTexSize; + batch->writeTexture(m_textures[t], std::span(pixels, texBytes), + layout, rhi::Extent3D{kTexSize, kTexSize, 1}); + + rhi::TextureViewDesc tvd{}; tvd.format = rhi::TextureFormat::RGBA8Unorm; + tvd.mipLevelCount = 1; tvd.arrayLayerCount = 1; + if (m_device->createTextureView(m_textures[t], tvd, m_textureViews[t]) != draco::ErrorCode::Ok) { + m_graphicsQueue->destroyTransferBatch(batch); return draco::ErrorCode::Unknown; + } + } + + batch->submit(); + m_graphicsQueue->destroyTransferBatch(batch); + return draco::ErrorCode::Ok; +} + +void BindlessSample::generatePixel(draco::u32 texIndex, draco::u32 x, draco::u32 y, draco::u8* rgba) { + float fx = static_cast(x) / static_cast(kTexSize); + float fy = static_cast(y) / static_cast(kTexSize); + + switch (texIndex) { + case 0: { // Red/white checkerboard + bool check = ((x / 8) + (y / 8)) % 2 == 0; + rgba[0] = check ? 220 : 255; + rgba[1] = check ? 30 : 255; + rgba[2] = check ? 30 : 255; + rgba[3] = 255; + break; + } + case 1: { // Green gradient with stripes + auto g = static_cast(fx * 255.0f); + bool stripe = (y % 16) < 8; + rgba[0] = stripe ? 30 : 10; + rgba[1] = stripe ? g : static_cast(g / 2); + rgba[2] = stripe ? 50 : 30; + rgba[3] = 255; + break; + } + case 2: { // Blue circles + float cx = fx - 0.5f, cy = fy - 0.5f; + float dist = std::sqrt(cx * cx + cy * cy); + float rings = std::sin(dist * 30.0f) * 0.5f + 0.5f; + rgba[0] = static_cast(rings * 60); + rgba[1] = static_cast(rings * 100); + rgba[2] = static_cast(rings * 255); + rgba[3] = 255; + break; + } + default: { // Yellow/purple diagonal + float diag = std::sin((fx + fy) * 10.0f) * 0.5f + 0.5f; + rgba[0] = static_cast(diag * 255 + (1.0f - diag) * 120); + rgba[1] = static_cast(diag * 220); + rgba[2] = static_cast((1.0f - diag) * 200); + rgba[3] = 255; + break; + } + } +} + +void BindlessSample::onRender() { + using draco::f32, draco::u32, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.08f, 0.06f, 0.12f, 1.0f); + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); + auto* rp = enc->beginRenderPass(rpd); + + rp->setPipeline(m_pipeline); + rp->setBindGroup(0, m_bindlessBg); + rp->setBindGroup(1, m_samplerBg); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0.0f, 1.0f); + rp->setScissor(0, 0, m_width, m_height); + + // Draw 4 quads, each with a different texture index via push constants + // Layout: 2x2 grid + float offsets[8] = { -0.45f, 0.45f, 0.45f, 0.45f, -0.45f, -0.45f, 0.45f, -0.45f }; + + for (u32 i = 0; i < kNumTextures; ++i) { + u32 pushData[4] = { i, 0, 0, 0 }; + std::memcpy(&pushData[1], &offsets[i * 2], 4); + std::memcpy(&pushData[2], &offsets[i * 2 + 1], 4); + rp->setPushConstants(rhi::ShaderStage::Vertex | rhi::ShaderStage::Fragment, 0, 16, pushData); + rp->draw(4); + } + + rp->end(); + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void BindlessSample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); + if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); + if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_samplerBg) m_device->destroyBindGroup(m_samplerBg); + if (m_samplerBgl) m_device->destroyBindGroupLayout(m_samplerBgl); + if (m_bindlessBg) m_device->destroyBindGroup(m_bindlessBg); + if (m_bindlessBgl) m_device->destroyBindGroupLayout(m_bindlessBgl); + if (m_sampler) m_device->destroySampler(m_sampler); + for (int i = kNumTextures - 1; i >= 0; --i) { + if (m_textureViews[i]) m_device->destroyTextureView(m_textureViews[i]); + if (m_textures[i]) m_device->destroyTexture(m_textures[i]); + } + if (m_ps) m_device->destroyShaderModule(m_ps); + if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { BindlessSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample019_BatchUpload/CMakeLists.txt b/Samples/cpp/RHI/Sample019_BatchUpload/CMakeLists.txt new file mode 100644 index 00000000..7ed0d6b5 --- /dev/null +++ b/Samples/cpp/RHI/Sample019_BatchUpload/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample019_BatchUpload) diff --git a/Samples/cpp/RHI/Sample019_BatchUpload/Main.cpp b/Samples/cpp/RHI/Sample019_BatchUpload/Main.cpp new file mode 100644 index 00000000..e1ea07dd --- /dev/null +++ b/Samples/cpp/RHI/Sample019_BatchUpload/Main.cpp @@ -0,0 +1,329 @@ +#include +/// Demonstrates batched GPU uploads using TransferBatch with async fence signaling. +/// Uploads a vertex buffer, index buffer, and a procedural texture in a single +/// batched transfer with submitAsync, then renders a textured quad once the +/// upload fence signals completion. + +#include +#include +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class BatchUploadSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample019 - Batch Upload (Async Transfer)"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onShutdown() override; +private: + draco::Status doBatchUpload(); + + static constexpr const char8_t kShader[] = u8R"( + Texture2D gTexture : register(t0, space0); + SamplerState gSampler : register(s0, space0); + + struct VSInput + { + float3 Position : TEXCOORD0; + float2 TexCoord : TEXCOORD1; + }; + + struct PSInput + { + float4 Position : SV_POSITION; + float2 TexCoord : TEXCOORD0; + }; + + cbuffer Transform : register(b0, space0) + { + float Time; + float Pad0; + float Pad1; + float Pad2; + }; + + PSInput VSMain(VSInput input) + { + PSInput output; + // Gentle rotation + float c = cos(Time * 0.5); + float s = sin(Time * 0.5); + float3 p = input.Position; + float x = p.x * c - p.y * s; + float y = p.x * s + p.y * c; + output.Position = float4(x, y, p.z, 1.0); + output.TexCoord = input.TexCoord; + return output; + } + + float4 PSMain(PSInput input) : SV_TARGET + { + return gTexture.Sample(gSampler, input.TexCoord); + } + )"; + + static constexpr draco::u32 kTexSize = 128; + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule* m_vs = nullptr; + rhi::ShaderModule* m_ps = nullptr; + + rhi::Buffer* m_vb = nullptr; + rhi::Buffer* m_ib = nullptr; + rhi::Texture* m_tex = nullptr; + rhi::TextureView* m_texView = nullptr; + rhi::Sampler* m_sampler = nullptr; + + rhi::Buffer* m_transformBuf = nullptr; + void* m_transformMapped = nullptr; + + rhi::BindGroupLayout* m_bgl = nullptr; + rhi::BindGroup* m_bg = nullptr; + rhi::PipelineLayout* m_pl = nullptr; + rhi::RenderPipeline* m_pipeline = nullptr; + rhi::CommandPool* m_pool = nullptr; + rhi::Fence* m_frameFence = nullptr; + draco::u64 m_frameFenceVal = 0; + + // Upload tracking + rhi::Fence* m_uploadFence = nullptr; + draco::u64 m_uploadFenceVal = 0; + bool m_uploadComplete = false; + float m_uploadStartTime = 0.0f; +}; + +draco::Status BatchUploadSample::onInit() { + using draco::Status, std::span, draco::u8, draco::u32; + + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"BatchVS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"BatchPS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Vertex buffer: 4 vertices x (pos3 + uv2) x 4 = 80 bytes + rhi::BufferDesc vbd{}; vbd.size = 80; vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; vbd.label = u8"BatchVB"; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Index buffer: 6 uint16 = 12 bytes + rhi::BufferDesc ibd{}; ibd.size = 12; ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; ibd.memory = rhi::MemoryLocation::GpuOnly; ibd.label = u8"BatchIB"; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Texture + rhi::TextureDesc td{}; td.format = rhi::TextureFormat::RGBA8Unorm; td.width = kTexSize; td.height = kTexSize; + td.mipLevelCount = 1; td.usage = rhi::TextureUsage::Sampled | rhi::TextureUsage::CopyDst; td.label = u8"BatchTex"; + if (m_device->createTexture(td, m_tex) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TextureViewDesc tvd{}; tvd.format = rhi::TextureFormat::RGBA8Unorm; tvd.mipLevelCount = 1; tvd.arrayLayerCount = 1; + if (m_device->createTextureView(m_tex, tvd, m_texView) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::SamplerDesc sd{}; sd.minFilter = rhi::FilterMode::Linear; sd.magFilter = rhi::FilterMode::Linear; + sd.addressU = rhi::AddressMode::Repeat; sd.addressV = rhi::AddressMode::Repeat; sd.label = u8"BatchSampler"; + if (m_device->createSampler(sd, m_sampler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Transform UBO + rhi::BufferDesc tbd{}; tbd.size = 16; tbd.usage = rhi::BufferUsage::Uniform; tbd.memory = rhi::MemoryLocation::CpuToGpu; tbd.label = u8"BatchTransform"; + if (m_device->createBuffer(tbd, m_transformBuf) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + m_transformMapped = m_transformBuf->map(); + + // Bind group layout: UBO + texture + sampler + rhi::BindGroupLayoutEntry bglEntries[3] = { + rhi::BindGroupLayoutEntry::uniformBuffer(0, rhi::ShaderStage::Vertex), + rhi::BindGroupLayoutEntry::sampledTexture(0, rhi::ShaderStage::Fragment), + rhi::BindGroupLayoutEntry::sampler(0, rhi::ShaderStage::Fragment), + }; + rhi::BindGroupLayoutDesc bgld{}; bgld.entries = std::span(bglEntries, 3); bgld.label = u8"BatchBGL"; + if (m_device->createBindGroupLayout(bgld, m_bgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::BindGroupEntry bgEntries[3] = { + rhi::BindGroupEntry::bufferEntry(m_transformBuf, 0, 16), + rhi::BindGroupEntry::textureEntry(m_texView), + rhi::BindGroupEntry::samplerEntry(m_sampler), + }; + rhi::BindGroupDesc bgd{}; bgd.layout = m_bgl; bgd.entries = std::span(bgEntries, 3); bgd.label = u8"BatchBG"; + if (m_device->createBindGroup(bgd, m_bg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Pipeline layout + rhi::BindGroupLayout* bgls[1] = { m_bgl }; + rhi::PipelineLayoutDesc pld{}; pld.bindGroupLayouts = std::span(bgls, 1); pld.label = u8"BatchPL"; + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Render pipeline + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x2, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 20; vbl.attributes = std::span(attrs, 2); + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.primitive.topology = rhi::PrimitiveTopology::TriangleList; + rpd.label = u8"BatchPipeline"; + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_frameFence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Upload fence + if (m_device->createFence(0, m_uploadFence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // === Batch upload: VB + IB + texture in one submission === + if (doBatchUpload() != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + return draco::ErrorCode::Ok; +} + +draco::Status BatchUploadSample::doBatchUpload() { + using draco::Status, std::span, draco::u8, draco::u32; + + m_uploadStartTime = m_totalTime; + + rhi::TransferBatch* transfer = nullptr; + if (m_graphicsQueue->createTransferBatch(transfer) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Vertex data: quad + float verts[20] = { + -0.6f, 0.6f, 0.0f, 0.0f, 0.0f, + 0.6f, 0.6f, 0.0f, 1.0f, 0.0f, + 0.6f, -0.6f, 0.0f, 1.0f, 1.0f, + -0.6f, -0.6f, 0.0f, 0.0f, 1.0f, + }; + transfer->writeBuffer(m_vb, 0, std::span(reinterpret_cast(verts), 80)); + + // Index data + draco::u16 indices[6] = { 0, 1, 2, 0, 2, 3 }; + transfer->writeBuffer(m_ib, 0, std::span(reinterpret_cast(indices), 12)); + + // Texture data: procedural mandelbrot-ish pattern + u32 texBytes = kTexSize * kTexSize * 4; + auto* pixels = new u8[texBytes]; + + for (u32 y = 0; y < kTexSize; y++) { + for (u32 x = 0; x < kTexSize; x++) { + float cr = static_cast(x) / static_cast(kTexSize) * 3.0f - 2.0f; + float ci = static_cast(y) / static_cast(kTexSize) * 2.4f - 1.2f; + float zr = 0, zi = 0; + int iter = 0; + for (iter = 0; iter < 64; iter++) { + float zr2 = zr * zr - zi * zi + cr; + float zi2 = 2.0f * zr * zi + ci; + zr = zr2; zi = zi2; + if (zr * zr + zi * zi > 4.0f) break; + } + + u32 off = (y * kTexSize + x) * 4; + if (iter == 64) { + pixels[off] = 10; pixels[off + 1] = 10; pixels[off + 2] = 30; pixels[off + 3] = 255; + } else { + float t = static_cast(iter) / 64.0f; + pixels[off] = static_cast(t * 200 + 55); + pixels[off + 1] = static_cast(t * t * 255); + pixels[off + 2] = static_cast(std::sqrt(t) * 255); + pixels[off + 3] = 255; + } + } + } + + rhi::TextureDataLayout layout{}; layout.bytesPerRow = kTexSize * 4; layout.rowsPerImage = kTexSize; + transfer->writeTexture(m_tex, std::span(pixels, texBytes), layout, rhi::Extent3D{kTexSize, kTexSize, 1}); + + delete[] pixels; + + // Async submit - signals fence when GPU transfer completes + m_uploadFenceVal = 1; + if (transfer->submitAsync(m_uploadFence, m_uploadFenceVal) != draco::ErrorCode::Ok) { + m_graphicsQueue->destroyTransferBatch(transfer); + return draco::ErrorCode::Unknown; + } + + std::printf("Batch upload submitted asynchronously (VB: 80B, IB: 12B, Tex: %uB)\n", texBytes); + m_graphicsQueue->destroyTransferBatch(transfer); + return draco::ErrorCode::Ok; +} + +void BatchUploadSample::onRender() { + using draco::f32, std::span; + + if (m_frameFenceVal > 0) m_frameFence->wait(m_frameFenceVal, ~0ull); + + // Check if async upload has completed + if (!m_uploadComplete) { + if (m_uploadFence->completedValue() >= m_uploadFenceVal) { + m_uploadComplete = true; + std::printf("Batch upload completed! Rendering enabled.\n"); + } + } + + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + // Update transform + float transform[4] = { m_totalTime, 0, 0, 0 }; + std::memcpy(m_transformMapped, transform, 16); + + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.05f, 0.05f, 0.08f, 1.0f); + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); + auto* rp = enc->beginRenderPass(rpd); + + if (m_uploadComplete) { + rp->setPipeline(m_pipeline); + rp->setBindGroup(0, m_bg); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0.0f, 1.0f); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vb, 0); + rp->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + rp->drawIndexed(6); + } + + rp->end(); + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + + rhi::CommandBuffer* cb = enc->finish(); m_frameFenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_frameFence, m_frameFenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void BatchUploadSample::onShutdown() { + if (m_transformBuf && m_transformMapped) m_transformBuf->unmap(); + + if (m_uploadFence) m_device->destroyFence(m_uploadFence); + if (m_frameFence) m_device->destroyFence(m_frameFence); + if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); + if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_bg) m_device->destroyBindGroup(m_bg); + if (m_bgl) m_device->destroyBindGroupLayout(m_bgl); + if (m_sampler) m_device->destroySampler(m_sampler); + if (m_texView) m_device->destroyTextureView(m_texView); + if (m_tex) m_device->destroyTexture(m_tex); + if (m_transformBuf) m_device->destroyBuffer(m_transformBuf); + if (m_ib) m_device->destroyBuffer(m_ib); + if (m_vb) m_device->destroyBuffer(m_vb); + if (m_ps) m_device->destroyShaderModule(m_ps); + if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { BatchUploadSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample020_MeshShaders/CMakeLists.txt b/Samples/cpp/RHI/Sample020_MeshShaders/CMakeLists.txt new file mode 100644 index 00000000..9bbc62b4 --- /dev/null +++ b/Samples/cpp/RHI/Sample020_MeshShaders/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample020_MeshShaders) diff --git a/Samples/cpp/RHI/Sample020_MeshShaders/Main.cpp b/Samples/cpp/RHI/Sample020_MeshShaders/Main.cpp new file mode 100644 index 00000000..04938690 --- /dev/null +++ b/Samples/cpp/RHI/Sample020_MeshShaders/Main.cpp @@ -0,0 +1,236 @@ +#include +/// Demonstrates mesh shader pipeline: a rotating triangle generated entirely in the mesh shader. + +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +struct PushData { + float time; + float aspectRatio; + float pad0; + float pad1; +}; + +class MeshShaderSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample020 - Mesh Shaders (Rotating Triangle)"; } +protected: + rhi::DeviceFeatures requiredFeatures() const override { + rhi::DeviceFeatures f{}; + f.meshShaders = true; + return f; + } + draco::Status onInit() override; + void onRender() override; + void onShutdown() override; +private: + static constexpr const char8_t kMeshShaderSource[] = u8R"( + struct PushConstants + { + float Time; + float AspectRatio; + float Pad0, Pad1; + }; + + [[vk::push_constant]] ConstantBuffer pc : register(b0, space0); + + struct MeshOutput + { + float4 Position : SV_POSITION; + float3 Color : TEXCOORD0; + }; + + [outputtopology("triangle")] + [numthreads(1, 1, 1)] + void MSMain(out vertices MeshOutput verts[3], out indices uint3 tris[1]) + { + SetMeshOutputCounts(3, 1); + + float angle = pc.Time * 0.5; + float c = cos(angle); + float s = sin(angle); + + float2 positions[3] = { + float2( 0.0, 0.5), + float2(-0.5, -0.5), + float2( 0.5, -0.5) + }; + + float3 colors[3] = { + float3(1.0, 0.0, 0.0), + float3(0.0, 1.0, 0.0), + float3(0.0, 0.0, 1.0) + }; + + for (uint i = 0; i < 3; i++) + { + float2 p = positions[i]; + float2 rotated = float2(p.x * c - p.y * s, p.x * s + p.y * c); + rotated.x /= pc.AspectRatio; + + verts[i].Position = float4(rotated, 0.0, 1.0); + verts[i].Color = colors[i]; + } + + tris[0] = uint3(0, 1, 2); + } + )"; + + static constexpr const char8_t kFragmentShaderSource[] = u8R"( + struct PSInput + { + float4 Position : SV_POSITION; + float3 Color : TEXCOORD0; + }; + + float4 PSMain(PSInput input) : SV_TARGET + { + return float4(input.Color, 1.0); + } + )"; + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule* m_meshModule = nullptr; + rhi::ShaderModule* m_fragModule = nullptr; + rhi::PipelineLayout* m_pipelineLayout = nullptr; + rhi::MeshPipeline* m_meshPipeline = nullptr; + rhi::CommandPool* m_pool = nullptr; + rhi::Fence* m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +draco::Status MeshShaderSample::onInit() { + using draco::Status, std::span; + + // Check mesh shader support. + if (!m_device->features.meshShaders) { + std::fprintf(stderr, "ERROR: Mesh shaders are not supported by this device/backend\n"); + return draco::ErrorCode::Unknown; + } + + // Shader compiler. + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Compile mesh shader (SM 6.5 required for mesh shaders). + if (sf::compileToModule(m_compiler, m_device, kMeshShaderSource, shaders::ShaderStage::Mesh, + u8"MSMain", u8"MeshShader", u8"6_5", m_meshModule) != draco::ErrorCode::Ok) + return draco::ErrorCode::Unknown; + + // Compile fragment shader. + if (sf::compileToModule(m_compiler, m_device, kFragmentShaderSource, shaders::ShaderStage::Fragment, + u8"PSMain", u8"FragmentShader", m_fragModule) != draco::ErrorCode::Ok) + return draco::ErrorCode::Unknown; + + // Pipeline layout with push constants. + rhi::PushConstantRange pushRange{}; + pushRange.stages = rhi::ShaderStage::Mesh; + pushRange.offset = 0; + pushRange.size = sizeof(PushData); + + rhi::PipelineLayoutDesc pld{}; + pld.pushConstantRanges = std::span(&pushRange, 1); + pld.label = u8"MeshPipelineLayout"; + if (m_device->createPipelineLayout(pld, m_pipelineLayout) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Create mesh pipeline. + rhi::ColorTargetState ct{}; + ct.format = m_swapChain->format(); + ct.writeMask = rhi::ColorWriteMask::All; + + rhi::MeshPipelineDesc mpd{}; + mpd.layout = m_pipelineLayout; + mpd.mesh = { m_meshModule, u8"MSMain", rhi::ShaderStage::Mesh }; + mpd.fragment = rhi::FragmentState{}; + mpd.fragment->shader = { m_fragModule, u8"PSMain", rhi::ShaderStage::Fragment }; + mpd.fragment->targets = std::span(&ct, 1); + mpd.colorTargets = std::span(&ct, 1); + mpd.label = u8"MeshShaderPipeline"; + if (m_device->createMeshPipeline(mpd, m_meshPipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Command pool and fence. + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + return draco::ErrorCode::Ok; +} + +void MeshShaderSample::onRender() { + using draco::f32, std::span; + + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + // Barrier: present -> render target. + enc->transitionTexture(m_swapChain->currentTexture(), + rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + + // Begin render pass. + rhi::ColorAttachment ca{}; + ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; + ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.05f, 0.05f, 0.08f, 1.0f); + + rhi::RenderPassDesc rpd{}; + rpd.colorAttachments.push_back(ca); + auto* rp = enc->beginRenderPass(rpd); + + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0.0f, 1.0f); + rp->setScissor(0, 0, m_width, m_height); + + // Draw with mesh shader. + if (auto* meshPass = rp->asMeshShaderExt()) { + meshPass->setMeshPipeline(m_meshPipeline); + + // Push constants (must be after pipeline is bound). + PushData pushData{}; + pushData.time = m_totalTime; + pushData.aspectRatio = static_cast(m_width) / static_cast(m_height); + pushData.pad0 = 0.0f; + pushData.pad1 = 0.0f; + rp->setPushConstants(rhi::ShaderStage::Mesh, 0, sizeof(PushData), &pushData); + + meshPass->drawMeshTasks(1); + } + + rp->end(); + + // Barrier: render target -> present. + enc->transitionTexture(m_swapChain->currentTexture(), + rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + + rhi::CommandBuffer* cb = enc->finish(); + m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void MeshShaderSample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); + if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_meshPipeline) m_device->destroyMeshPipeline(m_meshPipeline); + if (m_pipelineLayout) m_device->destroyPipelineLayout(m_pipelineLayout); + if (m_fragModule) m_device->destroyShaderModule(m_fragModule); + if (m_meshModule) m_device->destroyShaderModule(m_meshModule); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { MeshShaderSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample021_RayTracing/CMakeLists.txt b/Samples/cpp/RHI/Sample021_RayTracing/CMakeLists.txt new file mode 100644 index 00000000..f9c9d6e4 --- /dev/null +++ b/Samples/cpp/RHI/Sample021_RayTracing/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample021_RayTracing) diff --git a/Samples/cpp/RHI/Sample021_RayTracing/Main.cpp b/Samples/cpp/RHI/Sample021_RayTracing/Main.cpp new file mode 100644 index 00000000..05854a3a --- /dev/null +++ b/Samples/cpp/RHI/Sample021_RayTracing/Main.cpp @@ -0,0 +1,564 @@ +#include +/// Demonstrates hardware ray tracing: builds a BLAS/TLAS for a single triangle, +/// dispatches rays via TraceRays, and copies the RT output to the swap chain. + +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class RayTracingSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample021 - Ray Tracing (TraceRays)"; } +protected: + rhi::DeviceFeatures requiredFeatures() const override { + rhi::DeviceFeatures f{}; + f.rayTracing = true; + return f; + } + draco::Status onInit() override; + void onRender() override; + void onResize(draco::u32 w, draco::u32 h) override; + void onShutdown() override; +private: + // Ray tracing shader library (compiled as lib_6_3). + // All RT entry points are in a single source compiled once as a library. + static constexpr const char8_t kRtShaderSource[] = u8R"( + [[vk::image_format("rgba8")]] RWTexture2D gOutput : register(u0, space0); + RaytracingAccelerationStructure gScene : register(t0, space0); + + struct RayPayload + { + float3 Color; + }; + + [shader("raygeneration")] + void RayGen() + { + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + float2 uv = (float2(launchIndex) + 0.5) / float2(launchDim); + float2 ndc = uv * 2.0 - 1.0; + ndc.y = -ndc.y; + + RayDesc ray; + ray.Origin = float3(ndc.x, ndc.y, -1.0); + ray.Direction = float3(0.0, 0.0, 1.0); + ray.TMin = 0.001; + ray.TMax = 100.0; + + RayPayload payload; + payload.Color = float3(0.0, 0.0, 0.0); + + TraceRay(gScene, RAY_FLAG_FORCE_OPAQUE, 0xFF, 0, 0, 0, ray, payload); + + gOutput[launchIndex] = float4(payload.Color, 1.0); + } + + [shader("closesthit")] + void ClosestHit(inout RayPayload payload, BuiltInTriangleIntersectionAttributes attribs) + { + float3 bary = float3(1.0 - attribs.barycentrics.x - attribs.barycentrics.y, + attribs.barycentrics.x, + attribs.barycentrics.y); + + payload.Color = float3(bary.x, bary.y, bary.z); + } + + [shader("miss")] + void Miss(inout RayPayload payload) + { + float2 uv = (float2(DispatchRaysIndex().xy) + 0.5) / float2(DispatchRaysDimensions().xy); + payload.Color = float3(0.1, 0.1, 0.2) + float3(0.0, 0.0, 0.3) * uv.y; + } + )"; + + // BLAS triangle positions only (float3 per vertex, no color). + static constexpr float kBlasVertexData[9] = { + 0.0f, 0.5f, 0.0f, + -0.5f, -0.5f, 0.0f, + 0.5f, -0.5f, 0.0f + }; + + shaders::Compiler* m_compiler = nullptr; + + // RT resources. + rhi::ShaderModule* m_rtShaderModule = nullptr; + rhi::RayTracingPipeline* m_rtPipeline = nullptr; + rhi::AccelStruct* m_blas = nullptr; + rhi::AccelStruct* m_tlas = nullptr; + rhi::Buffer* m_scratchBuffer = nullptr; + rhi::Buffer* m_rtVertexBuffer = nullptr; // Triangle for BLAS. + rhi::Buffer* m_instanceBuffer = nullptr; // TLAS instance data. + rhi::Buffer* m_sbtBuffer = nullptr; // Shader binding table. + rhi::PipelineLayout* m_rtPipelineLayout = nullptr; + rhi::BindGroupLayout* m_rtBindGroupLayout = nullptr; + rhi::BindGroup* m_rtBindGroup = nullptr; + + // RT output texture. + rhi::Texture* m_outputTexture = nullptr; + rhi::TextureView* m_outputTextureView = nullptr; + rhi::ResourceState m_outputTextureState = rhi::ResourceState::Undefined; + + // SBT layout info (cached for traceRays). + draco::u32 m_sbtAlignedStride = 0; + + rhi::CommandPool* m_pool = nullptr; + rhi::Fence* m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +draco::Status RayTracingSample::onInit() { + using draco::Status, std::span; + + // ---- Check ray tracing support ---- + if (!m_device->features.rayTracing) { + std::fprintf(stderr, "ERROR: Ray tracing is not supported by this device/backend\n"); + return draco::ErrorCode::Unknown; + } + + std::printf("Ray tracing extension available:\n"); + std::printf(" shaderGroupHandleSize: %u\n", m_device->shaderGroupHandleSize); + std::printf(" shaderGroupHandleAlignment: %u\n", m_device->shaderGroupHandleAlignment); + std::printf(" shaderGroupBaseAlignment: %u\n", m_device->shaderGroupBaseAlignment); + + // ---- Shader compiler ---- + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // ---- Compile RT shader library (lib_6_3) ---- + // Use ShaderStage::RayGen so stagePrefix yields "lib"; SM 6_3 for RT. + if (sf::compileToModule(m_compiler, m_device, kRtShaderSource, shaders::ShaderStage::RayGen, + u8"", u8"RTShaderLib", u8"6_3", m_rtShaderModule) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "ERROR: RT shader library compilation failed\n"); + return draco::ErrorCode::Unknown; + } + + // ---- Command pool and fence ---- + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // ---- Create RT output texture (storage + copy source) ---- + { + rhi::TextureDesc td{}; + td.dimension = rhi::TextureDimension::Texture2D; + td.format = rhi::TextureFormat::RGBA8Unorm; + td.width = m_width; + td.height = m_height; + td.arrayLayerCount = 1; + td.mipLevelCount = 1; + td.sampleCount = 1; + td.usage = rhi::TextureUsage::Storage | rhi::TextureUsage::CopySrc; + td.label = u8"RTOutputTex"; + if (m_device->createTexture(td, m_outputTexture) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TextureViewDesc tvd{}; + tvd.label = u8"RTOutputView"; + if (m_device->createTextureView(m_outputTexture, tvd, m_outputTextureView) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // ---- Create BLAS vertex buffer (3 vertices * 12 bytes = 36 bytes) ---- + { + rhi::BufferDesc bd{}; + bd.size = 36; + bd.usage = rhi::BufferUsage::AccelStructInput | rhi::BufferUsage::CopyDst; + bd.memory = rhi::MemoryLocation::GpuOnly; + bd.label = u8"BLAS_VB"; + if (m_device->createBuffer(bd, m_rtVertexBuffer) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // Upload BLAS vertex data. + { + rhi::TransferBatch* transfer = nullptr; + if (m_graphicsQueue->createTransferBatch(transfer) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + transfer->writeBuffer(m_rtVertexBuffer, 0, + std::span(reinterpret_cast(kBlasVertexData), 36)); + transfer->submit(); + m_graphicsQueue->destroyTransferBatch(transfer); + } + + // ---- Create acceleration structures ---- + { + rhi::AccelStructDesc asd{}; + asd.type = rhi::AccelStructType::BottomLevel; + asd.label = u8"BLAS"; + if (m_device->createAccelStruct(asd, m_blas) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + asd.type = rhi::AccelStructType::TopLevel; + asd.label = u8"TLAS"; + if (m_device->createAccelStruct(asd, m_tlas) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // ---- Create scratch buffer (256 KB) ---- + { + rhi::BufferDesc bd{}; + bd.size = 256 * 1024; + bd.usage = rhi::BufferUsage::AccelStructScratch; + bd.memory = rhi::MemoryLocation::GpuOnly; + bd.label = u8"ScratchBuffer"; + if (m_device->createBuffer(bd, m_scratchBuffer) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // ---- Create instance buffer (64 bytes = sizeof(VkAccelerationStructureInstanceKHR)) ---- + { + rhi::BufferDesc bd{}; + bd.size = 64; + bd.usage = rhi::BufferUsage::AccelStructInput; + bd.memory = rhi::MemoryLocation::CpuToGpu; + bd.label = u8"InstanceBuffer"; + if (m_device->createBuffer(bd, m_instanceBuffer) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // Fill instance data. + { + auto* ptr = static_cast(m_instanceBuffer->map()); + if (!ptr) { std::fprintf(stderr, "ERROR: Failed to map instance buffer\n"); return draco::ErrorCode::Unknown; } + std::memset(ptr, 0, 64); + + // Identity transform (3x4 row-major float matrix). + auto* transform = reinterpret_cast(ptr); + transform[0] = 1.0f; // row 0, col 0 + transform[5] = 1.0f; // row 1, col 1 + transform[10] = 1.0f; // row 2, col 2 + + // instanceCustomIndex (24 bit) + mask (8 bit) at offset 48. + ptr[48] = 0; ptr[49] = 0; ptr[50] = 0; // customIndex = 0 + ptr[51] = 0xFF; // mask + + // SBT offset (24 bit) + flags (8 bit) at offset 52. + ptr[52] = 0; ptr[53] = 0; ptr[54] = 0; // sbtOffset = 0 + ptr[55] = 0x04; // VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR + + // accelerationStructureReference at offset 56. + *reinterpret_cast(ptr + 56) = m_blas->deviceAddress(); + + m_instanceBuffer->unmap(); + } + + // ---- Build BLAS and TLAS ---- + { + rhi::CommandEncoder* encoder = nullptr; + if (m_pool->createEncoder(encoder) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (auto* rtEnc = encoder->asRayTracingExt()) { + // Build BLAS from triangle geometry. + rhi::AccelStructGeometryTriangles triGeom{}; + triGeom.vertexBuffer = m_rtVertexBuffer; + triGeom.vertexOffset = 0; + triGeom.vertexCount = 3; + triGeom.vertexStride = 12; + triGeom.vertexFormat = rhi::VertexFormat::Float32x3; + triGeom.flags = rhi::GeometryFlags::Opaque; + + rtEnc->buildBottomLevelAccelStruct(m_blas, m_scratchBuffer, 0, + std::span(&triGeom, 1), + std::span{}); + + // Barrier between BLAS and TLAS build. + rhi::MemoryBarrier mb{}; + mb.oldState = rhi::ResourceState::AccelStructWrite; + mb.newState = rhi::ResourceState::AccelStructRead; + rhi::BarrierGroup bg{}; + bg.memoryBarriers = std::span(&mb, 1); + encoder->barrier(bg); + + // Build TLAS from instances. + rtEnc->buildTopLevelAccelStruct(m_tlas, m_scratchBuffer, 0, + m_instanceBuffer, 0, 1); + } else { + std::fprintf(stderr, "ERROR: Command encoder does not support ray tracing\n"); + m_pool->destroyEncoder(encoder); + return draco::ErrorCode::Unknown; + } + + rhi::CommandBuffer* cb = encoder->finish(); + m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + + // Wait for build to complete. + m_fence->wait(m_fenceVal); + m_pool->reset(); + m_pool->destroyEncoder(encoder); + } + + std::printf("BLAS and TLAS built successfully.\n"); + std::printf(" BLAS DeviceAddress: 0x%llx\n", static_cast(m_blas->deviceAddress())); + std::printf(" TLAS DeviceAddress: 0x%llx\n", static_cast(m_tlas->deviceAddress())); + + // ---- Create RT bind group layout and bind group ---- + { + // binding 0 (u0): RWTexture2D - storage texture, read-write + // binding 0 (t0): RaytracingAccelerationStructure - TLAS + // Both use register 0 in different HLSL spaces (u vs t), + // mapped to different Vulkan bindings via shifts (UAV=2000, SRV=1000). + rhi::BindGroupLayoutEntry layoutEntries[2]{}; + + // Storage texture (read-write). + layoutEntries[0].binding = 0; + layoutEntries[0].visibility = rhi::ShaderStage::RayGen; + layoutEntries[0].type = rhi::BindingType::StorageTextureReadWrite; + layoutEntries[0].storageTextureFormat = rhi::TextureFormat::RGBA8Unorm; + layoutEntries[0].count = 1; + + // Acceleration structure. + layoutEntries[1].binding = 0; + layoutEntries[1].visibility = rhi::ShaderStage::RayGen; + layoutEntries[1].type = rhi::BindingType::AccelerationStructure; + layoutEntries[1].count = 1; + + rhi::BindGroupLayoutDesc bgld{}; + bgld.entries = std::span(layoutEntries, 2); + bgld.label = u8"RTBindGroupLayout"; + if (m_device->createBindGroupLayout(bgld, m_rtBindGroupLayout) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Create bind group with output texture + TLAS. + rhi::BindGroupEntry bgEntries[2]{}; + bgEntries[0] = rhi::BindGroupEntry::textureEntry(m_outputTextureView); + bgEntries[1] = rhi::BindGroupEntry::accelStructEntry(m_tlas); + + rhi::BindGroupDesc bgd{}; + bgd.layout = m_rtBindGroupLayout; + bgd.entries = std::span(bgEntries, 2); + bgd.label = u8"RTBindGroup"; + if (m_device->createBindGroup(bgd, m_rtBindGroup) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // ---- Create RT pipeline layout with bind group ---- + { + rhi::BindGroupLayout* bglArr[1] = { m_rtBindGroupLayout }; + + rhi::PipelineLayoutDesc pld{}; + pld.bindGroupLayouts = std::span(bglArr, 1); + pld.label = u8"RTPipelineLayout"; + if (m_device->createPipelineLayout(pld, m_rtPipelineLayout) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // 3 stages: RayGen, ClosestHit, Miss - all from the same shader module. + rhi::ProgrammableStage stages[3]{}; + stages[0] = { m_rtShaderModule, u8"RayGen", rhi::ShaderStage::RayGen }; + stages[1] = { m_rtShaderModule, u8"ClosestHit", rhi::ShaderStage::ClosestHit }; + stages[2] = { m_rtShaderModule, u8"Miss", rhi::ShaderStage::Miss }; + + // 3 groups: raygen (general), hit group (triangles), miss (general). + rhi::RayTracingShaderGroup groups[3]{}; + groups[0].type = rhi::RayTracingShaderGroup::Type::General; + groups[0].generalShaderIndex = 0; + + groups[1].type = rhi::RayTracingShaderGroup::Type::TrianglesHitGroup; + groups[1].closestHitShaderIndex = 1; + + groups[2].type = rhi::RayTracingShaderGroup::Type::General; + groups[2].generalShaderIndex = 2; + + rhi::RayTracingPipelineDesc rtpd{}; + rtpd.layout = m_rtPipelineLayout; + rtpd.stages = std::span(stages, 3); + rtpd.groups = std::span(groups, 3); + rtpd.maxRecursionDepth = 1; + rtpd.label = u8"RTPipeline"; + if (m_device->createRayTracingPipeline(rtpd, m_rtPipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + std::printf("Ray tracing pipeline created successfully.\n"); + + // ---- Build Shader Binding Table ---- + { + draco::u32 handleSize = m_device->shaderGroupHandleSize; + draco::u32 baseAlignment = m_device->shaderGroupBaseAlignment; + draco::u32 groupCount = 3; + + // Aligned handle stride (round up to base alignment). + m_sbtAlignedStride = (handleSize + baseAlignment - 1) & ~(baseAlignment - 1); + + // Get shader group handles. + draco::u8 handleData[128]; // Enough for 3 handles (max ~32 bytes each). + if (m_device->getShaderGroupHandles(m_rtPipeline, 0, groupCount, + std::span(handleData, handleSize * groupCount)) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "ERROR: getShaderGroupHandles failed\n"); + return draco::ErrorCode::Unknown; + } + + // Create SBT buffer: 3 entries, each aligned to baseAlignment. + draco::u64 sbtSize = static_cast(m_sbtAlignedStride) * groupCount; + rhi::BufferDesc sbd{}; + sbd.size = sbtSize; + sbd.usage = rhi::BufferUsage::ShaderBindingTable; + sbd.memory = rhi::MemoryLocation::CpuToGpu; + sbd.label = u8"SBTBuffer"; + if (m_device->createBuffer(sbd, m_sbtBuffer) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Copy handles into SBT with proper alignment. + auto* sbtPtr = static_cast(m_sbtBuffer->map()); + if (!sbtPtr) { std::fprintf(stderr, "ERROR: Failed to map SBT buffer\n"); return draco::ErrorCode::Unknown; } + std::memset(sbtPtr, 0, static_cast(sbtSize)); + + for (draco::u32 i = 0; i < groupCount; i++) { + std::memcpy(sbtPtr + (i * m_sbtAlignedStride), + handleData + (i * handleSize), + handleSize); + } + m_sbtBuffer->unmap(); + + std::printf("SBT built: handleSize=%u, baseAlignment=%u, alignedStride=%u, totalSize=%llu\n", + handleSize, baseAlignment, m_sbtAlignedStride, static_cast(sbtSize)); + } + + std::printf("RT sample ready - TraceRays rendering active.\n"); + + return draco::ErrorCode::Ok; +} + +void RayTracingSample::onRender() { + using std::span; + + // Wait for previous frame. + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + + // Acquire next swap chain image. + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + // Reset and create encoder. + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + // ---- Transition output texture to ShaderWrite for TraceRays ---- + enc->transitionTexture(m_outputTexture, m_outputTextureState, rhi::ResourceState::ShaderWrite); + + // ---- Dispatch TraceRays ---- + if (auto* rtEnc = enc->asRayTracingExt()) { + rtEnc->setRayTracingPipeline(m_rtPipeline); + rtEnc->setBindGroup(0, m_rtBindGroup); + + // SBT layout: [0] = raygen, [1] = hit, [2] = miss. + draco::u64 raygenOffset = 0; + draco::u64 hitOffset = static_cast(1) * m_sbtAlignedStride; + draco::u64 missOffset = static_cast(2) * m_sbtAlignedStride; + draco::u64 stride = static_cast(m_sbtAlignedStride); + + rtEnc->traceRays( + m_sbtBuffer, raygenOffset, stride, + m_sbtBuffer, missOffset, stride, + m_sbtBuffer, hitOffset, stride, + m_width, m_height); + } + + // ---- Transition: output texture ShaderWrite -> CopySrc, swapchain Present -> CopyDst ---- + { + rhi::TextureBarrier texBarriers[2]{}; + texBarriers[0].texture = m_outputTexture; + texBarriers[0].oldState = rhi::ResourceState::ShaderWrite; + texBarriers[0].newState = rhi::ResourceState::CopySrc; + + texBarriers[1].texture = m_swapChain->currentTexture(); + texBarriers[1].oldState = rhi::ResourceState::Present; + texBarriers[1].newState = rhi::ResourceState::CopyDst; + + rhi::BarrierGroup bg{}; + bg.textureBarriers = std::span(texBarriers, 2); + enc->barrier(bg); + } + + // ---- Copy RT output to swapchain ---- + m_outputTextureState = rhi::ResourceState::CopySrc; + { + rhi::TextureCopyRegion region{}; + region.extent = rhi::Extent3D{ m_width, m_height, 1 }; + enc->copyTextureToTexture(m_outputTexture, m_swapChain->currentTexture(), region); + } + + // ---- Transition swapchain CopyDst -> Present ---- + enc->transitionTexture(m_swapChain->currentTexture(), + rhi::ResourceState::CopyDst, rhi::ResourceState::Present); + + // Finish and submit. + rhi::CommandBuffer* cb = enc->finish(); + m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + + // Present. + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void RayTracingSample::onResize(draco::u32 w, draco::u32 h) { + using draco::Status, std::span; + // Wait for GPU idle before destroying resources. + if (m_fence) m_fence->wait(m_fenceVal, ~0ull); + + // Destroy old bind group, view, and texture. + if (m_rtBindGroup) m_device->destroyBindGroup(m_rtBindGroup); + if (m_outputTextureView) m_device->destroyTextureView(m_outputTextureView); + if (m_outputTexture) m_device->destroyTexture(m_outputTexture); + m_rtBindGroup = nullptr; m_outputTextureView = nullptr; m_outputTexture = nullptr; + + // Recreate output texture at new size. + rhi::TextureDesc td{}; + td.dimension = rhi::TextureDimension::Texture2D; + td.format = rhi::TextureFormat::RGBA8Unorm; + td.width = w; + td.height = h; + td.arrayLayerCount = 1; + td.mipLevelCount = 1; + td.sampleCount = 1; + td.usage = rhi::TextureUsage::Storage | rhi::TextureUsage::CopySrc; + td.label = u8"RTOutputTex"; + m_device->createTexture(td, m_outputTexture); + + rhi::TextureViewDesc tvd{}; tvd.label = u8"RTOutputView"; + m_device->createTextureView(m_outputTexture, tvd, m_outputTextureView); + + // Recreate bind group with new texture view + same TLAS. + rhi::BindGroupEntry bgEntries[2]{}; + bgEntries[0] = rhi::BindGroupEntry::textureEntry(m_outputTextureView); + bgEntries[1] = rhi::BindGroupEntry::accelStructEntry(m_tlas); + rhi::BindGroupDesc bgd{}; + bgd.layout = m_rtBindGroupLayout; + bgd.entries = std::span(bgEntries, 2); + bgd.label = u8"RTBindGroup"; + m_device->createBindGroup(bgd, m_rtBindGroup); + + m_outputTextureState = rhi::ResourceState::Undefined; +} + +void RayTracingSample::onShutdown() { + if (m_fence) m_fence->wait(m_fenceVal, ~0ull); + + // RT bind group. + if (m_rtBindGroup) m_device->destroyBindGroup(m_rtBindGroup); + if (m_rtBindGroupLayout) m_device->destroyBindGroupLayout(m_rtBindGroupLayout); + + // RT output texture. + if (m_outputTextureView) m_device->destroyTextureView(m_outputTextureView); + if (m_outputTexture) m_device->destroyTexture(m_outputTexture); + + // RT resources. + if (m_sbtBuffer) m_device->destroyBuffer(m_sbtBuffer); + if (m_rtPipeline) m_device->destroyRayTracingPipeline(m_rtPipeline); + if (m_rtPipelineLayout) m_device->destroyPipelineLayout(m_rtPipelineLayout); + if (m_instanceBuffer) m_device->destroyBuffer(m_instanceBuffer); + if (m_scratchBuffer) m_device->destroyBuffer(m_scratchBuffer); + if (m_tlas) m_device->destroyAccelStruct(m_tlas); + if (m_blas) m_device->destroyAccelStruct(m_blas); + if (m_rtVertexBuffer) m_device->destroyBuffer(m_rtVertexBuffer); + if (m_rtShaderModule) m_device->destroyShaderModule(m_rtShaderModule); + + if (m_fence) m_device->destroyFence(m_fence); + if (m_pool) m_device->destroyCommandPool(m_pool); + + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { RayTracingSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample022_StencilOutline/CMakeLists.txt b/Samples/cpp/RHI/Sample022_StencilOutline/CMakeLists.txt new file mode 100644 index 00000000..f16971de --- /dev/null +++ b/Samples/cpp/RHI/Sample022_StencilOutline/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample022_StencilOutline) diff --git a/Samples/cpp/RHI/Sample022_StencilOutline/Main.cpp b/Samples/cpp/RHI/Sample022_StencilOutline/Main.cpp new file mode 100644 index 00000000..e600934a --- /dev/null +++ b/Samples/cpp/RHI/Sample022_StencilOutline/Main.cpp @@ -0,0 +1,275 @@ +#include +/// Demonstrates stencil buffer operations for object outlining. +/// Pass 1: Draw solid hexagon, write stencil = 1. +/// Pass 2: Draw scaled-up hexagon, only where stencil != 1 (outline effect). + +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class StencilOutlineSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample022 - Stencil Outline"; } + +protected: + draco::Status onInit() override; + void onRender() override; + void onResize(draco::u32 w, draco::u32 h) override { recreateDepthStencil(w, h); } + void onShutdown() override; + +private: + static constexpr const char8_t kShader[] = u8R"( + struct PushConstants + { + float Scale; + float AspectRatio; + float Time; + float _pad; + }; + + [[vk::push_constant]] ConstantBuffer pc : register(b0, space0); + + struct VSInput + { + float3 Position : TEXCOORD0; + float4 Color : TEXCOORD1; + }; + + struct PSInput + { + float4 Position : SV_POSITION; + float4 Color : COLOR0; + }; + + PSInput VSMain(VSInput input) + { + PSInput output; + float2 pos = input.Position.xy * pc.Scale; + pos.x /= pc.AspectRatio; + // Gentle rotation + float c = cos(pc.Time * 0.5); + float s = sin(pc.Time * 0.5); + float2 rotated = float2(pos.x * c - pos.y * s, pos.x * s + pos.y * c); + output.Position = float4(rotated, input.Position.z, 1.0); + output.Color = input.Color; + return output; + } + + float4 PSMain(PSInput input) : SV_TARGET + { + return input.Color; + } + )"; + + struct PushData { + float scale; + float aspectRatio; + float time; + float _pad; + }; + + // Hexagon: center + 6 outer vertices. + // Stride: 7 floats per vertex (pos xyz + color rgba). + static constexpr float kVerts[] = { + // Center + 0.0f, 0.0f, 0.5f, 0.9f, 0.9f, 0.9f, 1.0f, + // Outer vertices (radius 0.6) + 0.6f, 0.0f, 0.5f, 0.3f, 0.6f, 1.0f, 1.0f, + 0.3f, 0.52f, 0.5f, 0.3f, 1.0f, 0.6f, 1.0f, + -0.3f, 0.52f, 0.5f, 1.0f, 1.0f, 0.3f, 1.0f, + -0.6f, 0.0f, 0.5f, 1.0f, 0.6f, 0.3f, 1.0f, + -0.3f, -0.52f, 0.5f, 1.0f, 0.3f, 0.6f, 1.0f, + 0.3f, -0.52f, 0.5f, 0.6f, 0.3f, 1.0f, 1.0f, + }; + static constexpr draco::u16 kIdx[] = { + 0, 1, 2, + 0, 2, 3, + 0, 3, 4, + 0, 4, 5, + 0, 5, 6, + 0, 6, 1, + }; + + void recreateDepthStencil(draco::u32 w, draco::u32 h); + + shaders::Compiler* m_compiler = nullptr; + rhi::Buffer *m_vb = nullptr, *m_ib = nullptr; + rhi::ShaderModule *m_vs = nullptr, *m_ps = nullptr; + rhi::PipelineLayout* m_pl = nullptr; + rhi::RenderPipeline* m_stencilWritePipeline = nullptr; + rhi::RenderPipeline* m_stencilTestPipeline = nullptr; + rhi::Texture* m_depthStencilTex = nullptr; + rhi::TextureView* m_depthStencilView = nullptr; + rhi::CommandPool* m_pool = nullptr; + rhi::Fence* m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +void StencilOutlineSample::recreateDepthStencil(draco::u32 w, draco::u32 h) { + if (m_depthStencilView) { m_device->destroyTextureView(m_depthStencilView); m_depthStencilView = nullptr; } + if (m_depthStencilTex) { m_device->destroyTexture(m_depthStencilTex); m_depthStencilTex = nullptr; } + + rhi::TextureDesc td = rhi::TextureDesc::depthBuffer(rhi::TextureFormat::Depth24PlusStencil8, w, h, 1, u8"StencilDSTex"); + m_device->createTexture(td, m_depthStencilTex); + rhi::TextureViewDesc tvd{}; tvd.format = rhi::TextureFormat::Depth24PlusStencil8; tvd.dimension = rhi::TextureViewDimension::Texture2D; + tvd.mipLevelCount = 1; tvd.arrayLayerCount = 1; + m_device->createTextureView(m_depthStencilTex, tvd, m_depthStencilView); +} + +draco::Status StencilOutlineSample::onInit() { + using draco::Status, std::span, draco::u8; + + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"StencilVS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"StencilPS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Vertex & index buffers. + rhi::BufferDesc vbd{}; vbd.size = sizeof(kVerts); vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ibd{}; ibd.size = sizeof(kIdx); ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; ibd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(kVerts), sizeof(kVerts))); + batch->writeBuffer(m_ib, 0, std::span(reinterpret_cast(kIdx), sizeof(kIdx))); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + // Pipeline layout with push constants (no bind groups). + rhi::PushConstantRange pcRange{ rhi::ShaderStage::Vertex, 0, sizeof(PushData) }; + rhi::PipelineLayoutDesc pld{}; + pld.pushConstantRanges = std::span(&pcRange, 1); + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + recreateDepthStencil(m_width, m_height); + + // Shared vertex layout. + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x4, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 28; vbl.attributes = std::span(attrs, 2); + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + + // Pipeline 1: Stencil write - draw solid, always pass depth, write stencil = ref (1). + { + rhi::RenderPipelineDesc rpd{}; + rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.depthStencil = rhi::DepthStencilState{}; + rpd.depthStencil->format = rhi::TextureFormat::Depth24PlusStencil8; + rpd.depthStencil->depthWriteEnabled = true; + rpd.depthStencil->depthCompare = rhi::CompareFunction::Always; + rpd.depthStencil->stencilEnabled = true; + rpd.depthStencil->stencilReadMask = 0xFF; + rpd.depthStencil->stencilWriteMask = 0xFF; + rpd.depthStencil->stencilFront = { rhi::CompareFunction::Always, rhi::StencilOperation::Keep, rhi::StencilOperation::Keep, rhi::StencilOperation::Replace }; + rpd.depthStencil->stencilBack = { rhi::CompareFunction::Always, rhi::StencilOperation::Keep, rhi::StencilOperation::Keep, rhi::StencilOperation::Replace }; + if (m_device->createRenderPipeline(rpd, m_stencilWritePipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // Pipeline 2: Stencil test - draw outline, only where stencil != 1. + { + rhi::RenderPipelineDesc rpd{}; + rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.depthStencil = rhi::DepthStencilState{}; + rpd.depthStencil->format = rhi::TextureFormat::Depth24PlusStencil8; + rpd.depthStencil->depthWriteEnabled = false; + rpd.depthStencil->depthCompare = rhi::CompareFunction::Always; + rpd.depthStencil->stencilEnabled = true; + rpd.depthStencil->stencilReadMask = 0xFF; + rpd.depthStencil->stencilWriteMask = 0x00; + rpd.depthStencil->stencilFront = { rhi::CompareFunction::NotEqual, rhi::StencilOperation::Keep, rhi::StencilOperation::Keep, rhi::StencilOperation::Keep }; + rpd.depthStencil->stencilBack = { rhi::CompareFunction::NotEqual, rhi::StencilOperation::Keep, rhi::StencilOperation::Keep, rhi::StencilOperation::Keep }; + if (m_device->createRenderPipeline(rpd, m_stencilTestPipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void StencilOutlineSample::onRender() { + using draco::f32, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + enc->transitionTexture(m_depthStencilTex, rhi::ResourceState::Undefined, rhi::ResourceState::DepthStencilWrite); + + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.08f, 0.08f, 0.12f, 1.0f); + + rhi::DepthStencilAttachment dsa{}; dsa.view = m_depthStencilView; + dsa.depthLoadOp = rhi::LoadOp::Clear; dsa.depthStoreOp = rhi::StoreOp::Store; dsa.depthClearValue = 1.0f; + dsa.stencilLoadOp = rhi::LoadOp::Clear; dsa.stencilStoreOp = rhi::StoreOp::Store; dsa.stencilClearValue = 0; + + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); rpd.depthStencilAttachment = dsa; + auto* rp = enc->beginRenderPass(rpd); + + f32 aspect = static_cast(m_width) / static_cast(m_height); + + // Pass 1: Draw solid hexagon, write stencil = 1. + rp->setPipeline(m_stencilWritePipeline); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0.0f, 1.0f); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vb, 0); + rp->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + rp->setStencilReference(1); + PushData pc1{ 1.0f, aspect, m_totalTime, 0.0f }; + rp->setPushConstants(rhi::ShaderStage::Vertex, 0, sizeof(PushData), &pc1); + rp->drawIndexed(18); + + // Pass 2: Draw scaled-up hexagon, only where stencil != 1 (outline ring). + rp->setPipeline(m_stencilTestPipeline); + rp->setStencilReference(1); + PushData pc2{ 1.15f, aspect, m_totalTime, 0.0f }; + rp->setPushConstants(rhi::ShaderStage::Vertex, 0, sizeof(PushData), &pc2); + rp->drawIndexed(18); + + rp->end(); + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void StencilOutlineSample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); + if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_stencilTestPipeline) m_device->destroyRenderPipeline(m_stencilTestPipeline); + if (m_stencilWritePipeline) m_device->destroyRenderPipeline(m_stencilWritePipeline); + if (m_depthStencilView) m_device->destroyTextureView(m_depthStencilView); + if (m_depthStencilTex) m_device->destroyTexture(m_depthStencilTex); + if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_ps) m_device->destroyShaderModule(m_ps); + if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_ib) m_device->destroyBuffer(m_ib); + if (m_vb) m_device->destroyBuffer(m_vb); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { StencilOutlineSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample023_CubeMap/CMakeLists.txt b/Samples/cpp/RHI/Sample023_CubeMap/CMakeLists.txt new file mode 100644 index 00000000..f755bff2 --- /dev/null +++ b/Samples/cpp/RHI/Sample023_CubeMap/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample023_CubeMap) diff --git a/Samples/cpp/RHI/Sample023_CubeMap/Main.cpp b/Samples/cpp/RHI/Sample023_CubeMap/Main.cpp new file mode 100644 index 00000000..1c2f058e --- /dev/null +++ b/Samples/cpp/RHI/Sample023_CubeMap/Main.cpp @@ -0,0 +1,561 @@ +#include +/// Demonstrates cube map textures and comparison samplers. +/// Renders a fullscreen quad that samples a procedural cube map (skybox), +/// plus a second pass with a depth texture sampled via comparison sampler +/// to demonstrate shadow-map-style sampling. + +#include +#include +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class CubeMapSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample023 - Cube Map & Comparison Sampler"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onShutdown() override; +private: + draco::Status createCubeMap(); + draco::Status createDepthTexture(); + + // Skybox shader: fullscreen quad -> ray direction -> cube map lookup + static constexpr const char8_t kSkyboxShader[] = u8R"( + TextureCube gCubeMap : register(t0, space0); + SamplerState gSampler : register(s0, space0); + + struct PushConstants + { + float Time; + float AspectRatio; + float2 _pad; + }; + + [[vk::push_constant]] ConstantBuffer pc : register(b0, space1); + + struct PSInput + { + float4 Position : SV_POSITION; + float2 UV : TEXCOORD0; + }; + + PSInput VSMain(uint vertexID : SV_VertexID) + { + PSInput output; + // Fullscreen triangle + float2 uv = float2((vertexID << 1) & 2, vertexID & 2); + output.Position = float4(uv * 2.0 - 1.0, 0.5, 1.0); + output.UV = uv; + return output; + } + + float4 PSMain(PSInput input) : SV_TARGET + { + // Convert UV to ray direction + float2 ndc = input.UV * 2.0 - 1.0; + ndc.x *= pc.AspectRatio; + ndc.y = -ndc.y; + + // Simple rotation around Y + float c = cos(pc.Time * 0.3); + float s = sin(pc.Time * 0.3); + + float3 dir = normalize(float3(ndc.x, ndc.y, 1.0)); + float3 rotDir = float3(dir.x * c + dir.z * s, dir.y, -dir.x * s + dir.z * c); + + return gCubeMap.Sample(gSampler, rotDir); + } + )"; + + // Shadow test shader: renders a quad, samples a depth texture with comparison sampler + static constexpr const char8_t kShadowShader[] = u8R"( + Texture2D gShadowMap : register(t0, space0); + SamplerComparisonState gShadowSampler : register(s0, space0); + + struct PushConstants + { + float Time; + float AspectRatio; + float2 _pad; + }; + + [[vk::push_constant]] ConstantBuffer pc : register(b0, space1); + + struct VSInput + { + float3 Position : TEXCOORD0; + float2 TexCoord : TEXCOORD1; + }; + + struct PSInput + { + float4 Position : SV_POSITION; + float2 TexCoord : TEXCOORD0; + }; + + PSInput VSMain(VSInput input) + { + PSInput output; + output.Position = float4(input.Position, 1.0); + output.TexCoord = input.TexCoord; + return output; + } + + float4 PSMain(PSInput input) : SV_TARGET + { + // Compare at varying depth based on time for animated shadow boundary + float compareValue = 0.5 + 0.4 * sin(pc.Time); + float shadow = gShadowMap.SampleCmpLevelZero(gShadowSampler, input.TexCoord, compareValue); + float3 litColor = float3(0.9, 0.85, 0.7); + float3 shadowColor = float3(0.1, 0.1, 0.2); + float3 color = lerp(shadowColor, litColor, shadow); + return float4(color, 1.0); + } + )"; + + struct PushData { + float time; + float aspectRatio; + float _pad0; + float _pad1; + }; + + shaders::Compiler* m_compiler = nullptr; + + // Skybox resources + rhi::ShaderModule* m_skyboxVs = nullptr; + rhi::ShaderModule* m_skyboxPs = nullptr; + rhi::Texture* m_cubeTexture = nullptr; + rhi::TextureView* m_cubeView = nullptr; + rhi::Sampler* m_linearSampler = nullptr; + rhi::BindGroupLayout* m_skyboxBgl = nullptr; + rhi::BindGroup* m_skyboxBg = nullptr; + rhi::PipelineLayout* m_skyboxPl = nullptr; + rhi::RenderPipeline* m_skyboxPipeline = nullptr; + + // Shadow comparison resources + rhi::ShaderModule* m_shadowVs = nullptr; + rhi::ShaderModule* m_shadowPs = nullptr; + rhi::Texture* m_depthTexture = nullptr; + rhi::TextureView* m_depthView = nullptr; + rhi::Sampler* m_comparisonSampler = nullptr; + rhi::Buffer* m_quadVb = nullptr; + rhi::BindGroupLayout* m_shadowBgl = nullptr; + rhi::BindGroup* m_shadowBg = nullptr; + rhi::PipelineLayout* m_shadowPl = nullptr; + rhi::RenderPipeline* m_shadowPipeline = nullptr; + + rhi::CommandPool* m_pool = nullptr; + rhi::Fence* m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +draco::Status CubeMapSample::onInit() { + using draco::Status, std::span, draco::u8, draco::u32; + + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Compile skybox shaders + if (sf::compileToModule(m_compiler, m_device, kSkyboxShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"SkyboxVS", m_skyboxVs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kSkyboxShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"SkyboxPS", m_skyboxPs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Compile shadow shaders + if (sf::compileToModule(m_compiler, m_device, kShadowShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"ShadowVS", m_shadowVs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShadowShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"ShadowPS", m_shadowPs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Create procedural cube map (6 faces, 64x64, each a solid color) + if (createCubeMap() != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Create depth texture for comparison sampler (gradient) + if (createDepthTexture() != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Create samplers + { + rhi::SamplerDesc sd{}; + sd.minFilter = rhi::FilterMode::Linear; + sd.magFilter = rhi::FilterMode::Linear; + sd.label = u8"LinearSampler"; + if (m_device->createSampler(sd, m_linearSampler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + { + rhi::SamplerDesc sd{}; + sd.minFilter = rhi::FilterMode::Linear; + sd.magFilter = rhi::FilterMode::Linear; + sd.compare = rhi::CompareFunction::LessEqual; + sd.label = u8"ComparisonSampler"; + if (m_device->createSampler(sd, m_comparisonSampler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // Skybox bind group layout: cube texture + sampler + { + rhi::BindGroupLayoutEntry entries[2] = { + rhi::BindGroupLayoutEntry::sampledTexture(0, rhi::ShaderStage::Vertex | rhi::ShaderStage::Fragment, rhi::TextureViewDimension::TextureCube), + rhi::BindGroupLayoutEntry::sampler(0, rhi::ShaderStage::Fragment) + }; + rhi::BindGroupLayoutDesc bgld{}; + bgld.entries = std::span(entries, 2); + bgld.label = u8"SkyboxBGL"; + if (m_device->createBindGroupLayout(bgld, m_skyboxBgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // Skybox bind group + { + rhi::BindGroupEntry entries[2] = { + rhi::BindGroupEntry::textureEntry(m_cubeView), + rhi::BindGroupEntry::samplerEntry(m_linearSampler) + }; + rhi::BindGroupDesc bgd{}; + bgd.layout = m_skyboxBgl; + bgd.entries = std::span(entries, 2); + bgd.label = u8"SkyboxBG"; + if (m_device->createBindGroup(bgd, m_skyboxBg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // Skybox pipeline layout + { + rhi::BindGroupLayout* sets[1] = { m_skyboxBgl }; + rhi::PushConstantRange pcr{}; + pcr.stages = rhi::ShaderStage::Vertex | rhi::ShaderStage::Fragment; + pcr.offset = 0; + pcr.size = sizeof(PushData); + rhi::PushConstantRange pushRanges[1] = { pcr }; + rhi::PipelineLayoutDesc pld{}; + pld.bindGroupLayouts = std::span(sets, 1); + pld.pushConstantRanges = std::span(pushRanges, 1); + pld.label = u8"SkyboxPL"; + if (m_device->createPipelineLayout(pld, m_skyboxPl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // Skybox pipeline (fullscreen triangle, no vertex input) + { + rhi::ColorTargetState ct{}; + ct.format = m_swapChain->format(); + rhi::RenderPipelineDesc rpd{}; + rpd.layout = m_skyboxPl; + rpd.vertex.shader = { m_skyboxVs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.fragment = rhi::FragmentState{}; + rpd.fragment->shader = { m_skyboxPs, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.primitive.topology = rhi::PrimitiveTopology::TriangleList; + rpd.label = u8"SkyboxPipeline"; + if (m_device->createRenderPipeline(rpd, m_skyboxPipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // Shadow test quad vertices (bottom-right corner overlay) + { + float quadVerts[] = { + // pos xyz, uv + 0.3f, -0.9f, 0.0f, 0.0f, 1.0f, + 0.9f, -0.9f, 0.0f, 1.0f, 1.0f, + 0.9f, -0.3f, 0.0f, 1.0f, 0.0f, + 0.3f, -0.9f, 0.0f, 0.0f, 1.0f, + 0.9f, -0.3f, 0.0f, 1.0f, 0.0f, + 0.3f, -0.3f, 0.0f, 0.0f, 0.0f + }; + + u32 vbSize = sizeof(quadVerts); + rhi::BufferDesc bd{}; + bd.size = vbSize; + bd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; + bd.memory = rhi::MemoryLocation::GpuOnly; + bd.label = u8"ShadowQuadVB"; + if (m_device->createBuffer(bd, m_quadVb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TransferBatch* batch = nullptr; + m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_quadVb, 0, std::span(reinterpret_cast(quadVerts), vbSize)); + batch->submit(); + m_graphicsQueue->destroyTransferBatch(batch); + } + + // Shadow bind group layout: depth texture + comparison sampler + { + rhi::BindGroupLayoutEntry entries[2]; + entries[0] = rhi::BindGroupLayoutEntry::sampledTexture(0, rhi::ShaderStage::Fragment, rhi::TextureViewDimension::Texture2D); + entries[1] = {}; + entries[1].binding = 0; + entries[1].visibility = rhi::ShaderStage::Fragment; + entries[1].type = rhi::BindingType::ComparisonSampler; + rhi::BindGroupLayoutDesc bgld{}; + bgld.entries = std::span(entries, 2); + bgld.label = u8"ShadowBGL"; + if (m_device->createBindGroupLayout(bgld, m_shadowBgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // Shadow bind group + { + rhi::BindGroupEntry entries[2] = { + rhi::BindGroupEntry::textureEntry(m_depthView), + rhi::BindGroupEntry::samplerEntry(m_comparisonSampler) + }; + rhi::BindGroupDesc bgd{}; + bgd.layout = m_shadowBgl; + bgd.entries = std::span(entries, 2); + bgd.label = u8"ShadowBG"; + if (m_device->createBindGroup(bgd, m_shadowBg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // Shadow pipeline layout + { + rhi::BindGroupLayout* sets[1] = { m_shadowBgl }; + rhi::PushConstantRange pcr{}; + pcr.stages = rhi::ShaderStage::Vertex | rhi::ShaderStage::Fragment; + pcr.offset = 0; + pcr.size = sizeof(PushData); + rhi::PushConstantRange pushRanges[1] = { pcr }; + rhi::PipelineLayoutDesc pld{}; + pld.bindGroupLayouts = std::span(sets, 1); + pld.pushConstantRanges = std::span(pushRanges, 1); + pld.label = u8"ShadowPL"; + if (m_device->createPipelineLayout(pld, m_shadowPl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // Shadow pipeline + { + rhi::VertexAttribute attrs[2] = { + { rhi::VertexFormat::Float32x3, 0, 0 }, + { rhi::VertexFormat::Float32x2, 12, 1 } + }; + rhi::VertexBufferLayout vbl{}; + vbl.stride = 20; + vbl.attributes = std::span(attrs, 2); + + rhi::ColorTargetState ct{}; + ct.format = m_swapChain->format(); + + rhi::RenderPipelineDesc rpd{}; + rpd.layout = m_shadowPl; + rpd.vertex.shader = { m_shadowVs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; + rpd.fragment->shader = { m_shadowPs, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.primitive.topology = rhi::PrimitiveTopology::TriangleList; + rpd.label = u8"ShadowPipeline"; + if (m_device->createRenderPipeline(rpd, m_shadowPipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +draco::Status CubeMapSample::createCubeMap() { + using draco::Status, std::span, draco::u8, draco::u32; + + constexpr u32 faceSize = 64; + constexpr u32 BytesPerPixel = 4; + constexpr u32 faceBytes = faceSize * faceSize * BytesPerPixel; + + // Create cube map texture: 2D with 6 array layers + rhi::TextureDesc td{}; + td.dimension = rhi::TextureDimension::Texture2D; + td.format = rhi::TextureFormat::RGBA8UnormSrgb; + td.width = faceSize; + td.height = faceSize; + td.arrayLayerCount = 6; + td.mipLevelCount = 1; + td.sampleCount = 1; + td.usage = rhi::TextureUsage::Sampled | rhi::TextureUsage::CopyDst; + td.label = u8"CubeMapTex"; + if (m_device->createTexture(td, m_cubeTexture) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Create cube view + rhi::TextureViewDesc tvd{}; + tvd.format = rhi::TextureFormat::RGBA8UnormSrgb; + tvd.dimension = rhi::TextureViewDimension::TextureCube; + tvd.baseMipLevel = 0; + tvd.mipLevelCount = 1; + tvd.baseArrayLayer = 0; + tvd.arrayLayerCount = 6; + if (m_device->createTextureView(m_cubeTexture, tvd, m_cubeView) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Generate 6 face colors: +X red, -X cyan, +Y green, -Y magenta, +Z blue, -Z yellow + u8 faceColors[6][4] = { + {200, 60, 60, 255}, // +X: red + {60, 200, 200, 255}, // -X: cyan + {60, 200, 60, 255}, // +Y: green + {200, 60, 200, 255}, // -Y: magenta + {60, 60, 200, 255}, // +Z: blue + {200, 200, 60, 255} // -Z: yellow + }; + + u8 stagingBuf[faceBytes]; + rhi::TransferBatch* batch = nullptr; + m_graphicsQueue->createTransferBatch(batch); + + for (int face = 0; face < 6; face++) { + // Fill face with gradient from face color to white at center + for (u32 y = 0; y < faceSize; y++) { + for (u32 x = 0; x < faceSize; x++) { + float fx = (static_cast(x) / static_cast(faceSize)) * 2.0f - 1.0f; + float fy = (static_cast(y) / static_cast(faceSize)) * 2.0f - 1.0f; + float dist = std::min(1.0f, std::sqrt(fx * fx + fy * fy)); + float t = 1.0f - dist * 0.5f; + + u32 idx = (y * faceSize + x) * BytesPerPixel; + stagingBuf[idx + 0] = static_cast(faceColors[face][0] * t + 40 * (1.0f - t)); + stagingBuf[idx + 1] = static_cast(faceColors[face][1] * t + 40 * (1.0f - t)); + stagingBuf[idx + 2] = static_cast(faceColors[face][2] * t + 40 * (1.0f - t)); + stagingBuf[idx + 3] = 255; + } + } + + rhi::TextureDataLayout layout{}; + layout.bytesPerRow = faceSize * BytesPerPixel; + layout.rowsPerImage = faceSize; + batch->writeTexture(m_cubeTexture, + std::span(stagingBuf, faceBytes), + layout, rhi::Extent3D{faceSize, faceSize, 1}, + 0, static_cast(face)); + } + + batch->submit(); + m_graphicsQueue->destroyTransferBatch(batch); + return draco::ErrorCode::Ok; +} + +draco::Status CubeMapSample::createDepthTexture() { + using draco::Status; + + constexpr draco::u32 texSize = 64; + + rhi::TextureDesc td{}; + td.dimension = rhi::TextureDimension::Texture2D; + td.format = rhi::TextureFormat::Depth32Float; + td.width = texSize; + td.height = texSize; + td.arrayLayerCount = 1; + td.mipLevelCount = 1; + td.sampleCount = 1; + td.usage = rhi::TextureUsage::DepthStencil | rhi::TextureUsage::Sampled; + td.label = u8"ShadowDepthTex"; + if (m_device->createTexture(td, m_depthTexture) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TextureViewDesc tvd{}; + tvd.format = rhi::TextureFormat::Depth32Float; + tvd.dimension = rhi::TextureViewDimension::Texture2D; + if (m_device->createTextureView(m_depthTexture, tvd, m_depthView) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // We'll render a gradient depth in a render pass + // For simplicity, just clear to 0.5 so the comparison sampler has something to compare against + // (A real sample would render shadow casters here) + + return draco::ErrorCode::Ok; +} + +void CubeMapSample::onRender() { + using draco::f32, draco::u32, std::span; + + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + f32 aspect = static_cast(m_width) / static_cast(m_height); + + // Render a depth value into the shadow depth texture + enc->transitionTexture(m_depthTexture, rhi::ResourceState::Undefined, rhi::ResourceState::DepthStencilWrite); + { + rhi::DepthStencilAttachment dsa{}; + dsa.view = m_depthView; + dsa.depthLoadOp = rhi::LoadOp::Clear; + dsa.depthStoreOp = rhi::StoreOp::Store; + dsa.depthClearValue = 0.5f; + rhi::RenderPassDesc rpd{}; + rpd.depthStencilAttachment = dsa; + auto* rp = enc->beginRenderPass(rpd); + rp->end(); + } + + // Transition depth texture from DepthStencilWrite -> ShaderRead for sampling + enc->transitionTexture(m_depthTexture, rhi::ResourceState::DepthStencilWrite, rhi::ResourceState::ShaderRead); + + // Transition swapchain + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + + rhi::ColorAttachment ca{}; + ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; + ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.05f, 0.05f, 0.08f, 1.0f); + rhi::RenderPassDesc rpd{}; + rpd.colorAttachments.push_back(ca); + auto* rp = enc->beginRenderPass(rpd); + + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0.0f, 1.0f); + rp->setScissor(0, 0, m_width, m_height); + + // Draw skybox (fullscreen triangle, no VB needed - SV_VertexID) + rp->setPipeline(m_skyboxPipeline); + rp->setBindGroup(0, m_skyboxBg); + PushData pc{}; + pc.time = m_totalTime; + pc.aspectRatio = aspect; + rp->setPushConstants(rhi::ShaderStage::Vertex | rhi::ShaderStage::Fragment, 0, sizeof(PushData), &pc); + rp->draw(3); + + // Draw shadow comparison overlay quad + rp->setPipeline(m_shadowPipeline); + rp->setBindGroup(0, m_shadowBg); + rp->setPushConstants(rhi::ShaderStage::Vertex | rhi::ShaderStage::Fragment, 0, sizeof(PushData), &pc); + rp->setVertexBuffer(0, m_quadVb, 0); + rp->draw(6); + + rp->end(); + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + + // Transition depth texture back to DepthStencilWrite for next frame + enc->transitionTexture(m_depthTexture, rhi::ResourceState::ShaderRead, rhi::ResourceState::DepthStencilWrite); + + rhi::CommandBuffer* cb = enc->finish(); + m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void CubeMapSample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); + if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_shadowPipeline) m_device->destroyRenderPipeline(m_shadowPipeline); + if (m_skyboxPipeline) m_device->destroyRenderPipeline(m_skyboxPipeline); + if (m_shadowPl) m_device->destroyPipelineLayout(m_shadowPl); + if (m_skyboxPl) m_device->destroyPipelineLayout(m_skyboxPl); + if (m_shadowBg) m_device->destroyBindGroup(m_shadowBg); + if (m_skyboxBg) m_device->destroyBindGroup(m_skyboxBg); + if (m_shadowBgl) m_device->destroyBindGroupLayout(m_shadowBgl); + if (m_skyboxBgl) m_device->destroyBindGroupLayout(m_skyboxBgl); + if (m_comparisonSampler) m_device->destroySampler(m_comparisonSampler); + if (m_linearSampler) m_device->destroySampler(m_linearSampler); + if (m_quadVb) m_device->destroyBuffer(m_quadVb); + if (m_depthView) m_device->destroyTextureView(m_depthView); + if (m_depthTexture) m_device->destroyTexture(m_depthTexture); + if (m_cubeView) m_device->destroyTextureView(m_cubeView); + if (m_cubeTexture) m_device->destroyTexture(m_cubeTexture); + if (m_shadowPs) m_device->destroyShaderModule(m_shadowPs); + if (m_shadowVs) m_device->destroyShaderModule(m_shadowVs); + if (m_skyboxPs) m_device->destroyShaderModule(m_skyboxPs); + if (m_skyboxVs) m_device->destroyShaderModule(m_skyboxVs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { CubeMapSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample024_OcclusionQuery/CMakeLists.txt b/Samples/cpp/RHI/Sample024_OcclusionQuery/CMakeLists.txt new file mode 100644 index 00000000..d3b1ee3c --- /dev/null +++ b/Samples/cpp/RHI/Sample024_OcclusionQuery/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample024_OcclusionQuery) diff --git a/Samples/cpp/RHI/Sample024_OcclusionQuery/Main.cpp b/Samples/cpp/RHI/Sample024_OcclusionQuery/Main.cpp new file mode 100644 index 00000000..5299d334 --- /dev/null +++ b/Samples/cpp/RHI/Sample024_OcclusionQuery/Main.cpp @@ -0,0 +1,260 @@ +#include +/// Demonstrates occlusion queries and debug labels. +/// Renders an occluder quad, then two test quads behind it with occlusion queries. +/// Prints pixel counts to console. Uses debug labels to mark render sections. + +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class OcclusionQuerySample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample024 - Occlusion Queries & Debug Labels"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onResize(draco::u32 w, draco::u32 h) override { recreateDepth(w, h); } + void onShutdown() override; +private: + static constexpr const char8_t kShader[] = u8R"( + struct VSInput + { + float3 Position : TEXCOORD0; + float4 Color : TEXCOORD1; + }; + + struct PSInput + { + float4 Position : SV_POSITION; + float4 Color : COLOR0; + }; + + PSInput VSMain(VSInput input) + { + PSInput output; + output.Position = float4(input.Position, 1.0); + output.Color = input.Color; + return output; + } + + float4 PSMain(PSInput input) : SV_TARGET + { + return input.Color; + } + )"; + + // Geometry: 3 quads + // Quad 0: Occluder (opaque gray, z=0.3, center) + // Quad 1: Test A (red, z=0.7, partially behind occluder) + // Quad 2: Test B (blue, z=0.7, fully behind occluder) + static constexpr float kVerts[] = { + // Quad 0: Occluder - center, near + -0.3f, -0.4f, 0.3f, 0.4f, 0.4f, 0.4f, 1.0f, + 0.3f, -0.4f, 0.3f, 0.4f, 0.4f, 0.4f, 1.0f, + 0.3f, 0.4f, 0.3f, 0.5f, 0.5f, 0.5f, 1.0f, + -0.3f, 0.4f, 0.3f, 0.5f, 0.5f, 0.5f, 1.0f, + + // Quad 1: Test A - partially occluded (left side visible) + -0.7f, -0.3f, 0.7f, 1.0f, 0.3f, 0.3f, 1.0f, + 0.0f, -0.3f, 0.7f, 1.0f, 0.3f, 0.3f, 1.0f, + 0.0f, 0.3f, 0.7f, 1.0f, 0.5f, 0.5f, 1.0f, + -0.7f, 0.3f, 0.7f, 1.0f, 0.5f, 0.5f, 1.0f, + + // Quad 2: Test B - fully occluded (behind occluder) + -0.15f, -0.2f, 0.7f, 0.3f, 0.3f, 1.0f, 1.0f, + 0.15f, -0.2f, 0.7f, 0.3f, 0.3f, 1.0f, 1.0f, + 0.15f, 0.2f, 0.7f, 0.5f, 0.5f, 1.0f, 1.0f, + -0.15f, 0.2f, 0.7f, 0.5f, 0.5f, 1.0f, 1.0f, + }; + static constexpr draco::u16 kIdx[] = { + 0, 1, 2, 0, 2, 3, + 4, 5, 6, 4, 6, 7, + 8, 9, 10, 8, 10, 11, + }; + + void recreateDepth(draco::u32 w, draco::u32 h); + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_vs = nullptr, *m_ps = nullptr; + rhi::Buffer *m_vb = nullptr, *m_ib = nullptr; + rhi::PipelineLayout *m_pl = nullptr; + rhi::RenderPipeline *m_pipeline = nullptr; + rhi::Texture *m_depthTex = nullptr; + rhi::TextureView *m_depthView = nullptr; + rhi::QuerySet *m_occlusionQuerySet = nullptr; + rhi::Buffer *m_queryResultBuf = nullptr; + rhi::CommandPool *m_pool = nullptr; + rhi::Fence *m_fence = nullptr; + draco::u64 m_fenceVal = 0; + int m_frameCount = 0; + float m_lastReportTime = 0.0f; +}; + +void OcclusionQuerySample::recreateDepth(draco::u32 w, draco::u32 h) { + if (m_depthView) { m_device->destroyTextureView(m_depthView); m_depthView = nullptr; } + if (m_depthTex) { m_device->destroyTexture(m_depthTex); m_depthTex = nullptr; } + + rhi::TextureDesc td = rhi::TextureDesc::depthBuffer(rhi::TextureFormat::Depth24PlusStencil8, w, h, 1, u8"OccDepthTex"); + m_device->createTexture(td, m_depthTex); + rhi::TextureViewDesc tvd{}; tvd.format = rhi::TextureFormat::Depth24PlusStencil8; tvd.dimension = rhi::TextureViewDimension::Texture2D; + tvd.mipLevelCount = 1; tvd.arrayLayerCount = 1; + m_device->createTextureView(m_depthTex, tvd, m_depthView); +} + +draco::Status OcclusionQuerySample::onInit() { + using draco::Status, std::span, draco::u8; + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"OccVS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"OccPS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::BufferDesc vbd{}; vbd.size = sizeof(kVerts); vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; vbd.label = u8"OccVB"; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ibd{}; ibd.size = sizeof(kIdx); ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; ibd.memory = rhi::MemoryLocation::GpuOnly; ibd.label = u8"OccIB"; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(kVerts), sizeof(kVerts))); + batch->writeBuffer(m_ib, 0, std::span(reinterpret_cast(kIdx), sizeof(kIdx))); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + rhi::PipelineLayoutDesc pld{}; pld.label = u8"OccPL"; + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + recreateDepth(m_width, m_height); + + rhi::VertexAttribute attrs[2] = { {rhi::VertexFormat::Float32x3, 0, 0}, {rhi::VertexFormat::Float32x4, 12, 1} }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 28; vbl.attributes = std::span(attrs, 2); + + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.depthStencil = rhi::DepthStencilState{}; rpd.depthStencil->format = rhi::TextureFormat::Depth24PlusStencil8; + rpd.depthStencil->depthWriteEnabled = true; + rpd.depthStencil->depthCompare = rhi::CompareFunction::Less; + rpd.label = u8"OccPipeline"; + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Occlusion query set: 2 queries (one per test quad). + rhi::QuerySetDesc qsd{}; qsd.type = rhi::QueryType::Occlusion; qsd.count = 2; qsd.label = u8"OcclusionQS"; + if (m_device->createQuerySet(qsd, m_occlusionQuerySet) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Buffer for query results (2 * uint64 = 16 bytes). + rhi::BufferDesc qbd{}; qbd.size = 16; qbd.usage = rhi::BufferUsage::CopyDst; qbd.memory = rhi::MemoryLocation::GpuToCpu; qbd.label = u8"OccResultBuf"; + if (m_device->createBuffer(qbd, m_queryResultBuf) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void OcclusionQuerySample::onRender() { + using draco::f32, draco::u64, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + + // Read back previous frame's occlusion results (after fence wait ensures GPU is done). + if (m_frameCount > 1) { + void* mapped = m_queryResultBuf->map(); + if (mapped) { + auto* results = static_cast(mapped); + u64 pixelsA = results[0]; + u64 pixelsB = results[1]; + + if (m_totalTime - m_lastReportTime >= 2.0f) { + std::printf("Occlusion: QuadA=%llu pixels, QuadB=%llu pixels (B should be ~0)\n", + static_cast(pixelsA), static_cast(pixelsB)); + m_lastReportTime = m_totalTime; + } + m_queryResultBuf->unmap(); + } + } + + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + // Debug label: frame start. + enc->insertDebugLabel(u8"Frame Start", 0, 1, 0); + + // Reset queries for this frame. + enc->resetQuerySet(m_occlusionQuerySet, 0, 2); + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + enc->transitionTexture(m_depthTex, rhi::ResourceState::Undefined, rhi::ResourceState::DepthStencilWrite); + + // Debug label: render pass. + enc->beginDebugLabel(u8"Main Render Pass", 0.2f, 0.5f, 1.0f); + + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.08f, 0.08f, 0.12f, 1.0f); + rhi::DepthStencilAttachment dsa{}; dsa.view = m_depthView; + dsa.depthLoadOp = rhi::LoadOp::Clear; dsa.depthStoreOp = rhi::StoreOp::Store; dsa.depthClearValue = 1.0f; + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); rpd.depthStencilAttachment = dsa; + auto* rp = enc->beginRenderPass(rpd); + + rp->setPipeline(m_pipeline); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0, 1); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vb, 0); + rp->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + + // Draw occluder first (writes depth). + rp->drawIndexed(6, 1, 0, 0, 0); + + // Draw test quad A with occlusion query 0. + rp->beginOcclusionQuery(m_occlusionQuerySet, 0); + rp->drawIndexed(6, 1, 6, 0, 0); + rp->endOcclusionQuery(m_occlusionQuerySet, 0); + + // Draw test quad B with occlusion query 1. + rp->beginOcclusionQuery(m_occlusionQuerySet, 1); + rp->drawIndexed(6, 1, 12, 0, 0); + rp->endOcclusionQuery(m_occlusionQuerySet, 1); + + rp->end(); + + enc->endDebugLabel(); + + // Resolve occlusion queries to buffer. + enc->resolveQuerySet(m_occlusionQuerySet, 0, 2, m_queryResultBuf, 0); + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); + + m_frameCount++; +} + +void OcclusionQuerySample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_queryResultBuf) m_device->destroyBuffer(m_queryResultBuf); + if (m_occlusionQuerySet) m_device->destroyQuerySet(m_occlusionQuerySet); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_depthView) m_device->destroyTextureView(m_depthView); + if (m_depthTex) m_device->destroyTexture(m_depthTex); + if (m_ib) m_device->destroyBuffer(m_ib); if (m_vb) m_device->destroyBuffer(m_vb); + if (m_ps) m_device->destroyShaderModule(m_ps); if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { OcclusionQuerySample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample025_MultiDrawIndirect/CMakeLists.txt b/Samples/cpp/RHI/Sample025_MultiDrawIndirect/CMakeLists.txt new file mode 100644 index 00000000..79eb4aaf --- /dev/null +++ b/Samples/cpp/RHI/Sample025_MultiDrawIndirect/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample025_MultiDrawIndirect) diff --git a/Samples/cpp/RHI/Sample025_MultiDrawIndirect/Main.cpp b/Samples/cpp/RHI/Sample025_MultiDrawIndirect/Main.cpp new file mode 100644 index 00000000..6af39600 --- /dev/null +++ b/Samples/cpp/RHI/Sample025_MultiDrawIndirect/Main.cpp @@ -0,0 +1,325 @@ +#include +/// Renders 4 colored quads using a single drawIndexedIndirect call with drawCount=4, +/// then overlays white line wireframes using LineList topology. + +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +struct DrawIndexedIndirectArgs { + draco::u32 indexCountPerInstance; + draco::u32 instanceCount; + draco::u32 startIndexLocation; + draco::i32 baseVertexLocation; + draco::u32 startInstanceLocation; +}; + +class MultiDrawIndirectSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample025 - Multi-Draw Indirect & Lines"; } +protected: + rhi::DeviceFeatures requiredFeatures() const override { + rhi::DeviceFeatures f{}; + f.multiDrawIndirect = true; + return f; + } + draco::Status onInit() override; + void onRender() override; + void onShutdown() override; +private: + static constexpr const char8_t kShader[] = u8R"( + struct VSInput + { + float3 Position : TEXCOORD0; + float4 Color : TEXCOORD1; + }; + + struct PSInput + { + float4 Position : SV_POSITION; + float4 Color : COLOR0; + }; + + PSInput VSMain(VSInput input) + { + PSInput output; + output.Position = float4(input.Position, 1.0); + output.Color = input.Color; + return output; + } + + float4 PSMain(PSInput input) : SV_TARGET + { + return input.Color; + } + )"; + + draco::Status createGeometry(); + draco::Status createIndirectBuffer(); + draco::Status createLineGeometry(); + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_vs = nullptr, *m_ps = nullptr; + rhi::Buffer *m_vb = nullptr, *m_ib = nullptr, *m_indirectBuf = nullptr, *m_lineVb = nullptr; + rhi::PipelineLayout *m_pl = nullptr; + rhi::RenderPipeline *m_fillPipeline = nullptr, *m_linePipeline = nullptr; + rhi::CommandPool *m_pool = nullptr; + rhi::Fence *m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +draco::Status MultiDrawIndirectSample::onInit() { + using draco::Status, std::span, draco::u8; + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"VS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"PS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (createGeometry() != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (createIndirectBuffer() != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (createLineGeometry() != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Pipeline layout (empty - no bind groups needed). + rhi::PipelineLayoutDesc pld{}; + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Vertex layout: pos float3 + color float4, stride 28. + rhi::VertexAttribute attrs[2] = { + {rhi::VertexFormat::Float32x3, 0, 0}, + {rhi::VertexFormat::Float32x4, 12, 1} + }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 28; vbl.stepMode = rhi::VertexStepMode::Vertex; + vbl.attributes = std::span(attrs, 2); + + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + + // Fill pipeline (TriangleList). + { + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.primitive.topology = rhi::PrimitiveTopology::TriangleList; + if (m_device->createRenderPipeline(rpd, m_fillPipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // Line pipeline (LineList). + { + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.primitive.topology = rhi::PrimitiveTopology::LineList; + if (m_device->createRenderPipeline(rpd, m_linePipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +draco::Status MultiDrawIndirectSample::createGeometry() { + using draco::Status, std::span, draco::u8, draco::u32; + + // 4 quads at different positions. + static constexpr float verts[] = { + // Quad 0: top-left (red) + -0.9f, 0.1f, 0.5f, 0.8f, 0.2f, 0.2f, 1.0f, + -0.1f, 0.1f, 0.5f, 0.8f, 0.2f, 0.2f, 1.0f, + -0.1f, 0.9f, 0.5f, 1.0f, 0.4f, 0.4f, 1.0f, + -0.9f, 0.9f, 0.5f, 1.0f, 0.4f, 0.4f, 1.0f, + + // Quad 1: top-right (green) + 0.1f, 0.1f, 0.5f, 0.2f, 0.8f, 0.2f, 1.0f, + 0.9f, 0.1f, 0.5f, 0.2f, 0.8f, 0.2f, 1.0f, + 0.9f, 0.9f, 0.5f, 0.4f, 1.0f, 0.4f, 1.0f, + 0.1f, 0.9f, 0.5f, 0.4f, 1.0f, 0.4f, 1.0f, + + // Quad 2: bottom-left (blue) + -0.9f, -0.9f, 0.5f, 0.2f, 0.2f, 0.8f, 1.0f, + -0.1f, -0.9f, 0.5f, 0.2f, 0.2f, 0.8f, 1.0f, + -0.1f, -0.1f, 0.5f, 0.4f, 0.4f, 1.0f, 1.0f, + -0.9f, -0.1f, 0.5f, 0.4f, 0.4f, 1.0f, 1.0f, + + // Quad 3: bottom-right (yellow) + 0.1f, -0.9f, 0.5f, 0.8f, 0.8f, 0.2f, 1.0f, + 0.9f, -0.9f, 0.5f, 0.8f, 0.8f, 0.2f, 1.0f, + 0.9f, -0.1f, 0.5f, 1.0f, 1.0f, 0.4f, 1.0f, + 0.1f, -0.1f, 0.5f, 1.0f, 1.0f, 0.4f, 1.0f, + }; + + static constexpr draco::u16 indices[] = { + 0, 1, 2, 0, 2, 3, // Quad 0 + 4, 5, 6, 4, 6, 7, // Quad 1 + 8, 9, 10, 8, 10, 11, // Quad 2 + 12, 13, 14, 12, 14, 15, // Quad 3 + }; + + rhi::BufferDesc vbd{}; vbd.size = sizeof(verts); + vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; + vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::BufferDesc ibd{}; ibd.size = sizeof(indices); + ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; + ibd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(verts), sizeof(verts))); + batch->writeBuffer(m_ib, 0, std::span(reinterpret_cast(indices), sizeof(indices))); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + return draco::ErrorCode::Ok; +} + +draco::Status MultiDrawIndirectSample::createIndirectBuffer() { + using draco::Status, std::span, draco::u8; + + // 4 indirect draw commands - one per quad. + DrawIndexedIndirectArgs args[4] = { + { 6, 1, 0, 0, 0 }, + { 6, 1, 6, 0, 0 }, + { 6, 1, 12, 0, 0 }, + { 6, 1, 18, 0, 0 }, + }; + + rhi::BufferDesc bd{}; bd.size = sizeof(args); + bd.usage = rhi::BufferUsage::Indirect | rhi::BufferUsage::CopyDst; + bd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(bd, m_indirectBuf) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_indirectBuf, 0, std::span(reinterpret_cast(args), sizeof(args))); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + return draco::ErrorCode::Ok; +} + +draco::Status MultiDrawIndirectSample::createLineGeometry() { + using draco::Status, std::span, draco::u8; + + // Line wireframes for each quad: 4 edges per quad = 8 verts per quad, white lines. + static constexpr float lineVerts[] = { + // Quad 0 edges + -0.9f, 0.1f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + -0.1f, 0.1f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + -0.1f, 0.1f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + -0.1f, 0.9f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + -0.1f, 0.9f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + -0.9f, 0.9f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + -0.9f, 0.9f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + -0.9f, 0.1f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + + // Quad 1 edges + 0.1f, 0.1f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + 0.9f, 0.1f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + 0.9f, 0.1f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + 0.9f, 0.9f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + 0.9f, 0.9f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + 0.1f, 0.9f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + 0.1f, 0.9f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + 0.1f, 0.1f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + + // Quad 2 edges + -0.9f, -0.9f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + -0.1f, -0.9f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + -0.1f, -0.9f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + -0.1f, -0.1f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + -0.1f, -0.1f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + -0.9f, -0.1f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + -0.9f, -0.1f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + -0.9f, -0.9f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + + // Quad 3 edges + 0.1f, -0.9f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + 0.9f, -0.9f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + 0.9f, -0.9f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + 0.9f, -0.1f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + 0.9f, -0.1f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + 0.1f, -0.1f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + 0.1f, -0.1f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + 0.1f, -0.9f, 0.4f, 1.0f, 1.0f, 1.0f, 1.0f, + }; + + rhi::BufferDesc bd{}; bd.size = sizeof(lineVerts); + bd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; + bd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(bd, m_lineVb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_lineVb, 0, std::span(reinterpret_cast(lineVerts), sizeof(lineVerts))); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + return draco::ErrorCode::Ok; +} + +void MultiDrawIndirectSample::onRender() { + using draco::f32, std::span, draco::u32; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.06f, 0.06f, 0.1f, 1.0f); + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); + auto* rp = enc->beginRenderPass(rpd); + + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0.0f, 1.0f); + rp->setScissor(0, 0, m_width, m_height); + + // Pass 1: Draw all 4 quads with a single multi-draw indirect call. + rp->setPipeline(m_fillPipeline); + rp->setVertexBuffer(0, m_vb, 0); + rp->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + rp->drawIndexedIndirect(m_indirectBuf, 0, 4, static_cast(sizeof(DrawIndexedIndirectArgs))); + + // Pass 2: Draw line wireframes. + rp->setPipeline(m_linePipeline); + rp->setVertexBuffer(0, m_lineVb, 0); + rp->draw(32); // 4 quads * 4 edges * 2 verts = 32 line verts + + rp->end(); + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void MultiDrawIndirectSample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); + if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_linePipeline) m_device->destroyRenderPipeline(m_linePipeline); + if (m_fillPipeline) m_device->destroyRenderPipeline(m_fillPipeline); + if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_ps) m_device->destroyShaderModule(m_ps); + if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_lineVb) m_device->destroyBuffer(m_lineVb); + if (m_indirectBuf) m_device->destroyBuffer(m_indirectBuf); + if (m_ib) m_device->destroyBuffer(m_ib); + if (m_vb) m_device->destroyBuffer(m_vb); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { MultiDrawIndirectSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample026_DynamicOffsets/CMakeLists.txt b/Samples/cpp/RHI/Sample026_DynamicOffsets/CMakeLists.txt new file mode 100644 index 00000000..03735310 --- /dev/null +++ b/Samples/cpp/RHI/Sample026_DynamicOffsets/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample026_DynamicOffsets) diff --git a/Samples/cpp/RHI/Sample026_DynamicOffsets/Main.cpp b/Samples/cpp/RHI/Sample026_DynamicOffsets/Main.cpp new file mode 100644 index 00000000..3f8479f2 --- /dev/null +++ b/Samples/cpp/RHI/Sample026_DynamicOffsets/Main.cpp @@ -0,0 +1,237 @@ +#include +/// Demonstrates dynamic uniform buffer offsets and blend constants. +/// Draws 4 quads, each reading from a different offset in one shared UBO. +/// Uses setBlendConstant with BlendFactor::Constant for per-frame color modulation. + +#include +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class DynamicOffsetSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample026 - Dynamic Offsets & Blend Constants"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onShutdown() override; +private: + void updateUBO(); + + static constexpr const char8_t kShader[] = u8R"( + cbuffer ObjectData : register(b0, space0) + { + float4 TintColor; + float4 OffsetScale; // xy=offset, zw=scale + }; + + struct VSInput + { + float3 Position : TEXCOORD0; + }; + + struct PSInput + { + float4 Position : SV_POSITION; + }; + + PSInput VSMain(VSInput input) + { + PSInput output; + float2 pos = input.Position.xy * OffsetScale.zw + OffsetScale.xy; + output.Position = float4(pos, input.Position.z, 1.0); + return output; + } + + float4 PSMain(PSInput input) : SV_TARGET + { + return TintColor; + } + )"; + + struct ObjectData { + float tintColor[4]; + float offsetScale[4]; + // Pad to 256-byte alignment (D3D12 CBV minimum). + float _pad[56]; + }; + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_vs = nullptr, *m_ps = nullptr; + rhi::Buffer *m_vb = nullptr, *m_ib = nullptr, *m_ub = nullptr; + rhi::BindGroupLayout *m_bgl = nullptr; + rhi::BindGroup *m_bg = nullptr; + rhi::PipelineLayout *m_pl = nullptr; + rhi::RenderPipeline *m_pipeline = nullptr; + rhi::CommandPool *m_pool = nullptr; + rhi::Fence *m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +draco::Status DynamicOffsetSample::onInit() { + using draco::Status, std::span, draco::u8, draco::u32; + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"DynVS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"DynPS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Unit quad vertices (will be transformed by UBO data). + static constexpr float verts[] = { + -0.5f, -0.5f, 0.5f, + 0.5f, -0.5f, 0.5f, + 0.5f, 0.5f, 0.5f, + -0.5f, 0.5f, 0.5f, + }; + static constexpr draco::u16 indices[] = { 0, 1, 2, 0, 2, 3 }; + + rhi::BufferDesc vbd{}; vbd.size = sizeof(verts); vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ibd{}; ibd.size = sizeof(indices); ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; ibd.memory = rhi::MemoryLocation::GpuOnly; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(verts), sizeof(verts))); + batch->writeBuffer(m_ib, 0, std::span(reinterpret_cast(indices), sizeof(indices))); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + // Uniform buffer: 4 ObjectData structs (256 bytes each = 1024 total). + rhi::BufferDesc ubd{}; ubd.size = 256 * 4; ubd.usage = rhi::BufferUsage::Uniform; ubd.memory = rhi::MemoryLocation::CpuToGpu; + if (m_device->createBuffer(ubd, m_ub) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Initialize UBO data. + updateUBO(); + + // Bind group layout with dynamic offset UBO. + rhi::BindGroupLayoutEntry entry = rhi::BindGroupLayoutEntry::uniformBuffer(0, rhi::ShaderStage::Vertex | rhi::ShaderStage::Fragment); + entry.hasDynamicOffset = true; + rhi::BindGroupLayoutEntry entries[1] = { entry }; + rhi::BindGroupLayoutDesc bgld{}; bgld.entries = std::span(entries, 1); + if (m_device->createBindGroupLayout(bgld, m_bgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Bind group (bind the whole buffer, dynamic offset selects the slice). + rhi::BindGroupEntry bgEntries[1] = { rhi::BindGroupEntry::bufferEntry(m_ub, 0, 256) }; + rhi::BindGroupDesc bgd{}; bgd.layout = m_bgl; bgd.entries = std::span(bgEntries, 1); + if (m_device->createBindGroup(bgd, m_bg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Pipeline layout. + rhi::BindGroupLayout* sets[1] = { m_bgl }; + rhi::PipelineLayoutDesc pld{}; pld.bindGroupLayouts = std::span(sets, 1); + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Pipeline with blend constant support. + rhi::VertexAttribute attrs[1] = { { rhi::VertexFormat::Float32x3, 0, 0 } }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 12; vbl.attributes = std::span(attrs, 1); + + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); ct.writeMask = rhi::ColorWriteMask::All; + ct.blend = rhi::BlendState{ + { rhi::BlendFactor::Constant, rhi::BlendFactor::OneMinusConstant, rhi::BlendOperation::Add }, + { rhi::BlendFactor::One, rhi::BlendFactor::Zero, rhi::BlendOperation::Add } + }; + + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void DynamicOffsetSample::updateUBO() { + void* mapped = m_ub->map(); + if (!mapped) return; + + // 4 objects at different positions with different colors. + ObjectData objs[4] = {}; + + // Red, top-left. + objs[0].tintColor[0] = 1.0f; objs[0].tintColor[1] = 0.2f; objs[0].tintColor[2] = 0.2f; objs[0].tintColor[3] = 1.0f; + objs[0].offsetScale[0] = -0.45f; objs[0].offsetScale[1] = 0.45f; objs[0].offsetScale[2] = 0.4f; objs[0].offsetScale[3] = 0.4f; + + // Green, top-right. + objs[1].tintColor[0] = 0.2f; objs[1].tintColor[1] = 1.0f; objs[1].tintColor[2] = 0.2f; objs[1].tintColor[3] = 1.0f; + objs[1].offsetScale[0] = 0.45f; objs[1].offsetScale[1] = 0.45f; objs[1].offsetScale[2] = 0.4f; objs[1].offsetScale[3] = 0.4f; + + // Blue, bottom-left. + objs[2].tintColor[0] = 0.2f; objs[2].tintColor[1] = 0.3f; objs[2].tintColor[2] = 1.0f; objs[2].tintColor[3] = 1.0f; + objs[2].offsetScale[0] = -0.45f; objs[2].offsetScale[1] = -0.45f; objs[2].offsetScale[2] = 0.4f; objs[2].offsetScale[3] = 0.4f; + + // Yellow, bottom-right. + objs[3].tintColor[0] = 1.0f; objs[3].tintColor[1] = 1.0f; objs[3].tintColor[2] = 0.2f; objs[3].tintColor[3] = 1.0f; + objs[3].offsetScale[0] = 0.45f; objs[3].offsetScale[1] = -0.45f; objs[3].offsetScale[2] = 0.4f; objs[3].offsetScale[3] = 0.4f; + + std::memcpy(mapped, objs, sizeof(objs)); + m_ub->unmap(); +} + +void DynamicOffsetSample::onRender() { + using draco::f32, draco::u32, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + + rhi::ColorAttachment ca{}; ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.08f, 0.08f, 0.12f, 1.0f); + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); + auto* rp = enc->beginRenderPass(rpd); + + rp->setPipeline(m_pipeline); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0, 1); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vb, 0); + rp->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + + // Animate blend constant: pulsing between full visibility and half. + f32 pulse = 0.5f + 0.5f * std::sin(m_totalTime * 2.0f); + rp->setBlendConstant(pulse, pulse, pulse, 1.0f); + + // Draw 4 objects, each at a different dynamic offset. + for (u32 i = 0; i < 4; i++) { + u32 off[1] = { i * 256 }; + rp->setBindGroup(0, m_bg, std::span(off, 1)); + rp->drawIndexed(6); + } + + rp->end(); + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void DynamicOffsetSample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); + if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); + if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_bg) m_device->destroyBindGroup(m_bg); + if (m_bgl) m_device->destroyBindGroupLayout(m_bgl); + if (m_ub) m_device->destroyBuffer(m_ub); + if (m_ib) m_device->destroyBuffer(m_ib); + if (m_vb) m_device->destroyBuffer(m_vb); + if (m_ps) m_device->destroyShaderModule(m_ps); + if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { DynamicOffsetSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample027_3DTexture/CMakeLists.txt b/Samples/cpp/RHI/Sample027_3DTexture/CMakeLists.txt new file mode 100644 index 00000000..8fd5b546 --- /dev/null +++ b/Samples/cpp/RHI/Sample027_3DTexture/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample027_3DTexture) diff --git a/Samples/cpp/RHI/Sample027_3DTexture/Main.cpp b/Samples/cpp/RHI/Sample027_3DTexture/Main.cpp new file mode 100644 index 00000000..3afd4464 --- /dev/null +++ b/Samples/cpp/RHI/Sample027_3DTexture/Main.cpp @@ -0,0 +1,379 @@ +#include +/// Demonstrates 3D textures and 1D textures. +/// Generates a 3D noise volume, renders slices animated over time. +/// Uses a 1D gradient LUT for color mapping. + +#include +#include +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class Texture3DSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample027 - 3D Texture & 1D LUT"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onShutdown() override; +private: + draco::Status createVolumeTexture(); + draco::Status createLUTTexture(); + + static constexpr const char8_t kShader[] = u8R"( + Texture3D gVolume : register(t0, space0); + Texture1D gLUT : register(t1, space0); + SamplerState gSampler : register(s0, space0); + + struct PushConstants + { + float SliceZ; + float Time; + float2 _pad; + }; + + [[vk::push_constant]] ConstantBuffer pc : register(b0, space1); + + struct PSInput + { + float4 Position : SV_POSITION; + float2 UV : TEXCOORD0; + }; + + PSInput VSMain(uint vertexID : SV_VertexID) + { + PSInput output; + float2 uv = float2((vertexID << 1) & 2, vertexID & 2); + output.Position = float4(uv * 2.0 - 1.0, 0.5, 1.0); + output.UV = uv; + return output; + } + + float4 PSMain(PSInput input) : SV_TARGET + { + // Sample 3D volume at current slice + float3 uvw = float3(input.UV, pc.SliceZ); + float density = gVolume.Sample(gSampler, uvw).r; + + // Map density through 1D LUT + float4 color = gLUT.Sample(gSampler, density); + return color; + } + )"; + + struct PushData { + float sliceZ; + float time; + float _pad0; + float _pad1; + }; + + static constexpr draco::u32 kVolumeSize = 32; + static constexpr draco::u32 kLUTSize = 64; + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule* m_vs = nullptr; + rhi::ShaderModule* m_ps = nullptr; + + // 3D volume texture + rhi::Texture* m_volumeTexture = nullptr; + rhi::TextureView* m_volumeView = nullptr; + + // 1D LUT texture + rhi::Texture* m_lutTexture = nullptr; + rhi::TextureView* m_lutView = nullptr; + + rhi::Sampler* m_sampler = nullptr; + rhi::BindGroupLayout* m_bgl = nullptr; + rhi::BindGroup* m_bg = nullptr; + rhi::PipelineLayout* m_pl = nullptr; + rhi::RenderPipeline* m_pipeline = nullptr; + + rhi::CommandPool* m_pool = nullptr; + rhi::Fence* m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +draco::Status Texture3DSample::onInit() { + using draco::Status, std::span, draco::u8, draco::u32; + + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"Vol3DVS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"Vol3DPS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (createVolumeTexture() != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (createLUTTexture() != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Sampler + { + rhi::SamplerDesc sd{}; + sd.minFilter = rhi::FilterMode::Linear; + sd.magFilter = rhi::FilterMode::Linear; + sd.addressU = rhi::AddressMode::Repeat; + sd.addressV = rhi::AddressMode::Repeat; + sd.addressW = rhi::AddressMode::Repeat; + sd.label = u8"VolSampler"; + if (m_device->createSampler(sd, m_sampler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // Bind group layout: 3D tex, 1D tex, sampler + { + rhi::BindGroupLayoutEntry entries[3] = { + rhi::BindGroupLayoutEntry::sampledTexture(0, rhi::ShaderStage::Fragment, rhi::TextureViewDimension::Texture3D), + rhi::BindGroupLayoutEntry::sampledTexture(1, rhi::ShaderStage::Fragment, rhi::TextureViewDimension::Texture1D), + rhi::BindGroupLayoutEntry::sampler(0, rhi::ShaderStage::Fragment) + }; + rhi::BindGroupLayoutDesc bgld{}; + bgld.entries = std::span(entries, 3); + bgld.label = u8"VolBGL"; + if (m_device->createBindGroupLayout(bgld, m_bgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // Bind group + { + rhi::BindGroupEntry entries[3] = { + rhi::BindGroupEntry::textureEntry(m_volumeView), + rhi::BindGroupEntry::textureEntry(m_lutView), + rhi::BindGroupEntry::samplerEntry(m_sampler) + }; + rhi::BindGroupDesc bgd{}; + bgd.layout = m_bgl; + bgd.entries = std::span(entries, 3); + bgd.label = u8"VolBG"; + if (m_device->createBindGroup(bgd, m_bg) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // Pipeline layout with push constants + { + rhi::BindGroupLayout* sets[1] = { m_bgl }; + rhi::PushConstantRange pcr{}; + pcr.stages = rhi::ShaderStage::Fragment; + pcr.offset = 0; + pcr.size = sizeof(PushData); + rhi::PushConstantRange pushRanges[1] = { pcr }; + rhi::PipelineLayoutDesc pld{}; + pld.bindGroupLayouts = std::span(sets, 1); + pld.pushConstantRanges = std::span(pushRanges, 1); + pld.label = u8"VolPL"; + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // Render pipeline (fullscreen triangle, no vertex input) + { + rhi::ColorTargetState ct{}; + ct.format = m_swapChain->format(); + rhi::RenderPipelineDesc rpd{}; + rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.fragment = rhi::FragmentState{}; + rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.primitive.topology = rhi::PrimitiveTopology::TriangleList; + rpd.label = u8"VolPipeline"; + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +draco::Status Texture3DSample::createVolumeTexture() { + using draco::Status, std::span, draco::u8, draco::u32; + + rhi::TextureDesc td{}; + td.dimension = rhi::TextureDimension::Texture3D; + td.format = rhi::TextureFormat::R8Unorm; + td.width = kVolumeSize; + td.height = kVolumeSize; + td.depth = kVolumeSize; + td.mipLevelCount = 1; + td.sampleCount = 1; + td.usage = rhi::TextureUsage::Sampled | rhi::TextureUsage::CopyDst; + td.label = u8"VolumeTex3D"; + if (m_device->createTexture(td, m_volumeTexture) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TextureViewDesc tvd{}; + tvd.format = rhi::TextureFormat::R8Unorm; + tvd.dimension = rhi::TextureViewDimension::Texture3D; + if (m_device->createTextureView(m_volumeTexture, tvd, m_volumeView) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Generate procedural 3D noise data + constexpr u32 dataSize = kVolumeSize * kVolumeSize * kVolumeSize; + u8 data[dataSize]; + + for (u32 z = 0; z < kVolumeSize; z++) { + for (u32 y = 0; y < kVolumeSize; y++) { + for (u32 x = 0; x < kVolumeSize; x++) { + float fx = static_cast(x) / static_cast(kVolumeSize); + float fy = static_cast(y) / static_cast(kVolumeSize); + float fz = static_cast(z) / static_cast(kVolumeSize); + + // Simple 3D pattern: spherical blobs + frequency pattern + float cx = fx - 0.5f, cy = fy - 0.5f, cz = fz - 0.5f; + float dist = std::sqrt(cx * cx + cy * cy + cz * cz); + float sphere = std::max(0.0f, 1.0f - dist * 3.0f); + float pattern = std::sin(fx * 12.0f) * std::sin(fy * 12.0f) * std::sin(fz * 12.0f); + float v = std::clamp(sphere + pattern * 0.3f, 0.0f, 1.0f); + + u32 idx = z * kVolumeSize * kVolumeSize + y * kVolumeSize + x; + data[idx] = static_cast(v * 255.0f); + } + } + } + + rhi::TransferBatch* batch = nullptr; + m_graphicsQueue->createTransferBatch(batch); + rhi::TextureDataLayout layout{}; + layout.bytesPerRow = kVolumeSize; + layout.rowsPerImage = kVolumeSize; + batch->writeTexture(m_volumeTexture, + std::span(data, dataSize), + layout, rhi::Extent3D{kVolumeSize, kVolumeSize, kVolumeSize}); + batch->submit(); + m_graphicsQueue->destroyTransferBatch(batch); + + return draco::ErrorCode::Ok; +} + +draco::Status Texture3DSample::createLUTTexture() { + using draco::Status, std::span, draco::u8, draco::u32; + + rhi::TextureDesc td{}; + td.dimension = rhi::TextureDimension::Texture1D; + td.format = rhi::TextureFormat::RGBA8UnormSrgb; + td.width = kLUTSize; + td.height = 1; + td.arrayLayerCount = 1; + td.mipLevelCount = 1; + td.sampleCount = 1; + td.usage = rhi::TextureUsage::Sampled | rhi::TextureUsage::CopyDst; + td.label = u8"LUTTex1D"; + if (m_device->createTexture(td, m_lutTexture) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TextureViewDesc tvd{}; + tvd.format = rhi::TextureFormat::RGBA8UnormSrgb; + tvd.dimension = rhi::TextureViewDimension::Texture1D; + if (m_device->createTextureView(m_lutTexture, tvd, m_lutView) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Generate gradient LUT: dark blue -> cyan -> green -> yellow -> red -> white + u8 data[kLUTSize * 4]; + for (u32 i = 0; i < kLUTSize; i++) { + float t = static_cast(i) / static_cast(kLUTSize - 1); + float r, g, b; + if (t < 0.2f) { + float s = t / 0.2f; + r = 0.05f; g = 0.05f + s * 0.4f; b = 0.3f + s * 0.5f; + } else if (t < 0.4f) { + float s = (t - 0.2f) / 0.2f; + r = 0.05f; g = 0.45f + s * 0.5f; b = 0.8f - s * 0.5f; + } else if (t < 0.6f) { + float s = (t - 0.4f) / 0.2f; + r = s * 0.8f; g = 0.95f; b = 0.3f - s * 0.3f; + } else if (t < 0.8f) { + float s = (t - 0.6f) / 0.2f; + r = 0.8f + s * 0.2f; g = 0.95f - s * 0.6f; b = 0.0f; + } else { + float s = (t - 0.8f) / 0.2f; + r = 1.0f; g = 0.35f + s * 0.65f; b = s * 0.8f; + } + + u32 idx = i * 4; + data[idx + 0] = static_cast(r * 255.0f); + data[idx + 1] = static_cast(g * 255.0f); + data[idx + 2] = static_cast(b * 255.0f); + data[idx + 3] = 255; + } + + rhi::TransferBatch* batch = nullptr; + m_graphicsQueue->createTransferBatch(batch); + rhi::TextureDataLayout layout{}; + layout.bytesPerRow = kLUTSize * 4; + layout.rowsPerImage = 1; + batch->writeTexture(m_lutTexture, + std::span(data, kLUTSize * 4), + layout, rhi::Extent3D{kLUTSize, 1, 1}); + batch->submit(); + m_graphicsQueue->destroyTransferBatch(batch); + + return draco::ErrorCode::Ok; +} + +void Texture3DSample::onRender() { + using draco::f32, std::span; + + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + + rhi::ColorAttachment ca{}; + ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; + ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.02f, 0.02f, 0.05f, 1.0f); + rhi::RenderPassDesc rpd{}; + rpd.colorAttachments.push_back(ca); + + auto* rp = enc->beginRenderPass(rpd); + + rp->setPipeline(m_pipeline); + rp->setBindGroup(0, m_bg); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0.0f, 1.0f); + rp->setScissor(0, 0, m_width, m_height); + + // Animate slice through 3D volume + float sliceZ = 0.5f + 0.5f * std::sin(m_totalTime * 0.5f); + PushData pc{}; + pc.sliceZ = sliceZ; + pc.time = m_totalTime; + rp->setPushConstants(rhi::ShaderStage::Fragment, 0, sizeof(PushData), &pc); + + rp->draw(3); // Fullscreen triangle + + rp->end(); + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + + rhi::CommandBuffer* cb = enc->finish(); + m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void Texture3DSample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); + if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); + if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_bg) m_device->destroyBindGroup(m_bg); + if (m_bgl) m_device->destroyBindGroupLayout(m_bgl); + if (m_sampler) m_device->destroySampler(m_sampler); + if (m_lutView) m_device->destroyTextureView(m_lutView); + if (m_lutTexture) m_device->destroyTexture(m_lutTexture); + if (m_volumeView) m_device->destroyTextureView(m_volumeView); + if (m_volumeTexture) m_device->destroyTexture(m_volumeTexture); + if (m_ps) m_device->destroyShaderModule(m_ps); + if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { Texture3DSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample028_ProceduralRT/CMakeLists.txt b/Samples/cpp/RHI/Sample028_ProceduralRT/CMakeLists.txt new file mode 100644 index 00000000..dabeca4f --- /dev/null +++ b/Samples/cpp/RHI/Sample028_ProceduralRT/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample028_ProceduralRT) diff --git a/Samples/cpp/RHI/Sample028_ProceduralRT/Main.cpp b/Samples/cpp/RHI/Sample028_ProceduralRT/Main.cpp new file mode 100644 index 00000000..3e193320 --- /dev/null +++ b/Samples/cpp/RHI/Sample028_ProceduralRT/Main.cpp @@ -0,0 +1,618 @@ +#include +/// Demonstrates procedural ray tracing geometry using AABBs. +/// Renders spheres via intersection shaders inside axis-aligned bounding boxes. +/// Tests GeometryType.AABBs, ProceduralHitGroup, IntersectionShaderIndex. + +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class ProceduralRTSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample028 - Procedural RT (AABB Spheres)"; } +protected: + rhi::DeviceFeatures requiredFeatures() const override { + rhi::DeviceFeatures f{}; + f.rayTracing = true; + return f; + } + draco::Status onInit() override; + void onRender() override; + void onResize(draco::u32 w, draco::u32 h) override; + void onShutdown() override; +private: + // Ray tracing shader library (compiled as lib_6_3). + // All RT entry points are in a single source compiled once as a library. + static constexpr const char8_t kRtShaderSource[] = u8R"( + [[vk::image_format("rgba8")]] RWTexture2D gOutput : register(u0, space0); + RaytracingAccelerationStructure gScene : register(t0, space0); + + struct RayPayload + { + float3 Color; + float HitT; + float2 UV; + float2 _pad; + }; + + struct SphereAttribs + { + float3 Normal; + float HitDist; + }; + + [shader("raygeneration")] + void RayGen() + { + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + float2 uv = (float2(launchIndex) + 0.5) / float2(launchDim); + float2 ndc = uv * 2.0 - 1.0; + ndc.y = -ndc.y; + float aspect = float(launchDim.x) / float(launchDim.y); + ndc.x *= aspect; + + RayDesc ray; + ray.Origin = float3(ndc.x * 2.0, ndc.y * 2.0, -3.0); + ray.Direction = float3(0.0, 0.0, 1.0); + ray.TMin = 0.001; + ray.TMax = 100.0; + + RayPayload payload; + payload.Color = float3(0.0, 0.0, 0.0); + payload.HitT = -1.0; + payload.UV = uv; + payload._pad = float2(0, 0); + + TraceRay(gScene, RAY_FLAG_FORCE_OPAQUE, 0xFF, 0, 0, 0, ray, payload); + + gOutput[launchIndex] = float4(payload.Color, 1.0); + } + + [shader("intersection")] + void SphereIntersection() + { + // AABB center is at origin of the geometry instance, radius 0.5 + float3 center = float3(0, 0, 0); + float radius = 0.45; + + float3 origin = ObjectRayOrigin(); + float3 dir = ObjectRayDirection(); + float3 oc = origin - center; + + float a = dot(dir, dir); + float b = 2.0 * dot(oc, dir); + float c = dot(oc, oc) - radius * radius; + float discriminant = b * b - 4.0 * a * c; + + if (discriminant >= 0.0) + { + float t = (-b - sqrt(discriminant)) / (2.0 * a); + if (t >= RayTMin() && t <= RayTCurrent()) + { + float3 hitPos = origin + t * dir; + float3 normal = normalize(hitPos - center); + + SphereAttribs attribs; + attribs.Normal = normal; + attribs.HitDist = t; + ReportHit(t, 0, attribs); + } + } + } + + [shader("closesthit")] + void ClosestHit(inout RayPayload payload, SphereAttribs attribs) + { + // Simple diffuse shading + float3 lightDir = normalize(float3(0.5, 1.0, -0.5)); + float3 normal = normalize(mul((float3x3)ObjectToWorld3x4(), attribs.Normal)); + float ndotl = max(0.0, dot(normal, lightDir)); + float ambient = 0.15; + + // Color based on instance index + uint instID = InstanceIndex(); + float3 baseColor; + if (instID == 0) baseColor = float3(1.0, 0.3, 0.3); + else if (instID == 1) baseColor = float3(0.3, 1.0, 0.3); + else if (instID == 2) baseColor = float3(0.3, 0.3, 1.0); + else baseColor = float3(1.0, 1.0, 0.3); + + payload.Color = baseColor * (ndotl + ambient); + payload.HitT = attribs.HitDist; + } + + [shader("miss")] + void Miss(inout RayPayload payload) + { + payload.Color = float3(0.05, 0.05, 0.1) + float3(0.0, 0.0, 0.15) * payload.UV.y; + payload.HitT = -1.0; + } + )"; + + static constexpr int kSphereCount = 4; + + shaders::Compiler* m_compiler = nullptr; + + // RT resources. + rhi::ShaderModule* m_rtShaderModule = nullptr; + rhi::RayTracingPipeline* m_rtPipeline = nullptr; + rhi::AccelStruct* m_blas = nullptr; + rhi::AccelStruct* m_tlas = nullptr; + rhi::Buffer* m_scratchBuffer = nullptr; + rhi::Buffer* m_aabbBuffer = nullptr; // AABB for BLAS. + rhi::Buffer* m_instanceBuffer = nullptr; // TLAS instance data. + rhi::Buffer* m_sbtBuffer = nullptr; // Shader binding table. + rhi::PipelineLayout* m_rtPipelineLayout = nullptr; + rhi::BindGroupLayout* m_rtBindGroupLayout = nullptr; + rhi::BindGroup* m_rtBindGroup = nullptr; + + // RT output texture. + rhi::Texture* m_outputTexture = nullptr; + rhi::TextureView* m_outputTextureView = nullptr; + rhi::ResourceState m_outputTextureState = rhi::ResourceState::Undefined; + + // SBT layout info (cached for traceRays). + draco::u32 m_sbtAlignedStride = 0; + + rhi::CommandPool* m_pool = nullptr; + rhi::Fence* m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +draco::Status ProceduralRTSample::onInit() { + using draco::Status, std::span; + + // ---- Check ray tracing support ---- + if (!m_device->features.rayTracing) { + std::fprintf(stderr, "ERROR: Ray tracing is not supported by this device/backend\n"); + return draco::ErrorCode::Unknown; + } + + std::printf("Ray tracing extension available:\n"); + std::printf(" shaderGroupHandleSize: %u\n", m_device->shaderGroupHandleSize); + std::printf(" shaderGroupHandleAlignment: %u\n", m_device->shaderGroupHandleAlignment); + std::printf(" shaderGroupBaseAlignment: %u\n", m_device->shaderGroupBaseAlignment); + + // ---- Shader compiler ---- + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // ---- Compile RT shader library (lib_6_3) ---- + if (sf::compileToModule(m_compiler, m_device, kRtShaderSource, shaders::ShaderStage::RayGen, + u8"", u8"ProcRTLib", u8"6_3", m_rtShaderModule) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "ERROR: RT shader library compilation failed\n"); + return draco::ErrorCode::Unknown; + } + + // ---- Command pool and fence ---- + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // ---- Create RT output texture (storage + copy source) ---- + { + rhi::TextureDesc td{}; + td.dimension = rhi::TextureDimension::Texture2D; + td.format = rhi::TextureFormat::RGBA8Unorm; + td.width = m_width; + td.height = m_height; + td.arrayLayerCount = 1; + td.mipLevelCount = 1; + td.sampleCount = 1; + td.usage = rhi::TextureUsage::Storage | rhi::TextureUsage::CopySrc; + td.label = u8"ProcRTOutput"; + if (m_device->createTexture(td, m_outputTexture) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TextureViewDesc tvd{}; + tvd.label = u8"ProcRTOutputView"; + if (m_device->createTextureView(m_outputTexture, tvd, m_outputTextureView) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // ---- AABB buffer: one AABB per sphere (6 floats: minX, minY, minZ, maxX, maxY, maxZ) ---- + // All AABBs are unit cubes [-0.5, 0.5] centered at origin - instance transforms position them + { + float aabbs[6] = { + -0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f + }; + draco::u32 aabbSize = sizeof(aabbs); + + rhi::BufferDesc bd{}; + bd.size = aabbSize; + bd.usage = rhi::BufferUsage::AccelStructInput | rhi::BufferUsage::CopyDst; + bd.memory = rhi::MemoryLocation::GpuOnly; + bd.label = u8"AABBBuffer"; + if (m_device->createBuffer(bd, m_aabbBuffer) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TransferBatch* transfer = nullptr; + if (m_graphicsQueue->createTransferBatch(transfer) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + transfer->writeBuffer(m_aabbBuffer, 0, + std::span(reinterpret_cast(aabbs), aabbSize)); + transfer->submit(); + m_graphicsQueue->destroyTransferBatch(transfer); + } + + // ---- Create acceleration structures ---- + { + rhi::AccelStructDesc asd{}; + asd.type = rhi::AccelStructType::BottomLevel; + asd.label = u8"ProcBLAS"; + if (m_device->createAccelStruct(asd, m_blas) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + asd.type = rhi::AccelStructType::TopLevel; + asd.label = u8"ProcTLAS"; + if (m_device->createAccelStruct(asd, m_tlas) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // ---- Create scratch buffer (256 KB) ---- + { + rhi::BufferDesc bd{}; + bd.size = 256 * 1024; + bd.usage = rhi::BufferUsage::AccelStructScratch; + bd.memory = rhi::MemoryLocation::GpuOnly; + bd.label = u8"ProcScratch"; + if (m_device->createBuffer(bd, m_scratchBuffer) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // ---- Create instance buffer (64 bytes * 4 spheres) ---- + { + rhi::BufferDesc bd{}; + bd.size = 64 * kSphereCount; + bd.usage = rhi::BufferUsage::AccelStructInput; + bd.memory = rhi::MemoryLocation::CpuToGpu; + bd.label = u8"ProcInstances"; + if (m_device->createBuffer(bd, m_instanceBuffer) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // Fill instance data: 4 spheres at different positions. + { + auto* ptr = static_cast(m_instanceBuffer->map()); + if (!ptr) { std::fprintf(stderr, "ERROR: Failed to map instance buffer\n"); return draco::ErrorCode::Unknown; } + + float positions[4][3] = { + {-1.0f, 0.5f, 0.0f}, + { 1.0f, 0.5f, 0.0f}, + {-1.0f, -0.5f, 0.0f}, + { 1.0f, -0.5f, 0.0f} + }; + + for (int i = 0; i < kSphereCount; i++) { + draco::u8* inst = ptr + i * 64; + std::memset(inst, 0, 64); + + // 3x4 row-major transform. + auto* xform = reinterpret_cast(inst); + xform[0] = 1.0f; xform[3] = positions[i][0]; + xform[5] = 1.0f; xform[7] = positions[i][1]; + xform[10] = 1.0f; xform[11] = positions[i][2]; + + // instanceCustomIndex (24 bit) + mask (8 bit) at offset 48. + inst[48] = 0; inst[49] = 0; inst[50] = 0; + inst[51] = 0xFF; // mask + + // SBT offset (24 bit) + flags (8 bit) at offset 52. + inst[52] = 0; inst[53] = 0; inst[54] = 0; + inst[55] = 0x04; // VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR + + // accelerationStructureReference at offset 56. + *reinterpret_cast(inst + 56) = m_blas->deviceAddress(); + } + + m_instanceBuffer->unmap(); + } + + // ---- Build BLAS from AABB geometry, then TLAS ---- + { + rhi::CommandEncoder* encoder = nullptr; + if (m_pool->createEncoder(encoder) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (auto* rtEnc = encoder->asRayTracingExt()) { + // Build BLAS from AABB geometry. + rhi::AccelStructGeometryAABBs aabbGeom{}; + aabbGeom.aabbBuffer = m_aabbBuffer; + aabbGeom.offset = 0; + aabbGeom.count = 1; + aabbGeom.stride = 24; + aabbGeom.flags = rhi::GeometryFlags::Opaque; + + rtEnc->buildBottomLevelAccelStruct(m_blas, m_scratchBuffer, 0, + std::span{}, + std::span(&aabbGeom, 1)); + + // Barrier between BLAS and TLAS build. + rhi::MemoryBarrier mb{}; + mb.oldState = rhi::ResourceState::AccelStructWrite; + mb.newState = rhi::ResourceState::AccelStructRead; + rhi::BarrierGroup bg{}; + bg.memoryBarriers = std::span(&mb, 1); + encoder->barrier(bg); + + // Build TLAS from instances. + rtEnc->buildTopLevelAccelStruct(m_tlas, m_scratchBuffer, 0, + m_instanceBuffer, 0, kSphereCount); + } else { + std::fprintf(stderr, "ERROR: Command encoder does not support ray tracing\n"); + m_pool->destroyEncoder(encoder); + return draco::ErrorCode::Unknown; + } + + rhi::CommandBuffer* cb = encoder->finish(); + m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + + // Wait for build to complete. + m_fence->wait(m_fenceVal); + m_pool->reset(); + m_pool->destroyEncoder(encoder); + } + + std::printf("Procedural BLAS/TLAS built.\n"); + + // ---- Create RT bind group layout and bind group ---- + { + rhi::BindGroupLayoutEntry layoutEntries[2]{}; + + // Storage texture (read-write). + layoutEntries[0].binding = 0; + layoutEntries[0].visibility = rhi::ShaderStage::RayGen; + layoutEntries[0].type = rhi::BindingType::StorageTextureReadWrite; + layoutEntries[0].storageTextureFormat = rhi::TextureFormat::RGBA8Unorm; + layoutEntries[0].count = 1; + + // Acceleration structure. + layoutEntries[1].binding = 0; + layoutEntries[1].visibility = rhi::ShaderStage::RayGen | rhi::ShaderStage::ClosestHit; + layoutEntries[1].type = rhi::BindingType::AccelerationStructure; + layoutEntries[1].count = 1; + + rhi::BindGroupLayoutDesc bgld{}; + bgld.entries = std::span(layoutEntries, 2); + bgld.label = u8"ProcRTBGL"; + if (m_device->createBindGroupLayout(bgld, m_rtBindGroupLayout) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Create bind group with output texture + TLAS. + rhi::BindGroupEntry bgEntries[2]{}; + bgEntries[0] = rhi::BindGroupEntry::textureEntry(m_outputTextureView); + bgEntries[1] = rhi::BindGroupEntry::accelStructEntry(m_tlas); + + rhi::BindGroupDesc bgd{}; + bgd.layout = m_rtBindGroupLayout; + bgd.entries = std::span(bgEntries, 2); + bgd.label = u8"ProcRTBG"; + if (m_device->createBindGroup(bgd, m_rtBindGroup) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + } + + // ---- Create RT pipeline layout and pipeline ---- + { + rhi::BindGroupLayout* bglArr[1] = { m_rtBindGroupLayout }; + + rhi::PipelineLayoutDesc pld{}; + pld.bindGroupLayouts = std::span(bglArr, 1); + pld.label = u8"ProcRTPL"; + if (m_device->createPipelineLayout(pld, m_rtPipelineLayout) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // 4 stages: RayGen, Intersection, ClosestHit, Miss - all from the same shader module. + rhi::ProgrammableStage stages[4]{}; + stages[0] = { m_rtShaderModule, u8"RayGen", rhi::ShaderStage::RayGen }; + stages[1] = { m_rtShaderModule, u8"SphereIntersection", rhi::ShaderStage::Intersection }; + stages[2] = { m_rtShaderModule, u8"ClosestHit", rhi::ShaderStage::ClosestHit }; + stages[3] = { m_rtShaderModule, u8"Miss", rhi::ShaderStage::Miss }; + + // 3 groups: raygen (general), procedural hit group (intersection + closest hit), miss (general). + rhi::RayTracingShaderGroup groups[3]{}; + groups[0].type = rhi::RayTracingShaderGroup::Type::General; + groups[0].generalShaderIndex = 0; + + groups[1].type = rhi::RayTracingShaderGroup::Type::ProceduralHitGroup; + groups[1].intersectionShaderIndex = 1; + groups[1].closestHitShaderIndex = 2; + + groups[2].type = rhi::RayTracingShaderGroup::Type::General; + groups[2].generalShaderIndex = 3; + + rhi::RayTracingPipelineDesc rtpd{}; + rtpd.layout = m_rtPipelineLayout; + rtpd.stages = std::span(stages, 4); + rtpd.groups = std::span(groups, 3); + rtpd.maxRecursionDepth = 1; + rtpd.maxPayloadSize = 32; // RayPayload: float3 + float + float2 + float2 + rtpd.maxAttributeSize = 16; // SphereAttribs: float3 Normal + float HitDist + rtpd.label = u8"ProcRTPipeline"; + if (m_device->createRayTracingPipeline(rtpd, m_rtPipeline) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "ERROR: CreateRayTracingPipeline failed\n"); + return draco::ErrorCode::Unknown; + } + } + + std::printf("Procedural RT pipeline created successfully.\n"); + + // ---- Build Shader Binding Table ---- + { + draco::u32 handleSize = m_device->shaderGroupHandleSize; + draco::u32 baseAlignment = m_device->shaderGroupBaseAlignment; + draco::u32 groupCount = 3; + + // Aligned handle stride (round up to base alignment). + m_sbtAlignedStride = (handleSize + baseAlignment - 1) & ~(baseAlignment - 1); + + // Get shader group handles. + draco::u8 handleData[128]; // Enough for 3 handles (max ~32 bytes each). + if (m_device->getShaderGroupHandles(m_rtPipeline, 0, groupCount, + std::span(handleData, handleSize * groupCount)) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "ERROR: getShaderGroupHandles failed\n"); + return draco::ErrorCode::Unknown; + } + + // Create SBT buffer: 3 entries, each aligned to baseAlignment. + draco::u64 sbtSize = static_cast(m_sbtAlignedStride) * groupCount; + rhi::BufferDesc sbd{}; + sbd.size = sbtSize; + sbd.usage = rhi::BufferUsage::ShaderBindingTable; + sbd.memory = rhi::MemoryLocation::CpuToGpu; + sbd.label = u8"ProcSBT"; + if (m_device->createBuffer(sbd, m_sbtBuffer) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Copy handles into SBT with proper alignment. + auto* sbtPtr = static_cast(m_sbtBuffer->map()); + if (!sbtPtr) { std::fprintf(stderr, "ERROR: Failed to map SBT buffer\n"); return draco::ErrorCode::Unknown; } + std::memset(sbtPtr, 0, static_cast(sbtSize)); + + for (draco::u32 i = 0; i < groupCount; i++) { + std::memcpy(sbtPtr + (i * m_sbtAlignedStride), + handleData + (i * handleSize), + handleSize); + } + m_sbtBuffer->unmap(); + + std::printf("SBT built: handleSize=%u, baseAlignment=%u, alignedStride=%u, totalSize=%llu\n", + handleSize, baseAlignment, m_sbtAlignedStride, static_cast(sbtSize)); + } + + std::printf("Procedural RT sample ready.\n"); + + return draco::ErrorCode::Ok; +} + +void ProceduralRTSample::onRender() { + using std::span; + + // Wait for previous frame. + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + + // Acquire next swap chain image. + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + // Reset and create encoder. + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + // ---- Transition output texture to ShaderWrite for TraceRays ---- + enc->transitionTexture(m_outputTexture, m_outputTextureState, rhi::ResourceState::ShaderWrite); + + // ---- Dispatch TraceRays ---- + if (auto* rtEnc = enc->asRayTracingExt()) { + rtEnc->setRayTracingPipeline(m_rtPipeline); + rtEnc->setBindGroup(0, m_rtBindGroup); + + // SBT layout: [0] = raygen, [1] = hit, [2] = miss. + draco::u64 raygenOffset = 0; + draco::u64 hitOffset = static_cast(1) * m_sbtAlignedStride; + draco::u64 missOffset = static_cast(2) * m_sbtAlignedStride; + draco::u64 stride = static_cast(m_sbtAlignedStride); + + rtEnc->traceRays( + m_sbtBuffer, raygenOffset, stride, + m_sbtBuffer, missOffset, stride, + m_sbtBuffer, hitOffset, stride, + m_width, m_height); + } + + // ---- Transition: output texture ShaderWrite -> CopySrc, swapchain Present -> CopyDst ---- + { + rhi::TextureBarrier texBarriers[2]{}; + texBarriers[0].texture = m_outputTexture; + texBarriers[0].oldState = rhi::ResourceState::ShaderWrite; + texBarriers[0].newState = rhi::ResourceState::CopySrc; + + texBarriers[1].texture = m_swapChain->currentTexture(); + texBarriers[1].oldState = rhi::ResourceState::Present; + texBarriers[1].newState = rhi::ResourceState::CopyDst; + + rhi::BarrierGroup bg{}; + bg.textureBarriers = std::span(texBarriers, 2); + enc->barrier(bg); + } + + // ---- Copy RT output to swapchain ---- + m_outputTextureState = rhi::ResourceState::CopySrc; + { + rhi::TextureCopyRegion region{}; + region.extent = rhi::Extent3D{ m_width, m_height, 1 }; + enc->copyTextureToTexture(m_outputTexture, m_swapChain->currentTexture(), region); + } + + // ---- Transition swapchain CopyDst -> Present ---- + enc->transitionTexture(m_swapChain->currentTexture(), + rhi::ResourceState::CopyDst, rhi::ResourceState::Present); + + // Finish and submit. + rhi::CommandBuffer* cb = enc->finish(); + m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + + // Present. + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void ProceduralRTSample::onResize(draco::u32 w, draco::u32 h) { + using std::span; + if (m_fence) m_fence->wait(m_fenceVal, ~0ull); + + if (m_rtBindGroup) m_device->destroyBindGroup(m_rtBindGroup); + if (m_outputTextureView) m_device->destroyTextureView(m_outputTextureView); + if (m_outputTexture) m_device->destroyTexture(m_outputTexture); + m_rtBindGroup = nullptr; m_outputTextureView = nullptr; m_outputTexture = nullptr; + + rhi::TextureDesc td{}; + td.dimension = rhi::TextureDimension::Texture2D; td.format = rhi::TextureFormat::RGBA8Unorm; + td.width = w; td.height = h; td.arrayLayerCount = 1; td.mipLevelCount = 1; td.sampleCount = 1; + td.usage = rhi::TextureUsage::Storage | rhi::TextureUsage::CopySrc; td.label = u8"RTOutputTex"; + m_device->createTexture(td, m_outputTexture); + + rhi::TextureViewDesc tvd{}; tvd.label = u8"RTOutputView"; + m_device->createTextureView(m_outputTexture, tvd, m_outputTextureView); + + rhi::BindGroupEntry bgEntries[2]{}; + bgEntries[0] = rhi::BindGroupEntry::textureEntry(m_outputTextureView); + bgEntries[1] = rhi::BindGroupEntry::accelStructEntry(m_tlas); + rhi::BindGroupDesc bgd{}; bgd.layout = m_rtBindGroupLayout; + bgd.entries = std::span(bgEntries, 2); bgd.label = u8"RTBindGroup"; + m_device->createBindGroup(bgd, m_rtBindGroup); + + m_outputTextureState = rhi::ResourceState::Undefined; +} + +void ProceduralRTSample::onShutdown() { + if (m_fence) m_fence->wait(m_fenceVal, ~0ull); + + // RT bind group. + if (m_rtBindGroup) m_device->destroyBindGroup(m_rtBindGroup); + if (m_rtBindGroupLayout) m_device->destroyBindGroupLayout(m_rtBindGroupLayout); + + // RT output texture. + if (m_outputTextureView) m_device->destroyTextureView(m_outputTextureView); + if (m_outputTexture) m_device->destroyTexture(m_outputTexture); + + // RT resources. + if (m_sbtBuffer) m_device->destroyBuffer(m_sbtBuffer); + if (m_rtPipeline) m_device->destroyRayTracingPipeline(m_rtPipeline); + if (m_rtPipelineLayout) m_device->destroyPipelineLayout(m_rtPipelineLayout); + if (m_instanceBuffer) m_device->destroyBuffer(m_instanceBuffer); + if (m_scratchBuffer) m_device->destroyBuffer(m_scratchBuffer); + if (m_tlas) m_device->destroyAccelStruct(m_tlas); + if (m_blas) m_device->destroyAccelStruct(m_blas); + if (m_aabbBuffer) m_device->destroyBuffer(m_aabbBuffer); + if (m_rtShaderModule) m_device->destroyShaderModule(m_rtShaderModule); + + if (m_fence) m_device->destroyFence(m_fence); + if (m_pool) m_device->destroyCommandPool(m_pool); + + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { ProceduralRTSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample029_ResolveTexture/CMakeLists.txt b/Samples/cpp/RHI/Sample029_ResolveTexture/CMakeLists.txt new file mode 100644 index 00000000..327f0ab3 --- /dev/null +++ b/Samples/cpp/RHI/Sample029_ResolveTexture/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample029_ResolveTexture) diff --git a/Samples/cpp/RHI/Sample029_ResolveTexture/Main.cpp b/Samples/cpp/RHI/Sample029_ResolveTexture/Main.cpp new file mode 100644 index 00000000..c83039e9 --- /dev/null +++ b/Samples/cpp/RHI/Sample029_ResolveTexture/Main.cpp @@ -0,0 +1,210 @@ +#include +/// Demonstrates explicit MSAA resolve via CommandEncoder::resolveTexture(). +/// Unlike Sample010 (which uses ColorAttachment.resolveTarget for automatic +/// render-pass resolve), this sample renders to a 4x MSAA target and then +/// manually resolves to the swapchain using the resolveTexture command. + +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class ResolveTextureSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample029 - ResolveTexture (Explicit 4x MSAA)"; } +protected: + draco::Status onInit() override; + void onRender() override; + void onResize(draco::u32 w, draco::u32 h) override { recreateMsaaTarget(w, h); } + void onShutdown() override; +private: + static constexpr const char8_t kShader[] = u8R"( + struct VSInput { + float3 Position : TEXCOORD0; + float4 Color : TEXCOORD1; + }; + struct PSInput { + float4 Position : SV_POSITION; + float4 Color : COLOR0; + }; + PSInput VSMain(VSInput input) { + PSInput output; + output.Position = float4(input.Position, 1.0); + output.Color = input.Color; + return output; + } + float4 PSMain(PSInput input) : SV_TARGET { + return input.Color; + } + )"; + + // Star shape: great for showing MSAA on diagonal edges. + // Each vertex: float3 Position, float4 Color (stride = 28). + static constexpr float kVerts[] = { + // Center + 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, + // Outer tips (radius 0.7) + 0.0f, 0.7f, 0.0f, 1.0f, 0.2f, 0.2f, 1.0f, + 0.665f, 0.216f, 0.0f, 0.2f, 1.0f, 0.2f, 1.0f, + 0.411f, -0.566f, 0.0f, 0.2f, 0.3f, 1.0f, 1.0f, + -0.411f, -0.566f, 0.0f, 1.0f, 1.0f, 0.2f, 1.0f, + -0.665f, 0.216f, 0.0f, 1.0f, 0.2f, 1.0f, 1.0f, + // Inner notches (radius 0.25) + 0.238f, 0.327f, 0.0f, 0.8f, 0.7f, 0.5f, 1.0f, + 0.385f, -0.125f, 0.0f, 0.5f, 0.8f, 0.7f, 1.0f, + 0.0f, -0.405f, 0.0f, 0.5f, 0.5f, 0.9f, 1.0f, + -0.385f, -0.125f, 0.0f, 0.9f, 0.8f, 0.5f, 1.0f, + -0.238f, 0.327f, 0.0f, 0.9f, 0.5f, 0.8f, 1.0f, + }; + + static constexpr draco::u16 kIdx[] = { + 0, 1, 6, 0, 6, 2, + 0, 2, 7, 0, 7, 3, + 0, 3, 8, 0, 8, 4, + 0, 4, 9, 0, 9, 5, + 0, 5, 10, 0, 10, 1, + }; + + static constexpr draco::u32 kSamples = 4; + + void recreateMsaaTarget(draco::u32 w, draco::u32 h); + + shaders::Compiler* m_compiler = nullptr; + rhi::ShaderModule *m_vs = nullptr, *m_ps = nullptr; + rhi::Buffer *m_vb = nullptr, *m_ib = nullptr; + rhi::PipelineLayout *m_pl = nullptr; + rhi::RenderPipeline *m_pipeline = nullptr; + rhi::Texture *m_msaaTex = nullptr; + rhi::TextureView *m_msaaView = nullptr; + rhi::CommandPool *m_pool = nullptr; + rhi::Fence *m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +void ResolveTextureSample::recreateMsaaTarget(draco::u32 w, draco::u32 h) { + if (m_msaaView) { m_device->destroyTextureView(m_msaaView); m_msaaView = nullptr; } + if (m_msaaTex) { m_device->destroyTexture(m_msaaTex); m_msaaTex = nullptr; } + // Need RenderTarget (to draw into) and CopySrc (source for resolveTexture). + rhi::TextureDesc td{}; + td.format = m_swapChain->format(); td.width = w; td.height = h; td.sampleCount = kSamples; + td.usage = rhi::TextureUsage::RenderTarget | rhi::TextureUsage::CopySrc; + td.label = u8"MsaaRT"; + m_device->createTexture(td, m_msaaTex); + rhi::TextureViewDesc tvd{}; tvd.format = m_swapChain->format(); tvd.mipLevelCount = 1; tvd.arrayLayerCount = 1; + m_device->createTextureView(m_msaaTex, tvd, m_msaaView); +} + +draco::Status ResolveTextureSample::onInit() { + using draco::Status, std::span, draco::u8; + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Vertex, u8"VSMain", u8"ResolveVS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShader, shaders::ShaderStage::Fragment, u8"PSMain", u8"ResolvePS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // Vertex and index buffers. + rhi::BufferDesc vbd{}; vbd.size = sizeof(kVerts); vbd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; vbd.memory = rhi::MemoryLocation::GpuOnly; vbd.label = u8"ResolveVB"; + if (m_device->createBuffer(vbd, m_vb) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::BufferDesc ibd{}; ibd.size = sizeof(kIdx); ibd.usage = rhi::BufferUsage::Index | rhi::BufferUsage::CopyDst; ibd.memory = rhi::MemoryLocation::GpuOnly; ibd.label = u8"ResolveIB"; + if (m_device->createBuffer(ibd, m_ib) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TransferBatch* batch = nullptr; m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vb, 0, std::span(reinterpret_cast(kVerts), sizeof(kVerts))); + batch->writeBuffer(m_ib, 0, std::span(reinterpret_cast(kIdx), sizeof(kIdx))); + batch->submit(); m_graphicsQueue->destroyTransferBatch(batch); + + // Pipeline layout (no bind groups). + rhi::PipelineLayoutDesc pld{}; pld.label = u8"ResolvePL"; + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + // MSAA render target. + recreateMsaaTarget(m_width, m_height); + + // Render pipeline with multisample count = 4. + rhi::VertexAttribute attrs[2] = { + { rhi::VertexFormat::Float32x3, 0, 0 }, + { rhi::VertexFormat::Float32x4, 12, 1 }, + }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 28; vbl.attributes = std::span(attrs, 2); + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); + + rhi::RenderPipelineDesc rpd{}; rpd.layout = m_pl; rpd.label = u8"ResolvePipeline"; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.multisample.count = kSamples; + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void ResolveTextureSample::onRender() { + using draco::f32, std::span; + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + // === Step 1: Render star into MSAA texture === + enc->transitionTexture(m_msaaTex, rhi::ResourceState::CopySrc, rhi::ResourceState::RenderTarget); + + rhi::ColorAttachment ca{}; + ca.view = m_msaaView; + ca.resolveTarget = nullptr; // No auto-resolve -- we do it manually. + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.08f, 0.08f, 0.12f, 1.0f); + rhi::RenderPassDesc rpd{}; rpd.colorAttachments.push_back(ca); + auto* rp = enc->beginRenderPass(rpd); + rp->setPipeline(m_pipeline); + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0, 1); + rp->setScissor(0, 0, m_width, m_height); + rp->setVertexBuffer(0, m_vb, 0); + rp->setIndexBuffer(m_ib, rhi::IndexFormat::UInt16, 0); + rp->drawIndexed(30); + rp->end(); + + // === Step 2: Transition for resolve === + // MSAA texture: RenderTarget -> CopySrc + enc->transitionTexture(m_msaaTex, rhi::ResourceState::RenderTarget, rhi::ResourceState::CopySrc); + // Swapchain: Present -> CopyDst + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Present, rhi::ResourceState::CopyDst); + + // === Step 3: Explicit resolve MSAA -> swapchain === + enc->resolveTexture(m_msaaTex, m_swapChain->currentTexture()); + + // === Step 4: Transition swapchain back to Present === + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::CopyDst, rhi::ResourceState::Present); + + rhi::CommandBuffer* cb = enc->finish(); m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + m_swapChain->present(m_graphicsQueue); m_pool->destroyEncoder(enc); +} + +void ResolveTextureSample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); + if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); + if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_msaaView) m_device->destroyTextureView(m_msaaView); + if (m_msaaTex) m_device->destroyTexture(m_msaaTex); + if (m_ps) m_device->destroyShaderModule(m_ps); + if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_ib) m_device->destroyBuffer(m_ib); + if (m_vb) m_device->destroyBuffer(m_vb); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { ResolveTextureSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Sample030_RenderBundles/CMakeLists.txt b/Samples/cpp/RHI/Sample030_RenderBundles/CMakeLists.txt new file mode 100644 index 00000000..ceb06d8f --- /dev/null +++ b/Samples/cpp/RHI/Sample030_RenderBundles/CMakeLists.txt @@ -0,0 +1 @@ +draco_add_rhi_sample(Sample030_RenderBundles) diff --git a/Samples/cpp/RHI/Sample030_RenderBundles/Main.cpp b/Samples/cpp/RHI/Sample030_RenderBundles/Main.cpp new file mode 100644 index 00000000..5c374068 --- /dev/null +++ b/Samples/cpp/RHI/Sample030_RenderBundles/Main.cpp @@ -0,0 +1,174 @@ +#include +/// Sample030 - Render Bundles. Records the triangle's draw commands into a render bundle +/// (a reusable, off-thread-recordable command sequence) and replays it into the frame's render +/// pass via ExecuteBundles. The pass is begun with RenderPassContents::SecondaryCommandBuffers. +/// Exercises the RHI render-bundle path (Vulkan secondary command buffers / DX12 bundles). + +#include +#include +#include +#include + +import core; +import rhi; +import shaders; +import samples.rhi.framework; +import rhi.vk; + +namespace sf = draco::samples::framework; +namespace rhi = draco::rhi; +namespace shaders = draco::shaders; + +class RenderBundlesSample : public sf::SampleApp { +public: + using sf::SampleApp::SampleApp; + std::u8string_view title() const override { return u8"Sample030 - Render Bundles"; } + +protected: + draco::Status onInit() override; + void onRender() override; + void onShutdown() override; + +private: + static constexpr const char8_t kShaderSource[] = u8R"( + struct VSInput { float3 Position : TEXCOORD0; float3 Color : TEXCOORD1; }; + struct PSInput { float4 Position : SV_POSITION; float3 Color : TEXCOORD0; }; + PSInput VSMain(VSInput input) { + PSInput output; + output.Position = float4(input.Position, 1.0); + output.Color = input.Color; + return output; + } + float4 PSMain(PSInput input) : SV_TARGET { return float4(input.Color, 1.0); } + )"; + + static constexpr float kVertexData[] = { + 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, + 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, + -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, + }; + + shaders::Compiler* m_compiler = nullptr; + rhi::Buffer* m_vertexBuf = nullptr; + rhi::ShaderModule* m_vs = nullptr; + rhi::ShaderModule* m_ps = nullptr; + rhi::BindGroupLayout* m_bgl = nullptr; + rhi::PipelineLayout* m_pl = nullptr; + rhi::RenderPipeline* m_pipeline = nullptr; + rhi::CommandPool* m_pool = nullptr; + rhi::Fence* m_fence = nullptr; + draco::u64 m_fenceVal = 0; +}; + +draco::Status RenderBundlesSample::onInit() { + if (shaders::createCompiler(shaders::CompilerDesc{}, m_compiler) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShaderSource, shaders::ShaderStage::Vertex, u8"VSMain", u8"BundleVS", m_vs) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (sf::compileToModule(m_compiler, m_device, kShaderSource, shaders::ShaderStage::Fragment, u8"PSMain", u8"BundlePS", m_ps) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::BufferDesc bd{}; bd.size = sizeof(kVertexData); bd.usage = rhi::BufferUsage::Vertex | rhi::BufferUsage::CopyDst; + bd.memory = rhi::MemoryLocation::GpuOnly; bd.label = u8"BundleVB"; + if (m_device->createBuffer(bd, m_vertexBuf) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::TransferBatch* batch = nullptr; + m_graphicsQueue->createTransferBatch(batch); + batch->writeBuffer(m_vertexBuf, 0, std::span(reinterpret_cast(kVertexData), sizeof(kVertexData))); + batch->submit(); + m_graphicsQueue->destroyTransferBatch(batch); + + rhi::BindGroupLayoutDesc bglDesc{}; bglDesc.label = u8"EmptyBGL"; + if (m_device->createBindGroupLayout(bglDesc, m_bgl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + rhi::PipelineLayoutDesc pld{}; + rhi::BindGroupLayout* sets[1] = { m_bgl }; + pld.bindGroupLayouts = std::span(sets, 1); + pld.label = u8"BundlePL"; + if (m_device->createPipelineLayout(pld, m_pl) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + rhi::VertexAttribute attrs[2] = { { rhi::VertexFormat::Float32x3, 0, 0 }, { rhi::VertexFormat::Float32x3, 12, 1 } }; + rhi::VertexBufferLayout vbl{}; vbl.stride = 24; vbl.attributes = std::span(attrs, 2); + rhi::ColorTargetState ct{}; ct.format = m_swapChain->format(); ct.writeMask = rhi::ColorWriteMask::All; + + rhi::RenderPipelineDesc rpd{}; + rpd.layout = m_pl; + rpd.vertex.shader = { m_vs, u8"VSMain", rhi::ShaderStage::Vertex }; + rpd.vertex.buffers = std::span(&vbl, 1); + rpd.fragment = rhi::FragmentState{}; + rpd.fragment->shader = { m_ps, u8"PSMain", rhi::ShaderStage::Fragment }; + rpd.fragment->targets = std::span(&ct, 1); + rpd.primitive.topology = rhi::PrimitiveTopology::TriangleList; + rpd.label = u8"BundlePipeline"; + if (m_device->createRenderPipeline(rpd, m_pipeline) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + + if (m_device->createCommandPool(rhi::QueueType::Graphics, m_pool) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + if (m_device->createFence(0, m_fence) != draco::ErrorCode::Ok) return draco::ErrorCode::Unknown; + return draco::ErrorCode::Ok; +} + +void RenderBundlesSample::onRender() { + if (m_fenceVal > 0) m_fence->wait(m_fenceVal, ~0ull); + if (m_swapChain->acquireNextImage() != draco::ErrorCode::Ok) return; + + m_pool->reset(); + rhi::CommandEncoder* enc = nullptr; + if (m_pool->createEncoder(enc) != draco::ErrorCode::Ok || !enc) return; + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::Undefined, rhi::ResourceState::RenderTarget); + + // Record the triangle's draw into a reusable bundle (could be done off-thread). + rhi::RenderBundleDesc bdesc{}; + bdesc.colorFormats[0] = m_swapChain->format(); + bdesc.colorFormatCount = 1; + bdesc.width = m_width; + bdesc.height = m_height; + bdesc.label = u8"TriangleBundle"; + rhi::RenderBundleEncoder* be = enc->createRenderBundleEncoder(bdesc); + rhi::RenderBundle* bundle = nullptr; + if (be) { + be->setPipeline(m_pipeline); + be->setVertexBuffer(0, m_vertexBuf, 0); + be->draw(3); + bundle = be->finish(); + } + + rhi::ColorAttachment ca{}; + ca.view = m_swapChain->currentTextureView(); + ca.loadOp = rhi::LoadOp::Clear; ca.storeOp = rhi::StoreOp::Store; + ca.clearValue = rhi::ClearColor(0.1f, 0.1f, 0.15f, 1.0f); + + rhi::RenderPassDesc rpd{}; + rpd.colorAttachments.push_back(ca); + rpd.contents = rhi::RenderPassContents::SecondaryCommandBuffers; // pass body is supplied by bundles + + auto* rp = enc->beginRenderPass(rpd); + // Viewport/scissor must be set on the parent pass - DX12 bundles inherit these. + rp->setViewport(0, 0, static_cast(m_width), static_cast(m_height), 0, 1); + rp->setScissor(0, 0, m_width, m_height); + if (bundle) { + rhi::RenderBundle* bundles[1] = { bundle }; + rp->executeBundles(std::span(bundles, 1)); + } + rp->end(); + + enc->transitionTexture(m_swapChain->currentTexture(), rhi::ResourceState::RenderTarget, rhi::ResourceState::Present); + + rhi::CommandBuffer* cb = enc->finish(); + m_fenceVal++; + rhi::CommandBuffer* cbs[1] = { cb }; + m_graphicsQueue->submit(std::span(cbs, 1), m_fence, m_fenceVal); + + m_swapChain->present(m_graphicsQueue); + m_pool->destroyEncoder(enc); +} + +void RenderBundlesSample::onShutdown() { + if (m_fence) m_device->destroyFence(m_fence); + if (m_pool) m_device->destroyCommandPool(m_pool); + if (m_pipeline) m_device->destroyRenderPipeline(m_pipeline); + if (m_pl) m_device->destroyPipelineLayout(m_pl); + if (m_bgl) m_device->destroyBindGroupLayout(m_bgl); + if (m_ps) m_device->destroyShaderModule(m_ps); + if (m_vs) m_device->destroyShaderModule(m_vs); + if (m_vertexBuf) m_device->destroyBuffer(m_vertexBuf); + if (m_compiler) { m_compiler->destroy(); delete m_compiler; } +} + +int main(int argc, char** argv) { RenderBundlesSample app; return app.run(argc, argv); } diff --git a/Samples/cpp/RHI/Smoketest/CMakeLists.txt b/Samples/cpp/RHI/Smoketest/CMakeLists.txt new file mode 100644 index 00000000..f353c61f --- /dev/null +++ b/Samples/cpp/RHI/Smoketest/CMakeLists.txt @@ -0,0 +1,2 @@ +draco_add_rhi_sample(Smoketest) +target_link_libraries(Smoketest PRIVATE Rendering_RHI_Null) diff --git a/Samples/cpp/RHI/Smoketest/Main.cpp b/Samples/cpp/RHI/Smoketest/Main.cpp new file mode 100644 index 00000000..bdbb8c9e --- /dev/null +++ b/Samples/cpp/RHI/Smoketest/Main.cpp @@ -0,0 +1,434 @@ +#include +// RHI Smoketest - low-level API tour exercising the VK backend directly. +// No framework dependency; useful for debugging the RHI itself. + +#include +#include +#include +#include +#include +#include + +import core; +import rhi; +import rhi.vk; +import rhi.null; +import rhi.validation; +import shell; +import shell.desktop; +#ifdef DRACONIC_HAS_SHADERS +import shaders; +#endif +#ifdef DRACONIC_HAS_DX12 +import rhi.dx12; +#endif + +static const char* adapterTypeStr(draco::rhi::AdapterType t) { + using draco::rhi::AdapterType; + switch (t) { + case AdapterType::DiscreteGpu: return "DiscreteGpu"; + case AdapterType::IntegratedGpu: return "IntegratedGpu"; + case AdapterType::Cpu: return "Cpu"; + default: return "Unknown"; + } +} + +int main(int /*argc*/, char** /*argv*/) { + using namespace draco; + using namespace draco::rhi; + namespace vk = draco::rhi::vk; + namespace shell = draco::shell; + + // ---- Shell: window via the desktop (SDL3) shell ---- + shell::WindowSettings ws{}; + ws.title = u8"Draconic Smoketest"; + ws.width = 1280; + ws.height = 720; + std::unique_ptr plat{ shell::createShell(ws), shell::destroyShell }; + if (!plat || plat->mainWindow() == nullptr) { + std::fprintf(stderr, "shell/window init failed\n"); + return 1; + } + shell::IWindow* window = plat->mainWindow(); + + const shell::NativeWindow nw = window->native(); + void* native = nw.window; + void* display = nw.display; + if (!native) { + std::fprintf(stderr, "no native handle on shell window\n"); + return 1; + } + + // ---- VK backend (wrapped in validation layer) ---- + vk::VkBackendDesc vkDesc{ .enableValidation = true }; + Backend* rawBackend = nullptr; + if (vk::createBackend(vkDesc, rawBackend) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "createBackend failed\n"); + return 1; + } + Backend* backend = validation::createValidatedBackend(rawBackend); + + auto adapters = backend->enumerateAdapters(); + if (adapters.size() == 0) { backend->destroy(); return 1; } + + // Adapters are enumerated best-GPU-first (see Backend::enumerateAdapters). + Adapter* chosen = adapters[0]; + auto adapterInfo = chosen->info(); + const std::u8string adapterName = std::u8string(adapterInfo.name); + std::printf("adapter: %s (%s)\n", reinterpret_cast(adapterName.c_str()), adapterTypeStr(adapterInfo.type)); + + DeviceDesc dd{}; + dd.graphicsQueueCount = 1; + dd.computeQueueCount = 1; + dd.transferQueueCount = 1; + dd.requiredFeatures.meshShaders = adapterInfo.supportedFeatures.meshShaders; + Device* device = nullptr; + if (chosen->createDevice(dd, device) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "createDevice failed\n"); + backend->destroy(); return 1; + } + + // ---- Surface + swap chain ---- + Surface* surface = nullptr; + if (backend->createSurface(native, display, surface) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "createSurface failed\n"); + device->destroy(); return 1; + } + std::printf("surface created\n"); + + SwapChainDesc sd{}; + sd.width = 1280; sd.height = 720; + sd.format = TextureFormat::BGRA8UnormSrgb; + sd.presentMode = PresentMode::Fifo; + sd.bufferCount = 2; + sd.label = u8"main"; + SwapChain* swap = nullptr; + if (device->createSwapChain(surface, sd, swap) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "createSwapChain failed\n"); + device->destroySurface(surface); + device->destroy(); return 1; + } + std::printf("swap chain: %ux%u, bufferCount=%u\n", swap->width(), swap->height(), swap->bufferCount()); + + // ---- Buffer / Sampler / ShaderModule ---- + BufferDesc ubDesc{}; + ubDesc.size = 1024; + ubDesc.usage = BufferUsage::Uniform | BufferUsage::CopyDst; + ubDesc.memory = MemoryLocation::CpuToGpu; + ubDesc.label = u8"smoketest_uniform"; + Buffer* ub = nullptr; + if (device->createBuffer(ubDesc, ub) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "createBuffer failed\n"); + } else { + void* mapped = ub->map(); + std::printf("uniform buffer: size=%llu mapped=%p\n", + static_cast(ub->getSize()), mapped); + if (mapped) std::memset(mapped, 0xAB, 16); + ub->unmap(); + } + + SamplerDesc sampDesc{}; + sampDesc.maxAnisotropy = 16; + sampDesc.label = u8"smoketest_sampler"; + Sampler* samp = nullptr; + if (device->createSampler(sampDesc, samp) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "createSampler failed\n"); + } else { + std::printf("sampler created (aniso=%u)\n", samp->desc.maxAnisotropy); + } + + // Minimal SPIR-V noop fragment shader. + static const u32 kSpvNoop[] = { + 0x07230203u, 0x00010000u, 0x00080001u, 0x00000005u, 0x00000000u, + 0x00020011u, 0x00000001u, + 0x0003000Eu, 0x00000000u, 0x00000001u, + 0x0005000Fu, 0x00000004u, 0x00000001u, 0x6E69616Du, 0x00000000u, + 0x00030010u, 0x00000001u, 0x00000007u, + 0x00020013u, 0x00000002u, + 0x00030021u, 0x00000003u, 0x00000002u, + 0x00050036u, 0x00000002u, 0x00000001u, 0x00000000u, 0x00000003u, + 0x000200F8u, 0x00000004u, + 0x000100FDu, 0x00010038u, + }; + ShaderModuleDesc shDesc{}; + shDesc.code = std::span(reinterpret_cast(kSpvNoop), sizeof(kSpvNoop)); + shDesc.label = u8"smoketest_noop_fs"; + ShaderModule* sh = nullptr; + if (device->createShaderModule(shDesc, sh) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "createShaderModule failed\n"); + } else { + std::printf("shader module created (%zu bytes)\n", shDesc.code.size()); + device->destroyShaderModule(sh); + } + + // ---- BindGroupLayout / PipelineLayout / PipelineCache ---- + BindGroupLayoutEntry layoutEntries[2] = { + BindGroupLayoutEntry::uniformBuffer(0, ShaderStage::Vertex), + BindGroupLayoutEntry::sampledTexture(1, ShaderStage::Fragment), + }; + BindGroupLayoutDesc bglDesc{}; + bglDesc.entries = std::span(layoutEntries, 2); + bglDesc.label = u8"smoketest_bgl"; + BindGroupLayout* bgl = nullptr; + if (device->createBindGroupLayout(bglDesc, bgl) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "createBindGroupLayout failed\n"); + } else { + std::printf("bind group layout: %zu entries\n", bgl->entries().size()); + } + + PipelineLayoutDesc plDesc{}; + BindGroupLayout* plSets[1] = { bgl }; + plDesc.bindGroupLayouts = std::span(plSets, 1); + plDesc.label = u8"smoketest_pl"; + PipelineLayout* pl = nullptr; + if (device->createPipelineLayout(plDesc, pl) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "createPipelineLayout failed\n"); + } else { + std::printf("pipeline layout created\n"); + } + + PipelineCacheDesc pcDesc{}; + pcDesc.label = u8"smoketest_pc"; + PipelineCache* pc = nullptr; + if (device->createPipelineCache(pcDesc, pc) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "createPipelineCache failed\n"); + } else { + std::printf("pipeline cache created (size=%u)\n", pc->getDataSize()); + } + + device->destroyPipelineCache(pc); + device->destroyPipelineLayout(pl); + device->destroyBindGroupLayout(bgl); + device->destroySampler(samp); + device->destroyBuffer(ub); + + // ---- Command pool + fence ---- + CommandPool* pool = nullptr; + if (device->createCommandPool(QueueType::Graphics, pool) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "createCommandPool failed\n"); + } else { + std::printf("command pool created\n"); + } + + Fence* fence = nullptr; + if (device->createFence(0, fence) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "createFence failed\n"); + } else { + std::printf("fence created (initial=%llu)\n", + static_cast(fence->completedValue())); + } + + QuerySetDesc qsDesc{}; + qsDesc.type = QueryType::Timestamp; + qsDesc.count = 16; + qsDesc.label = u8"smoketest_qs"; + QuerySet* qs = nullptr; + if (device->createQuerySet(qsDesc, qs) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "createQuerySet failed\n"); + } else { + std::printf("query set created (type=%u count=%u)\n", + static_cast(qs->type), qs->count); + } + + // ---- Show window + acquire/present 3 frames ---- + + Queue* gfx = device->getQueue(QueueType::Graphics); + u64 fenceValue = 0; + for (int frame = 0; frame < 3; ++frame) { + if (swap->acquireNextImage() != draco::ErrorCode::Ok) { + std::fprintf(stderr, "acquireNextImage failed on frame %d\n", frame); + break; + } + + CommandEncoder* enc = nullptr; + if (pool && pool->createEncoder(enc) == draco::ErrorCode::Ok && enc) { + enc->transitionTexture(swap->currentTexture(), ResourceState::Undefined, ResourceState::Present); + + CommandBuffer* cb = enc->finish(); + CommandBuffer* cbs[1] = { cb }; + fenceValue++; + gfx->submit(std::span(cbs, 1), fence, fenceValue); + pool->destroyEncoder(enc); + } + + swap->present(gfx); + if (fence) fence->wait(fenceValue); + if (pool) pool->reset(); + std::printf("frame %d acquired image_index=%u fence=%llu\n", + frame, swap->currentImageIndex(), + static_cast(fenceValue)); + } + + // ---- Extension probes ---- + if (device->features.meshShaders) { + std::printf("mesh shaders: supported\n"); + } else { + std::printf("mesh shaders: not available\n"); + } + if (device->features.rayTracing) { + std::printf("ray tracing: supported (handle_size=%u)\n", device->shaderGroupHandleSize); + } else { + std::printf("ray tracing: not available\n"); + } + + // ---- Cleanup ---- + // ---- DXC shader compilation test ---- +#ifdef DRACONIC_HAS_SHADERS + { + namespace shaders = draco::shaders; + shaders::Compiler* shaderc = nullptr; + if (shaders::createCompiler(shaders::CompilerDesc{}, shaderc) != draco::ErrorCode::Ok) { + std::fprintf(stderr, "shaders: createCompiler failed\n"); + } else { + static const char kHlsl[] = + "float4 main(float2 uv : TEXCOORD0) : SV_Target {\n" + " return float4(uv, 0.0, 1.0);\n" + "}\n"; + shaders::CompileOptions opts{}; + opts.shaderModel = u8"6_0"; + opts.optimizationLevel = 3; + shaders::CompileResult cr{}; + draco::Status r = shaderc->compile( + reinterpret_cast(kHlsl), sizeof(kHlsl) - 1, + shaders::ShaderStage::Fragment, u8"main", shaders::ShaderTarget::SPIRV, opts, cr); + if (r == draco::ErrorCode::Ok) { + u32 magic = cr.bytecodeSize >= 4 ? *reinterpret_cast(cr.bytecode) : 0u; + std::printf("HLSL->SPIR-V: %zu bytes, magic=0x%08x %s\n", + cr.bytecodeSize, magic, magic == 0x07230203u ? "(SPIR-V OK)" : "(unexpected)"); + } else { + std::fprintf(stderr, "shaders: compile failed: %s\n", cr.messages ? cr.messages : "(no messages)"); + } + shaderc->freeResult(cr); + shaderc->destroy(); + delete shaderc; + } + } +#endif + + if (qs) device->destroyQuerySet(qs); + if (fence) device->destroyFence(fence); + if (pool) device->destroyCommandPool(pool); + + device->waitIdle(); + device->destroySwapChain(swap); + device->destroySurface(surface); + device->destroy(); + backend->destroy(); + + + + // ---- Null backend exercise ---- + std::printf("\n=== Null Backend ===\n"); + { + namespace null = draco::rhi::null; + Backend* nullBackend = nullptr; + null::createNullBackend(nullBackend); + + auto nullAdapters = nullBackend->enumerateAdapters(); + std::printf("null adapters: %zu\n", nullAdapters.size()); + + Device* nullDevice = nullptr; + nullAdapters[0]->createDevice(DeviceDesc{}, nullDevice); + + Surface* nullSurface = nullptr; + nullBackend->createSurface(nullptr, nullSurface); + + SwapChainDesc nullSd{}; nullSd.width = 800; nullSd.height = 600; nullSd.bufferCount = 2; + SwapChain* nullSwap = nullptr; + nullDevice->createSwapChain(nullSurface, nullSd, nullSwap); + std::printf("null swap chain: %ux%u\n", nullSwap->width(), nullSwap->height()); + + Buffer* nullBuf = nullptr; + BufferDesc nbd{}; nbd.size = 256; nbd.usage = BufferUsage::Uniform; nbd.memory = MemoryLocation::CpuToGpu; + nullDevice->createBuffer(nbd, nullBuf); + void* mapped = nullBuf->map(); + std::printf("null buffer mapped: %s\n", mapped ? "yes" : "no"); + nullBuf->unmap(); + + CommandPool* nullPool = nullptr; + nullDevice->createCommandPool(QueueType::Graphics, nullPool); + CommandEncoder* nullEnc = nullptr; + nullPool->createEncoder(nullEnc); + nullSwap->acquireNextImage(); + nullEnc->transitionTexture(nullSwap->currentTexture(), ResourceState::Undefined, ResourceState::Present); + CommandBuffer* nullCb = nullEnc->finish(); + Fence* nullFence = nullptr; + nullDevice->createFence(0, nullFence); + CommandBuffer* nullCbs[1] = { nullCb }; + nullDevice->getQueue(QueueType::Graphics)->submit(std::span(nullCbs, 1), nullFence, 1); + nullFence->wait(1, ~0ull); + nullSwap->present(nullDevice->getQueue(QueueType::Graphics)); + std::printf("null frame completed\n"); + + nullPool->destroyEncoder(nullEnc); + nullDevice->destroyFence(nullFence); + nullDevice->destroyCommandPool(nullPool); + nullDevice->destroyBuffer(nullBuf); + nullDevice->destroySwapChain(nullSwap); + nullDevice->destroySurface(nullSurface); + nullDevice->destroy(); + nullBackend->destroy(); + std::printf("null backend: OK\n"); + } + + // ===== DX12 backend (Windows only) ===== +#ifdef DRACONIC_HAS_DX12 + { + namespace dx12 = draco::rhi::dx12; + std::printf("\n=== DX12 Backend ===\n"); + + Backend* dx12Backend = nullptr; + dx12::DxBackendDesc dx12Desc{}; + dx12Desc.enableValidation = true; + if (dx12::createDxBackend(dx12Desc, dx12Backend) != ErrorCode::Ok) { + std::printf("DX12 backend: FAILED to create\n"); + } else { + auto dx12Adapters = dx12Backend->enumerateAdapters(); + std::printf("DX12 adapters: %zu\n", dx12Adapters.size()); + for (usize i = 0; i < dx12Adapters.size(); ++i) { + AdapterInfo ai = dx12Adapters[i]->info(); + const std::u8string name8 = std::u8string(ai.name); + std::printf(" [%zu] %s (%s)\n", i, + reinterpret_cast(name8.c_str()), + adapterTypeStr(ai.type)); + } + + if (dx12Adapters.size() > 0) { + Device* dx12Device = nullptr; + DeviceDesc dx12dd{}; dx12dd.graphicsQueueCount = 1; + if (dx12Adapters[0]->createDevice(dx12dd, dx12Device) == ErrorCode::Ok) { + std::printf("DX12 device created (type=%d)\n", static_cast(dx12Device->type)); + + // Create and destroy a buffer. + Buffer* dx12Buf = nullptr; + BufferDesc bd{}; bd.size = 256; bd.usage = BufferUsage::Uniform; + bd.memory = MemoryLocation::CpuToGpu; + dx12Device->createBuffer(bd, dx12Buf); + if (dx12Buf) { + void* mapped = dx12Buf->map(); + std::printf("DX12 buffer mapped: %s\n", mapped ? "OK" : "FAIL"); + if (mapped) dx12Buf->unmap(); + dx12Device->destroyBuffer(dx12Buf); + } + + // Create and destroy a fence. + Fence* dx12Fence = nullptr; + dx12Device->createFence(0, dx12Fence); + if (dx12Fence) { + std::printf("DX12 fence completed value: %llu\n", + static_cast(dx12Fence->completedValue())); + dx12Device->destroyFence(dx12Fence); + } + + dx12Device->destroy(); + } + } + + dx12Backend->destroy(); + std::printf("DX12 backend: OK\n"); + } + } +#endif + + return 0; +} diff --git a/Samples/cpp/Render/CMakeLists.txt b/Samples/cpp/Render/CMakeLists.txt index 5b4652c0..64b8beab 100644 --- a/Samples/cpp/Render/CMakeLists.txt +++ b/Samples/cpp/Render/CMakeLists.txt @@ -1,22 +1,14 @@ # -# Rendering Sample +# Render Sample - shell-only placeholder (no renderer yet) # add_executable(RenderSample RenderSample.cpp) -# Everything the sample needs comes through Runtime (which links the single Shell -# library, whose SDL3 dependency propagates for the createShell() backend). -target_link_libraries(RenderSample - PRIVATE - Runtime - bgfx - bx - bimg -) +# The single Shell library owns windowing + input; its SDL3 dependency is PRIVATE, so +# force-load SDL below to satisfy the backend at final link. +target_link_libraries(RenderSample PRIVATE Shell) -# SDL is pulled in (statically) through the Shell library; force-load the whole archive -# at the executable so SDL's constructor-registered video/input drivers are not stripped -# by the linker. This stays on the final binary rather than on Shell, so it does not also -# force a full SDL copy into the intermediate Runtime shared library. +# SDL is statically linked via Shell; force-load the whole archive so its +# constructor-registered video/input drivers are not stripped by the linker. if(UNIX AND NOT APPLE) target_link_libraries(RenderSample PRIVATE Threads::Threads dl) target_link_options(RenderSample PRIVATE -Wl,--whole-archive $ -Wl,--no-whole-archive) @@ -25,19 +17,3 @@ elseif(APPLE) elseif(MSVC) target_link_options(RenderSample PRIVATE /WHOLEARCHIVE:$) endif() - -compile_shaders(RenderSample) - -set(TEST_IMAGE - "${CMAKE_SOURCE_DIR}/Docs/assets/draconic_logo_no_text.png" -) - -add_custom_command( - TARGET RenderSample POST_BUILD - - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${TEST_IMAGE} - $/test.png - - COMMENT "Copying test texture..." -) diff --git a/Samples/cpp/Render/RenderSample.cpp b/Samples/cpp/Render/RenderSample.cpp index 73431fa1..8596bff5 100644 --- a/Samples/cpp/Render/RenderSample.cpp +++ b/Samples/cpp/Render/RenderSample.cpp @@ -1,227 +1,32 @@ #include -#include #include +#include -#include - -import draconic; +import shell; +import shell.desktop; using namespace draco::shell; -namespace -{ - // Map the shell's window system onto the RHI's native window type so bgfx - // interprets the handles correctly (only Wayland needs explicit flagging). - draco::rendering::rhi::NativeWindowType toRhiWindowType(WindowSystem sys) - { - using RhiType = draco::rendering::rhi::NativeWindowType; - switch (sys) - { - case WindowSystem::Win32: return RhiType::Win32; - case WindowSystem::X11: return RhiType::X11; - case WindowSystem::Wayland: return RhiType::Wayland; - case WindowSystem::Cocoa: return RhiType::Cocoa; - default: return RhiType::Default; - } - } -} - int main(int, char*[]) { auto shell = createShell(WindowSettings{ - .title = u8"Draconic Engine Rendering Sample", + .title = u8"Draconic Engine Sample", .width = 1280, .height = 720, }); - IWindow* mainWindow = shell->mainWindow(); - if (mainWindow == nullptr) + if (shell->mainWindow() == nullptr) { std::println("Failed to create shell window"); - destroyShell(shell); - return -1; - } - - // Capture the mouse for camera control. - shell->input()->mouse()->setRelativeMode(true); - - const NativeWindow native = mainWindow->native(); - const draco::u32 startW = mainWindow->width(); - const draco::u32 startH = mainWindow->height(); - - if (!draco::rendering::rhi::init(native.display, native.window, toRhiWindowType(native.system), - static_cast(startW), static_cast(startH))) - { - std::println("RHI init failed"); - destroyShell(shell); return -1; } - draco::rendering::renderer::init(startW, startH); - - auto cube_mesh = draco::rendering::mesh::createCube(); - auto plane_mesh = draco::rendering::mesh::createPlane(5.0f); - auto sphere_mesh = draco::rendering::mesh::createSphere(24, 16); - auto cylinder_mesh = draco::rendering::mesh::createCylinder(24, 2.0f); - auto capsule_mesh = draco::rendering::mesh::createCapsule(24, 12, 2.0f); - - auto img = draco::core::io::loader::image::loadImage("test.png"); - - draco::rendering::rhi::TextureHandle tex = draco::rendering::rhi::InvalidTexture; - - if (img.isValid) { - tex = draco::rendering::rhi::createTexture(img.pixels.data(), img.width, img.height); - } - - auto s_texColor = draco::rendering::rhi::createUniform("s_texColor", draco::rendering::rhi::UniformType::Sampler); - - auto vs = draco::core::io::filesystem::loadBinary("vs.bin"); - auto fs = draco::core::io::filesystem::loadBinary("fs.bin"); - - auto vs_quad = draco::core::io::filesystem::loadBinary("vs_quad.bin"); - auto fs_quad = draco::core::io::filesystem::loadBinary("fs_quad.bin"); - - if (vs.empty() || fs.empty() || vs_quad.empty() || fs_quad.empty()) { - std::println("Shader load failed"); - draco::rendering::rhi::shutdown(); - destroyShell(shell); - return -1; - } - - auto vsh = draco::rendering::rhi::createShader(vs.data(), (draco::u32)vs.size()); - auto fsh = draco::rendering::rhi::createShader(fs.data(), (draco::u32)fs.size()); - - auto vsh_quad = draco::rendering::rhi::createShader(vs_quad.data(), (draco::u32)vs_quad.size()); - auto fsh_quad = draco::rendering::rhi::createShader(fs_quad.data(), (draco::u32)fs_quad.size()); - - auto pipeline = draco::rendering::rhi::createPipeline({vsh, fsh, draco::rendering::rhi::PipelineState::WriteRGB | draco::rendering::rhi::PipelineState::WriteAlpha | draco::rendering::rhi::PipelineState::MSAA, draco::rendering::rhi::BlendMode::None, draco::rendering::rhi::DepthTest::Less, draco::rendering::rhi::CullMode::CCW, true}); - - auto pipeline_quad = draco::rendering::rhi::createPipeline({vsh_quad, fsh_quad, draco::rendering::rhi::PipelineState::WriteRGB | draco::rendering::rhi::PipelineState::WriteAlpha | draco::rendering::rhi::PipelineState::MSAA, draco::rendering::rhi::BlendMode::Alpha, draco::rendering::rhi::DepthTest::None, draco::rendering::rhi::CullMode::None, true}); - - draco::rendering::quad::QuadRenderer quad_renderer; - quad_renderer.init(pipeline_quad); - - draco::scene::CameraController camera; - camera.init(); - - auto u_tint = draco::rendering::rhi::createUniform("u_tint", draco::rendering::rhi::UniformType::Vec4); - auto u_offset = draco::rendering::rhi::createUniform("u_offset", draco::rendering::rhi::UniformType::Vec4); - - draco::rendering::rhi::registerUniform(draco::rendering::rhi::hashUniform("u_tint"), u_tint); - - draco::rendering::rhi::registerUniform(draco::rendering::rhi::hashUniform("u_offset"), u_offset); - - draco::f32 tint[4] = {1,1,1,1}; - draco::f32 offset[4] = {0,0,0,0}; - - bool mouse_captured = true; - - draco::rendering::material::Material mat{}; - mat.pipeline = pipeline; - mat.texture = tex; - mat.sampler = s_texColor; - - mat.uniforms.push_back({.nameHash = draco::rendering::rhi::hashUniform("u_tint"), .data = tint, .count = 1}); - mat.uniforms.push_back({.nameHash = draco::rendering::rhi::hashUniform("u_offset"), .data = offset, .count = 1}); - - draco::scene::Scene scene; - - static constexpr draco::math::Transform tr; - - scene.renderables.push_back({cube_mesh, tr, mat}); - scene.renderables.push_back({plane_mesh, tr, mat}); - scene.renderables.push_back({sphere_mesh, tr, mat}); - scene.renderables.push_back({cylinder_mesh, tr, mat}); - scene.renderables.push_back({capsule_mesh, tr, mat}); - - scene.renderables[0].transform.setPosition(-12.0f, 0.0f, 0.0f); - scene.renderables[1].transform.setPosition(-6.0f, 0.0f, 0.0f); - scene.renderables[2].transform.setPosition(0.0f, 0.0f, 0.0f); - scene.renderables[3].transform.setPosition(6.0f, 0.0f, 0.0f); - scene.renderables[4].transform.setPosition(12.0f, 0.0f, 0.0f); - - scene.renderables[1].transform.setRotation(-bx::kPiHalf, 0.0f, 0.0f); - - using clock = std::chrono::steady_clock; - const auto startTime = clock::now(); - auto lastTime = startTime; - + // No renderer yet: pump the shell each frame until the window is closed. while (shell->isRunning()) { shell->processEvents(); - - const auto nowTime = clock::now(); - const draco::f32 dt = std::chrono::duration(nowTime - lastTime).count(); - lastTime = nowTime; - const draco::f32 elapsed = std::chrono::duration(nowTime - startTime).count(); - - IInputManager* input = shell->input(); - IKeyboard* kb = input->keyboard(); - IMouse* ms = input->mouse(); - - // Toggle mouse capture on Escape (edge-triggered). - if (kb->isKeyPressed(KeyCode::Escape)) - { - mouse_captured = !mouse_captured; - ms->setRelativeMode(mouse_captured); - } - - const draco::u32 w = mainWindow->width(); - const draco::u32 h = mainWindow->height(); - - if (w == 0 || h == 0 || mainWindow->isMinimized()) - { - continue; - } - - draco::rendering::rhi::resize((draco::u16)w, (draco::u16)h); - draco::rendering::renderer::resize((draco::u16)w, (draco::u16)h); - - draco::scene::CameraInput camInput{}; - camInput.mouseDx = ms->deltaX(); - camInput.mouseDy = ms->deltaY(); - camInput.moveForward = kb->isKeyDown(KeyCode::W); - camInput.moveBackward = kb->isKeyDown(KeyCode::S); - camInput.moveLeft = kb->isKeyDown(KeyCode::A); - camInput.moveRight = kb->isKeyDown(KeyCode::D); - camera.update(dt, camInput); - - auto cam = camera.getCamera(); - - draco::rendering::renderer::beginFrame(cam); - - for (const auto& renderable : scene.renderables) - { - draco::rendering::renderer::submitRenderable(renderable.transform, renderable.material, renderable.mesh); - } - - quad_renderer.begin(); - - static draco::f32 quad_base_x = 400.0f; - static draco::f32 quad_base_y = 300.0f; - - for (int i = 0; i < 50; i++) - { - draco::rendering::quad::QuadCommand q{}; - - q.texture = tex; - q.color = 0xffffffff; - q.x = quad_base_x + std::sin(elapsed + i) * 50.0f; - q.y = quad_base_y + i * 6.0f; - q.width = 50.0f; - q.height = 50.0f; - q.rotation = elapsed; - - quad_renderer.submit(q); - } - - draco::rendering::renderer::submitUI(quad_renderer); - - draco::rendering::renderer::endFrame(); + std::this_thread::sleep_for(std::chrono::milliseconds(16)); } - draco::rendering::rhi::shutdown(); - destroyShell(shell); // destroys the window and shuts SDL down - return 0; } diff --git a/cmake/Shaders.cmake b/cmake/Shaders.cmake deleted file mode 100644 index 1d638ed8..00000000 --- a/cmake/Shaders.cmake +++ /dev/null @@ -1,84 +0,0 @@ -include_guard(GLOBAL) - -set(SHADER_SRC_DIR "${CMAKE_SOURCE_DIR}/Engine/cpp/Runtime/Rendering/Shaders") -set(SHADER_BIN_DIR "${CMAKE_BINARY_DIR}") -set(BGFX_INCLUDE "${CMAKE_SOURCE_DIR}/Engine/cpp/ThirdParty/bgfx/src") - -function(compile_shaders TARGET_NAME) - file(MAKE_DIRECTORY ${SHADER_BIN_DIR}) - - if(APPLE) - set(VERTEX_PROFILE "metal") - set(FRAGMENT_PROFILE "metal") - set(SHADER_PLATFORM "osx") - elseif(WIN32) - set(VERTEX_PROFILE "s_5_0") - set(FRAGMENT_PROFILE "s_5_0") - set(SHADER_PLATFORM "windows") - else() - set(VERTEX_PROFILE "spirv") - set(FRAGMENT_PROFILE "spirv") - set(SHADER_PLATFORM "linux") - endif() - - set(VERTEX_INPUT "${SHADER_SRC_DIR}/vs.sc") - set(VERTEX_OUTPUT "${SHADER_BIN_DIR}/vs.bin") - - set(FRAGMENT_INPUT "${SHADER_SRC_DIR}/fs.sc") - set(FRAGMENT_OUTPUT "${SHADER_BIN_DIR}/fs.bin") - - set(QUAD_VERTEX_INPUT "${SHADER_SRC_DIR}/vs_quad.sc") - set(QUAD_VERTEX_OUTPUT "${SHADER_BIN_DIR}/vs_quad.bin") - - set(QUAD_FRAGMENT_INPUT "${SHADER_SRC_DIR}/fs_quad.sc") - set(QUAD_FRAGMENT_OUTPUT "${SHADER_BIN_DIR}/fs_quad.bin") - - set(VARYING_DEF "${SHADER_SRC_DIR}/varying.def.sc") - set(QUAD_VARYING_DEF "${SHADER_SRC_DIR}/varying_quad.def.sc") - - add_custom_command( - TARGET ${TARGET_NAME} POST_BUILD - COMMENT "Compiling asset pipelines and core engine shaders via native tools..." - - COMMAND $ - -f ${VERTEX_INPUT} - -o ${VERTEX_OUTPUT} - --type vertex - --platform ${SHADER_PLATFORM} - -p ${VERTEX_PROFILE} - --varyingdef ${VARYING_DEF} - -i ${BGFX_INCLUDE} - - COMMAND $ - -f ${FRAGMENT_INPUT} - -o ${FRAGMENT_OUTPUT} - --type fragment - --platform ${SHADER_PLATFORM} - -p ${FRAGMENT_PROFILE} - --varyingdef ${VARYING_DEF} - -i ${BGFX_INCLUDE} - - COMMAND $ - -f ${QUAD_VERTEX_INPUT} - -o ${QUAD_VERTEX_OUTPUT} - --type vertex - --platform ${SHADER_PLATFORM} - -p ${VERTEX_PROFILE} - --varyingdef ${QUAD_VARYING_DEF} - -i ${BGFX_INCLUDE} - - COMMAND $ - -f ${QUAD_FRAGMENT_INPUT} - -o ${QUAD_FRAGMENT_OUTPUT} - --type fragment - --platform ${SHADER_PLATFORM} - -p ${FRAGMENT_PROFILE} - --varyingdef ${QUAD_VARYING_DEF} - -i ${BGFX_INCLUDE} - - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${VERTEX_OUTPUT} $ - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${FRAGMENT_OUTPUT} $ - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QUAD_VERTEX_OUTPUT} $ - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${QUAD_FRAGMENT_OUTPUT} $ - ) -endfunction()