Added code to create shaders and graphics pipelines.

This commit is contained in:
Patrick 2024-09-12 17:14:52 +02:00
parent 7b19170112
commit cd4ab6147a
8 changed files with 623 additions and 161 deletions

@ -1 +1 @@
Subproject commit 941f94a7b6ad9242ee1404663dfd855dcf203817 Subproject commit fe8f329b3852b0492cee6fc3c7d5ed71cf761c07

View File

@ -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 <vector>
#include <string>
#include <fstream>
#include <algorithm>
#include <set>
#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<dir>.
// - 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<tUserDataElement*>(result->userData);
delete result;
}
}
virtual std::set<std::string> getIncludedFiles()
{
return includedFiles;
}
virtual ~DirStackFileIncluder() override { }
protected:
typedef char tUserDataElement;
std::vector<std::string> directoryStack;
int externalLocalDirectoryCount;
std::set<std::string> 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 <system> 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);
}
};

View File

@ -3,6 +3,8 @@ Import('env')
src_files = Split(""" src_files = Split("""
main.cpp main.cpp
glsl_compiler.cpp
""") """)
prog_app = env.UnityProgram( prog_app = env.UnityProgram(
@ -15,7 +17,8 @@ prog_app = env.UnityProgram(
'ref': '76ce83801ade3ac922ad5ba6fddc49764c24206a' 'ref': '76ce83801ade3ac922ad5ba6fddc49764c24206a'
} }
}, },
'spdlog': {} 'spdlog': {},
'glslang': {}
} }
) )
env.Default(prog_app) env.Default(prog_app)

View File

