diff --git a/external/scons-plus-plus b/external/scons-plus-plus index 941f94a..fe8f329 160000 --- a/external/scons-plus-plus +++ b/external/scons-plus-plus @@ -1 +1 @@ -Subproject commit 941f94a7b6ad9242ee1404663dfd855dcf203817 +Subproject commit fe8f329b3852b0492cee6fc3c7d5ed71cf761c07 diff --git a/private/sdl_gpu_test/DirStackFileIncluder.h b/private/sdl_gpu_test/DirStackFileIncluder.h new file mode 100644 index 0000000..5a33c78 --- /dev/null +++ b/private/sdl_gpu_test/DirStackFileIncluder.h @@ -0,0 +1,149 @@ +// +// Copyright (C) 2002-2005 3Dlabs Inc. Ltd. +// Copyright (C) 2017 Google, Inc. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// Neither the name of 3Dlabs Inc. Ltd. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// + +#pragma once + +#include +#include +#include +#include +#include + +#include "./../glslang/Public/ShaderLang.h" + +// Default include class for normal include convention of search backward +// through the stack of active include paths (for nested includes). +// Can be overridden to customize. +class DirStackFileIncluder : public glslang::TShader::Includer { +public: + DirStackFileIncluder() : externalLocalDirectoryCount(0) { } + + virtual IncludeResult* includeLocal(const char* headerName, + const char* includerName, + size_t inclusionDepth) override + { + return readLocalPath(headerName, includerName, (int)inclusionDepth); + } + + virtual IncludeResult* includeSystem(const char* headerName, + const char* /*includerName*/, + size_t /*inclusionDepth*/) override + { + return readSystemPath(headerName); + } + + // Externally set directories. E.g., from a command-line -I. + // - Most-recently pushed are checked first. + // - All these are checked after the parse-time stack of local directories + // is checked. + // - This only applies to the "local" form of #include. + // - Makes its own copy of the path. + virtual void pushExternalLocalDirectory(const std::string& dir) + { + directoryStack.push_back(dir); + externalLocalDirectoryCount = (int)directoryStack.size(); + } + + virtual void releaseInclude(IncludeResult* result) override + { + if (result != nullptr) { + delete [] static_cast(result->userData); + delete result; + } + } + + virtual std::set getIncludedFiles() + { + return includedFiles; + } + + virtual ~DirStackFileIncluder() override { } + +protected: + typedef char tUserDataElement; + std::vector directoryStack; + int externalLocalDirectoryCount; + std::set includedFiles; + + // Search for a valid "local" path based on combining the stack of include + // directories and the nominal name of the header. + virtual IncludeResult* readLocalPath(const char* headerName, const char* includerName, int depth) + { + // Discard popped include directories, and + // initialize when at parse-time first level. + directoryStack.resize(depth + externalLocalDirectoryCount); + if (depth == 1) + directoryStack.back() = getDirectory(includerName); + + // Find a directory that works, using a reverse search of the include stack. + for (auto it = directoryStack.rbegin(); it != directoryStack.rend(); ++it) { + std::string path = *it + '/' + headerName; + std::replace(path.begin(), path.end(), '\\', '/'); + std::ifstream file(path, std::ios_base::binary | std::ios_base::ate); + if (file) { + directoryStack.push_back(getDirectory(path)); + includedFiles.insert(path); + return newIncludeResult(path, file, (int)file.tellg()); + } + } + + return nullptr; + } + + // Search for a valid path. + // Not implemented yet; returning nullptr signals failure to find. + virtual IncludeResult* readSystemPath(const char* /*headerName*/) const + { + return nullptr; + } + + // Do actual reading of the file, filling in a new include result. + virtual IncludeResult* newIncludeResult(const std::string& path, std::ifstream& file, int length) const + { + char* content = new tUserDataElement [length]; + file.seekg(0, file.beg); + file.read(content, length); + return new IncludeResult(path, content, length, content); + } + + // If no path markers, return current working directory. + // Otherwise, strip file name and return path leading up to it. + virtual std::string getDirectory(const std::string path) const + { + size_t last = path.find_last_of("/\\"); + return last == std::string::npos ? "." : path.substr(0, last); + } +}; diff --git a/private/sdl_gpu_test/SModule b/private/sdl_gpu_test/SModule index 003958c..df916ff 100644 --- a/private/sdl_gpu_test/SModule +++ b/private/sdl_gpu_test/SModule @@ -3,6 +3,8 @@ Import('env') src_files = Split(""" main.cpp + + glsl_compiler.cpp """) prog_app = env.UnityProgram( @@ -15,7 +17,8 @@ prog_app = env.UnityProgram( 'ref': '76ce83801ade3ac922ad5ba6fddc49764c24206a' } }, - 'spdlog': {} + 'spdlog': {}, + 'glslang': {} } ) env.Default(prog_app) diff --git a/private/sdl_gpu_test/glsl_compiler.cpp b/private/sdl_gpu_test/glsl_compiler.cpp new file mode 100644 index 0000000..24419d6 --- /dev/null +++ b/private/sdl_gpu_test/glsl_compiler.cpp @@ -0,0 +1,133 @@ + +#include "./glsl_compiler.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include "./DirStackFileIncluder.h" + +namespace sdl_gpu_test +{ +namespace +{ +EShLanguage mapShaderStage(ShaderStage stage) noexcept +{ + switch (stage) + { + case ShaderStage::VERTEX: + return EShLangVertex; + case ShaderStage::FRAGMENT: + return EShLangFragment; + } + MIJIN_FATAL("Invalid value for stage."); +} +} + +void initGLSLCompiler() +{ + if (!glslang::InitializeProcess()) + { + throw std::runtime_error("Error initializing Glslang."); + } +} + +void cleanupGLSLCompiler() noexcept +{ + glslang::FinalizeProcess(); +} + +[[nodiscard]] +std::vector compileGLSL(std::string_view source, const CompileGLSLArgs& args) +{ + const EShLanguage stage = mapShaderStage(args.stage); + std::unique_ptr shader = std::make_unique(stage); + const char* sourcePtr = source.data(); + int sourceLength = static_cast(source.size()); + shader->setStringsWithLengths(&sourcePtr, &sourceLength, 1); + shader->setDebugInfo(true); + shader->setEnvInput(glslang::EShSourceGlsl, stage, glslang::EShClientVulkan, glslang::EShTargetVulkan_1_3); + shader->setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_3); + shader->setEnvTarget(glslang::EShTargetLanguage::EshTargetSpv, glslang::EShTargetSpv_1_6); + + DirStackFileIncluder includer; + std::string preprocessedCode; + const bool couldPreprocess = shader->preprocess( + /* builtinResources = */ GetDefaultResources(), + /* defaultVersion = */ 450, + /* defaultProfile = */ ECoreProfile, + /* forceDefaultVersionAndProfile = */ false, + /* forwardCompatible = */ false, + /* message = */ static_cast(EShMsgDefault | EShMsgDebugInfo), + /* outputString = */ &preprocessedCode, + /* includer = */ includer + ); + if (!couldPreprocess) + { + spdlog::error("GLSL preprocessing failed:\ninfo log:\n{}\ndebug log:\n{}", + shader->getInfoLog(), shader->getInfoDebugLog() + ); + throw std::runtime_error("Error preprocessing GLSL."); + } + sourcePtr = preprocessedCode.c_str(); + shader->setStrings(&sourcePtr, 1); + + const bool couldParse = shader->parse( + /* builtinResources = */ GetDefaultResources(), + /* defaultVersion = */ 450, + /* forwardCompatible = */ false, + /* messages = */ static_cast(EShMsgDefault | EShMsgDebugInfo) + ); + if (!couldParse) + { + spdlog::error("GLSL parsing failed:\ninfo log:\n{}\ndebug log:\n{}", + shader->getInfoLog(), shader->getInfoDebugLog() + ); + throw std::runtime_error("Error parsing GLSL."); + } + + std::unique_ptr program = std::make_unique(); + program->addShader(shader.get()); + + if (!program->link(static_cast(EShMsgSpvRules | EShMsgVulkanRules | EShMsgDebugInfo))) + { + spdlog::error("GLSL compilation failed:\ninfo log:\n{}\ndebug log:\n{}", + program->getInfoLog(), program->getInfoDebugLog() + ); + throw std::runtime_error("Error linking GLSL."); + } + + glslang::SpvOptions spvOptions = + { + .generateDebugInfo = true, + .stripDebugInfo = false, + .disableOptimizer = true, + .optimizeSize = false, + .disassemble = false, + .validate = true, + .emitNonSemanticShaderDebugInfo = true, + .emitNonSemanticShaderDebugSource = false, // maybe? + .compileOnly = false + }; + spv::SpvBuildLogger logger; + const glslang::TIntermediate* intermediate = program->getIntermediate(stage); + std::vector spirv; + glslang::GlslangToSpv(*intermediate, spirv, &logger, &spvOptions); + + const std::string messages = logger.getAllMessages(); + if (!messages.empty()) + { + spdlog::warn("SpirV messages: {}", messages); + } + if (spirv.empty()) + { + throw std::runtime_error("Error generating SpirV."); + } + return spirv; +} +} diff --git a/private/sdl_gpu_test/glsl_compiler.hpp b/private/sdl_gpu_test/glsl_compiler.hpp new file mode 100644 index 0000000..863d0fb --- /dev/null +++ b/private/sdl_gpu_test/glsl_compiler.hpp @@ -0,0 +1,30 @@ + +#pragma once + +#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_GLSL_COMPILER_HPP_INCLUDED) +#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_GLSL_COMPILER_HPP_INCLUDED 1 + +#include +#include + +namespace sdl_gpu_test +{ +enum class ShaderStage +{ + VERTEX, + FRAGMENT +}; + +struct CompileGLSLArgs +{ + ShaderStage stage; +}; + +void initGLSLCompiler(); +void cleanupGLSLCompiler() noexcept; + +[[nodiscard]] +std::vector compileGLSL(std::string_view source, const CompileGLSLArgs& args); +} // namespace sdl_gpu_test + +#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_GLSL_COMPILER_HPP_INCLUDED) diff --git a/private/sdl_gpu_test/main.cpp b/private/sdl_gpu_test/main.cpp index c770031..ed5c86d 100644 --- a/private/sdl_gpu_test/main.cpp +++ b/private/sdl_gpu_test/main.cpp @@ -4,12 +4,37 @@ #include #include +#include "./glsl_compiler.hpp" #include "./sdlpp/event.hpp" #include "./sdlpp/gpu.hpp" #include "./sdlpp/window.hpp" +namespace +{ +const char* const VERTEX_SOURCE = R"( +#version 460 + +void main() +{ + +} +)"; + +const char* const FRAGMENT_SOURCE = R"( +#version 460 + +void main() +{ + +} +)"; +} + int main(int, char**) { + using namespace sdl_gpu_test; + + // init SDL if (SDL_Init(0) != SDL_TRUE) { spdlog::error("Error initializing SDL."); @@ -19,6 +44,12 @@ int main(int, char**) SDL_Quit(); }; + // init glslang + initGLSLCompiler(); + MIJIN_SCOPE_EXIT { + cleanupGLSLCompiler(); + }; + sdlpp::Window window; window.create({ .flags = {.vulkan = true} @@ -29,7 +60,30 @@ int main(int, char**) gpuDevice.claimWindow(window); + // create vertex shader + std::vector vertexSpv = compileGLSL(VERTEX_SOURCE, {.stage = ShaderStage::VERTEX}); + sdlpp::GPUShader vertexShader; + vertexShader.create(gpuDevice, { + .code = {reinterpret_cast(vertexSpv.data()), vertexSpv.size() * sizeof(std::uint32_t)}, + .format = sdlpp::GPUShaderFormat::SPIRV, + .stage = sdlpp::GPUShaderStage::VERTEX + }); + // create fragment shader + std::vector fragmentSpv = compileGLSL(FRAGMENT_SOURCE, {.stage = ShaderStage::FRAGMENT}); + sdlpp::GPUShader fragmentShader; + fragmentShader.create(gpuDevice, { + .code = {reinterpret_cast(fragmentSpv.data()), fragmentSpv.size() * sizeof(std::uint32_t)}, + .format = sdlpp::GPUShaderFormat::SPIRV, + .stage = sdlpp::GPUShaderStage::FRAGMENT + }); + + // create graphics pipeline + sdlpp::GPUGraphicsPipeline pipeline; + pipeline.create(gpuDevice, { + .vertexShader = vertexShader, + .fragmentShader = fragmentShader + }); bool running = true; while(running) diff --git a/private/sdl_gpu_test/sdlpp/common.hpp b/private/sdl_gpu_test/sdlpp/common.hpp index 4bc3dd8..91e9aa8 100644 --- a/private/sdl_gpu_test/sdlpp/common.hpp +++ b/private/sdl_gpu_test/sdlpp/common.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include diff --git a/private/sdl_gpu_test/sdlpp/gpu.hpp b/private/sdl_gpu_test/sdlpp/gpu.hpp index ee521c0..5fdf947 100644 --- a/private/sdl_gpu_test/sdlpp/gpu.hpp +++ b/private/sdl_gpu_test/sdlpp/gpu.hpp @@ -8,87 +8,15 @@ namespace sdlpp { -struct GpuShaderFormat : mijin::BitFlags -{ - bool private_ : 1 = false; - bool spirv : 1 = false; - bool dxbc : 1 = false; - bool dxil : 1 = false; - bool msl : 1 = false; - bool metallib : 1 = false; - - constexpr operator SDL_GPUShaderFormat() const noexcept - { - return std::bit_cast(*this); - } -}; - -struct GPUDeviceCreateArgs -{ - GpuShaderFormat formatFlags = {}; - bool debugMode = false; - const char* name = nullptr; -}; - -class GPUDevice : public Base -{ -public: - GPUDevice() noexcept = default; - GPUDevice(const GPUDevice&) = delete; - GPUDevice(GPUDevice&& other) noexcept : Base(std::move(other)) {} - - GPUDevice& operator=(const GPUDevice&) = delete; - GPUDevice& operator=(GPUDevice&& other) noexcept - { - Base::operator=(std::move(other)); - return *this; - } - auto operator<=>(const GPUDevice& other) const noexcept = default; - - void create(const GPUDeviceCreateArgs& args = {}) - { - MIJIN_ASSERT(mHandle == nullptr, "GPUDevice has already been created."); - mHandle = SDL_CreateGPUDevice(args.formatFlags, args.debugMode, args.name); - if (mHandle == nullptr) - { - throw SDLError(); - } - } - - void destroy() noexcept - { - if (mHandle != nullptr) - { - SDL_DestroyGPUDevice(mHandle); - mHandle = nullptr; - } - } - - void claimWindow(SDL_Window* window) const - { - if (!SDL_ClaimWindowForGPUDevice(*this, window)) - { - throw SDLError(); - } - } -}; - +// +// enums +// enum class GPUVertexInputRate { VERTEX = SDL_GPU_VERTEXINPUTRATE_VERTEX, INSTANCE = SDL_GPU_VERTEXINPUTRATE_INSTANCE }; -struct GPUVertexBinding -{ - Uint32 index; - Uint32 pitch; - GPUVertexInputRate inputRate = GPUVertexInputRate::VERTEX; - Uint32 instanceStepRate; -}; -static_assert(sizeof(GPUVertexBinding) == sizeof(SDL_GPUVertexBinding) - && alignof(GPUVertexBinding) == alignof(SDL_GPUVertexBinding)); - enum class GPUVertexElementFormat { INT = SDL_GPU_VERTEXELEMENTFORMAT_INT, @@ -123,22 +51,6 @@ enum class GPUVertexElementFormat HALF4 = SDL_GPU_VERTEXELEMENTFORMAT_HALF4 }; -struct GPUVertexAttribute -{ - Uint32 location; - Uint32 bindingIndex; - GPUVertexElementFormat format; - Uint32 offset; -}; -static_assert(sizeof(GPUVertexAttribute) == sizeof(SDL_GPUVertexAttribute) - && alignof(GPUVertexAttribute) == alignof(SDL_GPUVertexAttribute)); - -struct GPUVertexInputState -{ - std::vector vertexBindings; - std::vector vertexAttributes; -}; - enum class GPUPrimitiveType { POINTLIST = SDL_GPU_PRIMITIVETYPE_POINTLIST, @@ -167,17 +79,6 @@ enum class GPUFrontFace CLOCKWISE = SDL_GPU_FRONTFACE_CLOCKWISE }; -struct GPURasterizerState -{ - GPUFillMode fillMode = GPUFillMode::FILL; - GPUCullMode cullMode = GPUCullMode::NONE; - GPUFrontFace frontFace = GPUFrontFace::COUNTER_CLOCKWISE; - bool enableDepthBias = false; - float depthBiasConstantFactor; - float depthBiasClamp; - float depthBiasSlopeFactor; -}; - enum class GPUSampleCount { ONE = SDL_GPU_SAMPLECOUNT_1, @@ -186,12 +87,6 @@ enum class GPUSampleCount EIGHT = SDL_GPU_SAMPLECOUNT_8 }; -struct GPUMultisampleState -{ - GPUSampleCount sampleCount = GPUSampleCount::ONE; - Uint32 sampleMask = 0xFFFFFFFF; -}; - enum class GPUCompareOp { NEVER = SDL_GPU_COMPAREOP_NEVER, @@ -216,36 +111,6 @@ enum class GPUStencilOp DECREMENT_AND_WRAP = SDL_GPU_STENCILOP_DECREMENT_AND_WRAP }; -struct GPUStencilOpState -{ - GPUStencilOp failOp; - GPUStencilOp passOp; - GPUStencilOp depthFailOp; - GPUCompareOp compareOp; - - explicit operator SDL_GPUStencilOpState() const noexcept - { - return { - .fail_op = static_cast(failOp), - .pass_op = static_cast(passOp), - .depth_fail_op = static_cast(depthFailOp), - .compare_op = static_cast(compareOp), - }; - } -}; - -struct GPUDepthStencilState -{ - bool enableDepthTest = false; - bool enableDepthWrite = false; - bool enableStencilTest = false; - GPUCompareOp compareOp = GPUCompareOp::LESS_OR_EQUAL; - GPUStencilOpState backStencilState; - GPUStencilOpState frontStencilState; - Uint8 compareMask; - Uint8 writeMask; -}; - enum class GPUBlendFactor { ZERO = SDL_GPU_BLENDFACTOR_ZERO, @@ -272,26 +137,6 @@ enum class GPUBlendOp MAX = SDL_GPU_BLENDOP_MAX }; -struct GPUColorComponentFlags : mijin::BitFlags -{ - bool r : 1 = false; - bool g : 1 = false; - bool b : 1 = false; - bool a : 1 = false; -}; - -struct GPUColorTargetBlendState -{ - bool enableBlend = false; - GPUBlendFactor srcColorBlendfactor; - GPUBlendFactor dstColorBlendfactor; - GPUBlendOp colorBlendOp; - GPUBlendFactor srcAlphaBlendfactor; - GPUBlendFactor dstAlphaBlendfactor; - GPUBlendOp alphaBlendOp; - GPUColorComponentFlags colorWriteMask = {.r = true, .g = true, .b = true, .a = true}; -}; - enum class GPUTextureFormat { INVALID = SDL_GPU_TEXTUREFORMAT_INVALID, @@ -353,6 +198,136 @@ enum class GPUTextureFormat D32_FLOAT_S8_UINT = SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT }; +enum class GPUShaderFormat +{ + PRIVATE = SDL_GPU_SHADERFORMAT_PRIVATE, + SPIRV = SDL_GPU_SHADERFORMAT_SPIRV, + DXBC = SDL_GPU_SHADERFORMAT_DXBC, + DXIL = SDL_GPU_SHADERFORMAT_DXIL, + MSL = SDL_GPU_SHADERFORMAT_MSL, + METALLIB = SDL_GPU_SHADERFORMAT_METALLIB +}; + +enum class GPUShaderStage +{ + VERTEX = SDL_GPU_SHADERSTAGE_VERTEX, + FRAGMENT = SDL_GPU_SHADERSTAGE_FRAGMENT +}; + +// +// bitflags +// +struct GPUShaderFormatFlags : mijin::BitFlags +{ + bool private_ : 1 = false; + bool spirv : 1 = false; + bool dxbc : 1 = false; + bool dxil : 1 = false; + bool msl : 1 = false; + bool metallib : 1 = false; + + constexpr operator SDL_GPUShaderFormat() const noexcept + { + return std::bit_cast(*this); + } +}; + +struct GPUColorComponentFlags : mijin::BitFlags +{ + bool r : 1 = false; + bool g : 1 = false; + bool b : 1 = false; + bool a : 1 = false; +}; + +// +// structs +// +struct GPUVertexBinding +{ + Uint32 index; + Uint32 pitch; + GPUVertexInputRate inputRate = GPUVertexInputRate::VERTEX; + Uint32 instanceStepRate; +}; +static_assert(sizeof(GPUVertexBinding) == sizeof(SDL_GPUVertexBinding) + && alignof(GPUVertexBinding) == alignof(SDL_GPUVertexBinding)); + +struct GPUVertexAttribute +{ + Uint32 location; + Uint32 bindingIndex; + GPUVertexElementFormat format; + Uint32 offset; +}; +static_assert(sizeof(GPUVertexAttribute) == sizeof(SDL_GPUVertexAttribute) + && alignof(GPUVertexAttribute) == alignof(SDL_GPUVertexAttribute)); + +struct GPUVertexInputState +{ + std::span vertexBindings; + std::span vertexAttributes; +}; + +struct GPURasterizerState +{ + GPUFillMode fillMode = GPUFillMode::FILL; + GPUCullMode cullMode = GPUCullMode::NONE; + GPUFrontFace frontFace = GPUFrontFace::COUNTER_CLOCKWISE; + bool enableDepthBias = false; + float depthBiasConstantFactor; + float depthBiasClamp; + float depthBiasSlopeFactor; +}; + +struct GPUMultisampleState +{ + GPUSampleCount sampleCount = GPUSampleCount::ONE; + Uint32 sampleMask = 0xFFFFFFFF; +}; + +struct GPUStencilOpState +{ + GPUStencilOp failOp; + GPUStencilOp passOp; + GPUStencilOp depthFailOp; + GPUCompareOp compareOp; + + explicit operator SDL_GPUStencilOpState() const noexcept + { + return { + .fail_op = static_cast(failOp), + .pass_op = static_cast(passOp), + .depth_fail_op = static_cast(depthFailOp), + .compare_op = static_cast(compareOp), + }; + } +}; + +struct GPUDepthStencilState +{ + bool enableDepthTest = false; + bool enableDepthWrite = false; + bool enableStencilTest = false; + GPUCompareOp compareOp = GPUCompareOp::LESS_OR_EQUAL; + GPUStencilOpState backStencilState; + GPUStencilOpState frontStencilState; + Uint8 compareMask; + Uint8 writeMask; +}; + +struct GPUColorTargetBlendState +{ + bool enableBlend = false; + GPUBlendFactor srcColorBlendfactor; + GPUBlendFactor dstColorBlendfactor; + GPUBlendOp colorBlendOp; + GPUBlendFactor srcAlphaBlendfactor; + GPUBlendFactor dstAlphaBlendfactor; + GPUBlendOp alphaBlendOp; + GPUColorComponentFlags colorWriteMask = {.r = true, .g = true, .b = true, .a = true}; +}; + struct GPUColorTargetDescription { GPUTextureFormat format = GPUTextureFormat::INVALID; @@ -364,7 +339,7 @@ static_assert(sizeof(GPUColorTargetDescription) == sizeof(SDL_GPUColorTargetDesc struct GPUGraphicsPipelineTargetInfo { - std::vector colorTargetDescriptions; + std::span colorTargetDescriptions; bool hasDepthStencilTarget = false; GPUTextureFormat depthStencilFormat = GPUTextureFormat::INVALID; @@ -379,6 +354,59 @@ struct GPUGraphicsPipelineTargetInfo } }; +// +// classes +// +struct GPUDeviceCreateArgs +{ + GPUShaderFormatFlags formatFlags = {}; + bool debugMode = false; + const char* name = nullptr; +}; + +class GPUDevice : public Base +{ +public: + GPUDevice() noexcept = default; + GPUDevice(const GPUDevice&) = delete; + GPUDevice(GPUDevice&& other) noexcept : Base(std::move(other)) {} + + GPUDevice& operator=(const GPUDevice&) = delete; + GPUDevice& operator=(GPUDevice&& other) noexcept + { + Base::operator=(std::move(other)); + return *this; + } + auto operator<=>(const GPUDevice& other) const noexcept = default; + + void create(const GPUDeviceCreateArgs& args = {}) + { + MIJIN_ASSERT(mHandle == nullptr, "GPUDevice has already been created."); + mHandle = SDL_CreateGPUDevice(args.formatFlags, args.debugMode, args.name); + if (mHandle == nullptr) + { + throw SDLError(); + } + } + + void destroy() noexcept + { + if (mHandle != nullptr) + { + SDL_DestroyGPUDevice(mHandle); + mHandle = nullptr; + } + } + + void claimWindow(SDL_Window* window) const + { + if (!SDL_ClaimWindowForGPUDevice(*this, window)) + { + throw SDLError(); + } + } +}; + struct GPUGraphicsPipelineCreateArgs { SDL_GPUShader* vertexShader; @@ -404,6 +432,7 @@ public: GPUGraphicsPipeline& operator=(GPUGraphicsPipeline&& other) noexcept { Base::operator=(std::move(other)); + mDevice = other.mDevice; return *this; } auto operator<=>(const GPUGraphicsPipeline& other) const noexcept = default; @@ -466,6 +495,69 @@ public: } } }; + +struct GPUShaderCreateArgs +{ + std::span code; + std::string entrypoint = "main"; + GPUShaderFormat format; + GPUShaderStage stage; + Uint32 numSamplers = 0; + Uint32 numStorageTextures = 0; + Uint32 numStorageBuffers = 0; + Uint32 numUniformBuffers = 0; +}; + +class GPUShader : public Base +{ +private: + SDL_GPUDevice* mDevice = nullptr; +public: + GPUShader() noexcept = default; + GPUShader(const GPUShader&) = delete; + GPUShader(GPUShader&& other) noexcept : Base(std::move(other)) {} + + GPUShader& operator=(const GPUShader&) = delete; + GPUShader& operator=(GPUShader&& other) noexcept + { + Base::operator=(std::move(other)); + return *this; + } + auto operator<=>(const GPUShader& other) const noexcept = default; + + void create(SDL_GPUDevice* device, const GPUShaderCreateArgs& args) + { + MIJIN_ASSERT(mHandle == nullptr, "GPUShader has already been created."); + const SDL_GPUShaderCreateInfo createInfo = + { + .code_size = args.code.size(), + .code = args.code.data(), + .entrypoint = args.entrypoint.c_str(), + .format = static_cast(args.format), + .stage = static_cast(args.stage), + .num_samplers = args.numSamplers, + .num_storage_textures = args.numStorageTextures, + .num_storage_buffers = args.numStorageBuffers, + .num_uniform_buffers = args.numUniformBuffers + }; + mHandle = SDL_CreateGPUShader(device, &createInfo); + if (mHandle == nullptr) + { + throw SDLError(); + } + mDevice = device; + } + + void destroy() noexcept + { + if (mHandle != nullptr) + { + SDL_ReleaseGPUShader(mDevice, mHandle); + mHandle = nullptr; + mDevice = nullptr; + } + } +}; } // namespace sdlpp #endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_GPU_HPP_INCLUDED)