Added code to create shaders and graphics pipelines.
This commit is contained in:
parent
7b19170112
commit
cd4ab6147a
2
external/scons-plus-plus
vendored
2
external/scons-plus-plus
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 941f94a7b6ad9242ee1404663dfd855dcf203817
|
Subproject commit fe8f329b3852b0492cee6fc3c7d5ed71cf761c07
|
149
private/sdl_gpu_test/DirStackFileIncluder.h
Normal file
149
private/sdl_gpu_test/DirStackFileIncluder.h
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
@ -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)
|
||||||
|
133
private/sdl_gpu_test/glsl_compiler.cpp
Normal file
133
private/sdl_gpu_test/glsl_compiler.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
30
private/sdl_gpu_test/glsl_compiler.hpp
Normal file
30
private/sdl_gpu_test/glsl_compiler.hpp
Normal 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)
|
@ -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)
|
||||||
|
@ -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>
|
||||||
|
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user