diff --git a/.gitignore b/.gitignore index a2c5ee5..dde22e0 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,6 @@ __pycache__/ # Backup files *.bak + +# Compiled shaders +*.spv diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..63c1bcc --- /dev/null +++ b/NOTES.md @@ -0,0 +1,13 @@ + +Descriptor sets +=== + - hardcoded in SDL_gpu + - for graphics pipelines there are always 4 of them: + - 0 are vertex resources (first samplers, then storage images, then storage buffers) + - 1 are vertex uniform buffers + - 2 are fragment resources (again samplers, storage images and storage buffers) + - 3 are fragment uniform buffers + - compute has 3 sets: + - 0 are read-only resources (samplers, storage images, storage buffers) + - 1 are write-only resources (storage images and storage buffers) + - 2 are uniform buffers diff --git a/SConstruct b/SConstruct index 5596200..f43033a 100644 --- a/SConstruct +++ b/SConstruct @@ -1,5 +1,6 @@ config = { - 'PROJECT_NAME': 'sdl_gpu_test' + 'PROJECT_NAME': 'sdl_gpu_test', + 'TOOLS': ['glslang'] } env = SConscript('external/scons-plus-plus/SConscript', exports = ['config']) env.Append(CPPPATH = [Dir('private'), Dir('public')]) diff --git a/assets/shaders/glsl/color_from_uniform.frag b/assets/shaders/glsl/color_from_uniform.frag new file mode 100644 index 0000000..d2ffd83 --- /dev/null +++ b/assets/shaders/glsl/color_from_uniform.frag @@ -0,0 +1,15 @@ +#version 460 + +layout(set = 3, binding = 0) +uniform Parameters +{ + vec4 u_color; +}; + +layout(location = 0) +out vec4 o_color; + +void main() +{ + o_color = u_color; +} diff --git a/assets/shaders/glsl/triangles_from_buffer.vert b/assets/shaders/glsl/triangles_from_buffer.vert new file mode 100644 index 0000000..f097e05 --- /dev/null +++ b/assets/shaders/glsl/triangles_from_buffer.vert @@ -0,0 +1,8 @@ +#version 460 + +layout(location = 0) in vec2 i_position; + +void main() +{ + gl_Position = vec4(i_position, 0.0, 1.0); +} diff --git a/private/sdl_gpu_test/SModule b/private/sdl_gpu_test/SModule index e7b5a2c..754937c 100644 --- a/private/sdl_gpu_test/SModule +++ b/private/sdl_gpu_test/SModule @@ -3,28 +3,34 @@ Import('env') src_files = Split(""" main.cpp - - glsl_compiler.cpp """) +shader_files = Split(""" + #assets/shaders/glsl/color_from_uniform.frag + #assets/shaders/glsl/green.frag + #assets/shaders/glsl/triangle.vert + #assets/shaders/glsl/triangles_from_buffer.vert +""") + +env.Append(CPPDEFINES = ['GLM_FORCE_DEPTH_ZERO_TO_ONE', 'GLM_FORCE_RADIANS']) prog_app = env.UnityProgram( target = env['BIN_DIR'] + '/sdl_gpu_test', source = src_files, dependencies = { + 'glm': {}, 'mijin': {}, 'SDL': { 'options': { 'ref': '76ce83801ade3ac922ad5ba6fddc49764c24206a' } }, - 'spdlog': {}, - 'glslang': { - 'options': { - 'enable_hlsl': True - } - } + 'spdlog': {} } ) env.Default(prog_app) +for shader_file in shader_files: + spv = env.SpirV(source = shader_file, target=f'{shader_file}.spv') + env.Default(spv) + Return('env') diff --git a/private/sdl_gpu_test/main.cpp b/private/sdl_gpu_test/main.cpp index b979c89..c3ba801 100644 --- a/private/sdl_gpu_test/main.cpp +++ b/private/sdl_gpu_test/main.cpp @@ -1,4 +1,6 @@ +#include +#include #include #include #include @@ -7,7 +9,6 @@ #include #include -#include "./glsl_compiler.hpp" #include "./sdlpp/event.hpp" #include "./sdlpp/gpu.hpp" #include "./sdlpp/window.hpp" @@ -21,8 +22,8 @@ void initFileSystem(const fs::path& executablePath) noexcept gFileSystem.emplaceAdapter>(executablePath.parent_path() / "assets"); } -[[nodiscard]] -std::string getFileContents(const fs::path& path) +[[nodiscard]] [[maybe_unused]] +std::string getFileContentsText(const fs::path& path) { std::unique_ptr stream; mijin::throwOnError(gFileSystem.open(path, mijin::FileOpenMode::READ, stream), @@ -31,12 +32,21 @@ std::string getFileContents(const fs::path& path) mijin::throwOnError(stream->readAsString(content), "Error reading file contents."); return content; } + +[[nodiscard]] +mijin::TypelessBuffer getFileContentsBinary(const fs::path& path) +{ + std::unique_ptr stream; + mijin::throwOnError(gFileSystem.open(path, mijin::FileOpenMode::READ, stream), + "Error opening file for reading."); + mijin::TypelessBuffer content; + mijin::throwOnError(stream->readRest(content), "Error reading file contents."); + return content; +} } int main(int argc, char* argv[]) { - using namespace sdl_gpu_test; - if (argc < 1) { return 1; @@ -54,12 +64,6 @@ int main(int argc, char* argv[]) SDL_Quit(); }; - // init glslang - initGLSLCompiler(); - MIJIN_SCOPE_EXIT { - cleanupGLSLCompiler(); - }; - sdlpp::Window window; window.create({ .title = "SDL_gpu Test", @@ -75,23 +79,22 @@ int main(int argc, char* argv[]) gpuDevice.claimWindow(window); // create vertex shader - const std::string vertexSource = getFileContents("shaders/glsl/triangle.vert"); - std::vector vertexSpv = compileGLSL(vertexSource, {.stage = ShaderStage::VERTEX}); + mijin::TypelessBuffer vertexSpv = getFileContentsBinary("shaders/glsl/triangles_from_buffer.vert.spv"); sdlpp::GPUShader vertexShader; vertexShader.create(gpuDevice, { - .code = {reinterpret_cast(vertexSpv.data()), vertexSpv.size() * sizeof(std::uint32_t)}, + .code = {static_cast(vertexSpv.data()), vertexSpv.byteSize()}, .format = sdlpp::GPUShaderFormat::SPIRV, .stage = sdlpp::GPUShaderStage::VERTEX }); // create fragment shader - const std::string fragmentSource = getFileContents("shaders/glsl/green.frag"); - std::vector fragmentSpv = compileGLSL(fragmentSource, {.stage = ShaderStage::FRAGMENT}); + mijin::TypelessBuffer fragmentSpv = getFileContentsBinary("shaders/glsl/color_from_uniform.frag.spv"); sdlpp::GPUShader fragmentShader; fragmentShader.create(gpuDevice, { - .code = {reinterpret_cast(fragmentSpv.data()), fragmentSpv.size() * sizeof(std::uint32_t)}, + .code = {static_cast(fragmentSpv.data()), fragmentSpv.byteSize()}, .format = sdlpp::GPUShaderFormat::SPIRV, - .stage = sdlpp::GPUShaderStage::FRAGMENT + .stage = sdlpp::GPUShaderStage::FRAGMENT, + .numUniformBuffers = 1 }); // create graphics pipeline @@ -101,14 +104,71 @@ int main(int argc, char* argv[]) } }; sdlpp::GPUGraphicsPipeline pipeline; + std::array vertexBindings = { + sdlpp::GPUVertexBinding{ + .index = 0, + .pitch = sizeof(glm::vec2) + } + }; + std::array vertexAttributes = { + sdlpp::GPUVertexAttribute{ + .location = 0, + .bindingIndex = 0, + .format = sdlpp::GPUVertexElementFormat::FLOAT2, + .offset = 0 + } + }; pipeline.create(gpuDevice, { - .vertexShader = vertexShader, + .vertexShader = vertexShader, .fragmentShader = fragmentShader, + .vertexInputState = { + .vertexBindings = vertexBindings, + .vertexAttributes = vertexAttributes + }, .targetInfo = { .colorTargetDescriptions = colorTargetsDescs } }); + std::array vertices = + { + glm::vec2{-1.f, -1.f}, + glm::vec2{ 1.f, -1.f}, + glm::vec2{ 0.f, 1.f} + }; + + // create vertex buffer + sdlpp::GPUBuffer vertexBuffer; + vertexBuffer.create(gpuDevice, { + .usage = {.vertex = true}, + .size = sizeof(vertices) + }); + { + sdlpp::GPUTransferBuffer transferBuffer; + transferBuffer.create(gpuDevice, { + .usage = sdlpp::GPUTransferBufferUsage::UPLOAD, + .size = sizeof(vertices) + }); + void* ptr = transferBuffer.map(); + std::memcpy(ptr, vertices.data(), sizeof(vertices)); + transferBuffer.unmap(); + + sdlpp::GPUCommandBuffer cmdBuffer = gpuDevice.acquireCommandBuffer(); + sdlpp::GPUCopyPass copyPass = cmdBuffer.beginCopyPass(); + copyPass.uploadToGPUBuffer( + /* source = */ { + .transferBuffer = transferBuffer + }, + /* destination = */ { + .buffer = vertexBuffer, + .size = sizeof(vertices) + } + ); + copyPass.end(); + cmdBuffer.submit(); + } + + bool running = true; while(running) { @@ -132,7 +192,10 @@ int main(int argc, char* argv[]) sdlpp::GPURenderPass renderPass = cmdBuffer.beginRenderPass({ .colorTargetInfos = colorTargets }); + static const glm::vec4 BLUE(0.f, 0.f, 1.f, 1.f); + cmdBuffer.pushFragmentUniformData(0, std::span(&BLUE, 1)); renderPass.bindGraphicsPipeline(pipeline); + renderPass.bindVertexBuffer({.buffer = vertexBuffer}); renderPass.drawPrimitives({.numVertices = 3}); renderPass.end(); diff --git a/private/sdl_gpu_test/sdlpp/gpu.hpp b/private/sdl_gpu_test/sdlpp/gpu.hpp index ae92438..6ec12b7 100644 --- a/private/sdl_gpu_test/sdlpp/gpu.hpp +++ b/private/sdl_gpu_test/sdlpp/gpu.hpp @@ -227,6 +227,12 @@ enum class GPUStoreOp DONT_CARE = SDL_GPU_STOREOP_DONT_CARE }; +enum class GPUTransferBufferUsage +{ + UPLOAD = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, + DOWNLOAD = SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD +}; + // // bitflags // @@ -253,6 +259,21 @@ struct GPUColorComponentFlags : mijin::BitFlags bool a : 1 = false; }; +struct GPUBufferUsageFlags : mijin::BitFlags +{ + bool vertex : 1 = false; + bool index : 1 = false; + bool indirect : 1 = false; + bool graphicsStorageRead : 1 = false; + bool computeStorageRead : 1 = false; + bool computeStorageWrite : 1 = false; + + explicit operator SDL_GPUBufferUsageFlags() const noexcept + { + return std::bit_cast(*this); + } +}; + // // structs // @@ -396,6 +417,23 @@ struct GPUDepthStencilTargetInfo static_assert(sizeof(GPUDepthStencilTargetInfo) == sizeof(SDL_GPUDepthStencilTargetInfo) && alignof(GPUDepthStencilTargetInfo) == alignof(SDL_GPUDepthStencilTargetInfo)); +struct GPUTransferBufferLocation +{ + SDL_GPUTransferBuffer* transferBuffer; + Uint32 offset = 0; +}; +static_assert(sizeof(GPUTransferBufferLocation) == sizeof(SDL_GPUTransferBufferLocation)); + +struct GPUBufferRegion +{ + SDL_GPUBuffer* buffer; + Uint32 offset = 0; + Uint32 size; +}; +static_assert(sizeof(GPUBufferRegion) == sizeof(SDL_GPUBufferRegion)); + +using GPUBufferBinding = SDL_GPUBufferBinding; + // // classes // @@ -439,6 +477,21 @@ public: SDL_BindGPUGraphicsPipeline(mHandle, pipeline); } + void bindVertexBuffers(std::span bindings, Uint32 firstBinding = 0) const noexcept + { + SDL_BindGPUVertexBuffers( + /* render_pass = */ mHandle, + /* first_binding = */ firstBinding, + /* bindings = */ bindings.data(), + /* num_bindings = */ static_cast(bindings.size()) + ); + } + + void bindVertexBuffer(const GPUBufferBinding& binding, Uint32 offset = 0) const noexcept + { + bindVertexBuffers({&binding, 1}, offset); + } + void drawPrimitives(const GPUDrawPrimitivesArgs& args) const noexcept { SDL_DrawGPUPrimitives(mHandle, args.numVertices, args.numInstances, args.firstVertex, args.firstInstance); @@ -447,6 +500,45 @@ public: friend class GPUCommandBuffer; }; +class GPUCopyPass : public Base +{ +public: + GPUCopyPass() noexcept = default; + GPUCopyPass(const GPUCopyPass&) = delete; + GPUCopyPass(GPUCopyPass&& other) noexcept : Base(std::move(other)) {} + + GPUCopyPass& operator=(const GPUCopyPass&) = delete; + GPUCopyPass& operator=(GPUCopyPass&& other) noexcept + { + Base::operator=(std::move(other)); + return *this; + } + auto operator<=>(const GPUCopyPass& other) const noexcept = default; + + void end() noexcept + { + SDL_EndGPUCopyPass(mHandle); + mHandle = nullptr; + } + + void destroy() noexcept + { + MIJIN_ASSERT(mHandle == nullptr, "Copypass has not been ended."); + } + + void uploadToGPUBuffer(const GPUTransferBufferLocation& source, const GPUBufferRegion& destination, bool cycle = false) + { + SDL_UploadToGPUBuffer( + /* copy_pass = */ mHandle, + /* source = */ std::bit_cast(&source), + /* destination = */ std::bit_cast(&destination), + /* cycle = */ cycle + ); + } + + friend class GPUCommandBuffer; +}; + class GPUTexture : public Base { private: @@ -526,6 +618,14 @@ public: return renderPass; } + [[nodiscard]] + GPUCopyPass beginCopyPass() const noexcept + { + GPUCopyPass copyPass; + copyPass.mHandle = SDL_BeginGPUCopyPass(mHandle); + return copyPass; + } + [[nodiscard]] GPUTexture acquireSwapchainTexture(SDL_Window* window, Uint32& outWidth, Uint32& outHeight) noexcept { @@ -534,6 +634,17 @@ public: return texture; } + template + void pushFragmentUniformData(Uint32 slotIndex, std::span data) const noexcept + { + SDL_PushGPUFragmentUniformData( + /* command_buffer = */ mHandle, + /* slot_index = */ slotIndex, + /* data = */ data.data(), + /* length = */ data.size_bytes() + ); + } + friend class GPUDevice; }; @@ -752,6 +863,116 @@ public: } } }; + +struct GPUBufferCreateArgs +{ + GPUBufferUsageFlags usage; + Uint32 size; +}; + +class GPUBuffer : public Base +{ +private: + SDL_GPUDevice* mDevice = nullptr; +public: + GPUBuffer() noexcept = default; + GPUBuffer(const GPUBuffer&) = delete; + GPUBuffer(GPUBuffer&& other) noexcept : Base(std::move(other)) {} + + GPUBuffer& operator=(const GPUBuffer&) = delete; + GPUBuffer& operator=(GPUBuffer&& other) noexcept + { + Base::operator=(std::move(other)); + return *this; + } + auto operator<=>(const GPUBuffer& other) const noexcept = default; + + void create(SDL_GPUDevice* device, const GPUBufferCreateArgs& args) + { + MIJIN_ASSERT(mHandle == nullptr, "GPUBuffer has already been created."); + const SDL_GPUBufferCreateInfo createInfo = { + .usage = static_cast(args.usage), + .size = args.size, + .props = 0 + }; + mHandle = SDL_CreateGPUBuffer(device, &createInfo); + if (mHandle == nullptr) + { + throw SDLError(); + } + mDevice = device; + } + + void destroy() noexcept + { + if (mHandle != nullptr) + { + SDL_ReleaseGPUBuffer(mDevice, mHandle); + mHandle = nullptr; + mDevice = nullptr; + } + } +}; + +struct GPUTransferBufferCreateArgs +{ + GPUTransferBufferUsage usage; + Uint32 size; +}; + +class GPUTransferBuffer : public Base +{ +private: + SDL_GPUDevice* mDevice = nullptr; +public: + GPUTransferBuffer() noexcept = default; + GPUTransferBuffer(const GPUTransferBuffer&) = delete; + GPUTransferBuffer(GPUTransferBuffer&& other) noexcept : Base(std::move(other)) {} + + GPUTransferBuffer& operator=(const GPUTransferBuffer&) = delete; + GPUTransferBuffer& operator=(GPUTransferBuffer&& other) noexcept + { + Base::operator=(std::move(other)); + return *this; + } + auto operator<=>(const GPUTransferBuffer& other) const noexcept = default; + + void create(SDL_GPUDevice* device, const GPUTransferBufferCreateArgs& args) + { + MIJIN_ASSERT(mHandle == nullptr, "GPUTransferBuffer has already been created."); + const SDL_GPUTransferBufferCreateInfo createInfo = { + .usage = static_cast(args.usage), + .size = args.size, + .props = 0 + }; + mHandle = SDL_CreateGPUTransferBuffer(device, &createInfo); + if (mHandle == nullptr) + { + throw SDLError(); + } + mDevice = device; + } + + void destroy() noexcept + { + if (mHandle != nullptr) + { + SDL_ReleaseGPUTransferBuffer(mDevice, mHandle); + mHandle = nullptr; + mDevice = nullptr; + } + } + + void* map(bool cycle = false) noexcept + { + return SDL_MapGPUTransferBuffer(mDevice, mHandle, cycle); + } + + void unmap() noexcept + { + SDL_UnmapGPUTransferBuffer(mDevice, mHandle); + } +}; } // namespace sdlpp #endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_GPU_HPP_INCLUDED) diff --git a/site_scons/site_tools/glslang.py b/site_scons/site_tools/glslang.py new file mode 100644 index 0000000..d698428 --- /dev/null +++ b/site_scons/site_tools/glslang.py @@ -0,0 +1,12 @@ + +from SCons.Script import * + +def exists(env: Environment) -> bool: + return True + +def generate(env : Environment): + glslang_spv_builder = Builder( + action = '$GLSLANG -V -o $TARGET $SOURCE' + ) + env['GLSLANG'] = 'glslang' + env.Append(BUILDERS={'SpirV': glslang_spv_builder})