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..54a72431 100644 --- a/Engine/cpp/Runtime/CMakeLists.txt +++ b/Engine/cpp/Runtime/CMakeLists.txt @@ -1,10 +1,20 @@ 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) +add_modules_library(Rendering/Image) +add_modules_library(Rendering/Image/IO) 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 +43,44 @@ 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) +# Image: CPU-side image data + manipulation (no RHI). IO adds stb load/save; stb is +# PRIVATE (its headers live in the module's global fragment / the stb_impl TU). +target_link_libraries(Rendering_Image PUBLIC Core) +target_link_libraries(Rendering_Image_IO PUBLIC Rendering_Image Core PRIVATE stb_image) +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..0aeb5f18 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(IO PUBLIC Definitions stb_image) +target_link_libraries(Math PUBLIC Definitions) +target_link_libraries(IO PUBLIC Definitions) 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/Core.cppm b/Engine/cpp/Runtime/Core/Core.cppm index f7c8ebda..25feb11a 100644 --- a/Engine/cpp/Runtime/Core/Core.cppm +++ b/Engine/cpp/Runtime/Core/Core.cppm @@ -2,6 +2,7 @@ export module core; export import core.defs; export import core.version; export import core.math; +export import core.color; export import core.memory; export import core.io; export import core.logging; 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/IO/IO.cppm b/Engine/cpp/Runtime/Core/IO/IO.cppm index ae8d71ae..998c0ca1 100644 --- a/Engine/cpp/Runtime/Core/IO/IO.cppm +++ b/Engine/cpp/Runtime/Core/IO/IO.cppm @@ -1,4 +1,3 @@ export module core.io; export import core.io.filesystem; -export import core.io.loader.image; diff --git a/Engine/cpp/Runtime/Core/IO/ImageLoader.cpp b/Engine/cpp/Runtime/Core/IO/ImageLoader.cpp deleted file mode 100644 index 1e634881..00000000 --- a/Engine/cpp/Runtime/Core/IO/ImageLoader.cpp +++ /dev/null @@ -1,63 +0,0 @@ -module; - -#include -#include -#include -#include - -#define STB_IMAGE_IMPLEMENTATION -#include - -module core.io.loader.image; - -import core.stdtypes; - -// TODO: I'm too lazy to write code so we need somethin' better (AR-DEV-1) - -namespace draco::core::io::loader::image -{ - ImageData loadImage(const std::filesystem::path& path) - { - ImageData result; - - std::error_code ec; - if (!std::filesystem::exists(path, ec) || ec) { - std::println("Error: Image path does not exist: {}", path.string()); - return result; - } - - int width, height, channels; - // STBI_rgb_alpha forces the output to be 4 bytes per pixel (RGBA) - unsigned char* data = stbi_load(path.string().c_str(), &width, &height, &channels, STBI_rgb_alpha); - - if (!data) { - std::println("Error: Failed to decode image: {}", path.string()); - return result; - } - - if (width <= 0 || height <= 0) { - stbi_image_free(data); - return result; - } - - const usize w = static_cast(width); - const usize h = static_cast(height); - if (w > (std::numeric_limits::max() / 4) / h) { - stbi_image_free(data); - return result; - } - - usize size = w * h * 4; - - result.pixels.assign(data, data + size); - result.width = static_cast(width); - result.height = static_cast(height); - result.channels = 4; - result.isValid = true; - - // Free the memory allocated by stb - stbi_image_free(data); - - return result; - } -} \ No newline at end of file diff --git a/Engine/cpp/Runtime/Core/IO/ImageLoader.cppm b/Engine/cpp/Runtime/Core/IO/ImageLoader.cppm deleted file mode 100644 index 0c94d4bf..00000000 --- a/Engine/cpp/Runtime/Core/IO/ImageLoader.cppm +++ /dev/null @@ -1,23 +0,0 @@ -module; - -#include -#include - -export module core.io.loader.image; - -import core.stdtypes; - -export namespace draco::core::io::loader::image -{ - struct ImageData - { - std::vector pixels{}; - u32 width = 0; - u32 height = 0; - u8 channels = 0; - bool isValid = false; - }; - - // Load an image file (PNG, JPG, etc.) & decode it to raw RGBA8 - ImageData loadImage(const std::filesystem::path& path); -} \ No newline at end of file diff --git a/Engine/cpp/Runtime/Core/Math/Color.cppm b/Engine/cpp/Runtime/Core/Math/Color.cppm new file mode 100644 index 00000000..35e7eb8b --- /dev/null +++ b/Engine/cpp/Runtime/Core/Math/Color.cppm @@ -0,0 +1,166 @@ +module; + +#include +#include + +export module core.color; + +import core.stdtypes; + +export namespace draco +{ + // ======================================================================= + // Color - linear RGBA, float components (typically 0..1). + // ======================================================================= + struct Color + { + f32 r = 0.0f; + f32 g = 0.0f; + f32 b = 0.0f; + f32 a = 1.0f; + + constexpr Color() noexcept = default; + constexpr Color(f32 inR, f32 inG, f32 inB, f32 inA = 1.0f) noexcept : r(inR), g(inG), b(inB), a(inA) {} + + // Packs to 0xRRGGBBAA (components clamped to 0..1). + [[nodiscard]] u32 toRGBA8() const noexcept + { + const auto byteOf = [](f32 c) -> u32 + { + return static_cast(std::clamp(c, 0.0f, 1.0f) * 255.0f + 0.5f); + }; + return (byteOf(r) << 24) | (byteOf(g) << 16) | (byteOf(b) << 8) | byteOf(a); + } + + [[nodiscard]] static Color fromRGBA8(u32 packed) noexcept + { + return Color{ static_cast((packed >> 24) & 0xFFu) / 255.0f, + static_cast((packed >> 16) & 0xFFu) / 255.0f, + static_cast((packed >> 8) & 0xFFu) / 255.0f, + static_cast(packed & 0xFFu) / 255.0f }; + } + + static const Color White; + static const Color Black; + static const Color Red; + static const Color Green; + static const Color Blue; + static const Color Transparent; + }; + + inline constexpr Color Color::White{ 1.0f, 1.0f, 1.0f, 1.0f }; + inline constexpr Color Color::Black{ 0.0f, 0.0f, 0.0f, 1.0f }; + inline constexpr Color Color::Red{ 1.0f, 0.0f, 0.0f, 1.0f }; + inline constexpr Color Color::Green{ 0.0f, 1.0f, 0.0f, 1.0f }; + inline constexpr Color Color::Blue{ 0.0f, 0.0f, 1.0f, 1.0f }; + inline constexpr Color Color::Transparent{ 0.0f, 0.0f, 0.0f, 0.0f }; + + [[nodiscard]] constexpr Color operator*(Color c, f32 s) noexcept { return { c.r * s, c.g * s, c.b * s, c.a * s }; } + [[nodiscard]] constexpr Color operator+(Color a, Color b) noexcept { return { a.r + b.r, a.g + b.g, a.b + b.b, a.a + b.a }; } + [[nodiscard]] constexpr bool operator==(Color a, Color b) noexcept + { + return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a; + } + + [[nodiscard]] constexpr Color lerp(Color a, Color b, f32 t) noexcept + { + return { std::lerp(a.r, b.r, t), std::lerp(a.g, b.g, t), std::lerp(a.b, b.b, t), std::lerp(a.a, b.a, t) }; + } + + [[nodiscard]] inline bool nearlyEqual(Color a, Color b, f32 epsilon = 1e-5f) noexcept + { + return std::abs(a.r - b.r) <= epsilon && std::abs(a.g - b.g) <= epsilon + && std::abs(a.b - b.b) <= epsilon && std::abs(a.a - b.a) <= epsilon; + } + + // ======================================================================= + // Color32 - packed 8-bit RGBA, for compact storage: image pixels, vertex + // colors, asset interchange. The byte-color counterpart of the float Color + // (which stays the linear/HDR runtime currency). Conversions are plain + // 0..255 <-> 0..1 with NO gamma; sRGB<->linear is handled explicitly at the + // texture/format edge (ImageColorSpace, *UnormSrgb formats). + // ======================================================================= + struct Color32 + { + u8 r = 0; + u8 g = 0; + u8 b = 0; + u8 a = 255; + + constexpr Color32() noexcept = default; + constexpr Color32(u8 inR, u8 inG, u8 inB, u8 inA = 255) noexcept : r(inR), g(inG), b(inB), a(inA) {} + + // Packs to 0xRRGGBBAA. + [[nodiscard]] constexpr u32 toRGBA8() const noexcept + { + return (static_cast(r) << 24) | (static_cast(g) << 16) + | (static_cast(b) << 8) | static_cast(a); + } + + [[nodiscard]] static constexpr Color32 fromRGBA8(u32 packed) noexcept + { + return Color32{ static_cast((packed >> 24) & 0xFFu), + static_cast((packed >> 16) & 0xFFu), + static_cast((packed >> 8) & 0xFFu), + static_cast(packed & 0xFFu) }; + } + + static const Color32 White; + static const Color32 Black; + static const Color32 Red; + static const Color32 Green; + static const Color32 Blue; + static const Color32 Transparent; + }; + + inline constexpr Color32 Color32::White{ 255, 255, 255, 255 }; + inline constexpr Color32 Color32::Black{ 0, 0, 0, 255 }; + inline constexpr Color32 Color32::Red{ 255, 0, 0, 255 }; + inline constexpr Color32 Color32::Green{ 0, 255, 0, 255 }; + inline constexpr Color32 Color32::Blue{ 0, 0, 255, 255 }; + inline constexpr Color32 Color32::Transparent{ 0, 0, 0, 0 }; + + [[nodiscard]] constexpr bool operator==(Color32 a, Color32 b) noexcept + { + return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a; + } + + // Float Color -> packed bytes (components clamped to 0..1, rounded). + [[nodiscard]] constexpr Color32 toColor32(Color c) noexcept + { + const auto byteOf = [](f32 v) -> u8 + { + return static_cast(std::clamp(v, 0.0f, 1.0f) * 255.0f + 0.5f); + }; + return Color32{ byteOf(c.r), byteOf(c.g), byteOf(c.b), byteOf(c.a) }; + } + + // Packed bytes -> float Color (exact 0..255 -> 0..1; round-trips toColor32). + [[nodiscard]] constexpr Color toColor(Color32 c) noexcept + { + return Color{ static_cast(c.r) / 255.0f, static_cast(c.g) / 255.0f, + static_cast(c.b) / 255.0f, static_cast(c.a) / 255.0f }; + } + + // sRGB <-> linear transfer functions (the standard IEC 61966-2-1 EOTF). For + // decoding sRGB-authored colors to linear before linear-space blending/shading. + [[nodiscard]] inline f32 srgbToLinear(f32 c) noexcept + { + return (c <= 0.04045f) ? (c / 12.92f) : std::pow((c + 0.055f) / 1.055f, 2.4f); + } + + [[nodiscard]] inline f32 linearToSrgb(f32 c) noexcept + { + return (c <= 0.0031308f) ? (c * 12.92f) : (1.055f * std::pow(c, 1.0f / 2.4f) - 0.055f); + } + + // Color32 (sRGB-authored) -> linear float Color (RGB through the sRGB EOTF; + // alpha is linear). For uploading UI/SVG colors to a linear/sRGB pipeline. + [[nodiscard]] inline Color toLinear(Color32 c) noexcept + { + return Color{ srgbToLinear(static_cast(c.r) / 255.0f), + srgbToLinear(static_cast(c.g) / 255.0f), + srgbToLinear(static_cast(c.b) / 255.0f), + static_cast(c.a) / 255.0f }; + } +} diff --git a/Engine/cpp/Runtime/Core/Math/Color.test.cpp b/Engine/cpp/Runtime/Core/Math/Color.test.cpp new file mode 100644 index 00000000..ea855d9d --- /dev/null +++ b/Engine/cpp/Runtime/Core/Math/Color.test.cpp @@ -0,0 +1,39 @@ +#include +#include + +import core.stdtypes; +import core.color; + +using namespace draco; + +TEST_CASE("color: Color32 packs and unpacks 0xRRGGBBAA") +{ + const Color32 c{ 12, 34, 56, 78 }; + CHECK(c.toRGBA8() == 0x0C22384Eu); + CHECK(Color32::fromRGBA8(0x0C22384Eu) == c); + CHECK(Color32::fromRGBA8(c.toRGBA8()) == c); // round-trip +} + +TEST_CASE("color: named constants") +{ + CHECK(Color32::White == Color32{ 255, 255, 255, 255 }); + CHECK(Color32::Black == Color32{ 0, 0, 0, 255 }); + CHECK(Color32::Transparent.a == 0); + CHECK(Color::Red == Color{ 1.0f, 0.0f, 0.0f, 1.0f }); + CHECK(Color::White.toRGBA8() == 0xFFFFFFFFu); +} + +TEST_CASE("color: Color <-> Color32 round-trips exactly") +{ + const Color32 c{ 10, 20, 30, 255 }; + CHECK(toColor32(toColor(c)) == c); + CHECK(toColor32(Color{ 1.0f, 0.0f, 0.5f, 1.0f }) == Color32{ 255, 0, 128, 255 }); +} + +TEST_CASE("color: sRGB transfer round-trips") +{ + for (f32 v : { 0.0f, 0.25f, 0.5f, 1.0f }) + { + CHECK(std::abs(linearToSrgb(srgbToLinear(v)) - v) < 1e-4f); + } +} 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/Image/AtlasBuilder.cppm b/Engine/cpp/Runtime/Rendering/Image/AtlasBuilder.cppm new file mode 100644 index 00000000..1ff1880a --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Image/AtlasBuilder.cppm @@ -0,0 +1,209 @@ +// Draconic::Image - :atlas_builder partition. +// +// RectI (integer rectangle for atlas regions) and ImageAtlasBuilder - a +// general-purpose shelf-packing atlas packer that combines multiple RGBA8 +// images into one atlas texture (UI themes, sprite sheets, ...). Ported from +// Sedulous.Images/ImageAtlasBuilder.bf. + +module; + +#include + +#include +#include +#include +#include +#include + +export module image:atlas_builder; + +import core; +import :pixel_format; +import :image_data; + +using namespace draco; + +export namespace draco::image +{ + /// Integer rectangle for atlas regions. + struct RectI + { + i32 x = 0; + i32 y = 0; + i32 width = 0; + i32 height = 0; + + constexpr RectI() noexcept = default; + constexpr RectI(i32 inX, i32 inY, i32 w, i32 h) noexcept : x(inX), y(inY), width(w), height(h) {} + }; + + /// General-purpose image atlas packer (shelf packing). Combines RGBA8 images + /// into one atlas texture. Images are not owned (caller keeps them alive). + class ImageAtlasBuilder + { + public: + /// minSize/maxSize: atlas dimensions (rounded up to powers of two). + /// padding: pixels between packed images. + explicit ImageAtlasBuilder(u32 minSize = 256, u32 maxSize = 4096, u32 padding = 1) + : m_minSize(nextPowerOf2(minSize)), m_maxSize(nextPowerOf2(maxSize)), m_padding(padding) {} + + /// The built atlas image. Null until build() succeeds. + [[nodiscard]] const ImageData* atlas() const { return m_built ? &m_atlas : nullptr; } + + /// Number of entries added. + [[nodiscard]] usize entryCount() const { return m_entries.size(); } + + /// Add an image to be packed. Name must be unique. Image is not owned. + void addImage(std::u8string_view name, const ImageData* image) + { + if (image == nullptr) return; + Entry entry; + entry.name = std::u8string(name); + entry.image = image; + m_entries.push_back(std::move(entry)); + } + + /// Pack all added images into a single RGBA8 atlas. Returns true on success. + bool build() + { + if (m_entries.empty()) + { + const u8 emptyPixel[4] = { 0, 0, 0, 0 }; + m_atlas = OwnedImageData(1, 1, PixelFormat::RGBA8, std::span(emptyPixel, 4)); + m_built = true; + return true; + } + + // Sort by height descending for better shelf packing. + sortByHeightDesc(); + + // Try increasing atlas sizes until everything fits. + for (u32 size = m_minSize; size <= m_maxSize; size *= 2) + { + if (tryPack(size, size)) + { + m_built = true; + return true; + } + } + return false; // Couldn't fit in max size. + } + + /// The pixel-space region of a packed image by name (null if not found). + [[nodiscard]] const RectI* getRegion(std::u8string_view name) const { + auto it = m_regions.find(std::u8string(name)); + return it != m_regions.end() ? &it->second : nullptr; + } + + private: + struct Entry + { + std::u8string name; + const ImageData* image = nullptr; + }; + + void sortByHeightDesc() + { + // Insertion sort (entry counts are small); stable-ish, descending height. + for (usize i = 1; i < m_entries.size(); ++i) + { + Entry key = std::move(m_entries[i]); + const u32 keyH = key.image->height(); + usize j = i; + while (j > 0 && m_entries[j - 1].image->height() < keyH) + { + m_entries[j] = std::move(m_entries[j - 1]); + --j; + } + m_entries[j] = std::move(key); + } + } + + bool tryPack(u32 atlasW, u32 atlasH) + { + m_regions.clear(); + + // Shelf packing: place images left-to-right, start a new row when full. + u32 curX = m_padding; + u32 curY = m_padding; + u32 rowHeight = 0; + + for (usize e = 0; e < m_entries.size(); ++e) + { + const Entry& entry = m_entries[e]; + const u32 imgW = entry.image->width(); + const u32 imgH = entry.image->height(); + + if (curX + imgW + m_padding > atlasW) + { + curX = m_padding; + curY += rowHeight + m_padding; + rowHeight = 0; + } + + if (curY + imgH + m_padding > atlasH) + return false; + + m_regions.insert_or_assign(std::u8string(entry.name), + RectI{ static_cast(curX), static_cast(curY), static_cast(imgW), static_cast(imgH) }); + + curX += imgW + m_padding; + rowHeight = std::max(rowHeight, imgH); + } + + // Build the atlas pixel data. + std::vector pixelData; + pixelData.resize(static_cast(atlasW) * atlasH * 4); + std::memset(pixelData.data(), 0, pixelData.size()); + + for (usize e = 0; e < m_entries.size(); ++e) + { + const Entry& entry = m_entries[e]; + auto regionIt = m_regions.find(std::u8string(entry.name)); + const RectI* region = regionIt != m_regions.end() ? ®ionIt->second : nullptr; + if (region == nullptr) continue; + + const ImageData* src = entry.image; + if (src->format() == PixelFormat::RGBA8) + { + const std::span srcData = src->pixelData(); + const u32 srcStride = src->width() * 4; + const u32 dstStride = atlasW * 4; + + for (u32 y = 0; y < src->height(); ++y) + { + const u32 srcOffset = y * srcStride; + const u32 dstOffset = (static_cast(region->y) + y) * dstStride + static_cast(region->x) * 4; + + if (srcOffset + srcStride <= srcData.size() && dstOffset + srcStride <= pixelData.size()) + std::memcpy(pixelData.data() + dstOffset, srcData.data() + srcOffset, srcStride); + } + } + } + + m_atlas = OwnedImageData(atlasW, atlasH, PixelFormat::RGBA8, std::move(pixelData)); + return true; + } + + [[nodiscard]] static u32 nextPowerOf2(u32 v) + { + u32 n = v; + --n; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + ++n; + return std::max(n, 1u); + } + + std::vector m_entries; + OwnedImageData m_atlas; + std::unordered_map m_regions; + u32 m_minSize; + u32 m_maxSize; + u32 m_padding; + bool m_built = false; + }; +} diff --git a/Engine/cpp/Runtime/Rendering/Image/IO/ImageIOModule.cppm b/Engine/cpp/Runtime/Rendering/Image/IO/ImageIOModule.cppm new file mode 100644 index 00000000..c437fcf2 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Image/IO/ImageIOModule.cppm @@ -0,0 +1,112 @@ +/// Concrete image loading and saving via stb_image / stb_image_write. +/// No abstract loader/writer - direct stb dependency. +/// Works with draco::image::Image directly. + +module; + +#include +#include + +#include +#include +#include +#include + +#include "stb_image.h" +#include "stb_image_write.h" + +export module image.io; + +import core; +import image; + +using namespace draco; + +export namespace draco::image::io { + +/// File format for saving. +enum class ImageFileFormat : u32 { PNG, JPG, BMP }; + +/// Load an image from a file path. Returns RGBA8 for LDR, RGBA32F for HDR. +[[nodiscard]] inline Status loadImage(std::u8string_view path, Image& out) { + const std::string cPath(reinterpret_cast(path.data()), path.size()); + int x = 0, y = 0, channels = 0; + constexpr int desired = 4; + + bool isHDR = stbi_is_hdr(cPath.c_str()) != 0; + void* data = nullptr; + if (isHDR) + data = stbi_loadf(cPath.c_str(), &x, &y, &channels, desired); + else + data = stbi_load(cPath.c_str(), &x, &y, &channels, desired); + + if (!data) return ErrorCode::Unknown; + + usize dataSize = isHDR + ? static_cast(x) * y * desired * sizeof(float) + : static_cast(x) * y * desired; + + PixelFormat fmt = isHDR ? PixelFormat::RGBA32F : PixelFormat::RGBA8; + out = Image(static_cast(x), static_cast(y), fmt, + std::span(static_cast(data), dataSize)); + out.setColorSpace(isHDR ? ImageColorSpace::Linear : ImageColorSpace::Srgb); + stbi_image_free(data); + return ErrorCode::Ok; +} + +/// Load an image from a memory buffer. +[[nodiscard]] inline Status loadImageFromMemory(std::span buffer, Image& out) { + int x = 0, y = 0, channels = 0; + constexpr int desired = 4; + + bool isHDR = stbi_is_hdr_from_memory(buffer.data(), static_cast(buffer.size())) != 0; + void* data = nullptr; + if (isHDR) + data = stbi_loadf_from_memory(buffer.data(), static_cast(buffer.size()), + &x, &y, &channels, desired); + else + data = stbi_load_from_memory(buffer.data(), static_cast(buffer.size()), + &x, &y, &channels, desired); + + if (!data) return ErrorCode::Unknown; + + usize dataSize = isHDR + ? static_cast(x) * y * desired * sizeof(float) + : static_cast(x) * y * desired; + + PixelFormat fmt = isHDR ? PixelFormat::RGBA32F : PixelFormat::RGBA8; + out = Image(static_cast(x), static_cast(y), fmt, + std::span(static_cast(data), dataSize)); + out.setColorSpace(isHDR ? ImageColorSpace::Linear : ImageColorSpace::Srgb); + stbi_image_free(data); + return ErrorCode::Ok; +} + +/// Save an image to a file. Only supports 8-bit formats (R8, RG8, RGB8, RGBA8). +[[nodiscard]] inline Status saveImage(const Image& image, std::u8string_view path, + ImageFileFormat format, i32 jpgQuality = 90) { + if (image.width() == 0 || image.height() == 0) return ErrorCode::Unknown; + + switch (image.format()) { + case PixelFormat::R8: case PixelFormat::RG8: + case PixelFormat::RGB8: case PixelFormat::RGBA8: + break; + default: return ErrorCode::Unknown; + } + + const std::string cPath(reinterpret_cast(path.data()), path.size()); + int w = static_cast(image.width()); + int h = static_cast(image.height()); + int ch = static_cast(channelCount(image.format())); + const void* data = image.pixelData().data(); + + int ok = 0; + switch (format) { + case ImageFileFormat::PNG: ok = stbi_write_png(cPath.c_str(), w, h, ch, data, w * ch); break; + case ImageFileFormat::JPG: ok = stbi_write_jpg(cPath.c_str(), w, h, ch, data, jpgQuality); break; + case ImageFileFormat::BMP: ok = stbi_write_bmp(cPath.c_str(), w, h, ch, data); break; + } + return ok != 0 ? ErrorCode::Ok : ErrorCode::Unknown; +} + +} // namespace draco::image::io diff --git a/Engine/cpp/Runtime/Rendering/Image/IO/ImageTests.test.cpp b/Engine/cpp/Runtime/Rendering/Image/IO/ImageTests.test.cpp new file mode 100644 index 00000000..bc398971 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Image/IO/ImageTests.test.cpp @@ -0,0 +1,35 @@ +#include + +import core; +import image; +import image.io; + +using namespace draco; +using namespace draco::image; + +TEST_CASE("image: procedural create + pixel access") +{ + Image img = Image::createSolidColor(4, 4, Color32{ 10, 20, 30, 255 }); + CHECK(img.width() == 4u); + CHECK(img.height() == 4u); + CHECK(img.format() == PixelFormat::RGBA8); + const Color32 p = img.getPixel(1, 1); + CHECK(p.r == 10); + CHECK(p.g == 20); + CHECK(p.b == 30); +} + +TEST_CASE("image: save PNG and reload via stb") +{ + Image img = Image::createCheckerboard(64); + REQUIRE(img.width() == 64u); + + const std::u8string_view path = u8"/tmp/draco_image_roundtrip.png"; + REQUIRE(io::saveImage(img, path, io::ImageFileFormat::PNG).isOk()); + + Image loaded; + REQUIRE(io::loadImage(path, loaded).isOk()); + CHECK(loaded.width() == 64u); + CHECK(loaded.height() == 64u); + CHECK(loaded.format() == PixelFormat::RGBA8); // stb loads as RGBA8 +} diff --git a/Engine/cpp/Runtime/Rendering/Image/IO/stb_impl.cpp b/Engine/cpp/Runtime/Rendering/Image/IO/stb_impl.cpp new file mode 100644 index 00000000..801634ca --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Image/IO/stb_impl.cpp @@ -0,0 +1,10 @@ +// Single translation unit for stb implementations. +// Module units cannot define these macros (static symbol collisions), +// so the implementations live in a regular .cpp. + +#define STB_IMAGE_IMPLEMENTATION +#define STBI_NO_STDIO_WINDOWS_UTF8 0 +#include "stb_image.h" + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" diff --git a/Engine/cpp/Runtime/Rendering/Image/Image.cppm b/Engine/cpp/Runtime/Rendering/Image/Image.cppm new file mode 100644 index 00000000..126345f4 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Image/Image.cppm @@ -0,0 +1,435 @@ +/// Image - owns a CPU-side pixel buffer with manipulation methods. +/// Implements ImageData so it can be passed to anything accepting the base type. + +module; + +#include + +#include +#include +#include +#include + +export module image:image; + +import core; +import :pixel_format; +import :image_data; + +using namespace draco; + +export namespace draco::image { + +// Pixel access uses the engine's packed byte color, core::Color32 (the image +// library previously defined its own duplicate `Color` - unified away). + +/// Image that owns a pixel buffer. Inherits ImageData for polymorphic use. +/// Supports pixel access, flips, format conversion, and procedural factories. +class Image : public ImageData { +public: + Image() = default; + + Image(u32 w, u32 h, PixelFormat fmt, std::span srcData = {}) + : m_width(w), m_height(h), m_format(fmt) + { + usize needed = dataSize(); + m_data.resize(needed); + if (srcData.size() >= needed) + std::memcpy(m_data.data(), srcData.data(), needed); + else + clear(); + } + + Image(const Image&) = default; + Image(Image&&) noexcept = default; + Image& operator=(const Image&) = default; + Image& operator=(Image&&) noexcept = default; + + // ---- ImageData interface ---- + [[nodiscard]] u32 width() const override { return m_width; } + [[nodiscard]] u32 height() const override { return m_height; } + [[nodiscard]] PixelFormat format() const override { return m_format; } + [[nodiscard]] ImageColorSpace colorSpace() const override { return m_colorSpace; } + [[nodiscard]] std::span pixelData() const override { return { m_data.data(), m_data.size() }; } + + // ---- Mutable access ---- + [[nodiscard]] std::span pixelDataMut() { return { m_data.data(), m_data.size() }; } + [[nodiscard]] u32 pixelCount() const { return m_width * m_height; } + [[nodiscard]] usize dataSize() const { return static_cast(pixelCount()) * bytesPerPixel(m_format); } + void setColorSpace(ImageColorSpace cs) { m_colorSpace = cs; } + + /// Replace dimensions, format, and pixel data in-place (hot-reload). + void replaceData(u32 w, u32 h, PixelFormat fmt, std::span src) { + m_width = w; m_height = h; m_format = fmt; + usize needed = dataSize(); + m_data.resize(needed); + usize copy = std::min(needed, src.size()); + if (copy > 0) std::memcpy(m_data.data(), src.data(), copy); + if (copy < needed) std::memset(m_data.data() + copy, 0, needed - copy); + } + + // ---- Pixel access ---- + + void clear() { + if (draco::image::hasAlpha(m_format)) + fillColor(Color32::Transparent); + else + std::memset(m_data.data(), 0, m_data.size()); + } + + /// Clear the image to a specific color. + void clear(Color32 color) { fillColor(color); } + + /// Bytes per pixel for a format (static helper mirroring Sedulous's API). + [[nodiscard]] static i32 getBytesPerPixel(PixelFormat format) { return static_cast(bytesPerPixel(format)); } + + /// Whether this image's format carries an alpha channel. + [[nodiscard]] bool hasAlpha() const { return draco::image::hasAlpha(m_format); } + /// Number of channels in this image's pixel format. + [[nodiscard]] i32 getChannelCount() const { return static_cast(draco::image::channelCount(m_format)); } + + void fillColor(Color32 c) { + u32 bpp = bytesPerPixel(m_format); + for (usize i = 0; i < m_data.size(); i += bpp) { + switch (m_format) { + case PixelFormat::R8: + m_data[i] = static_cast((c.r + c.g + c.b) / 3); + break; + case PixelFormat::RG8: + m_data[i] = c.r; m_data[i+1] = c.g; + break; + case PixelFormat::RGB8: + m_data[i] = c.r; m_data[i+1] = c.g; m_data[i+2] = c.b; + break; + case PixelFormat::RGBA8: + m_data[i] = c.r; m_data[i+1] = c.g; m_data[i+2] = c.b; m_data[i+3] = c.a; + break; + case PixelFormat::BGR8: + m_data[i] = c.b; m_data[i+1] = c.g; m_data[i+2] = c.r; + break; + case PixelFormat::BGRA8: + m_data[i] = c.b; m_data[i+1] = c.g; m_data[i+2] = c.r; m_data[i+3] = c.a; + break; + default: break; + } + } + } + + [[nodiscard]] Color32 getPixel(u32 x, u32 y) const { + if (x >= m_width || y >= m_height) return Color32::Black; + usize off = pixelOffset(x, y); + switch (m_format) { + case PixelFormat::R8: { u8 g = m_data[off]; return {g,g,g,255}; } + case PixelFormat::RGB8: return {m_data[off], m_data[off+1], m_data[off+2], 255}; + case PixelFormat::RGBA8: return {m_data[off], m_data[off+1], m_data[off+2], m_data[off+3]}; + case PixelFormat::BGR8: return {m_data[off+2], m_data[off+1], m_data[off], 255}; + case PixelFormat::BGRA8: return {m_data[off+2], m_data[off+1], m_data[off], m_data[off+3]}; + default: return Color32::Black; + } + } + + void setPixel(u32 x, u32 y, Color32 c) { + if (x >= m_width || y >= m_height) return; + usize off = pixelOffset(x, y); + switch (m_format) { + case PixelFormat::R8: m_data[off] = static_cast((c.r+c.g+c.b)/3); break; + case PixelFormat::RGB8: m_data[off]=c.r; m_data[off+1]=c.g; m_data[off+2]=c.b; break; + case PixelFormat::RGBA8: m_data[off]=c.r; m_data[off+1]=c.g; m_data[off+2]=c.b; m_data[off+3]=c.a; break; + case PixelFormat::BGR8: m_data[off]=c.b; m_data[off+1]=c.g; m_data[off+2]=c.r; break; + case PixelFormat::BGRA8: m_data[off]=c.b; m_data[off+1]=c.g; m_data[off+2]=c.r; m_data[off+3]=c.a; break; + default: break; + } + } + + // ---- Flips ---- + + void flipVertical() { + u32 rowSize = m_width * bytesPerPixel(m_format); + std::vector tmp(rowSize); + for (u32 y = 0; y < m_height / 2; ++y) { + u8* top = m_data.data() + y * rowSize; + u8* bot = m_data.data() + (m_height - 1 - y) * rowSize; + std::memcpy(tmp.data(), top, rowSize); + std::memcpy(top, bot, rowSize); + std::memcpy(bot, tmp.data(), rowSize); + } + } + + void flipHorizontal() { + u32 bpp = bytesPerPixel(m_format); + std::vector tmp(bpp); + for (u32 y = 0; y < m_height; ++y) { + for (u32 x = 0; x < m_width / 2; ++x) { + u8* left = m_data.data() + pixelOffset(x, y); + u8* right = m_data.data() + pixelOffset(m_width - 1 - x, y); + std::memcpy(tmp.data(), left, bpp); + std::memcpy(left, right, bpp); + std::memcpy(right, tmp.data(), bpp); + } + } + } + + // ---- Format conversion ---- + + [[nodiscard]] Image convertFormat(PixelFormat newFmt) const { + if (newFmt == m_format) return *this; + Image out(m_width, m_height, newFmt); + for (u32 y = 0; y < m_height; ++y) + for (u32 x = 0; x < m_width; ++x) + out.setPixel(x, y, getPixel(x, y)); + return out; + } + + // ---- Factories ---- + + static Image createSolidColor(u32 w, u32 h, Color32 c, PixelFormat fmt = PixelFormat::RGBA8) { + Image img(w, h, fmt); + img.fillColor(c); + return img; + } + + static Image createCheckerboard(u32 size = 256, Color32 c1 = Color32::White, Color32 c2 = Color32::Black, + u32 checkSize = 32, PixelFormat fmt = PixelFormat::RGBA8) { + Image img(size, size, fmt); + for (u32 y = 0; y < size; ++y) + for (u32 x = 0; x < size; ++x) + img.setPixel(x, y, ((x/checkSize + y/checkSize) % 2 == 0) ? c1 : c2); + return img; + } + + static Image createGradient(u32 w, u32 h, Color32 top, Color32 bottom, PixelFormat fmt = PixelFormat::RGBA8) { + Image img(w, h, fmt); + for (u32 y = 0; y < h; ++y) { + f32 t = static_cast(y) / static_cast(h > 1 ? h - 1 : 1); + Color32 c{ + static_cast(top.r + t * (bottom.r - top.r)), + static_cast(top.g + t * (bottom.g - top.g)), + static_cast(top.b + t * (bottom.b - top.b)), + static_cast(top.a + t * (bottom.a - top.a)), + }; + for (u32 x = 0; x < w; ++x) img.setPixel(x, y, c); + } + return img; + } + + // ---- Normal-map factories ---- + // (n*0.5+0.5)*255; the neutral up-normal (0,0,1) -> (128,128,255). + + static Image createFlatNormalMap(u32 width = 256, u32 height = 256, PixelFormat format = PixelFormat::RGBA8) { + Image image(width, height, format); + image.fillColor(Color32(128, 128, 255, 255)); // (0,0,1) + return image; + } + + static Image createWaveNormalMap(u32 width = 256, u32 height = 256, + f32 waveFrequencyX = 8.0f, f32 waveFrequencyY = 6.0f, + f32 amplitude = 0.3f, PixelFormat format = PixelFormat::RGBA8) { + Image image(width, height, format); + for (u32 y = 0; y < height; ++y) { + for (u32 x = 0; x < width; ++x) { + const f32 fx = static_cast(x) / static_cast(width); + const f32 fy = static_cast(y) / static_cast(height); + const f32 heightValue = math::sin(fx * math::PI * waveFrequencyX) * amplitude + math::sin(fy * math::PI * waveFrequencyY) * amplitude * 0.7f; + const f32 heightRight = math::sin((fx + 1.0f / static_cast(width)) * math::PI * waveFrequencyX) * amplitude + math::sin(fy * math::PI * waveFrequencyY) * amplitude * 0.7f; + const f32 heightDown = math::sin(fx * math::PI * waveFrequencyX) * amplitude + math::sin((fy + 1.0f / static_cast(height)) * math::PI * waveFrequencyY) * amplitude * 0.7f; + const f32 dx = heightRight - heightValue; + const f32 dy = heightDown - heightValue; + image.setPixel(x, y, encodeNormal(math::normalize(math::Vector3{ -dx * 20.0f, -dy * 20.0f, 1.0f }))); + } + } + return image; + } + + static Image createBrickNormalMap(u32 width = 256, u32 height = 256, u32 bricksX = 8, u32 bricksY = 4, + f32 mortarDepth = 0.3f, PixelFormat format = PixelFormat::RGBA8) { + Image image(width, height, format); + const u32 brickWidth = width / bricksX; + const u32 brickHeight = height / bricksY; + const u32 mortarWidth = std::max(brickWidth / 16u, 2u); + + for (u32 y = 0; y < height; ++y) { + for (u32 x = 0; x < width; ++x) { + const u32 brickY = y / brickHeight; + u32 adjustedX = x; + if (brickY % 2 == 1) + adjustedX = (x + brickWidth / 2) % width; + + const u32 localX = adjustedX % brickWidth; + const u32 localY = y % brickHeight; + + const bool isHorizontalMortar = localY < mortarWidth || localY >= (brickHeight - mortarWidth); + const bool isVerticalMortar = localX < mortarWidth || localX >= (brickWidth - mortarWidth); + const bool isMortar = isHorizontalMortar || isVerticalMortar; + + math::Vector3 normal; + if (isMortar) { + normal = math::Vector3{ 0.0f, 0.0f, 1.0f - mortarDepth * 2.0f }; + } else { + const f32 brickVariation = math::sin(static_cast(localX) * 0.2f) * math::sin(static_cast(localY) * 0.15f) * 0.1f; + normal = math::Vector3{ 0.0f, 0.0f, 1.0f + brickVariation }; + } + image.setPixel(x, y, encodeNormal(math::normalize(normal))); + } + } + return image; + } + + static Image createCircularBumpNormalMap(u32 width = 256, u32 height = 256, f32 bumpHeight = 0.5f, + f32 falloff = 2.0f, PixelFormat format = PixelFormat::RGBA8) { + Image image(width, height, format); + const f32 centerX = static_cast(width) * 0.5f; + const f32 centerY = static_cast(height) * 0.5f; + const f32 maxRadius = static_cast(std::min(width, height)) * 0.4f; + + for (u32 y = 0; y < height; ++y) { + for (u32 x = 0; x < width; ++x) { + const f32 dx = static_cast(x) - centerX; + const f32 dy = static_cast(y) - centerY; + const f32 distance = sqrt(dx * dx + dy * dy); + + math::Vector3 normal; + if (distance < maxRadius && distance > 0.001f) { + const f32 normalizedDist = distance / maxRadius; + const f32 heightDerivative = -falloff * pow(1.0f - normalizedDist, falloff - 1.0f) * bumpHeight * 3.0f / maxRadius; + const f32 nx = (dx / distance) * heightDerivative; + const f32 ny = (dy / distance) * heightDerivative; + normal = math::normalize(math::Vector3{ nx, ny, 1.0f }); + } else { + normal = math::Vector3{ 0.0f, 0.0f, 1.0f }; + } + image.setPixel(x, y, encodeNormal(normal)); + } + } + return image; + } + + static Image createNoiseNormalMap(u32 width = 256, u32 height = 256, f32 scale = 0.1f, + f32 amplitude = 0.2f, i32 seed = 12345, PixelFormat format = PixelFormat::RGBA8) { + Image image(width, height, format); + + std::vector heightMap; + heightMap.resize(static_cast(width) * height); + + for (u32 y = 0; y < height; ++y) { + for (u32 x = 0; x < width; ++x) { + f32 noise = 0.0f; + f32 freq = scale; + f32 amp = amplitude; + for (i32 octave = 0; octave < 4; ++octave) { + const f32 fx = static_cast(x) * freq; + const f32 fy = static_cast(y) * freq; + const u32 ix = static_cast(fx); + const u32 iy = static_cast(fy); + const f32 fracX = fx - static_cast(ix); + const f32 fracY = fy - static_cast(iy); + + const f32 a = hashToFloat(seed + static_cast(ix) + static_cast(iy) * 1000 + octave * 10000); + const f32 b = hashToFloat(seed + static_cast(ix + 1) + static_cast(iy) * 1000 + octave * 10000); + const f32 c = hashToFloat(seed + static_cast(ix) + static_cast(iy + 1) * 1000 + octave * 10000); + const f32 d = hashToFloat(seed + static_cast(ix + 1) + static_cast(iy + 1) * 1000 + octave * 10000); + + const f32 smoothX = fracX * fracX * (3.0f - 2.0f * fracX); + const f32 smoothY = fracY * fracY * (3.0f - 2.0f * fracY); + + const f32 i1 = math::lerp(a, b, smoothX); + const f32 i2 = math::lerp(c, d, smoothX); + const f32 value = math::lerp(i1, i2, smoothY); + + noise += value * amp; + freq *= 2.0f; + amp *= 0.5f; + } + heightMap[static_cast(y) * width + x] = noise; + } + } + + for (u32 y = 0; y < height; ++y) { + for (u32 x = 0; x < width; ++x) { + // Edge-clamped neighbours (avoids the u32 underflow latent in the original). + const u32 xl = (x > 0) ? x - 1 : 0; + const u32 xr = (x + 1 < width) ? x + 1 : width - 1; + const u32 yu = (y > 0) ? y - 1 : 0; + const u32 yd = (y + 1 < height) ? y + 1 : height - 1; + const f32 heightL = heightMap[static_cast(y) * width + xl]; + const f32 heightR = heightMap[static_cast(y) * width + xr]; + const f32 heightU = heightMap[static_cast(yu) * width + x]; + const f32 heightD = heightMap[static_cast(yd) * width + x]; + + const f32 dx = heightR - heightL; + const f32 dy = heightD - heightU; + image.setPixel(x, y, encodeNormal(math::normalize(math::Vector3{ -dx * 8.0f, -dy * 8.0f, 1.0f }))); + } + } + return image; + } + + static Image createTestPatternNormalMap(u32 width = 256, u32 height = 256, PixelFormat format = PixelFormat::RGBA8) { + Image image(width, height, format); + for (u32 y = 0; y < height; ++y) { + for (u32 x = 0; x < width; ++x) { + const f32 fx = static_cast(x) / static_cast(width); + const f32 fy = static_cast(y) / static_cast(height); + + math::Vector3 normal; + if (fx < 0.5f && fy < 0.5f) { + normal = math::Vector3{ 0.0f, 0.0f, 1.0f }; // Top-left: flat. + } else if (fx >= 0.5f && fy < 0.5f) { + const f32 bump = math::sin(fx * math::PI * 16.0f) * 0.5f; // Top-right: X bumps. + normal = math::normalize(math::Vector3{ bump, 0.0f, 1.0f }); + } else if (fx < 0.5f && fy >= 0.5f) { + const f32 bump = math::sin(fy * math::PI * 16.0f) * 0.5f; // Bottom-left: Y bumps. + normal = math::normalize(math::Vector3{ 0.0f, bump, 1.0f }); + } else { + const f32 centerX = 0.75f, centerY = 0.75f; // Bottom-right: circular. + const f32 dx = fx - centerX; + const f32 dy = fy - centerY; + const f32 dist = sqrt(dx * dx + dy * dy); + if (dist < 0.2f) { + const f32 angle = atan2(dy, dx); + normal = math::normalize(math::Vector3{ math::cos(angle) * 0.3f, math::sin(angle) * 0.3f, 1.0f }); + } else { + normal = math::Vector3{ 0.0f, 0.0f, 1.0f }; + } + } + image.setPixel(x, y, encodeNormal(normal)); + } + } + return image; + } + + /// Build a normal from neighbouring heights (heightmap -> normal conversion). + [[nodiscard]] static math::Vector3 calculateNormalFromHeight(f32 heightL, f32 heightR, f32 heightU, f32 heightD, f32 scale = 1.0f) { + const f32 dx = (heightR - heightL) * scale; + const f32 dy = (heightD - heightU) * scale; + return math::normalize(math::Vector3{ -dx, -dy, 1.0f }); + } + +private: + /// Encode a unit normal into a packed RGB color ((n*0.5+0.5)*255), alpha 255. + [[nodiscard]] static Color32 encodeNormal(math::Vector3 n) { + return Color32(static_cast((n.x * 0.5f + 0.5f) * 255.0f), + static_cast((n.y * 0.5f + 0.5f) * 255.0f), + static_cast((n.z * 0.5f + 0.5f) * 255.0f), 255); + } + + /// LCG-style integer hash -> f32 in [-1, 1] (deterministic; for value noise). + [[nodiscard]] static f32 hashToFloat(i32 value) { + u32 hash = static_cast(value); + hash = hash * 1103515245u + 12345u; + hash = (hash >> 16) ^ hash; + hash = hash * 0x85ebca6bu; + hash = (hash >> 13) ^ hash; + return (static_cast(hash & 0x7FFFFFFFu) / static_cast(0x7FFFFFFF)) * 2.0f - 1.0f; + } + + [[nodiscard]] usize pixelOffset(u32 x, u32 y) const { + return static_cast(y * m_width + x) * bytesPerPixel(m_format); + } + + u32 m_width = 0, m_height = 0; + PixelFormat m_format = PixelFormat::RGBA8; + ImageColorSpace m_colorSpace = ImageColorSpace::Srgb; + std::vector m_data; +}; + +} // namespace draco::image diff --git a/Engine/cpp/Runtime/Rendering/Image/ImageData.cppm b/Engine/cpp/Runtime/Rendering/Image/ImageData.cppm new file mode 100644 index 00000000..d46f5127 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Image/ImageData.cppm @@ -0,0 +1,109 @@ +/// Image data types: ImageColorSpace, OwnedImageData, ImageDataRef. + +module; + +#include + +#include +#include +#include + +export module image:image_data; + +import core; +import :pixel_format; + +using namespace draco; + +export namespace draco::image { + +/// Color space of the pixel data. +enum class ImageColorSpace : u32 { + /// sRGB-encoded (photos, UI). GPU decodes sRGB→linear on sample. + Srgb, + /// Linear data (normal maps, masks, HDR). Sampled as-is. + Linear, +}; + +/// Abstract interface for image data. Implemented by OwnedImageData (owning) +/// and ImageDataRef (non-owning). Allows renderers and other consumers to +/// accept either type through a common base. +class ImageData { +public: + virtual ~ImageData() = default; + [[nodiscard]] virtual u32 width() const = 0; + [[nodiscard]] virtual u32 height() const = 0; + [[nodiscard]] virtual PixelFormat format() const = 0; + [[nodiscard]] virtual std::span pixelData() const = 0; + [[nodiscard]] virtual ImageColorSpace colorSpace() const = 0; +}; + +/// Owns a CPU-side pixel buffer. +class OwnedImageData : public ImageData { +public: + OwnedImageData() = default; + + /// Creates from a copy of the provided data. + OwnedImageData(u32 w, u32 h, PixelFormat fmt, std::span data, + ImageColorSpace cs = ImageColorSpace::Srgb) + : m_width(w), m_height(h), m_format(fmt), m_colorSpace(cs) + { + m_data.resize(data.size()); + if (data.size() > 0) { std::memcpy(m_data.data(), data.data(), data.size()); } + } + + /// Takes ownership of data by move. + OwnedImageData(u32 w, u32 h, PixelFormat fmt, std::vector&& data, + ImageColorSpace cs = ImageColorSpace::Srgb) + : m_width(w), m_height(h), m_format(fmt), m_colorSpace(cs), + m_data(static_cast&&>(data)) {} + + [[nodiscard]] u32 width() const override { return m_width; } + [[nodiscard]] u32 height() const override { return m_height; } + [[nodiscard]] PixelFormat format() const override { return m_format; } + [[nodiscard]] ImageColorSpace colorSpace() const override { return m_colorSpace; } + [[nodiscard]] std::span pixelData() const override { return { m_data.data(), m_data.size() }; } + [[nodiscard]] std::span pixelDataMut() { return { m_data.data(), m_data.size() }; } + [[nodiscard]] u32 dataSize() const { return m_width * m_height * bytesPerPixel(m_format); } + +private: + u32 m_width = 0, m_height = 0; + PixelFormat m_format = PixelFormat::RGBA8; + ImageColorSpace m_colorSpace = ImageColorSpace::Srgb; + std::vector m_data; +}; + +/// References external pixel data (non-owning). +/// Caller must ensure data outlives this reference. +class ImageDataRef : public ImageData { +public: + ImageDataRef() = default; + + /// No pixel data (for GPU-managed textures). + ImageDataRef(u32 w, u32 h, PixelFormat fmt = PixelFormat::RGBA8, + ImageColorSpace cs = ImageColorSpace::Srgb) + : m_width(w), m_height(h), m_format(fmt), m_colorSpace(cs) {} + + /// Points to external pixel data. + ImageDataRef(u32 w, u32 h, PixelFormat fmt, const u8* data, usize length, + ImageColorSpace cs = ImageColorSpace::Srgb) + : m_width(w), m_height(h), m_format(fmt), m_colorSpace(cs), + m_ptr(data), m_length(length) {} + + [[nodiscard]] u32 width() const override { return m_width; } + [[nodiscard]] u32 height() const override { return m_height; } + [[nodiscard]] PixelFormat format() const override { return m_format; } + [[nodiscard]] ImageColorSpace colorSpace() const override { return m_colorSpace; } + [[nodiscard]] std::span pixelData() const override { + return m_ptr ? std::span(m_ptr, m_length) : std::span(); + } + +private: + u32 m_width = 0, m_height = 0; + PixelFormat m_format = PixelFormat::RGBA8; + ImageColorSpace m_colorSpace = ImageColorSpace::Srgb; + const u8* m_ptr = nullptr; + usize m_length = 0; +}; + +} // namespace draco::image diff --git a/Engine/cpp/Runtime/Rendering/Image/ImageDataTests.test.cpp b/Engine/cpp/Runtime/Rendering/Image/ImageDataTests.test.cpp new file mode 100644 index 00000000..d0a0af50 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Image/ImageDataTests.test.cpp @@ -0,0 +1,249 @@ +// NineSlice, ImageAtlasBuilder, PixelFormat. Mirrors the Sedulous assertions so +// the ported lib inherits that suite's coverage. (Test.Assert -> CHECK; Beef +// scope/new -> stack/Array; nullable RectangleI -> const RectI*.) +#include +#include +import core; +import image; + +using namespace draco; +using namespace draco::image; + +// ============================================================ OwnedImageData + +TEST_CASE("image.owned: construct from span") +{ + u8 pixels[16]; + for (i32 i = 0; i < 16; ++i) pixels[i] = static_cast(i); + + OwnedImageData img(2, 2, PixelFormat::RGBA8, std::span(pixels, 16)); + CHECK(img.width() == 2u); + CHECK(img.height() == 2u); + CHECK(img.format() == PixelFormat::RGBA8); + CHECK(img.pixelData().size() == 16u); + CHECK(img.pixelData().data()[0] == 0); + CHECK(img.pixelData().data()[4] == 4); +} + +TEST_CASE("image.owned: construct from array (move)") +{ + std::vector data; + data.resize(8); + data[0] = 255; + data[7] = 128; + OwnedImageData img(2, 1, PixelFormat::RGBA8, move(data)); + CHECK(img.width() == 2u); + CHECK(img.height() == 1u); + CHECK(img.pixelData().data()[0] == 255); + CHECK(img.pixelData().data()[7] == 128); +} + +TEST_CASE("image.owned: R8 format") +{ + std::vector data; data.resize(4); + OwnedImageData img(2, 2, PixelFormat::R8, move(data)); + CHECK(img.format() == PixelFormat::R8); + CHECK(img.pixelData().size() == 4u); +} + +// ==================================================================== NineSlice + +TEST_CASE("image.nineslice: construct four values") +{ + const NineSlice ns(4, 6, 8, 10); + CHECK(ns.left == 4); + CHECK(ns.top == 6); + CHECK(ns.right == 8); + CHECK(ns.bottom == 10); +} + +TEST_CASE("image.nineslice: construct uniform") +{ + const NineSlice ns(5.0f); + CHECK(ns.left == 5); + CHECK(ns.top == 5); + CHECK(ns.right == 5); + CHECK(ns.bottom == 5); +} + +TEST_CASE("image.nineslice: construct symmetric") +{ + const NineSlice ns(3, 7); + CHECK(ns.left == 3); + CHECK(ns.right == 3); + CHECK(ns.top == 7); + CHECK(ns.bottom == 7); +} + +TEST_CASE("image.nineslice: horizontal border") +{ + const NineSlice ns(4, 0, 6, 0); + CHECK(ns.horizontalBorder() == 10); +} + +TEST_CASE("image.nineslice: vertical border") +{ + const NineSlice ns(0, 3, 0, 5); + CHECK(ns.verticalBorder() == 8); +} + +TEST_CASE("image.nineslice: IsValid non-zero") +{ + const NineSlice ns(1, 0, 0, 0); + CHECK(ns.isValid()); +} + +TEST_CASE("image.nineslice: IsValid all-zero") +{ + const NineSlice ns(0, 0, 0, 0); + CHECK_FALSE(ns.isValid()); +} + +// =========================================================== ImageAtlasBuilder + +namespace +{ + OwnedImageData makeImage(u32 w, u32 h, u8 fill) + { + std::vector data; + data.resize(static_cast(w) * h * 4); + for (usize i = 0; i < data.size(); ++i) data[i] = fill; + return OwnedImageData(w, h, PixelFormat::RGBA8, move(data)); + } + + bool overlaps(const RectI& a, const RectI& b) + { + return a.x < b.x + b.width && a.x + a.width > b.x + && a.y < b.y + b.height && a.y + a.height > b.y; + } + + bool isPow2(u32 v) { return v > 0 && (v & (v - 1)) == 0; } +} + +TEST_CASE("image.atlas: empty build") +{ + ImageAtlasBuilder builder; + CHECK(builder.build()); + REQUIRE(builder.atlas() != nullptr); + CHECK(builder.atlas()->width() >= 1u); + CHECK(builder.atlas()->height() >= 1u); +} + +TEST_CASE("image.atlas: single image") +{ + const OwnedImageData img = makeImage(32, 32, 128); + + ImageAtlasBuilder builder; + builder.addImage(u8"test", &img); + CHECK(builder.build()); + + const RectI* region = builder.getRegion(u8"test"); + REQUIRE(region != nullptr); + CHECK(region->width == 32); + CHECK(region->height == 32); +} + +TEST_CASE("image.atlas: multiple images do not overlap") +{ + const OwnedImageData img1 = makeImage(64, 64, 100); + const OwnedImageData img2 = makeImage(32, 32, 200); + const OwnedImageData img3 = makeImage(48, 48, 150); + + ImageAtlasBuilder builder; + builder.addImage(u8"a", &img1); + builder.addImage(u8"b", &img2); + builder.addImage(u8"c", &img3); + CHECK(builder.build()); + + const RectI* r1 = builder.getRegion(u8"a"); + const RectI* r2 = builder.getRegion(u8"b"); + const RectI* r3 = builder.getRegion(u8"c"); + REQUIRE(r1 != nullptr); + REQUIRE(r2 != nullptr); + REQUIRE(r3 != nullptr); + + CHECK_FALSE(overlaps(*r1, *r2)); + CHECK_FALSE(overlaps(*r1, *r3)); + CHECK_FALSE(overlaps(*r2, *r3)); +} + +TEST_CASE("image.atlas: dimensions are powers of two") +{ + const OwnedImageData img = makeImage(100, 100, 0); + + ImageAtlasBuilder builder; + builder.addImage(u8"big", &img); + CHECK(builder.build()); + + CHECK(isPow2(builder.atlas()->width())); + CHECK(isPow2(builder.atlas()->height())); +} + +TEST_CASE("image.atlas: grows when needed") +{ + ImageAtlasBuilder builder(256, 4096); + std::vector images; + images.reserve(20); + for (i32 i = 0; i < 20; ++i) + images.push_back(makeImage(64, 64, static_cast(i))); + + // Names must outlive build(); build them up front. + for (i32 i = 0; i < 20; ++i) + { + std::u8string name = u8"img"; + const std::string digits = std::to_string(i); + name.append(reinterpret_cast(digits.data()), digits.size()); + builder.addImage(name, &images[static_cast(i)]); + } + + CHECK(builder.build()); + // 20 x 64x64 = 81920 px; 256x256=65536 too small -> should have grown. + CHECK((builder.atlas()->width() >= 512u || builder.atlas()->height() >= 512u)); +} + +TEST_CASE("image.atlas: GetRegion missing") +{ + ImageAtlasBuilder builder; + builder.build(); + CHECK(builder.getRegion(u8"nonexistent") == nullptr); +} + +TEST_CASE("image.atlas: pixel data copied") +{ + const OwnedImageData img = makeImage(4, 4, 42); + + ImageAtlasBuilder builder(256); + builder.addImage(u8"px", &img); + CHECK(builder.build()); + + const RectI* region = builder.getRegion(u8"px"); + REQUIRE(region != nullptr); + const ImageData* atlas = builder.atlas(); + const u32 stride = atlas->width() * 4; + + const usize offset = static_cast(region->y) * stride + static_cast(region->x) * 4; + CHECK(atlas->pixelData().data()[offset] == 42); +} + +TEST_CASE("image.atlas: large image (bigger than min size)") +{ + const OwnedImageData img = makeImage(300, 300, 77); + + ImageAtlasBuilder builder(256, 4096); + builder.addImage(u8"large", &img); + CHECK(builder.build()); + + const RectI* region = builder.getRegion(u8"large"); + REQUIRE(region != nullptr); + CHECK(region->width == 300); + CHECK(region->height == 300); + CHECK(builder.atlas()->width() >= 302u); // 300 + padding +} + +// =================================================================== PixelFormat + +TEST_CASE("image.pixelformat: distinct values") +{ + CHECK(PixelFormat::R8 != PixelFormat::RGBA8); + CHECK(PixelFormat::RGBA8 != PixelFormat::BGRA8); +} diff --git a/Engine/cpp/Runtime/Rendering/Image/ImageManipulationTests.test.cpp b/Engine/cpp/Runtime/Rendering/Image/ImageManipulationTests.test.cpp new file mode 100644 index 00000000..fd533dc0 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Image/ImageManipulationTests.test.cpp @@ -0,0 +1,240 @@ +// get/set, clear/fill, flips, format conversion, channel helpers. Mirrors the +// Sedulous assertions (Test.Assert -> CHECK; pixel.R/G/B/A -> .r/.g/.b/.a; +// ConvertFormat returns Image directly so no `.Value`; Sedulous Color32.Lime +// (0,255,0) maps to our Color32::Green). +#include +import core; +import image; + +using namespace draco; +using namespace draco::image; + +TEST_CASE("image.manip: creation") +{ + const Image image(64, 64, PixelFormat::RGBA8); + CHECK(image.width() == 64u); + CHECK(image.height() == 64u); + CHECK(image.format() == PixelFormat::RGBA8); + CHECK(image.pixelCount() == 64u * 64u); + CHECK(image.dataSize() == 64u * 64u * 4u); +} + +TEST_CASE("image.manip: creation with data") +{ + u8 data[4 * 4] = {}; // 2x2 RGBA + data[0] = 255; data[1] = 0; data[2] = 0; data[3] = 255; // pixel 0 = red + data[4] = 0; data[5] = 255; data[6] = 0; data[7] = 255; // pixel 1 = green + + const Image image(2, 2, PixelFormat::RGBA8, std::span(data, 16)); + const Color32 pixel0 = image.getPixel(0, 0); + CHECK((pixel0.r == 255 && pixel0.g == 0 && pixel0.b == 0 && pixel0.a == 255)); + const Color32 pixel1 = image.getPixel(1, 0); + CHECK((pixel1.r == 0 && pixel1.g == 255 && pixel1.b == 0 && pixel1.a == 255)); +} + +TEST_CASE("image.manip: copy") +{ + Image original(4, 4, PixelFormat::RGBA8); + original.setPixel(0, 0, Color32::Red); + original.setPixel(1, 1, Color32::Green); // Sedulous Color32.Lime + + const Image copy(original); + CHECK(copy.width() == original.width()); + CHECK(copy.height() == original.height()); + CHECK(copy.format() == original.format()); + + const Color32 pixel00 = copy.getPixel(0, 0); + CHECK((pixel00.r == 255 && pixel00.g == 0 && pixel00.b == 0)); + const Color32 pixel11 = copy.getPixel(1, 1); + CHECK((pixel11.r == 0 && pixel11.g == 255 && pixel11.b == 0)); +} + +TEST_CASE("image.manip: get/set pixel") +{ + Image image(8, 8, PixelFormat::RGBA8); + image.setPixel(0, 0, Color32::Red); + image.setPixel(1, 0, Color32::Green); // Lime + image.setPixel(2, 0, Color32::Blue); + image.setPixel(3, 0, Color32::White); + image.setPixel(4, 0, Color32::Black); + image.setPixel(5, 0, Color32(128, 64, 32, 200)); + + const Color32 red = image.getPixel(0, 0); + CHECK((red.r == 255 && red.g == 0 && red.b == 0 && red.a == 255)); + const Color32 green = image.getPixel(1, 0); + CHECK((green.r == 0 && green.g == 255 && green.b == 0 && green.a == 255)); + const Color32 blue = image.getPixel(2, 0); + CHECK((blue.r == 0 && blue.g == 0 && blue.b == 255 && blue.a == 255)); + const Color32 white = image.getPixel(3, 0); + CHECK((white.r == 255 && white.g == 255 && white.b == 255 && white.a == 255)); + const Color32 black = image.getPixel(4, 0); + CHECK((black.r == 0 && black.g == 0 && black.b == 0 && black.a == 255)); + const Color32 custom = image.getPixel(5, 0); + CHECK((custom.r == 128 && custom.g == 64 && custom.b == 32 && custom.a == 200)); +} + +TEST_CASE("image.manip: get pixel out of bounds returns black") +{ + const Image image(4, 4, PixelFormat::RGBA8); + CHECK(image.getPixel(10, 10) == Color32::Black); +} + +TEST_CASE("image.manip: set pixel out of bounds is a no-op") +{ + Image image(4, 4, PixelFormat::RGBA8); + image.setPixel(100, 100, Color32::Red); // must not crash/corrupt + + image.setPixel(0, 0, Color32::Green); // Lime + const Color32 pixel = image.getPixel(0, 0); + CHECK((pixel.r == 0 && pixel.g == 255 && pixel.b == 0)); +} + +TEST_CASE("image.manip: clear with color") +{ + Image image(4, 4, PixelFormat::RGBA8); + image.setPixel(0, 0, Color32::Red); + image.setPixel(1, 1, Color32::Green); + + image.clear(Color32::Blue); + for (u32 y = 0; y < 4; ++y) + for (u32 x = 0; x < 4; ++x) + { + const Color32 pixel = image.getPixel(x, y); + CHECK((pixel.r == 0 && pixel.g == 0 && pixel.b == 255 && pixel.a == 255)); + } +} + +TEST_CASE("image.manip: fill color") +{ + Image image(4, 4, PixelFormat::RGBA8); + image.fillColor(Color32(100, 150, 200, 250)); + for (u32 y = 0; y < 4; ++y) + for (u32 x = 0; x < 4; ++x) + { + const Color32 pixel = image.getPixel(x, y); + CHECK((pixel.r == 100 && pixel.g == 150 && pixel.b == 200 && pixel.a == 250)); + } +} + +TEST_CASE("image.manip: flip vertical") +{ + Image image(2, 4, PixelFormat::RGBA8); + image.setPixel(0, 0, Color32::Red); + image.setPixel(1, 0, Color32::Red); + image.setPixel(0, 3, Color32::Blue); + image.setPixel(1, 3, Color32::Blue); + + image.flipVertical(); + + const Color32 topLeft = image.getPixel(0, 0); + CHECK((topLeft.b == 255 && topLeft.r == 0)); + const Color32 bottomLeft = image.getPixel(0, 3); + CHECK((bottomLeft.r == 255 && bottomLeft.b == 0)); +} + +TEST_CASE("image.manip: flip horizontal") +{ + Image image(4, 2, PixelFormat::RGBA8); + image.setPixel(0, 0, Color32::Red); + image.setPixel(0, 1, Color32::Red); + image.setPixel(3, 0, Color32::Blue); + image.setPixel(3, 1, Color32::Blue); + + image.flipHorizontal(); + + const Color32 left = image.getPixel(0, 0); + CHECK((left.b == 255 && left.r == 0)); + const Color32 right = image.getPixel(3, 0); + CHECK((right.r == 255 && right.b == 0)); +} + +TEST_CASE("image.manip: convert format RGBA8 -> RGB8") +{ + Image rgba(4, 4, PixelFormat::RGBA8); + rgba.setPixel(0, 0, Color32::Red); + rgba.setPixel(1, 0, Color32::Green); + rgba.setPixel(2, 0, Color32::Blue); + rgba.setPixel(3, 0, Color32(100, 150, 200, 255)); + + const Image rgb = rgba.convertFormat(PixelFormat::RGB8); + CHECK(rgb.format() == PixelFormat::RGB8); + CHECK((rgb.width() == 4u && rgb.height() == 4u)); + + const Color32 red = rgb.getPixel(0, 0); + CHECK((red.r == 255 && red.g == 0 && red.b == 0)); + const Color32 custom = rgb.getPixel(3, 0); + CHECK((custom.r == 100 && custom.g == 150 && custom.b == 200)); +} + +TEST_CASE("image.manip: convert to same format") +{ + Image original(4, 4, PixelFormat::RGBA8); + original.setPixel(0, 0, Color32::Red); + + const Image converted = original.convertFormat(PixelFormat::RGBA8); + CHECK(converted.format() == PixelFormat::RGBA8); + const Color32 pixel = converted.getPixel(0, 0); + CHECK((pixel.r == 255 && pixel.g == 0 && pixel.b == 0)); +} + +TEST_CASE("image.manip: HasAlpha") +{ + CHECK(Image(4, 4, PixelFormat::RGBA8).hasAlpha()); + CHECK_FALSE(Image(4, 4, PixelFormat::RGB8).hasAlpha()); + CHECK_FALSE(Image(4, 4, PixelFormat::R8).hasAlpha()); + CHECK(Image(4, 4, PixelFormat::BGRA8).hasAlpha()); +} + +TEST_CASE("image.manip: GetChannelCount") +{ + CHECK(Image(4, 4, PixelFormat::R8).getChannelCount() == 1); + CHECK(Image(4, 4, PixelFormat::RG8).getChannelCount() == 2); + CHECK(Image(4, 4, PixelFormat::RGB8).getChannelCount() == 3); + CHECK(Image(4, 4, PixelFormat::RGBA8).getChannelCount() == 4); +} + +TEST_CASE("image.manip: GetBytesPerPixel") +{ + CHECK(Image::getBytesPerPixel(PixelFormat::R8) == 1); + CHECK(Image::getBytesPerPixel(PixelFormat::RG8) == 2); + CHECK(Image::getBytesPerPixel(PixelFormat::RGB8) == 3); + CHECK(Image::getBytesPerPixel(PixelFormat::RGBA8) == 4); + CHECK(Image::getBytesPerPixel(PixelFormat::BGR8) == 3); + CHECK(Image::getBytesPerPixel(PixelFormat::BGRA8) == 4); + CHECK(Image::getBytesPerPixel(PixelFormat::R32F) == 4); + CHECK(Image::getBytesPerPixel(PixelFormat::RGBA32F) == 16); +} + +TEST_CASE("image.manip: RGB8 format preserves RGB, alpha 255") +{ + Image image(4, 4, PixelFormat::RGB8); + image.setPixel(0, 0, Color32(100, 150, 200, 255)); + const Color32 pixel = image.getPixel(0, 0); + CHECK((pixel.r == 100 && pixel.g == 150 && pixel.b == 200 && pixel.a == 255)); +} + +TEST_CASE("image.manip: BGR8 format swaps channels correctly") +{ + Image image(4, 4, PixelFormat::BGR8); + image.setPixel(0, 0, Color32(100, 150, 200, 255)); + const Color32 pixel = image.getPixel(0, 0); + CHECK((pixel.r == 100 && pixel.g == 150 && pixel.b == 200)); +} + +TEST_CASE("image.manip: BGRA8 format") +{ + Image image(4, 4, PixelFormat::BGRA8); + image.setPixel(0, 0, Color32(100, 150, 200, 128)); + const Color32 pixel = image.getPixel(0, 0); + CHECK((pixel.r == 100 && pixel.g == 150 && pixel.b == 200 && pixel.a == 128)); +} + +TEST_CASE("image.manip: R8 format averages to grayscale") +{ + Image image(4, 4, PixelFormat::R8); + image.setPixel(0, 0, Color32(90, 120, 150, 255)); // average = 120 + const Color32 pixel = image.getPixel(0, 0); + CHECK(pixel.r == pixel.g); + CHECK(pixel.g == pixel.b); + CHECK(pixel.a == 255); +} diff --git a/Engine/cpp/Runtime/Rendering/Image/ImageModule.cppm b/Engine/cpp/Runtime/Rendering/Image/ImageModule.cppm new file mode 100644 index 00000000..1b2c99a2 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Image/ImageModule.cppm @@ -0,0 +1,9 @@ +/// Primary module for image. Re-exports all partitions. + +export module image; + +export import :pixel_format; +export import :image_data; +export import :image; +export import :nine_slice; +export import :atlas_builder; diff --git a/Engine/cpp/Runtime/Rendering/Image/NineSlice.cppm b/Engine/cpp/Runtime/Rendering/Image/NineSlice.cppm new file mode 100644 index 00000000..0cf1eacc --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Image/NineSlice.cppm @@ -0,0 +1,42 @@ +// Draconic::Image - :nine_slice partition. +// +// NineSlice: border insets for 9-slice image scaling - corners stay fixed, +// edges stretch along one axis, the center stretches both ways. Ported from +// Sedulous.Images/NineSlice.bf. + +module; + +export module image:nine_slice; + +import core; + +using namespace draco; + +export namespace draco::image +{ + /// Border insets (in pixels) for 9-slice image scaling. + struct NineSlice + { + f32 left = 0.0f; ///< Left border width. + f32 top = 0.0f; ///< Top border height. + f32 right = 0.0f; ///< Right border width. + f32 bottom = 0.0f; ///< Bottom border height. + + constexpr NineSlice() noexcept = default; + + constexpr NineSlice(f32 inLeft, f32 inTop, f32 inRight, f32 inBottom) noexcept + : left(inLeft), top(inTop), right(inRight), bottom(inBottom) {} + + /// Uniform borders on all sides. + explicit constexpr NineSlice(f32 all) noexcept + : left(all), top(all), right(all), bottom(all) {} + + /// Horizontal and vertical borders. + constexpr NineSlice(f32 horizontal, f32 vertical) noexcept + : left(horizontal), top(vertical), right(horizontal), bottom(vertical) {} + + [[nodiscard]] constexpr f32 horizontalBorder() const noexcept { return left + right; } + [[nodiscard]] constexpr f32 verticalBorder() const noexcept { return top + bottom; } + [[nodiscard]] constexpr bool isValid() const noexcept { return left > 0.0f || top > 0.0f || right > 0.0f || bottom > 0.0f; } + }; +} diff --git a/Engine/cpp/Runtime/Rendering/Image/NormalMapTests.test.cpp b/Engine/cpp/Runtime/Rendering/Image/NormalMapTests.test.cpp new file mode 100644 index 00000000..d7970cf7 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Image/NormalMapTests.test.cpp @@ -0,0 +1,200 @@ +// generators + CalculateNormalFromHeight. Mirrors the Sedulous assertions +// (Test.Assert -> CHECK; pixel.R/G/B/A -> .r/.g/.b/.a; math::Vector3.X/Y/Z -> .x/.y/.z). +#include +import core; +import image; + +using namespace draco; +using namespace draco::image; + +namespace +{ + // Neutral up-normal (0,0,1) encoded as (128,128,255). + constexpr u8 NeutralX = 128; + constexpr u8 NeutralY = 128; + constexpr u8 NeutralZ = 255; +} + +TEST_CASE("image.normalmap: flat is uniform neutral") +{ + const Image image = Image::createFlatNormalMap(32, 32); + CHECK(image.width() == 32u); + CHECK(image.height() == 32u); + CHECK(image.format() == PixelFormat::RGBA8); + + for (u32 y = 0; y < 32; ++y) + for (u32 x = 0; x < 32; ++x) + { + const Color32 pixel = image.getPixel(x, y); + CHECK(pixel.r == NeutralX); + CHECK(pixel.g == NeutralY); + CHECK(pixel.b == NeutralZ); + CHECK(pixel.a == 255); + } +} + +TEST_CASE("image.normalmap: flat honors format") +{ + const Image image = Image::createFlatNormalMap(16, 16, PixelFormat::RGB8); + CHECK(image.format() == PixelFormat::RGB8); + const Color32 pixel = image.getPixel(8, 8); + CHECK((pixel.r == NeutralX && pixel.g == NeutralY && pixel.b == NeutralZ)); +} + +TEST_CASE("image.normalmap: wave varies in X and Y, Z stays up") +{ + const Image image = Image::createWaveNormalMap(64, 64, 4.0f, 4.0f, 0.3f); + CHECK(image.width() == 64u); + CHECK(image.height() == 64u); + + bool hasXVariation = false; + bool hasYVariation = false; + for (u32 y = 0; y < 64; ++y) + for (u32 x = 0; x < 64; ++x) + { + const Color32 pixel = image.getPixel(x, y); + if (abs(static_cast(pixel.r) - static_cast(NeutralX)) > 5) hasXVariation = true; + if (abs(static_cast(pixel.g) - static_cast(NeutralY)) > 5) hasYVariation = true; + CHECK(pixel.b >= 128); + } + CHECK(hasXVariation); + CHECK(hasYVariation); +} + +TEST_CASE("image.normalmap: brick has variation, Z stays up") +{ + const Image image = Image::createBrickNormalMap(64, 64, 4, 2, 0.3f); + CHECK(image.width() == 64u); + CHECK(image.height() == 64u); + + bool hasVariation = false; + for (u32 y = 0; y < 64; ++y) + for (u32 x = 0; x < 64; ++x) + { + const Color32 pixel = image.getPixel(x, y); + if (pixel.r != NeutralX || pixel.g != NeutralY || pixel.b != NeutralZ) hasVariation = true; + CHECK(pixel.b >= 128); + } + CHECK(hasVariation); +} + +TEST_CASE("image.normalmap: circular bump has variation, Z stays up") +{ + const Image image = Image::createCircularBumpNormalMap(64, 64, 0.5f, 2.0f); + CHECK(image.width() == 64u); + CHECK(image.height() == 64u); + + bool hasVariation = false; + for (u32 y = 0; y < 64 && !hasVariation; ++y) + for (u32 x = 0; x < 64 && !hasVariation; ++x) + { + const Color32 pixel = image.getPixel(x, y); + if (pixel.r != NeutralX || pixel.g != NeutralY) hasVariation = true; + } + CHECK(hasVariation); + + for (u32 y = 0; y < 64; ++y) + for (u32 x = 0; x < 64; ++x) + CHECK(image.getPixel(x, y).b >= 128); +} + +TEST_CASE("image.normalmap: noise varies across most pixels, Z stays up") +{ + const Image image = Image::createNoiseNormalMap(64, 64, 0.1f, 0.2f, 12345); + CHECK(image.width() == 64u); + CHECK(image.height() == 64u); + + bool hasVariation = false; + i32 variationCount = 0; + for (u32 y = 0; y < 64; ++y) + for (u32 x = 0; x < 64; ++x) + { + const Color32 pixel = image.getPixel(x, y); + if (pixel.r != NeutralX || pixel.g != NeutralY) { hasVariation = true; ++variationCount; } + CHECK(pixel.b >= 128); + } + CHECK(hasVariation); + CHECK(variationCount > 64 * 64 / 2); +} + +TEST_CASE("image.normalmap: noise is deterministic for a seed") +{ + const Image image1 = Image::createNoiseNormalMap(32, 32, 0.1f, 0.2f, 42); + const Image image2 = Image::createNoiseNormalMap(32, 32, 0.1f, 0.2f, 42); + bool identical = true; + for (u32 y = 0; y < 32; ++y) + for (u32 x = 0; x < 32; ++x) + { + const Color32 p1 = image1.getPixel(x, y); + const Color32 p2 = image2.getPixel(x, y); + if (!(p1.r == p2.r && p1.g == p2.g && p1.b == p2.b)) identical = false; + } + CHECK(identical); +} + +TEST_CASE("image.normalmap: different seeds differ") +{ + const Image image1 = Image::createNoiseNormalMap(32, 32, 0.1f, 0.2f, 100); + const Image image2 = Image::createNoiseNormalMap(32, 32, 0.1f, 0.2f, 200); + bool hasDifference = false; + for (u32 y = 0; y < 32 && !hasDifference; ++y) + for (u32 x = 0; x < 32 && !hasDifference; ++x) + { + const Color32 p1 = image1.getPixel(x, y); + const Color32 p2 = image2.getPixel(x, y); + if (p1.r != p2.r || p1.g != p2.g || p1.b != p2.b) hasDifference = true; + } + CHECK(hasDifference); +} + +TEST_CASE("image.normalmap: test pattern varies in X and Y, Z stays up") +{ + const Image image = Image::createTestPatternNormalMap(64, 64); + CHECK(image.width() == 64u); + CHECK(image.height() == 64u); + + bool hasXVariation = false; + bool hasYVariation = false; + for (u32 y = 0; y < 64; ++y) + for (u32 x = 0; x < 64; ++x) + { + const Color32 pixel = image.getPixel(x, y); + if (pixel.r != NeutralX) hasXVariation = true; + if (pixel.g != NeutralY) hasYVariation = true; + CHECK(pixel.b >= 128); + } + CHECK(hasXVariation); + CHECK(hasYVariation); +} + +TEST_CASE("image.normalmap: CalculateNormalFromHeight") +{ + const math::Vector3 flatNormal = Image::calculateNormalFromHeight(0.5f, 0.5f, 0.5f, 0.5f); + CHECK(abs(flatNormal.x) < 0.01f); + CHECK(abs(flatNormal.y) < 0.01f); + CHECK(flatNormal.z > 0.99f); + + const math::Vector3 rightSlope = Image::calculateNormalFromHeight(0.0f, 1.0f, 0.5f, 0.5f); + CHECK(rightSlope.x < 0.0f); // normal points against the slope + CHECK(rightSlope.z > 0.0f); + + const math::Vector3 downSlope = Image::calculateNormalFromHeight(0.5f, 0.5f, 0.0f, 1.0f); + CHECK(downSlope.y < 0.0f); + CHECK(downSlope.z > 0.0f); +} + +TEST_CASE("image.normalmap: all generators keep Z positive (B >= 128)") +{ + std::vector images; + images.push_back(Image::createFlatNormalMap(16, 16)); + images.push_back(Image::createWaveNormalMap(16, 16)); + images.push_back(Image::createBrickNormalMap(16, 16)); + images.push_back(Image::createCircularBumpNormalMap(16, 16)); + images.push_back(Image::createNoiseNormalMap(16, 16)); + images.push_back(Image::createTestPatternNormalMap(16, 16)); + + for (usize i = 0; i < images.size(); ++i) + for (u32 y = 0; y < 16; ++y) + for (u32 x = 0; x < 16; ++x) + CHECK(images[i].getPixel(x, y).b >= 128); +} diff --git a/Engine/cpp/Runtime/Rendering/Image/PixelFormat.cppm b/Engine/cpp/Runtime/Rendering/Image/PixelFormat.cppm new file mode 100644 index 00000000..f18c0412 --- /dev/null +++ b/Engine/cpp/Runtime/Rendering/Image/PixelFormat.cppm @@ -0,0 +1,55 @@ +/// Pixel format enum for CPU-side image data. + +export module image:pixel_format; + +import core; + +using namespace draco; + +export namespace draco::image { + +enum class PixelFormat : u32 { + R8, RG8, RGB8, RGBA8, + R16F, RG16F, RGB16F, RGBA16F, + R32F, RG32F, RGB32F, RGBA32F, + BGR8, BGRA8, +}; + +[[nodiscard]] constexpr u32 bytesPerPixel(PixelFormat f) { + switch (f) { + case PixelFormat::R8: return 1; + case PixelFormat::RG8: return 2; + case PixelFormat::RGB8: return 3; + case PixelFormat::BGR8: return 3; + case PixelFormat::RGBA8: return 4; + case PixelFormat::BGRA8: return 4; + case PixelFormat::R16F: return 2; + case PixelFormat::RG16F: return 4; + case PixelFormat::RGB16F: return 6; + case PixelFormat::RGBA16F: return 8; + case PixelFormat::R32F: return 4; + case PixelFormat::RG32F: return 8; + case PixelFormat::RGB32F: return 12; + case PixelFormat::RGBA32F: return 16; + } + return 4; +} + +[[nodiscard]] constexpr u32 channelCount(PixelFormat f) { + switch (f) { + case PixelFormat::R8: case PixelFormat::R16F: case PixelFormat::R32F: return 1; + case PixelFormat::RG8: case PixelFormat::RG16F: case PixelFormat::RG32F: return 2; + case PixelFormat::RGB8: case PixelFormat::BGR8: + case PixelFormat::RGB16F: case PixelFormat::RGB32F: return 3; + case PixelFormat::RGBA8: case PixelFormat::BGRA8: + case PixelFormat::RGBA16F: case PixelFormat::RGBA32F: return 4; + } + return 0; +} + +[[nodiscard]] constexpr bool hasAlpha(PixelFormat f) { + return f == PixelFormat::RGBA8 || f == PixelFormat::BGRA8 || + f == PixelFormat::RGBA16F || f == PixelFormat::RGBA32F; +} + +} // namespace draco::image 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/Engine/cpp/ThirdParty/stb/stb_image.h b/Engine/cpp/ThirdParty/stb/stb_image.h index d4b5c87d..9eedabed 100644 --- a/Engine/cpp/ThirdParty/stb/stb_image.h +++ b/Engine/cpp/ThirdParty/stb/stb_image.h @@ -7985,4 +7985,4 @@ AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ -*/ \ No newline at end of file +*/ diff --git a/Engine/cpp/ThirdParty/stb/stb_image_write.h b/Engine/cpp/ThirdParty/stb/stb_image_write.h new file mode 100644 index 00000000..e4b32ed1 --- /dev/null +++ b/Engine/cpp/ThirdParty/stb/stb_image_write.h @@ -0,0 +1,1724 @@ +/* stb_image_write - v1.16 - public domain - http://nothings.org/stb + writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio or a callback. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation; though providing a custom + zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. + This library is designed for source code compactness and simplicity, + not optimal image file size or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can #define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function + for PNG compression (instead of the builtin one), it must have the following signature: + unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); + The returned data will be freed with STBIW_FREE() (free() by default), + so it must be heap allocated with STBIW_MALLOC() (malloc() by default), + +UNICODE: + + If compiling for Windows and you wish to use Unicode filenames, compile + with + #define STBIW_WINDOWS_UTF8 + and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert + Windows wchar_t filenames to utf8. + +USAGE: + + There are five functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically + + There are also five equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can configure it with these global variables: + int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE + int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression + int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode + + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + PNG allows you to set the deflate compression level by setting the global + variable 'stbi_write_png_compression_level' (it defaults to 8). + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + + Sean Barrett - PNG/BMP/TGA + Baldur Karlsson - HDR + Jean-Sebastien Guay - TGA monochrome + Tim Kelsey - misc enhancements + Alan Hickman - TGA RLE + Emmanuel Julien - initial file IO callback implementation + Jon Olick - original jo_jpeg.cpp code + Daniel Gibson - integrate JPEG, allow external zlib + Aarni Koskela - allow choosing PNG filter + + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + github:xeekworx + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + Andrew Kensler + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#include + +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' +#ifndef STBIWDEF +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#ifdef __cplusplus +#define STBIWDEF extern "C" +#else +#define STBIWDEF extern +#endif +#endif +#endif + +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations +STBIWDEF int stbi_write_tga_with_rle; +STBIWDEF int stbi_write_png_compression_level; +STBIWDEF int stbi_write_force_png_filter; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); + +#ifdef STBIW_WINDOWS_UTF8 +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + +STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_png_compression_level = 8; +static int stbi_write_tga_with_rle = 1; +static int stbi_write_force_png_filter = -1; +#else +int stbi_write_png_compression_level = 8; +int stbi_write_tga_with_rle = 1; +int stbi_write_force_png_filter = -1; +#endif + +static int stbi__flip_vertically_on_write = 0; + +STBIWDEF void stbi_flip_vertically_on_write(int flag) +{ + stbi__flip_vertically_on_write = flag; +} + +typedef struct +{ + stbi_write_func *func; + void *context; + unsigned char buffer[64]; + int buf_used; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) +#ifdef __cplusplus +#define STBIW_EXTERN extern "C" +#else +#define STBIW_EXTERN extern +#endif +STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbiw__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = stbiw__fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write_flush(stbi__write_context *s) +{ + if (s->buf_used) { + s->func(s->context, &s->buffer, s->buf_used); + s->buf_used = 0; + } +} + +static void stbiw__putc(stbi__write_context *s, unsigned char c) +{ + s->func(s->context, &c, 1); +} + +static void stbiw__write1(stbi__write_context *s, unsigned char a) +{ + if ((size_t)s->buf_used + 1 > sizeof(s->buffer)) + stbiw__write_flush(s); + s->buffer[s->buf_used++] = a; +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + int n; + if ((size_t)s->buf_used + 3 > sizeof(s->buffer)) + stbiw__write_flush(s); + n = s->buf_used; + s->buf_used = n+3; + s->buffer[n+0] = a; + s->buffer[n+1] = b; + s->buffer[n+2] = c; +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + stbiw__write1(s, d[comp - 1]); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + stbiw__write1(s, d[0]); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + stbiw__write1(s, d[comp - 1]); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (stbi__flip_vertically_on_write) + vdir *= -1; + + if (vdir < 0) { + j_end = -1; j = y-1; + } else { + j_end = y; j = 0; + } + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + stbiw__write_flush(s); + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + if (comp != 4) { + // write RGB bitmap + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header + } else { + // RGBA bitmaps need a v4 header + // use BI_BITFIELDS mode with 32bpp and alpha mask + // (straight BI_RGB with alpha mask doesn't work in most readers) + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0, + "11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444", + 'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header + 108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header + } +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i,j,k; + int jend, jdir; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + if (stbi__flip_vertically_on_write) { + j = 0; + jend = y; + jdir = 1; + } else { + j = y-1; + jend = -1; + jdir = -1; + } + for (; j != jend; j += jdir) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + stbiw__write1(s, header); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + stbiw__write1(s, header); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + stbiw__write_flush(s); + } + return 1; +} + +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +#ifndef STBI_WRITE_NO_STDIO + +static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + +#ifdef __STDC_LIB_EXT1__ + len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#else + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#endif + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); + STBIW_FREE(scratch); + return 1; + } +} + +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +#ifndef STBIW_ZLIB_COMPRESS +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (void *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +#endif // STBIW_ZLIB_COMPRESS + +STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ +#ifdef STBIW_ZLIB_COMPRESS + // user provided a zlib compress implementation, use that + return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); +#else // use builtin + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) { best=d; bestloc=hlist[j]; } + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + // store uncompressed instead if compression was worse + if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) { + stbiw__sbn(out) = 2; // truncate to DEFLATE 32K window and FLEVEL = 1 + for (j = 0; j < data_len;) { + int blocklen = data_len - j; + if (blocklen > 32767) blocklen = 32767; + stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression + stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN + stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN + stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8)); + memcpy(out+stbiw__sbn(out), data+j, blocklen); + stbiw__sbn(out) += blocklen; + j += blocklen; + } + } + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } + s1 %= 65521; s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +#endif // STBIW_ZLIB_COMPRESS +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ +#ifdef STBIW_CRC32 + return STBIW_CRC32(buffer, len); +#else + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +#endif +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) +{ + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = (y != 0) ? mapping : firstmap; + int i; + int type = mymap[filter_type]; + unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); + int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + + if (type==0) { + memcpy(line_buffer, z, width*n); + return; + } + + // first loop isn't optimized since it's just one pixel + for (i = 0; i < n; ++i) { + switch (type) { + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + } + switch (type) { + case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; + case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; + case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; + case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } +} + +STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int force_filter = stbi_write_force_png_filter; + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int j,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + if (force_filter >= 5) { + force_filter = -1; + } + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + int filter_type; + if (force_filter > -1) { + filter_type = force_filter; + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); + } else { // Estimate the best filter by running through all of them: + int best_filter = 0, best_filter_val = 0x7fffffff, est, i; + for (filter_type = 0; filter_type < 5; filter_type++) { + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); + + // Estimate the entropy of the line using this filter; the less, the better. + est = 0; + for (i = 0; i < x*n; ++i) { + est += abs((signed char) line_buffer[i]); + } + if (est < best_filter_val) { + best_filter_val = est; + best_filter = filter_type; + } + } + if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); + filter_type = best_filter; + } + } + // when we get here, filter_type contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) filter_type; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + + f = stbiw__fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + + +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, + 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while(bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if(c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val-1 : val; + bits[1] = 1; + while(tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if(end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for(i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; DU[i]==0 && i<=end0pos; ++i) { + } + nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + int nrmarker; + for (nrmarker=1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if(end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; + static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; + static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, + 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; + static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + + int row, col, i, k, subsample; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if(!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + subsample = quality <= 90 ? 1 : 0; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for(i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i]*quality+50)/100; + YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i]*quality+50)/100; + UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for(row = 0, k = 0; row < 8; ++row) { + for(col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), + 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + s->func(s->context, (void*)head0, sizeof(head0)); + s->func(s->context, (void*)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void*)head1, sizeof(head1)); + s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); + s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); + s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + s->func(s->context, (void*)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = {0x7F, 7}; + int DCY=0, DCU=0, DCV=0; + int bitBuf=0, bitCnt=0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + const unsigned char *dataR = (const unsigned char *)data; + const unsigned char *dataG = dataR + ofsG; + const unsigned char *dataB = dataR + ofsB; + int x, y, pos; + if(subsample) { + for(y = 0; y < height; y += 16) { + for(x = 0; x < width; x += 16) { + float Y[256], U[256], V[256]; + for(row = y, pos = 0; row < y+16; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+16; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + + // subsample U,V + { + float subU[64], subV[64]; + int yy, xx; + for(yy = 0, pos = 0; yy < 8; ++yy) { + for(xx = 0; xx < 8; ++xx, ++pos) { + int j = yy*32+xx*2; + subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f; + subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f; + } + } + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + } else { + for(y = 0; y < height; y += 8) { + for(x = 0; x < width; x += 8) { + float Y[64], U[64], V[64]; + for(row = y, pos = 0; row < y+8; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+8; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); +} + + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.16 (2021-07-11) + make Deflate code emit uncompressed blocks when it would otherwise expand + support writing BMPs with alpha channel + 1.15 (2020-07-13) unknown + 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels + 1.13 + 1.12 + 1.11 (2019-08-11) + + 1.10 (2019-02-07) + support utf8 filenames in Windows; fix warnings and platform ifdefs + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ + 1.08 (2018-01-29) + add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter + 1.07 (2017-07-24) + doc fix + 1.06 (2017-07-23) + writing JPEG (using Jon Olick's code) + 1.05 ??? + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/Engine/cpp/ThirdParty/stb/stb_truetype.h b/Engine/cpp/ThirdParty/stb/stb_truetype.h new file mode 100644 index 00000000..90a5c2e2 --- /dev/null +++ b/Engine/cpp/ThirdParty/stb/stb_truetype.h @@ -0,0 +1,5079 @@ +// stb_truetype.h - v1.26 - public domain +// authored from 2009-2021 by Sean Barrett / RAD Game Tools +// +// ======================================================================= +// +// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES +// +// This library does no range checking of the offsets found in the file, +// meaning an attacker can use it to read arbitrary memory. +// +// ======================================================================= +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe +// Cass Everitt Martins Mozeiko github:aloucks +// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam +// Brian Hook Omar Cornut github:vassvik +// Walter van Niftrik Ryan Griege +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. Brian Costabile +// Ken Voskuil (kaesve) Yakov Galka +// +// VERSION HISTORY +// +// 1.26 (2021-08-28) fix broken rasterizer +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places need to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include + #define STBTT_sqrt(x) sqrt(x) + #define STBTT_pow(x,y) pow(x,y) + #endif + + #ifndef STBTT_fmod + #include + #define STBTT_fmod(x,y) fmod(x,y) + #endif + + #ifndef STBTT_cos + #include + #define STBTT_cos(x) cos(x) + #define STBTT_acos(x) acos(x) + #endif + + #ifndef STBTT_fabs + #include + #define STBTT_fabs(x) fabs(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// private structure +typedef struct +{ + unsigned char *data; + int cursor; + int size; +} stbtt__buf; + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); +// Query the font vertical metrics without having to create a font first. + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; +typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION +typedef struct stbrp_rect stbrp_rect; +#endif + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is width * height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. Note that you can call this multiple +// times within a single PackBegin/PackEnd. + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given +// pack context. The default (no oversampling) is achieved by h_oversample=1 +// and v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts +// +// To use with PackFontRangesGather etc., you must set it before calls +// call to PackFontRangesGatherRects. + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); +// If skip != 0, this tells stb_truetype to skip any codepoints for which +// there is no corresponding glyph. If skip=0, which is the default, then +// codepoints without a glyph recived the font's "missing character" glyph, +// typically an empty box by convention. + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); +// Calling these functions in sequence is roughly equivalent to calling +// stbtt_PackFontRanges(). If you more control over the packing of multiple +// fonts, or if you want to pack custom data into a font texture, take a look +// at the source to of stbtt_PackFontRanges() and create a custom version +// using these functions, e.g. call GatherRects multiple times, +// building up a single array of rects, then call PackRects once, +// then call RenderIntoRects repeatedly. This may result in a +// better packing than calling PackFontRanges multiple times +// (or it may not). + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + int skip_missing; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); +// This function will determine the number of fonts in a font file. TrueType +// collection (.ttc) files may contain multiple fonts, while TrueType font +// (.ttf) files only contain one font. The number of fonts can be used for +// indexing with the previous function where the index is between zero and one +// less than the total fonts. If an error occurs, -1 is returned. + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. + +// The following structure is defined publicly so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict +}; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. +// Returns 0 if the character codepoint is not defined in the font. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); +// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 +// table (specific to MS/Windows TTF files). +// +// Returns 1 on success (table present), 0 on failure. + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + +typedef struct stbtt_kerningentry +{ + int glyph1; // use stbtt_FindGlyphIndex + int glyph2; + int advance; +} stbtt_kerningentry; + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); +// Retrieves a complete list of all of the kerning pairs provided by the font +// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. +// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of contours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +STBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl); +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); +// fills svg with the character's SVG data. +// returns data size or 0 if SVG not found. + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); +// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering +// is performed (see stbtt_PackSetOversampling) + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +// rasterize a shape with quadratic beziers into a bitmap +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + +////////////////////////////////////////////////////////////////////////////// +// +// Signed Distance Function (or Field) rendering + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); +// frees the SDF bitmap allocated below + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); +// These functions compute a discretized SDF field for a single character, suitable for storing +// in a single-channel texture, sampling with bilinear filtering, and testing against +// larger than some threshold to produce scalable fonts. +// info -- the font +// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap +// glyph/codepoint -- the character to generate the SDF for +// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), +// which allows effects like bit outlines +// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) +// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) +// if positive, > onedge_value is inside; if negative, < onedge_value is inside +// width,height -- output height & width of the SDF bitmap (including padding) +// xoff,yoff -- output origin of the character +// return value -- a 2D array of bytes 0..255, width*height in size +// +// pixel_dist_scale & onedge_value are a scale & bias that allows you to make +// optimal use of the limited 0..255 for your application, trading off precision +// and special effects. SDF values outside the range 0..255 are clamped to 0..255. +// +// Example: +// scale = stbtt_ScaleForPixelHeight(22) +// padding = 5 +// onedge_value = 180 +// pixel_dist_scale = 180/5.0 = 36.0 +// +// This will create an SDF bitmap in which the character is about 22 pixels +// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled +// shape, sample the SDF at each pixel and fill the pixel if the SDF value +// is greater than or equal to 180/255. (You'll actually want to antialias, +// which is beyond the scope of this example.) Additionally, you can compute +// offset outlines (e.g. to stroke the character border inside & outside, +// or only outside). For example, to fill outside the character up to 3 SDF +// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above +// choice of variables maps a range from 5 pixels outside the shape to +// 2 pixels inside the shape to 0..255; this is intended primarily for apply +// outside effects only (the interior range is needed to allow proper +// antialiasing of the font at *smaller* sizes) +// +// The function computes the SDF analytically at each SDF pixel, not by e.g. +// building a higher-res bitmap and approximating it. In theory the quality +// should be as high as possible for an SDF of this size & representation, but +// unclear if this is true in practice (perhaps building a higher-res bitmap +// and computing from that can allow drop-out prevention). +// +// The algorithm has not been optimized at all, so expect it to be slow +// if computing lots of characters or very large sizes. + + + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +// since most people won't use this, find this table the first time it's needed +static int stbtt__get_svg(stbtt_fontinfo *info) +{ + stbtt_uint32 t; + if (info->svg < 0) { + t = stbtt__find_table(info->data, info->fontstart, "SVG "); + if (t) { + stbtt_uint32 offset = ttULONG(info->data + t + 2); + info->svg = t + offset; + } else { + info->svg = 0; + } + } + return info->svg; +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + info->svg = -1; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start, last; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + last = ttUSHORT(data + endCount + 2*item); + if (unicode_codepoint < start || unicode_codepoint > last) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours < 0) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0 && vertices) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // FALLTHROUGH + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && b0 < 32) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) +{ + stbtt_uint8 *data = info->data + info->kern; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + return ttUSHORT(data+10); +} + +STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) +{ + stbtt_uint8 *data = info->data + info->kern; + int k, length; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + length = ttUSHORT(data+10); + if (table_length < length) + length = table_length; + + for (k = 0; k < length; k++) + { + table[k].glyph1 = ttUSHORT(data+18+(k*6)); + table[k].glyph2 = ttUSHORT(data+20+(k*6)); + table[k].advance = ttSHORT(data+22+(k*6)); + } + + return length; +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch (coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + break; + } + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + break; + } + + default: return -1; // unsupported + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch (classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + break; + } + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + break; + } + + default: + return -1; // Unsupported definition type, return an error. + } + + // "All glyphs not assigned to a class fall into class 0". (OpenType spec) + return 0; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i, sti; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i= pairSetCount) return 0; + + needle=glyph2; + r=pairValueCount-1; + l=0; + + // Binary search. + while (l <= r) { + stbtt_uint16 secondGlyph; + stbtt_uint8 *pairValue; + m = (l + r) >> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } else + return 0; + break; + } + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats? + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + stbtt_uint8 *class1Records, *class2Records; + stbtt_int16 xAdvance; + + if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed + if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed + + class1Records = table + 16; + class2Records = class1Records + 2 * (glyph1class * class2Count); + xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } else + return 0; + break; + } + + default: + return 0; // Unsupported position format + } + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + else if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) +{ + int i; + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); + + int numEntries = ttUSHORT(svg_doc_list); + stbtt_uint8 *svg_docs = svg_doc_list + 2; + + for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) + return svg_doc; + } + return 0; +} + +STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) +{ + stbtt_uint8 *data = info->data; + stbtt_uint8 *svg_doc; + + if (info->svg == 0) + return 0; + + svg_doc = stbtt_FindSVGDoc(info, gl); + if (svg_doc != NULL) { + *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); + return ttULONG(svg_doc + 8); + } else { + return 0; + } +} + +STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) +{ + return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; + #if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; + #elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; + #else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" + #endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width) +{ + STBTT_assert(top_width >= 0); + STBTT_assert(bottom_width >= 0); + return (top_width + bottom_width) / 2.0f * height; +} + +static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1) +{ + return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0); +} + +static float stbtt__sized_triangle_area(float height, float width) +{ + return height * width / 2; +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = (sy1 - sy0) * e->direction; + STBTT_assert(x >= 0 && x < len); + scanline[x] += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f); + scanline_fill[x] += height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, y_final, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + STBTT_assert(dy >= 0); + STBTT_assert(dx >= 0); + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = y_top + dy * (x1+1 - x0); + + // compute intersection with y axis at x2 + y_final = y_top + dy * (x2 - x0); + + // x1 x_top x2 x_bottom + // y_top +------|-----+------------+------------+--------|---+------------+ + // | | | | | | + // | | | | | | + // sy0 | Txxxxx|............|............|............|............| + // y_crossing | *xxxxx.......|............|............|............| + // | | xxxxx..|............|............|............| + // | | /- xx*xxxx........|............|............| + // | | dy < | xxxxxx..|............|............| + // y_final | | \- | xx*xxx.........|............| + // sy1 | | | | xxxxxB...|............| + // | | | | | | + // | | | | | | + // y_bottom +------------+------------+------------+------------+------------+ + // + // goal is to measure the area covered by '.' in each pixel + + // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 + // @TODO: maybe test against sy1 rather than y_bottom? + if (y_crossing > y_bottom) + y_crossing = y_bottom; + + sign = e->direction; + + // area of the rectangle covered from sy0..y_crossing + area = sign * (y_crossing-sy0); + + // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing) + scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top); + + // check if final y_crossing is blown up; no test case for this + if (y_final > y_bottom) { + y_final = y_bottom; + dy = (y_final - y_crossing ) / (x2 - (x1+1)); // if denom=0, y_final = y_crossing, so y_final <= y_bottom + } + + // in second pixel, area covered by line segment found in first pixel + // is always a rectangle 1 wide * the height of that line segment; this + // is exactly what the variable 'area' stores. it also gets a contribution + // from the line segment within it. the THIRD pixel will get the first + // pixel's rectangle contribution, the second pixel's rectangle contribution, + // and its own contribution. the 'own contribution' is the same in every pixel except + // the leftmost and rightmost, a trapezoid that slides down in each pixel. + // the second pixel's contribution to the third pixel will be the + // rectangle 1 wide times the height change in the second pixel, which is dy. + + step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x, + // which multiplied by 1-pixel-width is how much pixel area changes for each step in x + // so the area advances by 'step' every time + + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; // area of trapezoid is 1*step/2 + area += step; + } + STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down + STBTT_assert(sy1 > y_final-0.01f); + + // area covered in the last pixel is the rectangle from all the pixels to the left, + // plus the trapezoid filled by the line segment in this pixel all the way to the right edge + scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f); + + // the rest of the line is filled based on the total height of the line segment in this pixel + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + // note though that this does happen some of the time because + // x_top and x_bottom can be extrapolated at the top & bottom of + // the shape and actually lie outside the bounding box + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + if (j == 0 && off_y != 0) { + if (z->ey < scan_y_top) { + // this can happen due to subpixel positioning and some kind of fp rounding error i think + z->ey = scan_y_top; + } + } + STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else + #error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + spc->skip_missing = 0; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) +{ + spc->skip_missing = skip; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + int missing_glyph_added = 0; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { + rects[k].w = rects[k].h = 0; + } else { + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + if (glyph == 0) + missing_glyph_added = 1; + } + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, missing_glyph = -1, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed && r->w != 0 && r->h != 0) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + + if (glyph == 0) + missing_glyph = j; + } else if (spc->skip_missing) { + return_value = 0; + } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { + ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) +{ + int i_ascent, i_descent, i_lineGap; + float scale; + stbtt_fontinfo info; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); + scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); + stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); + *ascent = (float) i_ascent * scale; + *descent = (float) i_descent * scale; + *lineGap = (float) i_lineGap * scale; +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + + orig[0] = x; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + a*x^2 + b*x + c = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + if (scale == 0) return NULL; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + // distance from singular values (in the same units as the pixel grid) + const float eps = 1./1024, eps2 = eps*eps; + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist < eps) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 >= eps2) + precompute[i] = 1.0f / len2; + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3] = {0.f,0.f,0.f}; + float px,py,t,it,dist2; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (STBTT_fabs(a) < eps2) { // if a is 0, it's linear + if (STBTT_fabs(b) >= eps2) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.25 (2021-07-11) many fixes +// 1.24 (2020-02-05) fix warning +// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) +// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined +// 1.21 (2019-02-25) fix warning +// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ 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()