@ -0,0 +1,133 @@
#include "./glsl_compiler.hpp"
#include <stdexcept>
#include <glslang/Public/ResourceLimits.h>
#include <glslang/Public/ShaderLang.h>
#include <glslang/SPIRV/GlslangToSpv.h>
#include <mijin/debug/assert.hpp>
#include <spdlog/spdlog.h>
#include <mijin/util/winundef.hpp>
#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<std::uint32_t> compileGLSL(std::string_view source, const CompileGLSLArgs& args)
{
const EShLanguage stage = mapShaderStage(args.stage);
std::unique_ptr<glslang::TShader> shader = std::make_unique<glslang::TShader>(stage);
const char* sourcePtr = source.data();
int sourceLength = static_cast<int>(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<EShMessages>(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<EShMessages>(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<glslang::TProgram> program = std::make_unique<glslang::TProgram>();
program->addShader(shader.get());
if (!program->link(static_cast<EShMessages>(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<std::uint32_t> 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;
}
}

View File

@ -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 <string_view>
#include <vector>
namespace sdl_gpu_test
{
enum class ShaderStage
{
VERTEX,
FRAGMENT
};
struct CompileGLSLArgs
{
ShaderStage stage;
};
void initGLSLCompiler();
void cleanupGLSLCompiler() noexcept;
[[nodiscard]]
std::vector<std::uint32_t> 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)

View File

@ -4,12 +4,37 @@
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <mijin/util/winundef.hpp> #include <mijin/util/winundef.hpp>
#include "./glsl_compiler.hpp"
#include "./sdlpp/event.hpp" #include "./sdlpp/event.hpp"
#include "./sdlpp/gpu.hpp" #include "./sdlpp/gpu.hpp"
#include "./sdlpp/window.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**) int main(int, char**)
{ {
using namespace sdl_gpu_test;
// init SDL
if (SDL_Init(0) != SDL_TRUE) if (SDL_Init(0) != SDL_TRUE)
{ {
spdlog::error("Error initializing SDL."); spdlog::error("Error initializing SDL.");
@ -19,6 +44,12 @@ int main(int, char**)
SDL_Quit(); SDL_Quit();
}; };
// init glslang
initGLSLCompiler();
MIJIN_SCOPE_EXIT {
cleanupGLSLCompiler();
};
sdlpp::Window window; sdlpp::Window window;
window.create({ window.create({
.flags = {.vulkan = true} .flags = {.vulkan = true}
@ -29,7 +60,30 @@ int main(int, char**)
gpuDevice.claimWindow(window); gpuDevice.claimWindow(window);
// create vertex shader
std::vector<std::uint32_t> vertexSpv = compileGLSL(VERTEX_SOURCE, {.stage = ShaderStage::VERTEX});
sdlpp::GPUShader vertexShader;
vertexShader.create(gpuDevice, {
.code = {reinterpret_cast<const Uint8*>(vertexSpv.data()), vertexSpv.size() * sizeof(std::uint32_t)},
.format = sdlpp::GPUShaderFormat::SPIRV,
.stage = sdlpp::GPUShaderStage::VERTEX
});
// create fragment shader
std::vector<std::uint32_t> fragmentSpv = compileGLSL(FRAGMENT_SOURCE, {.stage = ShaderStage::FRAGMENT});
sdlpp::GPUShader fragmentShader;
fragmentShader.create(gpuDevice, {
.code = {reinterpret_cast<const Uint8*>(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; bool running = true;
while(running) while(running)

View File

@ -6,6 +6,7 @@
#include <stdexcept> #include <stdexcept>
#include <utility> #include <utility>
#include <span>
#include <vector> #include <vector>
#include <mijin/debug/assert.hpp> #include <mijin/debug/assert.hpp>

View File

@ -8,87 +8,15 @@
namespace sdlpp namespace sdlpp
{ {
struct GpuShaderFormat : mijin::BitFlags<GpuShaderFormat> //
{ // enums
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<std::uint8_t>(*this);
}
};
struct GPUDeviceCreateArgs
{
GpuShaderFormat formatFlags = {};
bool debugMode = false;
const char* name = nullptr;
};
class GPUDevice : public Base<SDL_GPUDevice, GPUDevice>
{
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();
}
}
};
enum class GPUVertexInputRate enum class GPUVertexInputRate
{ {
VERTEX = SDL_GPU_VERTEXINPUTRATE_VERTEX, VERTEX = SDL_GPU_VERTEXINPUTRATE_VERTEX,
INSTANCE = SDL_GPU_VERTEXINPUTRATE_INSTANCE 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 enum class GPUVertexElementFormat
{ {
INT = SDL_GPU_VERTEXELEMENTFORMAT_INT, INT = SDL_GPU_VERTEXELEMENTFORMAT_INT,
@ -123,22 +51,6 @@ enum class GPUVertexElementFormat
HALF4 = SDL_GPU_VERTEXELEMENTFORMAT_HALF4 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<GPUVertexBinding> vertexBindings;
std::vector<GPUVertexAttribute> vertexAttributes;
};
enum class GPUPrimitiveType enum class GPUPrimitiveType
{ {
POINTLIST = SDL_GPU_PRIMITIVETYPE_POINTLIST, POINTLIST = SDL_GPU_PRIMITIVETYPE_POINTLIST,
@ -167,17 +79,6 @@ enum class GPUFrontFace
CLOCKWISE = SDL_GPU_FRONTFACE_CLOCKWISE 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 enum class GPUSampleCount
{ {
ONE = SDL_GPU_SAMPLECOUNT_1, ONE = SDL_GPU_SAMPLECOUNT_1,
@ -186,12 +87,6 @@ enum class GPUSampleCount
EIGHT = SDL_GPU_SAMPLECOUNT_8 EIGHT = SDL_GPU_SAMPLECOUNT_8
}; };
struct GPUMultisampleState
{
GPUSampleCount sampleCount = GPUSampleCount::ONE;
Uint32 sampleMask = 0xFFFFFFFF;
};
enum class GPUCompareOp enum class GPUCompareOp
{ {
NEVER = SDL_GPU_COMPAREOP_NEVER, NEVER = SDL_GPU_COMPAREOP_NEVER,
@ -216,36 +111,6 @@ enum class GPUStencilOp
DECREMENT_AND_WRAP = SDL_GPU_STENCILOP_DECREMENT_AND_WRAP 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<SDL_GPUStencilOp>(failOp),
.pass_op = static_cast<SDL_GPUStencilOp>(passOp),
.depth_fail_op = static_cast<SDL_GPUStencilOp>(depthFailOp),
.compare_op = static_cast<SDL_GPUCompareOp>(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 enum class GPUBlendFactor
{ {
ZERO = SDL_GPU_BLENDFACTOR_ZERO, ZERO = SDL_GPU_BLENDFACTOR_ZERO,
@ -272,26 +137,6 @@ enum class GPUBlendOp
MAX = SDL_GPU_BLENDOP_MAX MAX = SDL_GPU_BLENDOP_MAX
}; };
struct GPUColorComponentFlags : mijin::BitFlags<GPUColorComponentFlags>
{
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 enum class GPUTextureFormat
{ {
INVALID = SDL_GPU_TEXTUREFORMAT_INVALID, INVALID = SDL_GPU_TEXTUREFORMAT_INVALID,
@ -353,6 +198,136 @@ enum class GPUTextureFormat
D32_FLOAT_S8_UINT = SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT 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<GPUShaderFormatFlags>
{
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<std::uint8_t>(*this);
}
};
struct GPUColorComponentFlags : mijin::BitFlags<GPUColorComponentFlags>
{
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<const GPUVertexBinding> vertexBindings;
std::span<const GPUVertexAttribute> 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<SDL_GPUStencilOp>(failOp),
.pass_op = static_cast<SDL_GPUStencilOp>(passOp),
.depth_fail_op = static_cast<SDL_GPUStencilOp>(depthFailOp),
.compare_op = static_cast<SDL_GPUCompareOp>(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 struct GPUColorTargetDescription
{ {
GPUTextureFormat format = GPUTextureFormat::INVALID; GPUTextureFormat format = GPUTextureFormat::INVALID;
@ -364,7 +339,7 @@ static_assert(sizeof(GPUColorTargetDescription) == sizeof(SDL_GPUColorTargetDesc
struct GPUGraphicsPipelineTargetInfo struct GPUGraphicsPipelineTargetInfo
{ {
std::vector<GPUColorTargetDescription> colorTargetDescriptions; std::span<const GPUColorTargetDescription> colorTargetDescriptions;
bool hasDepthStencilTarget = false; bool hasDepthStencilTarget = false;
GPUTextureFormat depthStencilFormat = GPUTextureFormat::INVALID; 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<SDL_GPUDevice, GPUDevice>
{
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 struct GPUGraphicsPipelineCreateArgs
{ {
SDL_GPUShader* vertexShader; SDL_GPUShader* vertexShader;
@ -404,6 +432,7 @@ public:
GPUGraphicsPipeline& operator=(GPUGraphicsPipeline&& other) noexcept GPUGraphicsPipeline& operator=(GPUGraphicsPipeline&& other) noexcept
{ {
Base::operator=(std::move(other)); Base::operator=(std::move(other));
mDevice = other.mDevice;
return *this; return *this;
} }
auto operator<=>(const GPUGraphicsPipeline& other) const noexcept = default; auto operator<=>(const GPUGraphicsPipeline& other) const noexcept = default;
@ -466,6 +495,69 @@ public:
} }
} }
}; };
struct GPUShaderCreateArgs
{
std::span<const Uint8> 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<SDL_GPUShader, GPUShader>
{
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<SDL_GPUShaderFormat>(args.format),
.stage = static_cast<SDL_GPUShaderStage>(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 } // namespace sdlpp
#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_GPU_HPP_INCLUDED) #endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_GPU_HPP_INCLUDED)