From c073dc47c126664984539c4a938eafccfa5af1ca Mon Sep 17 00:00:00 2001 From: Sophomore <17792626+WSQS@users.noreply.github.com> Date: Sun, 16 Nov 2025 11:34:37 +0800 Subject: [PATCH 1/8] feat: include std:excepted --- main.cpp | 1 + sdl_wrapper/sdl_wrapper.gpu.cpp | 29 +++++--- sdl_wrapper/sdl_wrapper.gpu.ixx | 116 +++++++++++++++++++++----------- 3 files changed, 99 insertions(+), 47 deletions(-) diff --git a/main.cpp b/main.cpp index b053b87..e877aad 100644 --- a/main.cpp +++ b/main.cpp @@ -1,3 +1,4 @@ +// main.cpp #include #include #include diff --git a/sdl_wrapper/sdl_wrapper.gpu.cpp b/sdl_wrapper/sdl_wrapper.gpu.cpp index b759408..2830172 100644 --- a/sdl_wrapper/sdl_wrapper.gpu.cpp +++ b/sdl_wrapper/sdl_wrapper.gpu.cpp @@ -1,19 +1,32 @@ -// +// sdl_wrapper.gpu.cpp // Created by wsqsy on 11/14/2025. // module; -#include +#include "SDL3/SDL_gpu.h" +#include "SDL3/SDL_log.h" module sdl_wrapper; import :gpu; import :pipeline; namespace sopho { - BufferWrapper GpuWrapper::create_buffer(SDL_GPUBufferUsageFlags flag, uint32_t size) + std::expected GpuWrapper::create_buffer(SDL_GPUBufferUsageFlags flag, uint32_t size) { - SDL_GPUBufferCreateInfo create_info{flag, size}; - auto buffer = SDL_CreateGPUBuffer(m_device, &create_info); - BufferWrapper result(shared_from_this(), buffer); - return result; + return m_device.and_then( + [&](auto device) -> std::expected + { + SDL_GPUBufferCreateInfo create_info{flag, size}; + auto buffer = SDL_CreateGPUBuffer(device, &create_info); + if (!buffer) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); + return std::unexpected(GpuError::CREATE_BUFFER_FAILED); + } + return BufferWrapper{shared_from_this(), buffer}; + }); + } + std::expected GpuWrapper::create_pipeline() { + return m_device.transform([&](auto device)-> PipelineWrapper { + return PipelineWrapper{shared_from_this()}; + }); } - PipelineWrapper GpuWrapper::create_pipeline() { return PipelineWrapper{shared_from_this()}; } } // namespace sopho diff --git a/sdl_wrapper/sdl_wrapper.gpu.ixx b/sdl_wrapper/sdl_wrapper.gpu.ixx index 23592ca..46421cb 100644 --- a/sdl_wrapper/sdl_wrapper.gpu.ixx +++ b/sdl_wrapper/sdl_wrapper.gpu.ixx @@ -1,7 +1,8 @@ -// +// sdl_wrapper.gpu.ixx // Created by sophomore on 11/12/25. // module; +#include #include #include "SDL3/SDL_gpu.h" #include "SDL3/SDL_log.h" @@ -12,79 +13,116 @@ import :buffer; import :pipeline; export namespace sopho { + + enum class GpuError + { + CREATE_DEVICE_FAILED, + CREATE_WINDOW_FAILED, + CREATE_BUFFER_FAILED, + CREATE_SHADER_FAILED, + GET_TEXTUREFORMAT_FAILED, + }; + class GpuWrapper : public std::enable_shared_from_this { - SDL_GPUDevice* m_device{}; + std::expected m_device{}; // TODO: Consider multi window situation - SDL_Window* m_window{}; + std::expected m_window{}; public: - GpuWrapper() : m_device(SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV, true, nullptr)) + GpuWrapper() { - if (m_device == nullptr) + m_device = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV, true, nullptr); + if (!m_device) { SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); + m_device = std::unexpected(GpuError::CREATE_DEVICE_FAILED); + } + m_window = SDL_CreateWindow("Hello, Triangle!", 960, 540, SDL_WINDOW_RESIZABLE); + if (!m_window) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); + m_window = std::unexpected(GpuError::CREATE_WINDOW_FAILED); } } ~GpuWrapper() { - if (m_window) + m_window.and_then([&](auto window) { - SDL_ReleaseWindowFromGPUDevice(m_device, m_window); - SDL_DestroyWindow(m_window); - } - if (m_device) + m_device.and_then([&](auto device) + { + SDL_ReleaseWindowFromGPUDevice(window, device); + }); + SDL_DestroyWindow(window); + }); + m_device.and_then([&](auto device) { - SDL_DestroyGPUDevice(m_device); - } - m_device = nullptr; + SDL_DestroyGPUDevice(device); + }); + } auto data() { return m_device; } - BufferWrapper create_buffer(SDL_GPUBufferUsageFlags flag, uint32_t size); + std::expected create_buffer(SDL_GPUBufferUsageFlags flag, uint32_t size); - PipelineWrapper create_pipeline(); + std::expected create_pipeline(); - auto create_shader(const std::vector& p_shader, SDL_GPUShaderStage p_stage,uint32_t p_num_uniform_buffers) + auto create_shader(const std::vector& shader, SDL_GPUShaderStage stage, + uint32_t num_uniform_buffers) { - SDL_GPUShaderCreateInfo vertexInfo{}; - vertexInfo.code = p_shader.data(); - vertexInfo.code_size = p_shader.size(); - vertexInfo.entrypoint = "main"; - vertexInfo.format = SDL_GPU_SHADERFORMAT_SPIRV; - vertexInfo.stage = p_stage; - vertexInfo.num_samplers = 0; - vertexInfo.num_storage_buffers = 0; - vertexInfo.num_storage_textures = 0; - vertexInfo.num_uniform_buffers = p_num_uniform_buffers; - return SDL_CreateGPUShader(m_device, &vertexInfo); + return m_device.and_then( + [&](auto p_device) -> std::expected + { + SDL_GPUShaderCreateInfo info{}; + info.code = shader.data(); + info.code_size = shader.size(); + info.entrypoint = "main"; + info.format = SDL_GPU_SHADERFORMAT_SPIRV; + info.stage = stage; + info.num_samplers = 0; + info.num_storage_buffers = 0; + info.num_storage_textures = 0; + info.num_uniform_buffers = num_uniform_buffers; + auto shader = SDL_CreateGPUShader(p_device, &info); + if (!shader) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); + return std::unexpected(GpuError::CREATE_SHADER_FAILED); + } + return shader; + }); } - auto release_shader(SDL_GPUShader* shader) + auto release_shader(const std::expected& shader_expected) { - if (shader) - { - SDL_ReleaseGPUShader(m_device, shader); - } + m_device.and_then( + [&](auto p_device) + { shader_expected.and_then([&](auto shader) { SDL_ReleaseGPUShader(p_device, shader); }); }); } auto acquire_window() { - if (!m_window) - { - m_window = SDL_CreateWindow("Hello, Triangle!", 960, 540, SDL_WINDOW_RESIZABLE); - SDL_ClaimWindowForGPUDevice(m_device, m_window); - } return m_window; } auto get_texture_format() { - return SDL_GetGPUSwapchainTextureFormat(m_device, acquire_window()); + return m_device.and_then([&](auto device) -> std::expected + { + return m_window.and_then([&](auto window) + { + auto format = SDL_GetGPUSwapchainTextureFormat(device, window); + if (format == SDL_GPU_TEXTUREFORMAT_INVALID) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); + return std::unexpected(GpuError::GET_TEXTUREFORMAT_FAILED); + } + return format; + }); + }); } - }; } // namespace sopho From 755acba6ef9c1320ad06e5b1e63d6327acf50d9e Mon Sep 17 00:00:00 2001 From: Sophomore <17792626+WSQS@users.noreply.github.com> Date: Sun, 16 Nov 2025 13:16:30 +0800 Subject: [PATCH 2/8] fix:finish compile --- main.cpp | 235 +++++++++++++++++---------- sdl_wrapper/sdl_wrapper.buffer.cpp | 18 +- sdl_wrapper/sdl_wrapper.buffer.ixx | 4 + sdl_wrapper/sdl_wrapper.decl.ixx | 10 ++ sdl_wrapper/sdl_wrapper.gpu.cpp | 3 +- sdl_wrapper/sdl_wrapper.gpu.ixx | 129 ++++++++++----- sdl_wrapper/sdl_wrapper.pipeline.cpp | 89 +++++++--- sdl_wrapper/sdl_wrapper.pipeline.ixx | 7 +- 8 files changed, 328 insertions(+), 167 deletions(-) diff --git a/main.cpp b/main.cpp index e877aad..795b262 100644 --- a/main.cpp +++ b/main.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include "shaderc/shaderc.hpp" @@ -37,8 +38,9 @@ struct CameraUniform class UserApp : public sopho::App { std::shared_ptr gpu_wrapper{std::make_shared()}; - sopho::BufferWrapper vertex_buffer{gpu_wrapper->create_buffer(SDL_GPU_BUFFERUSAGE_VERTEX, sizeof(vertices))}; - sopho::PipelineWrapper pipeline_wrapper{gpu_wrapper->create_pipeline()}; + std::expected m_vertex_buffer{ + gpu_wrapper->create_buffer(SDL_GPU_BUFFERUSAGE_VERTEX, sizeof(vertices))}; + std::expected m_pipeline_wrapper{gpu_wrapper->create_pipeline_wrapper()}; float yaw = 0.0f; float pitch = 0.0f; @@ -92,12 +94,20 @@ void main() */ virtual SDL_AppResult init(int argc, char** argv) override { - - pipeline_wrapper.set_vertex_shader(vertex_source); - pipeline_wrapper.set_fragment_shader(fragment_source); - pipeline_wrapper.submit(); - - vertex_buffer.upload(&vertices, sizeof(vertices), 0); + m_pipeline_wrapper.and_then( + [&](auto& pipeline_wrapper) -> std::expected + { + pipeline_wrapper.set_vertex_shader(vertex_source); + pipeline_wrapper.set_fragment_shader(fragment_source); + pipeline_wrapper.submit(); + return std::monostate{}; + }); + m_vertex_buffer.and_then( + [&](auto& vertex_buffer) -> std::expected + { + vertex_buffer.upload(&vertices, sizeof(vertices), 0); + return std::monostate{}; + }); { // identity @@ -131,16 +141,25 @@ void main() // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this // unnecessary. We leave both here for documentation purpose) + gpu_wrapper->acquire_window().and_then( + [&](auto window) -> std::expected + { + ImGui_ImplSDL3_InitForSDLGPU(window); + return std::monostate{}; + }); // Setup Platform/Renderer backends - ImGui_ImplSDL3_InitForSDLGPU(gpu_wrapper->acquire_window()); - ImGui_ImplSDLGPU3_InitInfo init_info = {}; - init_info.Device = gpu_wrapper->data(); - init_info.ColorTargetFormat = - SDL_GetGPUSwapchainTextureFormat(gpu_wrapper->data(), gpu_wrapper->acquire_window()); - init_info.MSAASamples = SDL_GPU_SAMPLECOUNT_1; // Only used in multi-viewports mode. - init_info.SwapchainComposition = SDL_GPU_SWAPCHAINCOMPOSITION_SDR; // Only used in multi-viewports mode. - init_info.PresentMode = SDL_GPU_PRESENTMODE_VSYNC; - ImGui_ImplSDLGPU3_Init(&init_info); + gpu_wrapper->get_texture_format().and_then( + [&](auto format) -> std::expected + { + ImGui_ImplSDLGPU3_InitInfo init_info = {}; + init_info.Device = gpu_wrapper->data().value(); + init_info.ColorTargetFormat = format; + init_info.MSAASamples = SDL_GPU_SAMPLECOUNT_1; // Only used in multi-viewports mode. + init_info.SwapchainComposition = SDL_GPU_SWAPCHAINCOMPOSITION_SDR; // Only used in multi-viewports mode. + init_info.PresentMode = SDL_GPU_PRESENTMODE_VSYNC; + ImGui_ImplSDLGPU3_Init(&init_info); + return std::monostate{}; + }); return SDL_APP_CONTINUE; } @@ -178,7 +197,12 @@ void main() if (change) { // TODO: shouldn't upload in tick, we should delay this into draw function. - vertex_buffer.upload(&vertices, sizeof(vertices), 0); + m_vertex_buffer.and_then( + [&](auto& vertex_buffer) -> std::expected + { + vertex_buffer.upload(&vertices, sizeof(vertices), 0); + return std::monostate{}; + }); } } break; @@ -191,7 +215,12 @@ void main() if (ImGui::InputTextMultiline("##vertex editor", &vertex_source, size, ImGuiInputTextFlags_AllowTabInput)) { - pipeline_wrapper.set_vertex_shader(vertex_source); + m_pipeline_wrapper.and_then( + [&](auto& pipeline_wrapper) -> std::expected + { + pipeline_wrapper.set_vertex_shader(vertex_source); + return std::monostate{}; + }); } } break; @@ -204,7 +233,12 @@ void main() if (ImGui::InputTextMultiline("##fragment editor", &fragment_source, size, ImGuiInputTextFlags_AllowTabInput)) { - pipeline_wrapper.set_fragment_shader(fragment_source); + m_pipeline_wrapper.and_then( + [&](auto& pipeline_wrapper) -> std::expected + { + pipeline_wrapper.set_fragment_shader(fragment_source); + return std::monostate{}; + }); } } break; @@ -232,84 +266,105 @@ void main() ImGui::Render(); ImDrawData* draw_data = ImGui::GetDrawData(); - pipeline_wrapper.submit(); - - // acquire the command buffer - SDL_GPUCommandBuffer* commandBuffer = SDL_AcquireGPUCommandBuffer(gpu_wrapper->data()); - - // get the swapchain texture - SDL_GPUTexture* swapchainTexture; - Uint32 width, height; - SDL_WaitAndAcquireGPUSwapchainTexture(commandBuffer, gpu_wrapper->acquire_window(), &swapchainTexture, &width, - &height); - - // end the frame early if a swapchain texture is not available - if (swapchainTexture == NULL) - { - // you must always submit the command buffer - SDL_SubmitGPUCommandBuffer(commandBuffer); - return SDL_APP_CONTINUE; - } - - ImGui_ImplSDLGPU3_PrepareDrawData(draw_data, commandBuffer); - - // create the color target - SDL_GPUColorTargetInfo colorTargetInfo{}; - colorTargetInfo.clear_color = {240 / 255.0F, 240 / 255.0F, 240 / 255.0F, 255 / 255.0F}; - colorTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR; - colorTargetInfo.store_op = SDL_GPU_STOREOP_STORE; - colorTargetInfo.texture = swapchainTexture; - - // begin a render pass - SDL_GPURenderPass* renderPass = SDL_BeginGPURenderPass(commandBuffer, &colorTargetInfo, 1, NULL); - - // draw calls go here - SDL_BindGPUGraphicsPipeline(renderPass, pipeline_wrapper.data()); - - { - - float cy = std::cos(yaw); - float sy = std::sin(yaw); - float cp = std::cos(pitch); - float sp = std::sin(pitch); - - std::array Ry = {cy, 0.0F, sy, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, -sy, 0.0F, cy, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F}; - - std::array Rx = {1.0F, 0.0F, 0.0F, 0.0F, 0.0F, cp, sp, 0.0F, 0.0F, -sp, cp, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F}; - - auto mulMat4 = [](const float* A, const float* B, float* C) + m_pipeline_wrapper.and_then( + [](auto& pipeline_wrapper) -> std::expected { - for (int col = 0; col < 4; ++col) - { - for (int row = 0; row < 4; ++row) + pipeline_wrapper.submit(); + return std::monostate{}; + }); + gpu_wrapper->data().and_then( + [&](auto gpu)->std::expected + { + // acquire the command buffer + SDL_GPUCommandBuffer* commandBuffer = SDL_AcquireGPUCommandBuffer(gpu); + // get the swapchain texture + SDL_GPUTexture* swapchainTexture; + Uint32 width, height; + gpu_wrapper->acquire_window().and_then( + [&](auto window) ->std::expected { - C[col * 4 + row] = A[0 * 4 + row] * B[col * 4 + 0] + A[1 * 4 + row] * B[col * 4 + 1] + - A[2 * 4 + row] * B[col * 4 + 2] + A[3 * 4 + row] * B[col * 4 + 3]; - } + SDL_WaitAndAcquireGPUSwapchainTexture(commandBuffer, window, &swapchainTexture, &width, + &height); + return std::monostate{}; + }); + // end the frame early if a swapchain texture is not available + if (swapchainTexture == NULL) + { + // you must always submit the command buffer + SDL_SubmitGPUCommandBuffer(commandBuffer); + // return SDL_APP_CONTINUE; } - }; - - // uView = Rx * Ry - mulMat4(Rx.data(), Ry.data(), cam.m.data()); - SDL_PushGPUVertexUniformData(commandBuffer, 0, cam.m.data(), sizeof(cam.m)); - } - - // bind the vertex buffer - SDL_GPUBufferBinding bufferBindings[1]; - bufferBindings[0].buffer = vertex_buffer.data(); // index 0 is slot 0 in this example - bufferBindings[0].offset = 0; // start from the first byte + ImGui_ImplSDLGPU3_PrepareDrawData(draw_data, commandBuffer); + // create the color target + SDL_GPUColorTargetInfo colorTargetInfo{}; + colorTargetInfo.clear_color = {240 / 255.0F, 240 / 255.0F, 240 / 255.0F, 255 / 255.0F}; + colorTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR; + colorTargetInfo.store_op = SDL_GPU_STOREOP_STORE; + colorTargetInfo.texture = swapchainTexture; + + + // begin a render pass + SDL_GPURenderPass* renderPass = SDL_BeginGPURenderPass(commandBuffer, &colorTargetInfo, 1, NULL); + m_pipeline_wrapper.and_then( + [&](auto& pipeline_wrapper)->std::expected + { + // draw calls go here + SDL_BindGPUGraphicsPipeline(renderPass, pipeline_wrapper.data()); + return std::monostate{}; + }); + { - SDL_BindGPUVertexBuffers(renderPass, 0, bufferBindings, 1); // bind one buffer starting from slot 0 + float cy = std::cos(yaw); + float sy = std::sin(yaw); + float cp = std::cos(pitch); + float sp = std::sin(pitch); - SDL_DrawGPUPrimitives(renderPass, 3, 1, 0, 0); + std::array Ry = {cy, 0.0F, sy, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, + -sy, 0.0F, cy, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F}; - ImGui_ImplSDLGPU3_RenderDrawData(draw_data, commandBuffer, renderPass); - // end the render pass - SDL_EndGPURenderPass(renderPass); + std::array Rx = {1.0F, 0.0F, 0.0F, 0.0F, 0.0F, cp, sp, 0.0F, + 0.0F, -sp, cp, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F}; - // submit the command buffer - SDL_SubmitGPUCommandBuffer(commandBuffer); + auto mulMat4 = [](const float* A, const float* B, float* C) + { + for (int col = 0; col < 4; ++col) + { + for (int row = 0; row < 4; ++row) + { + C[col * 4 + row] = A[0 * 4 + row] * B[col * 4 + 0] + A[1 * 4 + row] * B[col * 4 + 1] + + A[2 * 4 + row] * B[col * 4 + 2] + A[3 * 4 + row] * B[col * 4 + 3]; + } + } + }; + + // uView = Rx * Ry + mulMat4(Rx.data(), Ry.data(), cam.m.data()); + SDL_PushGPUVertexUniformData(commandBuffer, 0, cam.m.data(), sizeof(cam.m)); + } + m_vertex_buffer.and_then( + [&](auto& vertex_buffer)->std::expected + { + // bind the vertex buffer + SDL_GPUBufferBinding bufferBindings[1]; + bufferBindings[0].buffer = vertex_buffer.data(); // index 0 is slot 0 in this example + bufferBindings[0].offset = 0; // start from the first byte + + SDL_BindGPUVertexBuffers(renderPass, 0, bufferBindings, + 1); // bind one buffer starting from slot 0 + return std::monostate{}; + }); + + SDL_DrawGPUPrimitives(renderPass, 3, 1, 0, 0); + + ImGui_ImplSDLGPU3_RenderDrawData(draw_data, commandBuffer, renderPass); + // end the render pass + SDL_EndGPURenderPass(renderPass); + + // submit the command buffer + SDL_SubmitGPUCommandBuffer(commandBuffer); + return std::monostate{}; + }); return SDL_APP_CONTINUE; } diff --git a/sdl_wrapper/sdl_wrapper.buffer.cpp b/sdl_wrapper/sdl_wrapper.buffer.cpp index 38e3cb1..86efedc 100644 --- a/sdl_wrapper/sdl_wrapper.buffer.cpp +++ b/sdl_wrapper/sdl_wrapper.buffer.cpp @@ -23,11 +23,15 @@ namespace sopho BufferWrapper::~BufferWrapper() { - SDL_ReleaseGPUBuffer(m_gpu->data(), m_vertex_buffer); + if (!m_gpu) + { + return; + } + m_gpu->release_buffer(m_vertex_buffer); m_vertex_buffer = nullptr; if (m_transfer_buffer) { - SDL_ReleaseGPUTransferBuffer(m_gpu->data(), m_transfer_buffer); + SDL_ReleaseGPUTransferBuffer(m_gpu->data().value(), m_transfer_buffer); m_transfer_buffer = nullptr; m_transfer_buffer_size = 0; } @@ -50,19 +54,19 @@ namespace sopho { if (m_transfer_buffer != nullptr) { - SDL_ReleaseGPUTransferBuffer(m_gpu->data(), m_transfer_buffer); + SDL_ReleaseGPUTransferBuffer(m_gpu->data().value(), m_transfer_buffer); } SDL_GPUTransferBufferCreateInfo transfer_info{SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, p_size, 0}; - m_transfer_buffer = SDL_CreateGPUTransferBuffer(m_gpu->data(), &transfer_info); + m_transfer_buffer = SDL_CreateGPUTransferBuffer(m_gpu->data().value(), &transfer_info); m_transfer_buffer_size = transfer_info.size; } - auto data = SDL_MapGPUTransferBuffer(m_gpu->data(), m_transfer_buffer, false); + auto data = SDL_MapGPUTransferBuffer(m_gpu->data().value(), m_transfer_buffer, false); SDL_memcpy(data, p_data, p_size); - SDL_UnmapGPUTransferBuffer(m_gpu->data(), m_transfer_buffer); + SDL_UnmapGPUTransferBuffer(m_gpu->data().value(), m_transfer_buffer); // TODO: Delay submit command in collect - auto command_buffer = SDL_AcquireGPUCommandBuffer(m_gpu->data()); + auto command_buffer = SDL_AcquireGPUCommandBuffer(m_gpu->data().value()); auto copy_pass = SDL_BeginGPUCopyPass(command_buffer); SDL_GPUTransferBufferLocation location{}; diff --git a/sdl_wrapper/sdl_wrapper.buffer.ixx b/sdl_wrapper/sdl_wrapper.buffer.ixx index 163d9d6..2f14bb9 100644 --- a/sdl_wrapper/sdl_wrapper.buffer.ixx +++ b/sdl_wrapper/sdl_wrapper.buffer.ixx @@ -22,6 +22,10 @@ export namespace sopho } public: + BufferWrapper(const BufferWrapper&) = delete; + BufferWrapper& operator=(const BufferWrapper&) = delete; + BufferWrapper(BufferWrapper&& other) noexcept = default; + BufferWrapper& operator=(BufferWrapper&& other) noexcept = default; void upload(void* p_data, uint32_t p_size, uint32_t p_offset); auto data() { return m_vertex_buffer; } diff --git a/sdl_wrapper/sdl_wrapper.decl.ixx b/sdl_wrapper/sdl_wrapper.decl.ixx index 0ac8bce..c45051d 100644 --- a/sdl_wrapper/sdl_wrapper.decl.ixx +++ b/sdl_wrapper/sdl_wrapper.decl.ixx @@ -6,6 +6,16 @@ export module sdl_wrapper:decl; export namespace sopho { + enum class GpuError + { + CREATE_DEVICE_FAILED, + CREATE_WINDOW_FAILED, + CREATE_BUFFER_FAILED, + CREATE_SHADER_FAILED, + CREATE_PIPELINE_FAILED, + GET_TEXTUREFORMAT_FAILED, + }; + class App; class GpuWrapper; class BufferWrapper; diff --git a/sdl_wrapper/sdl_wrapper.gpu.cpp b/sdl_wrapper/sdl_wrapper.gpu.cpp index 2830172..845e2ad 100644 --- a/sdl_wrapper/sdl_wrapper.gpu.cpp +++ b/sdl_wrapper/sdl_wrapper.gpu.cpp @@ -2,6 +2,7 @@ // Created by wsqsy on 11/14/2025. // module; +#include #include "SDL3/SDL_gpu.h" #include "SDL3/SDL_log.h" module sdl_wrapper; @@ -24,7 +25,7 @@ namespace sopho return BufferWrapper{shared_from_this(), buffer}; }); } - std::expected GpuWrapper::create_pipeline() { + std::expected GpuWrapper::create_pipeline_wrapper() { return m_device.transform([&](auto device)-> PipelineWrapper { return PipelineWrapper{shared_from_this()}; }); diff --git a/sdl_wrapper/sdl_wrapper.gpu.ixx b/sdl_wrapper/sdl_wrapper.gpu.ixx index 46421cb..86f4b9f 100644 --- a/sdl_wrapper/sdl_wrapper.gpu.ixx +++ b/sdl_wrapper/sdl_wrapper.gpu.ixx @@ -4,31 +4,25 @@ module; #include #include +#include + #include "SDL3/SDL_gpu.h" #include "SDL3/SDL_log.h" #include "SDL3/SDL_video.h" #include "shaderc/shaderc.hpp" export module sdl_wrapper:gpu; +import :decl; import :buffer; import :pipeline; export namespace sopho { - enum class GpuError - { - CREATE_DEVICE_FAILED, - CREATE_WINDOW_FAILED, - CREATE_BUFFER_FAILED, - CREATE_SHADER_FAILED, - GET_TEXTUREFORMAT_FAILED, - }; - class GpuWrapper : public std::enable_shared_from_this { std::expected m_device{}; // TODO: Consider multi window situation - std::expected m_window{}; + std::expected m_window{}; public: GpuWrapper() @@ -45,33 +39,74 @@ export namespace sopho SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); m_window = std::unexpected(GpuError::CREATE_WINDOW_FAILED); } + m_device.and_then( + [&](auto device) + { + return m_window.and_then( + [&](auto window) -> std::expected + { + SDL_ClaimWindowForGPUDevice(device, window); + return std::monostate{}; + }); + }); } ~GpuWrapper() { - m_window.and_then([&](auto window) - { - m_device.and_then([&](auto device) + m_window.and_then( + [&](auto window) { - SDL_ReleaseWindowFromGPUDevice(window, device); + SDL_DestroyWindow(window); + return m_device.and_then( + [&](auto device) -> std::expected + { + SDL_ReleaseWindowFromGPUDevice(device, window); + return std::monostate{}; + }); + }); + m_device.and_then( + [&](auto device) -> std::expected + { + SDL_DestroyGPUDevice(device); + return std::monostate{}; }); - SDL_DestroyWindow(window); - }); - m_device.and_then([&](auto device) - { - SDL_DestroyGPUDevice(device); - }); - } auto data() { return m_device; } std::expected create_buffer(SDL_GPUBufferUsageFlags flag, uint32_t size); - std::expected create_pipeline(); + auto release_buffer(SDL_GPUBuffer* buffer) + { + m_device.and_then( + [&](auto device) -> std::expected + { + SDL_ReleaseGPUBuffer(device, buffer); + return std::monostate{}; + }); + } + + std::expected create_pipeline_wrapper(); - auto create_shader(const std::vector& shader, SDL_GPUShaderStage stage, - uint32_t num_uniform_buffers) + auto create_pipeline(const std::expected& create_info) + { + return m_device.and_then( + [&](auto device) + { + return create_info.and_then( + [&](auto pipeline_info) -> std::expected + { + auto pipeline = SDL_CreateGPUGraphicsPipeline(device, &pipeline_info); + if (!pipeline) + { + return std::unexpected(GpuError::CREATE_PIPELINE_FAILED); + } + return pipeline; + }); + }); + } + + auto create_shader(const std::vector& shader, SDL_GPUShaderStage stage, uint32_t num_uniform_buffers) { return m_device.and_then( [&](auto p_device) -> std::expected @@ -99,30 +134,46 @@ export namespace sopho auto release_shader(const std::expected& shader_expected) { m_device.and_then( - [&](auto p_device) - { shader_expected.and_then([&](auto shader) { SDL_ReleaseGPUShader(p_device, shader); }); }); + [&](auto device) + { + return shader_expected.and_then( + [&](auto shader) -> std::expected + { + SDL_ReleaseGPUShader(device, shader); + return std::monostate{}; + }); + }); } - auto acquire_window() + auto release_pipeline(SDL_GPUGraphicsPipeline* graphics_pipeline) { - return m_window; + m_device.and_then( + [&](auto device) -> std::expected + { + SDL_ReleaseGPUGraphicsPipeline(device, graphics_pipeline); + return std::monostate{}; + }); } + auto acquire_window() { return m_window; } + auto get_texture_format() { - return m_device.and_then([&](auto device) -> std::expected - { - return m_window.and_then([&](auto window) + return m_device.and_then( + [&](auto device) -> std::expected { - auto format = SDL_GetGPUSwapchainTextureFormat(device, window); - if (format == SDL_GPU_TEXTUREFORMAT_INVALID) - { - SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); - return std::unexpected(GpuError::GET_TEXTUREFORMAT_FAILED); - } - return format; + return m_window.and_then( + [&](auto window) -> std::expected + { + auto format = SDL_GetGPUSwapchainTextureFormat(device, window); + if (format == SDL_GPU_TEXTUREFORMAT_INVALID) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); + return std::unexpected(GpuError::GET_TEXTUREFORMAT_FAILED); + } + return format; + }); }); - }); } }; } // namespace sopho diff --git a/sdl_wrapper/sdl_wrapper.pipeline.cpp b/sdl_wrapper/sdl_wrapper.pipeline.cpp index 95fd6e5..f0dbb9e 100644 --- a/sdl_wrapper/sdl_wrapper.pipeline.cpp +++ b/sdl_wrapper/sdl_wrapper.pipeline.cpp @@ -2,7 +2,9 @@ // Created by sophomore on 11/13/25. // module; +#include #include +#include #include "SDL3/SDL_gpu.h" #include "SDL3/SDL_log.h" #include "shaderc/shaderc.hpp" @@ -25,28 +27,36 @@ namespace sopho options.SetTargetEnvironment(shaderc_target_env_vulkan, 0); m_vertex_buffer_description.emplace_back(0, 28, SDL_GPU_VERTEXINPUTRATE_VERTEX, 0); - m_pipeline_info.vertex_input_state.vertex_buffer_descriptions = m_vertex_buffer_description.data(); - m_pipeline_info.vertex_input_state.num_vertex_buffers = m_vertex_buffer_description.size(); + m_pipeline_info = m_device->get_texture_format().transform( + [&](auto format) + { + SDL_GPUGraphicsPipelineCreateInfo pipeline_info{}; + + pipeline_info.vertex_input_state.vertex_buffer_descriptions = m_vertex_buffer_description.data(); + pipeline_info.vertex_input_state.num_vertex_buffers = m_vertex_buffer_description.size(); - m_vertex_attribute.emplace_back(0, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, 0); - m_vertex_attribute.emplace_back(1, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4, sizeof(float) * 3); - m_pipeline_info.vertex_input_state.vertex_attributes = m_vertex_attribute.data(); - m_pipeline_info.vertex_input_state.num_vertex_attributes = m_vertex_attribute.size(); + m_vertex_attribute.emplace_back(0, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, 0); + m_vertex_attribute.emplace_back(1, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4, sizeof(float) * 3); + pipeline_info.vertex_input_state.vertex_attributes = m_vertex_attribute.data(); + pipeline_info.vertex_input_state.num_vertex_attributes = m_vertex_attribute.size(); - m_pipeline_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; + pipeline_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; - m_color_target_description.emplace_back(m_device->get_texture_format(), - SDL_GPUColorTargetBlendState{SDL_GPU_BLENDFACTOR_SRC_ALPHA, - SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, - SDL_GPU_BLENDOP_ADD, - SDL_GPU_BLENDFACTOR_SRC_ALPHA, - SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, - SDL_GPU_BLENDOP_ADD, - {}, - true}); + m_color_target_description.emplace_back( + format, + SDL_GPUColorTargetBlendState{SDL_GPU_BLENDFACTOR_SRC_ALPHA, + SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + SDL_GPU_BLENDOP_ADD, + SDL_GPU_BLENDFACTOR_SRC_ALPHA, + SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + SDL_GPU_BLENDOP_ADD, + {}, + true}); + pipeline_info.target_info.color_target_descriptions = m_color_target_description.data(); + pipeline_info.target_info.num_color_targets = m_color_target_description.size(); + return pipeline_info; + }); - m_pipeline_info.target_info.color_target_descriptions = m_color_target_description.data(); - m_pipeline_info.target_info.num_color_targets = m_color_target_description.size(); m_modified = true; } @@ -62,7 +72,7 @@ namespace sopho { if (m_graphics_pipeline) { - SDL_ReleaseGPUGraphicsPipeline(m_device->data(), m_graphics_pipeline); + m_device->release_pipeline(m_graphics_pipeline); m_graphics_pipeline = nullptr; } m_device->release_shader(m_vertex_shader); @@ -81,17 +91,22 @@ namespace sopho if (m_modified) { m_modified = false; - m_pipeline_info.vertex_input_state.vertex_buffer_descriptions = m_vertex_buffer_description.data(); - m_pipeline_info.vertex_input_state.vertex_attributes = m_vertex_attribute.data(); - m_pipeline_info.target_info.color_target_descriptions = m_color_target_description.data(); - auto new_graphics_pipeline = SDL_CreateGPUGraphicsPipeline(m_device->data(), &m_pipeline_info); + m_pipeline_info = m_pipeline_info.transform( + [&](auto pipeline_info) + { + pipeline_info.vertex_input_state.vertex_buffer_descriptions = m_vertex_buffer_description.data(); + pipeline_info.vertex_input_state.vertex_attributes = m_vertex_attribute.data(); + pipeline_info.target_info.color_target_descriptions = m_color_target_description.data(); + return pipeline_info; + }); + auto new_graphics_pipeline = m_device->create_pipeline(m_pipeline_info); if (new_graphics_pipeline) { if (m_graphics_pipeline) { - SDL_ReleaseGPUGraphicsPipeline(m_device->data(), m_graphics_pipeline); + m_device->release_pipeline(m_graphics_pipeline); } - m_graphics_pipeline = new_graphics_pipeline; + m_graphics_pipeline = new_graphics_pipeline.value(); } else { @@ -126,7 +141,17 @@ namespace sopho auto ptr = reinterpret_cast(result.cbegin()); std::vector code{ptr, ptr + code_size}; m_vertex_shader = m_device->create_shader(code, SDL_GPU_SHADERSTAGE_VERTEX, 1); - m_pipeline_info.vertex_shader = m_vertex_shader; + m_vertex_shader.and_then( + [&](auto shader) -> std::expected + { + m_pipeline_info = m_pipeline_info.transform( + [&](auto pipeline_info) + { + pipeline_info.vertex_shader = shader; + return pipeline_info; + }); + return std::monostate{}; + }); m_modified = true; } } @@ -155,7 +180,17 @@ namespace sopho auto ptr = reinterpret_cast(result.cbegin()); std::vector code{ptr, ptr + code_size}; m_fragment_shader = m_device->create_shader(code, SDL_GPU_SHADERSTAGE_FRAGMENT, 0); - m_pipeline_info.fragment_shader = m_fragment_shader; + m_fragment_shader.and_then( + [&](auto shader) -> std::expected + { + m_pipeline_info = m_pipeline_info.transform( + [&](auto pipeline_info) + { + pipeline_info.fragment_shader = shader; + return pipeline_info; + }); + return std::monostate{}; + }); m_modified = true; } } diff --git a/sdl_wrapper/sdl_wrapper.pipeline.ixx b/sdl_wrapper/sdl_wrapper.pipeline.ixx index c410292..bfc5d96 100644 --- a/sdl_wrapper/sdl_wrapper.pipeline.ixx +++ b/sdl_wrapper/sdl_wrapper.pipeline.ixx @@ -2,6 +2,7 @@ // Created by sophomore on 11/11/25. // module; +#include #include #include "SDL3/SDL_gpu.h" #include "shaderc/shaderc.hpp" @@ -14,12 +15,12 @@ export namespace sopho SDL_GPUGraphicsPipeline* m_graphics_pipeline{}; std::shared_ptr m_device{}; - SDL_GPUShader* m_vertex_shader{}; - SDL_GPUShader* m_fragment_shader{}; + std::expected m_vertex_shader{}; + std::expected m_fragment_shader{}; std::vector m_vertex_buffer_description{}; std::vector m_vertex_attribute{}; std::vector m_color_target_description{}; - SDL_GPUGraphicsPipelineCreateInfo m_pipeline_info{}; + std::expected m_pipeline_info{}; shaderc::Compiler compiler{}; shaderc::CompileOptions options{}; From 701a26a706124e432d9868dc086e20a4beab886f Mon Sep 17 00:00:00 2001 From: Sophomore <17792626+WSQS@users.noreply.github.com> Date: Sun, 16 Nov 2025 18:44:49 +0800 Subject: [PATCH 3/8] fix:change init check logic --- sdl_wrapper/sdl_wrapper.gpu.ixx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/sdl_wrapper/sdl_wrapper.gpu.ixx b/sdl_wrapper/sdl_wrapper.gpu.ixx index 86f4b9f..d7bf428 100644 --- a/sdl_wrapper/sdl_wrapper.gpu.ixx +++ b/sdl_wrapper/sdl_wrapper.gpu.ixx @@ -27,18 +27,26 @@ export namespace sopho public: GpuWrapper() { - m_device = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV, true, nullptr); - if (!m_device) + auto device = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV, true, nullptr); + if (device == nullptr) { SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); m_device = std::unexpected(GpuError::CREATE_DEVICE_FAILED); } - m_window = SDL_CreateWindow("Hello, Triangle!", 960, 540, SDL_WINDOW_RESIZABLE); - if (!m_window) + else + { + m_device = device; + } + auto window = SDL_CreateWindow("Hello, Triangle!", 960, 540, SDL_WINDOW_RESIZABLE); + if (window == nullptr) { SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); m_window = std::unexpected(GpuError::CREATE_WINDOW_FAILED); } + else + { + m_window = window; + } m_device.and_then( [&](auto device) { From 216056b517fc6dbd5e51e6e81e892a4f72986f49 Mon Sep 17 00:00:00 2001 From: Sophomore <17792626+WSQS@users.noreply.github.com> Date: Sun, 16 Nov 2025 21:13:22 +0800 Subject: [PATCH 4/8] refactor: refactor whole code --- main.cpp | 526 ++++++++++++++------------- sdl_wrapper/sdl_wrapper.buffer.cpp | 132 +++++-- sdl_wrapper/sdl_wrapper.buffer.ixx | 47 ++- sdl_wrapper/sdl_wrapper.decl.ixx | 11 + sdl_wrapper/sdl_wrapper.gpu.cpp | 29 +- sdl_wrapper/sdl_wrapper.gpu.ixx | 394 ++++++++++++-------- sdl_wrapper/sdl_wrapper.pipeline.cpp | 351 ++++++++++-------- sdl_wrapper/sdl_wrapper.pipeline.ixx | 67 +++- 8 files changed, 926 insertions(+), 631 deletions(-) diff --git a/main.cpp b/main.cpp index 795b262..4f29769 100644 --- a/main.cpp +++ b/main.cpp @@ -4,8 +4,9 @@ #include #include #include +#include #include -#include "shaderc/shaderc.hpp" +#include #include "imgui.h" #include "imgui_impl_sdl3.h" @@ -15,19 +16,18 @@ #include "SDL3/SDL.h" #include "SDL3/SDL_gpu.h" #include "SDL3/SDL_keycode.h" + import sdl_wrapper; // the vertex input layout struct Vertex { float x, y, z; // vec3 position - float r, g, b, a; /** - * @brief Get a pointer to the vertex position's first component. - * - * @return float* Pointer to the `x` member; can be used to access the contiguous position - * components `(x, y, z)`. - */ - auto position() { return &x; } + float r, g, b, a; // vec4 color + + /// Returns a pointer to the first component of the position (x). + /// This can be passed to ImGui::DragFloat3 etc. + float* position() { return &x; } }; struct CameraUniform @@ -37,11 +37,12 @@ struct CameraUniform class UserApp : public sopho::App { - std::shared_ptr gpu_wrapper{std::make_shared()}; - std::expected m_vertex_buffer{ - gpu_wrapper->create_buffer(SDL_GPU_BUFFERUSAGE_VERTEX, sizeof(vertices))}; - std::expected m_pipeline_wrapper{gpu_wrapper->create_pipeline_wrapper()}; + // GPU + resources + std::shared_ptr m_gpu{}; + std::expected m_vertex_buffer{std::unexpected(sopho::GpuError::UNINITIALIZED)}; + std::expected m_pipeline_wrapper{std::unexpected(sopho::GpuError::UNINITIALIZED)}; + // camera state float yaw = 0.0f; float pitch = 0.0f; @@ -71,6 +72,7 @@ void main() gl_Position = uView * vec4(a_position, 1.0f); v_color = a_color; })WSQ"; + std::string fragment_source = R"WSQ(#version 460 @@ -82,97 +84,131 @@ void main() FragColor = v_color; })WSQ"; +public: /** * @brief Initialize application resources, GPU pipeline, vertex data, and Dear ImGui. * - * Configures the graphics pipeline and vertex input, uploads the initial vertex buffer contents, - * initializes the camera uniform to the identity matrix, and sets up Dear ImGui (context, style/DPI - * scaling, and SDL3/SDLGPU backends). - * - * @return SDL_AppResult `SDL_APP_CONTINUE` to enter the main loop, `SDL_APP_SUCCESS` to request immediate - * termination. + * Creates the GPU device, window/pipeline/buffer wrappers, compiles shaders, + * uploads the initial vertex data, initializes the camera to identity, + * and sets up Dear ImGui and its SDL3/SDLGPU backends. */ - virtual SDL_AppResult init(int argc, char** argv) override + SDL_AppResult init(int argc, char** argv) override { - m_pipeline_wrapper.and_then( - [&](auto& pipeline_wrapper) -> std::expected - { - pipeline_wrapper.set_vertex_shader(vertex_source); - pipeline_wrapper.set_fragment_shader(fragment_source); - pipeline_wrapper.submit(); - return std::monostate{}; - }); - m_vertex_buffer.and_then( - [&](auto& vertex_buffer) -> std::expected - { - vertex_buffer.upload(&vertices, sizeof(vertices), 0); - return std::monostate{}; - }); + // 1. Create GPU wrapper (device + window + claim), monadic style. + auto gpu_result = sopho::GpuWrapper::create(); + if (!gpu_result) + { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create GpuWrapper, error = %d", + static_cast(gpu_result.error())); + return SDL_APP_FAILURE; + } + m_gpu = std::move(gpu_result.value()); + // 2. Create vertex buffer. + m_vertex_buffer = + m_gpu->create_buffer(SDL_GPU_BUFFERUSAGE_VERTEX, static_cast(sizeof(vertices))); + if (!m_vertex_buffer) { - // identity - cam.m[0] = cam.m[5] = cam.m[10] = cam.m[15] = 1.0F; - cam.m[1] = cam.m[2] = cam.m[3] = 0.0F; - cam.m[4] = cam.m[6] = cam.m[7] = 0.0F; - cam.m[8] = cam.m[9] = cam.m[11] = 0.0F; - cam.m[12] = cam.m[13] = cam.m[14] = 0.0F; + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create vertex buffer, error = %d", + static_cast(m_vertex_buffer.error())); + return SDL_APP_FAILURE; } + // 3. Create pipeline wrapper. + auto pw_result = m_gpu->create_pipeline_wrapper(); + if (!pw_result) + { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create pipeline wrapper, error = %d", + static_cast(pw_result.error())); + return SDL_APP_FAILURE; + } + m_pipeline_wrapper.emplace(std::move(pw_result.value())); + + // 4. Compile shaders and build initial pipeline. + auto pipeline_init = + m_pipeline_wrapper.and_then([&](auto& pipeline) { return pipeline.set_vertex_shader(vertex_source); }) + .and_then([&](std::monostate) { return m_pipeline_wrapper->set_fragment_shader(fragment_source); }) + .and_then([&](std::monostate) { return m_pipeline_wrapper->submit(); }); + + if (!pipeline_init) + { + SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Failed to initialize pipeline, error = %d", + static_cast(pipeline_init.error())); + return SDL_APP_FAILURE; + } - // Setup Dear ImGui context + // 5. Upload initial vertex data. + auto upload_result = m_vertex_buffer.and_then( + [&](auto& vertex_buffer) + { return vertex_buffer.upload(vertices.data(), static_cast(sizeof(vertices)), 0); }); + + if (!upload_result) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to upload initial vertex data, error = %d", + static_cast(upload_result.error())); + return SDL_APP_FAILURE; + } + + // 6. Initialize camera matrix to identity. + { + cam.m.fill(0.0F); + cam.m[0] = 1.0F; + cam.m[5] = 1.0F; + cam.m[10] = 1.0F; + cam.m[15] = 1.0F; + } + + // 7. Setup Dear ImGui context. IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls - io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; - // Setup Dear ImGui style ImGui::StyleColorsDark(); - // ImGui::StyleColorsLight(); float main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); - // Setup scaling + ImGuiStyle& style = ImGui::GetStyle(); style.ScaleAllSizes(main_scale); - // Bake a fixed style scale. (until we have a solution for dynamic style - // scaling, changing this requires resetting Style + calling this again) style.FontScaleDpi = main_scale; - // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this - // unnecessary. We leave both here for documentation purpose) - gpu_wrapper->acquire_window().and_then( - [&](auto window) -> std::expected - { - ImGui_ImplSDL3_InitForSDLGPU(window); - return std::monostate{}; - }); - // Setup Platform/Renderer backends - gpu_wrapper->get_texture_format().and_then( - [&](auto format) -> std::expected - { - ImGui_ImplSDLGPU3_InitInfo init_info = {}; - init_info.Device = gpu_wrapper->data().value(); - init_info.ColorTargetFormat = format; - init_info.MSAASamples = SDL_GPU_SAMPLECOUNT_1; // Only used in multi-viewports mode. - init_info.SwapchainComposition = SDL_GPU_SWAPCHAINCOMPOSITION_SDR; // Only used in multi-viewports mode. - init_info.PresentMode = SDL_GPU_PRESENTMODE_VSYNC; - ImGui_ImplSDLGPU3_Init(&init_info); - return std::monostate{}; - }); + // 8. Initialize ImGui SDL3 backend. + if (SDL_Window* window = m_gpu->window()) + { + ImGui_ImplSDL3_InitForSDLGPU(window); + } + else + { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, + "GpuWrapper::window() returned null; ImGui SDL3 backend not initialized"); + return SDL_APP_FAILURE; + } + + // 9. Initialize ImGui SDLGPU backend. + auto format_result = m_gpu->get_texture_format(); + if (!format_result) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to get swapchain texture format, error = %d", + static_cast(format_result.error())); + return SDL_APP_FAILURE; + } + + ImGui_ImplSDLGPU3_InitInfo init_info{}; + init_info.Device = m_gpu->device(); + init_info.ColorTargetFormat = format_result.value(); + init_info.MSAASamples = SDL_GPU_SAMPLECOUNT_1; + init_info.SwapchainComposition = SDL_GPU_SWAPCHAINCOMPOSITION_SDR; + init_info.PresentMode = SDL_GPU_PRESENTMODE_VSYNC; + + ImGui_ImplSDLGPU3_Init(&init_info); return SDL_APP_CONTINUE; } /** - * @brief Advance the UI frame, present interactive editors for triangle vertices and shader source, and apply - * edits. - * - * Presents a NodeEditor with draggable 3D position editors for each vertex and a SourceEditor with a multiline - * shader text editor. If a vertex position is modified, the vertex buffer is updated with the new vertex data. - * If the shader source is modified, the pipeline's vertex shader source is updated. - * - * @return SDL_AppResult SDL_APP_CONTINUE to indicate the application should continue running. + * @brief Advance the UI frame, present editors for triangle vertices and shader sources. */ SDL_AppResult tick() { @@ -182,203 +218,215 @@ void main() ImGui::ShowDemoWindow(); + ImGui::Begin("Editor"); + static int current = 0; + std::array items = {"Node", "Vertex", "Fragment"}; + ImGui::Combo("##Object", ¤t, items.data(), static_cast(items.size())); + + switch (current) { - ImGui::Begin("Editor"); - static int current = 0; - std::array items = {"Node", "Vertex", "Fragment"}; - ImGui::Combo("##Object", ¤t, items.data(), items.size()); - switch (current) + case 0: // Vertex positions { - case 0: + bool changed = false; + changed = ImGui::DragFloat3("##node1", vertices[0].position(), 0.01f, -1.f, 1.f) || changed; + changed = ImGui::DragFloat3("##node2", vertices[1].position(), 0.01f, -1.f, 1.f) || changed; + changed = ImGui::DragFloat3("##node3", vertices[2].position(), 0.01f, -1.f, 1.f) || changed; + + if (changed) { - auto change = ImGui::DragFloat3("##node1", vertices[0].position(), 0.01f, -1.f, 1.f); - change = ImGui::DragFloat3("##node2", vertices[1].position(), 0.01f, -1.f, 1.f) || change; - change = ImGui::DragFloat3("##node3", vertices[2].position(), 0.01f, -1.f, 1.f) || change; - if (change) + auto upload_result = m_vertex_buffer.and_then( + [&](auto& vertex_buffer) + { + return vertex_buffer.upload(vertices.data(), static_cast(sizeof(vertices)), + 0); + }); + if (!upload_result) { - // TODO: shouldn't upload in tick, we should delay this into draw function. - m_vertex_buffer.and_then( - [&](auto& vertex_buffer) -> std::expected - { - vertex_buffer.upload(&vertices, sizeof(vertices), 0); - return std::monostate{}; - }); + SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to upload vertex buffer in tick(), error = %d", + static_cast(upload_result.error())); } } - break; - case 1: + } + break; + + case 1: // Vertex shader editor + { + auto line_count = std::count(vertex_source.begin(), vertex_source.end(), '\n'); + ImVec2 size(ImGui::GetContentRegionAvail().x, + std::min(ImGui::GetTextLineHeight() * (line_count + 3), ImGui::GetContentRegionAvail().y)); + + if (ImGui::InputTextMultiline("##vertex editor", &vertex_source, size, + ImGuiInputTextFlags_AllowTabInput)) { - auto line_count = std::count(vertex_source.begin(), vertex_source.end(), '\n'); - ImVec2 size = ImVec2( - ImGui::GetContentRegionAvail().x, - std::min(ImGui::GetTextLineHeight() * (line_count + 3), ImGui::GetContentRegionAvail().y)); - if (ImGui::InputTextMultiline("##vertex editor", &vertex_source, size, - ImGuiInputTextFlags_AllowTabInput)) + auto result = m_pipeline_wrapper.and_then( + [&](auto& pipeline_wrapper) { return pipeline_wrapper.set_vertex_shader(vertex_source); }); + if (!result) { - m_pipeline_wrapper.and_then( - [&](auto& pipeline_wrapper) -> std::expected - { - pipeline_wrapper.set_vertex_shader(vertex_source); - return std::monostate{}; - }); + SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Failed to set vertex shader from editor, error = %d", + static_cast(result.error())); } } - break; - case 2: + } + break; + + case 2: // Fragment shader editor + { + auto line_count = std::count(fragment_source.begin(), fragment_source.end(), '\n'); + ImVec2 size(ImGui::GetContentRegionAvail().x, + std::min(ImGui::GetTextLineHeight() * (line_count + 3), ImGui::GetContentRegionAvail().y)); + + if (ImGui::InputTextMultiline("##fragment editor", &fragment_source, size, + ImGuiInputTextFlags_AllowTabInput)) { - auto line_count = std::count(fragment_source.begin(), fragment_source.end(), '\n'); - ImVec2 size = ImVec2( - ImGui::GetContentRegionAvail().x, - std::min(ImGui::GetTextLineHeight() * (line_count + 3), ImGui::GetContentRegionAvail().y)); - if (ImGui::InputTextMultiline("##fragment editor", &fragment_source, size, - ImGuiInputTextFlags_AllowTabInput)) + auto result = m_pipeline_wrapper.and_then( + [&](auto& pipeline_wrapper) { return pipeline_wrapper.set_fragment_shader(fragment_source); }); + if (!result) { - m_pipeline_wrapper.and_then( - [&](auto& pipeline_wrapper) -> std::expected - { - pipeline_wrapper.set_fragment_shader(fragment_source); - return std::monostate{}; - }); + SDL_LogError(SDL_LOG_CATEGORY_RENDER, "Failed to set fragment shader from editor, error = %d", + static_cast(result.error())); } } - break; - default: - break; } - ImGui::End(); + break; + + default: + break; } + ImGui::End(); ImGui::EndFrame(); return SDL_APP_CONTINUE; } /** - * @brief Render the application's triangle and ImGui UI to the GPU and present the current swapchain frame. - * - * Records and submits GPU commands for drawing the triangle, uploads the per-frame camera uniform, - * renders ImGui draw data into the same render pass, and presents the swapchain texture. - * If no swapchain texture is available, the function still submits any recorded command buffer and continues. - * - * @return SDL_AppResult `SDL_APP_CONTINUE` to continue the application main loop. + * @brief Render the triangle and ImGui UI to the GPU and present the swapchain frame. */ SDL_AppResult draw() { ImGui::Render(); ImDrawData* draw_data = ImGui::GetDrawData(); + // Rebuild pipeline if needed. + auto pipeline_submit = + m_pipeline_wrapper.and_then([](auto& pipeline_wrapper) { return pipeline_wrapper.submit(); }); + if (!pipeline_submit) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "Pipeline submit failed, error = %d", + static_cast(pipeline_submit.error())); + } + + SDL_GPUDevice* device = m_gpu->device(); + if (!device) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "GpuWrapper::device() returned null in draw()"); + return SDL_APP_CONTINUE; + } + + SDL_GPUCommandBuffer* commandBuffer = SDL_AcquireGPUCommandBuffer(device); + if (!commandBuffer) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to acquire GPU command buffer"); + return SDL_APP_CONTINUE; + } + + SDL_GPUTexture* swapchainTexture = nullptr; + Uint32 width = 0, height = 0; + + SDL_Window* window = m_gpu->window(); + if (!window) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "GpuWrapper::window() returned null in draw()"); + SDL_SubmitGPUCommandBuffer(commandBuffer); + return SDL_APP_CONTINUE; + } + + if (!SDL_WaitAndAcquireGPUSwapchainTexture(commandBuffer, window, &swapchainTexture, &width, &height)) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to acquire swapchain texture: %s", SDL_GetError()); + SDL_SubmitGPUCommandBuffer(commandBuffer); + return SDL_APP_CONTINUE; + } + + if (swapchainTexture == nullptr) + { + // You must always submit the command buffer, even if no texture is available. + SDL_SubmitGPUCommandBuffer(commandBuffer); + return SDL_APP_CONTINUE; + } + + ImGui_ImplSDLGPU3_PrepareDrawData(draw_data, commandBuffer); + + // Create the color target. + SDL_GPUColorTargetInfo colorTargetInfo{}; + colorTargetInfo.clear_color = {240 / 255.0F, 240 / 255.0F, 240 / 255.0F, 255 / 255.0F}; + colorTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR; + colorTargetInfo.store_op = SDL_GPU_STOREOP_STORE; + colorTargetInfo.texture = swapchainTexture; + + SDL_GPURenderPass* renderPass = SDL_BeginGPURenderPass(commandBuffer, &colorTargetInfo, 1, nullptr); + + // Bind pipeline if available. m_pipeline_wrapper.and_then( - [](auto& pipeline_wrapper) -> std::expected + [&](auto& pipeline_wrapper) -> std::expected { - pipeline_wrapper.submit(); + SDL_BindGPUGraphicsPipeline(renderPass, pipeline_wrapper.data()); return std::monostate{}; }); - gpu_wrapper->data().and_then( - [&](auto gpu)->std::expected - { - // acquire the command buffer - SDL_GPUCommandBuffer* commandBuffer = SDL_AcquireGPUCommandBuffer(gpu); - // get the swapchain texture - SDL_GPUTexture* swapchainTexture; - Uint32 width, height; - gpu_wrapper->acquire_window().and_then( - [&](auto window) ->std::expected - { - SDL_WaitAndAcquireGPUSwapchainTexture(commandBuffer, window, &swapchainTexture, &width, - &height); - return std::monostate{}; - }); - // end the frame early if a swapchain texture is not available - if (swapchainTexture == NULL) - { - // you must always submit the command buffer - SDL_SubmitGPUCommandBuffer(commandBuffer); - // return SDL_APP_CONTINUE; - } - ImGui_ImplSDLGPU3_PrepareDrawData(draw_data, commandBuffer); - // create the color target - SDL_GPUColorTargetInfo colorTargetInfo{}; - colorTargetInfo.clear_color = {240 / 255.0F, 240 / 255.0F, 240 / 255.0F, 255 / 255.0F}; - colorTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR; - colorTargetInfo.store_op = SDL_GPU_STOREOP_STORE; - colorTargetInfo.texture = swapchainTexture; - - - // begin a render pass - SDL_GPURenderPass* renderPass = SDL_BeginGPURenderPass(commandBuffer, &colorTargetInfo, 1, NULL); - m_pipeline_wrapper.and_then( - [&](auto& pipeline_wrapper)->std::expected - { - // draw calls go here - SDL_BindGPUGraphicsPipeline(renderPass, pipeline_wrapper.data()); - return std::monostate{}; - }); - { - float cy = std::cos(yaw); - float sy = std::sin(yaw); - float cp = std::cos(pitch); - float sp = std::sin(pitch); + // Compute camera matrix and upload as a vertex uniform. + { + float cy = std::cos(yaw); + float sy = std::sin(yaw); + float cp = std::cos(pitch); + float sp = std::sin(pitch); - std::array Ry = {cy, 0.0F, sy, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, - -sy, 0.0F, cy, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F}; + std::array Ry = {cy, 0.0F, sy, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, + -sy, 0.0F, cy, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F}; - std::array Rx = {1.0F, 0.0F, 0.0F, 0.0F, 0.0F, cp, sp, 0.0F, - 0.0F, -sp, cp, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F}; + std::array Rx = {1.0F, 0.0F, 0.0F, 0.0F, 0.0F, cp, sp, 0.0F, + 0.0F, -sp, cp, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F}; - auto mulMat4 = [](const float* A, const float* B, float* C) + auto mulMat4 = [](const float* A, const float* B, float* C) + { + for (int col = 0; col < 4; ++col) + { + for (int row = 0; row < 4; ++row) { - for (int col = 0; col < 4; ++col) - { - for (int row = 0; row < 4; ++row) - { - C[col * 4 + row] = A[0 * 4 + row] * B[col * 4 + 0] + A[1 * 4 + row] * B[col * 4 + 1] + - A[2 * 4 + row] * B[col * 4 + 2] + A[3 * 4 + row] * B[col * 4 + 3]; - } - } - }; - - // uView = Rx * Ry - mulMat4(Rx.data(), Ry.data(), cam.m.data()); - SDL_PushGPUVertexUniformData(commandBuffer, 0, cam.m.data(), sizeof(cam.m)); + C[col * 4 + row] = A[0 * 4 + row] * B[col * 4 + 0] + A[1 * 4 + row] * B[col * 4 + 1] + + A[2 * 4 + row] * B[col * 4 + 2] + A[3 * 4 + row] * B[col * 4 + 3]; + } } + }; - m_vertex_buffer.and_then( - [&](auto& vertex_buffer)->std::expected - { - // bind the vertex buffer - SDL_GPUBufferBinding bufferBindings[1]; - bufferBindings[0].buffer = vertex_buffer.data(); // index 0 is slot 0 in this example - bufferBindings[0].offset = 0; // start from the first byte - - SDL_BindGPUVertexBuffers(renderPass, 0, bufferBindings, - 1); // bind one buffer starting from slot 0 - return std::monostate{}; - }); - - SDL_DrawGPUPrimitives(renderPass, 3, 1, 0, 0); + // uView = Rx * Ry + mulMat4(Rx.data(), Ry.data(), cam.m.data()); + SDL_PushGPUVertexUniformData(commandBuffer, 0, cam.m.data(), static_cast(sizeof(cam.m))); + } - ImGui_ImplSDLGPU3_RenderDrawData(draw_data, commandBuffer, renderPass); - // end the render pass - SDL_EndGPURenderPass(renderPass); + // Bind vertex buffer and draw. + m_vertex_buffer.and_then( + [&](auto& vertex_buffer) -> std::expected + { + SDL_GPUBufferBinding bufferBindings[1]{}; + bufferBindings[0].buffer = vertex_buffer.data(); + bufferBindings[0].offset = 0; - // submit the command buffer - SDL_SubmitGPUCommandBuffer(commandBuffer); + SDL_BindGPUVertexBuffers(renderPass, 0, bufferBindings, 1); return std::monostate{}; }); + + SDL_DrawGPUPrimitives(renderPass, 3, 1, 0, 0); + + ImGui_ImplSDLGPU3_RenderDrawData(draw_data, commandBuffer, renderPass); + + SDL_EndGPURenderPass(renderPass); + SDL_SubmitGPUCommandBuffer(commandBuffer); + return SDL_APP_CONTINUE; } - /** - * @brief Advance the application one iteration by performing per-frame updates and rendering. - * - * Performs per-frame UI and state updates; if those updates indicate continuation, executes rendering and submits - * GPU work for presentation. - * - * @return `SDL_APP_CONTINUE` to continue the main loop, or another `SDL_AppResult` to terminate. - */ - virtual SDL_AppResult iterate() override + SDL_AppResult iterate() override { - auto result = tick(); if (result == SDL_APP_CONTINUE) { @@ -387,13 +435,7 @@ void main() return result; } - /** - * @brief Handle an SDL event by forwarding it to ImGui and handling window-close requests. - * - * @param event Pointer to the SDL event to process. - * @return SDL_AppResult `SDL_APP_SUCCESS` when a window close was requested, `SDL_APP_CONTINUE` otherwise. - */ - virtual SDL_AppResult event(SDL_Event* event) override + SDL_AppResult event(SDL_Event* event) override { ImGui_ImplSDL3_ProcessEvent(event); if (event->type == SDL_EVENT_KEY_DOWN) @@ -402,11 +444,11 @@ void main() { case SDLK_UP: pitch += 0.1F; - pitch = std::clamp(pitch, -std::numbers::pi / 2, std::numbers::pi / 2); + pitch = std::clamp(pitch, -std::numbers::pi_v / 2, +std::numbers::pi_v / 2); break; case SDLK_DOWN: pitch -= 0.1F; - pitch = std::clamp(pitch, -std::numbers::pi / 2, std::numbers::pi / 2); + pitch = std::clamp(pitch, -std::numbers::pi_v / 2, +std::numbers::pi_v / 2); break; case SDLK_LEFT: yaw += 0.1F; @@ -418,7 +460,7 @@ void main() break; } } - // close the window on request + if (event->type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) { return SDL_APP_SUCCESS; @@ -427,17 +469,9 @@ void main() return SDL_APP_CONTINUE; } - /** - * @brief Clean up UI and GPU resources and close the application window. - * - * Shuts down ImGui SDL3 and SDLGPU backends, destroys the ImGui context, - * releases the application's association with the GPU device for the window, - * and destroys the SDL window. - * - * @param result Application exit result code provided by the SDL app framework. - */ - virtual void quit(SDL_AppResult result) override + void quit(SDL_AppResult result) override { + (void)result; ImGui_ImplSDL3_Shutdown(); ImGui_ImplSDLGPU3_Shutdown(); ImGui::DestroyContext(); diff --git a/sdl_wrapper/sdl_wrapper.buffer.cpp b/sdl_wrapper/sdl_wrapper.buffer.cpp index 86efedc..7eddcb2 100644 --- a/sdl_wrapper/sdl_wrapper.buffer.cpp +++ b/sdl_wrapper/sdl_wrapper.buffer.cpp @@ -1,9 +1,14 @@ -// +// sdl_wrapper.buffer.cpp // Created by sophomore on 11/12/25. // module; +#include +#include #include +#include + #include "SDL3/SDL_gpu.h" +#include "SDL3/SDL_log.h" #include "SDL3/SDL_stdinc.h" module sdl_wrapper; import :buffer; @@ -11,63 +16,103 @@ import :gpu; namespace sopho { - /** - * @brief Releases GPU resources owned by this BufferWrapper. - * - * Releases the GPU vertex buffer and, if present, the transfer (staging) buffer, - * then clears the corresponding handles and resets the transfer buffer size. - * - * @note If the associated GPU has already been destroyed, releasing these resources - * may have no effect or may be too late to perform a proper cleanup. - */ - - BufferWrapper::~BufferWrapper() + BufferWrapper::~BufferWrapper() noexcept { if (!m_gpu) { return; } - m_gpu->release_buffer(m_vertex_buffer); - m_vertex_buffer = nullptr; + + // Release vertex buffer + if (m_vertex_buffer) + { + m_gpu->release_buffer(m_vertex_buffer); + m_vertex_buffer = nullptr; + } + + // Release transfer buffer if (m_transfer_buffer) { - SDL_ReleaseGPUTransferBuffer(m_gpu->data().value(), m_transfer_buffer); + SDL_ReleaseGPUTransferBuffer(m_gpu->device(), m_transfer_buffer); m_transfer_buffer = nullptr; m_transfer_buffer_size = 0; } } - /** - * @brief Uploads a block of data into the wrapped GPU vertex buffer at the specified byte offset. - * - * Reallocates the internal staging (transfer) buffer if its capacity is less than the requested size, - * copies p_size bytes from p_data into the staging buffer, and enqueues a GPU copy pass that writes the data - * into the vertex buffer at p_offset. The GPU command buffer is submitted immediately. - * - * @param p_data Pointer to the source data to upload. - * @param p_size Size in bytes of the data to upload. - * @param p_offset Byte offset within the vertex buffer where the data will be written. - */ - void BufferWrapper::upload(void* p_data, uint32_t p_size, uint32_t p_offset) + [[nodiscard]] std::expected + BufferWrapper::upload(const void* src_data, std::uint32_t size, std::uint32_t offset) { - if (p_size > m_transfer_buffer_size) + // Bounds check to avoid writing past the end of the GPU buffer. + if (offset + size > m_vertex_buffer_size) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d buffer overflow: size=%u, offset=%u, buffer_size=%u", __FILE__, + __LINE__, size, offset, m_vertex_buffer_size); + + // NOTE: You should add this value to your GpuError enum. + return std::unexpected(GpuError::BUFFER_OVERFLOW); + } + + auto* device = m_gpu->device(); + + // 1. Ensure the transfer buffer capacity is sufficient. + if (size > m_transfer_buffer_size) { if (m_transfer_buffer != nullptr) { - SDL_ReleaseGPUTransferBuffer(m_gpu->data().value(), m_transfer_buffer); + SDL_ReleaseGPUTransferBuffer(device, m_transfer_buffer); + m_transfer_buffer = nullptr; + m_transfer_buffer_size = 0; + } + + SDL_GPUTransferBufferCreateInfo transfer_info{}; + transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; + transfer_info.size = size; + transfer_info.props = 0; + + m_transfer_buffer = SDL_CreateGPUTransferBuffer(device, &transfer_info); + if (!m_transfer_buffer) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d failed to create transfer buffer: %s", __FILE__, __LINE__, + SDL_GetError()); + + return std::unexpected(GpuError::CREATE_TRANSFER_BUFFER_FAILED); } - SDL_GPUTransferBufferCreateInfo transfer_info{SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, p_size, 0}; - m_transfer_buffer = SDL_CreateGPUTransferBuffer(m_gpu->data().value(), &transfer_info); + m_transfer_buffer_size = transfer_info.size; } - auto data = SDL_MapGPUTransferBuffer(m_gpu->data().value(), m_transfer_buffer, false); - SDL_memcpy(data, p_data, p_size); - SDL_UnmapGPUTransferBuffer(m_gpu->data().value(), m_transfer_buffer); + // 2. Map the transfer buffer and copy data into it. + void* dst = SDL_MapGPUTransferBuffer(device, m_transfer_buffer, false); + if (!dst) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d failed to map transfer buffer: %s", __FILE__, __LINE__, + SDL_GetError()); + + return std::unexpected(GpuError::MAP_TRANSFER_BUFFER_FAILED); + } + + SDL_memcpy(dst, src_data, size); + SDL_UnmapGPUTransferBuffer(device, m_transfer_buffer); + + // 3. Acquire a command buffer and enqueue the copy pass. + auto* command_buffer = SDL_AcquireGPUCommandBuffer(device); + if (!command_buffer) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d failed to acquire GPU command buffer: %s", __FILE__, __LINE__, + SDL_GetError()); + + return std::unexpected(GpuError::ACQUIRE_COMMAND_BUFFER_FAILED); + } + + auto* copy_pass = SDL_BeginGPUCopyPass(command_buffer); - // TODO: Delay submit command in collect - auto command_buffer = SDL_AcquireGPUCommandBuffer(m_gpu->data().value()); - auto copy_pass = SDL_BeginGPUCopyPass(command_buffer); + if (!copy_pass) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d failed to begin GPU copy pass: %s", __FILE__, __LINE__, + SDL_GetError()); + SDL_SubmitGPUCommandBuffer(command_buffer); + return std::unexpected(GpuError::BEGIN_COPY_PASS_FAILED); + } SDL_GPUTransferBufferLocation location{}; location.transfer_buffer = m_transfer_buffer; @@ -75,12 +120,19 @@ namespace sopho SDL_GPUBufferRegion region{}; region.buffer = m_vertex_buffer; - region.size = p_size; - region.offset = p_offset; + region.size = size; + region.offset = offset; SDL_UploadToGPUBuffer(copy_pass, &location, ®ion, false); SDL_EndGPUCopyPass(copy_pass); - SDL_SubmitGPUCommandBuffer(command_buffer); + if (!SDL_SubmitGPUCommandBuffer(command_buffer)) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); + return std::unexpected(GpuError::SUBMIT_COMMAND_FAILED); + } + + return std::monostate{}; } + } // namespace sopho diff --git a/sdl_wrapper/sdl_wrapper.buffer.ixx b/sdl_wrapper/sdl_wrapper.buffer.ixx index 2f14bb9..22daa4b 100644 --- a/sdl_wrapper/sdl_wrapper.buffer.ixx +++ b/sdl_wrapper/sdl_wrapper.buffer.ixx @@ -1,9 +1,15 @@ -// +// sdl_wrapper.buffer.ixx // Created by sophomore on 11/8/25. // + module; + +#include #include +#include + #include "SDL3/SDL_gpu.h" + export module sdl_wrapper:buffer; import :decl; @@ -11,26 +17,41 @@ export namespace sopho { class BufferWrapper { - std::shared_ptr m_gpu{}; - SDL_GPUBuffer* m_vertex_buffer{}; - SDL_GPUTransferBuffer* m_transfer_buffer{}; - uint32_t m_transfer_buffer_size{}; + std::shared_ptr m_gpu{}; // Owns the device lifetime + SDL_GPUBuffer* m_vertex_buffer{}; // Target GPU buffer + SDL_GPUTransferBuffer* m_transfer_buffer{}; // Staging/transfer buffer + std::uint32_t m_vertex_buffer_size{}; // Total size of the GPU buffer + std::uint32_t m_transfer_buffer_size{}; // Current capacity of the transfer buffer - BufferWrapper(std::shared_ptr<::sopho::GpuWrapper> p_gpu, SDL_GPUBuffer* p_buffer) : - m_gpu(p_gpu), m_vertex_buffer(p_buffer) + // Only GpuWrapper is allowed to construct this type. + BufferWrapper(std::shared_ptr gpu, SDL_GPUBuffer* buffer, std::uint32_t size) noexcept : + m_gpu(std::move(gpu)), m_vertex_buffer(buffer), m_vertex_buffer_size(size) { } public: BufferWrapper(const BufferWrapper&) = delete; BufferWrapper& operator=(const BufferWrapper&) = delete; - BufferWrapper(BufferWrapper&& other) noexcept = default; - BufferWrapper& operator=(BufferWrapper&& other) noexcept = default; - void upload(void* p_data, uint32_t p_size, uint32_t p_offset); + BufferWrapper(BufferWrapper&&) = default; + BufferWrapper& operator=(BufferWrapper&&) = default; + + /// Upload a block of data into the GPU buffer at the given byte offset. + /// + /// This function may fail in several ways: + /// - The requested upload range exceeds the buffer size. + /// - The transfer buffer cannot be created or resized. + /// - Mapping the transfer buffer fails. + /// - Acquiring a GPU command buffer fails. + /// + /// All such failures are reported via the returned std::expected. + [[nodiscard]] std::expected upload(const void* data, std::uint32_t size, + std::uint32_t offset); + + /// Returns the underlying SDL_GPUBuffer pointer. + [[nodiscard]] SDL_GPUBuffer* data() const noexcept { return m_vertex_buffer; } - auto data() { return m_vertex_buffer; } + ~BufferWrapper() noexcept; - ~BufferWrapper(); - friend GpuWrapper; + friend class GpuWrapper; }; } // namespace sopho diff --git a/sdl_wrapper/sdl_wrapper.decl.ixx b/sdl_wrapper/sdl_wrapper.decl.ixx index c45051d..f2ce808 100644 --- a/sdl_wrapper/sdl_wrapper.decl.ixx +++ b/sdl_wrapper/sdl_wrapper.decl.ixx @@ -8,12 +8,23 @@ export namespace sopho { enum class GpuError { + UNINITIALIZED, CREATE_DEVICE_FAILED, CREATE_WINDOW_FAILED, + CLAIM_WINDOW_FAILED, CREATE_BUFFER_FAILED, + CREATE_TRANSFER_BUFFER_FAILED, CREATE_SHADER_FAILED, CREATE_PIPELINE_FAILED, GET_TEXTUREFORMAT_FAILED, + BUFFER_OVERFLOW, + MAP_TRANSFER_BUFFER_FAILED, + ACQUIRE_COMMAND_BUFFER_FAILED, + SUBMIT_COMMAND_FAILED, + BEGIN_COPY_PASS_FAILED, + COMPILE_VERTEX_SHADER_FAILED, + COMPILE_FRAGMENT_SHADER_FAILED, + }; class App; diff --git a/sdl_wrapper/sdl_wrapper.gpu.cpp b/sdl_wrapper/sdl_wrapper.gpu.cpp index 845e2ad..7bdd5d2 100644 --- a/sdl_wrapper/sdl_wrapper.gpu.cpp +++ b/sdl_wrapper/sdl_wrapper.gpu.cpp @@ -12,22 +12,19 @@ namespace sopho { std::expected GpuWrapper::create_buffer(SDL_GPUBufferUsageFlags flag, uint32_t size) { - return m_device.and_then( - [&](auto device) -> std::expected - { - SDL_GPUBufferCreateInfo create_info{flag, size}; - auto buffer = SDL_CreateGPUBuffer(device, &create_info); - if (!buffer) - { - SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); - return std::unexpected(GpuError::CREATE_BUFFER_FAILED); - } - return BufferWrapper{shared_from_this(), buffer}; - }); + SDL_GPUBufferCreateInfo create_info{.usage = flag, .size = size}; + auto buffer = SDL_CreateGPUBuffer(device(), &create_info); + if (!buffer) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); + return std::unexpected(GpuError::CREATE_BUFFER_FAILED); + } + return BufferWrapper{shared_from_this(), buffer, size}; } - std::expected GpuWrapper::create_pipeline_wrapper() { - return m_device.transform([&](auto device)-> PipelineWrapper { - return PipelineWrapper{shared_from_this()}; - }); + std::expected GpuWrapper::create_pipeline_wrapper() + { + // Query texture format, then construct PipelineWrapper + return get_texture_format().transform([self = shared_from_this()](SDL_GPUTextureFormat format) + { return PipelineWrapper{self, format}; }); } } // namespace sopho diff --git a/sdl_wrapper/sdl_wrapper.gpu.ixx b/sdl_wrapper/sdl_wrapper.gpu.ixx index d7bf428..9e7a46c 100644 --- a/sdl_wrapper/sdl_wrapper.gpu.ixx +++ b/sdl_wrapper/sdl_wrapper.gpu.ixx @@ -4,7 +4,6 @@ module; #include #include -#include #include "SDL3/SDL_gpu.h" #include "SDL3/SDL_log.h" @@ -17,171 +16,272 @@ import :pipeline; export namespace sopho { - class GpuWrapper : public std::enable_shared_from_this + struct DeviceHandle { - std::expected m_device{}; + SDL_GPUDevice* raw{}; - // TODO: Consider multi window situation - std::expected m_window{}; + explicit DeviceHandle(SDL_GPUDevice* d) noexcept : raw(d) {} - public: - GpuWrapper() + DeviceHandle(DeviceHandle&& other) noexcept : raw(other.raw) { other.raw = nullptr; } + DeviceHandle& operator=(DeviceHandle&& other) noexcept { - auto device = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV, true, nullptr); - if (device == nullptr) + if (this != &other) { - SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); - m_device = std::unexpected(GpuError::CREATE_DEVICE_FAILED); + reset(); + raw = other.raw; + other.raw = nullptr; + } + return *this; + } + + DeviceHandle(const DeviceHandle&) = delete; + DeviceHandle& operator=(const DeviceHandle&) = delete; + + ~DeviceHandle() { reset(); } + + void reset() noexcept + { + if (raw) + { + SDL_DestroyGPUDevice(raw); + raw = nullptr; } - else + } + + [[nodiscard]] bool valid() const noexcept { return raw != nullptr; } + }; + + struct WindowHandle + { + SDL_Window* raw{}; + + WindowHandle() = default; + + explicit WindowHandle(SDL_Window* w) noexcept : raw(w) {} + + WindowHandle(const WindowHandle&) = delete; + WindowHandle& operator=(const WindowHandle&) = delete; + + WindowHandle(WindowHandle&& other) noexcept : raw(other.raw) { other.raw = nullptr; } + + WindowHandle& operator=(WindowHandle&& other) noexcept + { + if (this != &other) { - m_device = device; + reset(); + raw = other.raw; + other.raw = nullptr; } - auto window = SDL_CreateWindow("Hello, Triangle!", 960, 540, SDL_WINDOW_RESIZABLE); - if (window == nullptr) + return *this; + } + + ~WindowHandle() noexcept { reset(); } + + void reset() noexcept + { + if (raw) { - SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); - m_window = std::unexpected(GpuError::CREATE_WINDOW_FAILED); + SDL_DestroyWindow(raw); + raw = nullptr; + } + } + + [[nodiscard]] bool valid() const noexcept { return raw != nullptr; } + }; + + struct ClaimedWindow + { + SDL_GPUDevice* device{}; + SDL_Window* window{}; + + ClaimedWindow() = default; + + ClaimedWindow(SDL_GPUDevice* d, SDL_Window* w) noexcept : device(d), window(w) {} + + ClaimedWindow(const ClaimedWindow&) = delete; + ClaimedWindow& operator=(const ClaimedWindow&) = delete; + + ClaimedWindow(ClaimedWindow&& other) noexcept : device(other.device), window(other.window) + { + other.device = nullptr; + other.window = nullptr; + } + + ClaimedWindow& operator=(ClaimedWindow&& other) noexcept + { + if (this != &other) + { + reset(); + device = other.device; + window = other.window; + other.device = nullptr; + other.window = nullptr; } - else + return *this; + } + + ~ClaimedWindow() noexcept { reset(); } + + void reset() noexcept + { + if (device && window) { - m_window = window; + SDL_ReleaseWindowFromGPUDevice(device, window); + device = nullptr; + window = nullptr; } - m_device.and_then( - [&](auto device) - { - return m_window.and_then( - [&](auto window) -> std::expected - { - SDL_ClaimWindowForGPUDevice(device, window); - return std::monostate{}; - }); - }); - } - - ~GpuWrapper() - { - m_window.and_then( - [&](auto window) - { - SDL_DestroyWindow(window); - return m_device.and_then( - [&](auto device) -> std::expected - { - SDL_ReleaseWindowFromGPUDevice(device, window); - return std::monostate{}; - }); - }); - m_device.and_then( - [&](auto device) -> std::expected - { - SDL_DestroyGPUDevice(device); - return std::monostate{}; - }); - } - - auto data() { return m_device; } - - std::expected create_buffer(SDL_GPUBufferUsageFlags flag, uint32_t size); + } + + [[nodiscard]] bool valid() const noexcept { return device && window; } + }; + + struct GpuContext + { + DeviceHandle device; + WindowHandle window; + ClaimedWindow claimed; + + GpuContext(DeviceHandle d, WindowHandle w, ClaimedWindow c) noexcept : + device(std::move(d)), window(std::move(w)), claimed(std::move(c)) + { + } + + GpuContext(const GpuContext&) = delete; + GpuContext& operator=(const GpuContext&) = delete; + + GpuContext(GpuContext&&) = default; + GpuContext& operator=(GpuContext&&) = default; + + [[nodiscard]] SDL_GPUDevice* device_raw() const noexcept { return device.raw; } + [[nodiscard]] SDL_Window* window_raw() const noexcept { return window.raw; } + }; + + using GpuContextResult = std::expected; + + inline GpuContextResult create_gpu_context() + { + auto* dev_raw = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV, true, nullptr); + if (!dev_raw) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); + return std::unexpected(GpuError::CREATE_DEVICE_FAILED); + } + DeviceHandle device{dev_raw}; + + auto* win_raw = SDL_CreateWindow("Hello, Triangle!", 960, 540, SDL_WINDOW_RESIZABLE); + if (!win_raw) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); + return std::unexpected(GpuError::CREATE_WINDOW_FAILED); + } + WindowHandle window{win_raw}; + + if (!SDL_ClaimWindowForGPUDevice(device.raw, window.raw)) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); + return std::unexpected(GpuError::CLAIM_WINDOW_FAILED); + } + ClaimedWindow claimed{device.raw, window.raw}; + + return GpuContext{std::move(device), std::move(window), std::move(claimed)}; + } + + class GpuWrapper : public std::enable_shared_from_this + { + + GpuContext m_ctx; + + explicit GpuWrapper(GpuContext ctx) noexcept : m_ctx(std::move(ctx)) {} + + public: + [[nodiscard]] static std::expected, GpuError> create() + { + return create_gpu_context().transform( + [](GpuContext ctx) { return std::shared_ptr(new GpuWrapper(std::move(ctx))); }); + } + + ~GpuWrapper() = default; + + GpuWrapper(const GpuWrapper&) = delete; + GpuWrapper& operator=(const GpuWrapper&) = delete; + + GpuWrapper(GpuWrapper&&) = delete; + GpuWrapper& operator=(GpuWrapper&&) = delete; + + [[nodiscard]] auto device() const { return m_ctx.device.raw; } + [[nodiscard]] auto window() const { return m_ctx.window.raw; } + + [[nodiscard]] std::expected create_buffer(SDL_GPUBufferUsageFlags flag, uint32_t size); auto release_buffer(SDL_GPUBuffer* buffer) { - m_device.and_then( - [&](auto device) -> std::expected - { - SDL_ReleaseGPUBuffer(device, buffer); - return std::monostate{}; - }); - } - - std::expected create_pipeline_wrapper(); - - auto create_pipeline(const std::expected& create_info) - { - return m_device.and_then( - [&](auto device) - { - return create_info.and_then( - [&](auto pipeline_info) -> std::expected - { - auto pipeline = SDL_CreateGPUGraphicsPipeline(device, &pipeline_info); - if (!pipeline) - { - return std::unexpected(GpuError::CREATE_PIPELINE_FAILED); - } - return pipeline; - }); - }); - } - - auto create_shader(const std::vector& shader, SDL_GPUShaderStage stage, uint32_t num_uniform_buffers) - { - return m_device.and_then( - [&](auto p_device) -> std::expected - { - SDL_GPUShaderCreateInfo info{}; - info.code = shader.data(); - info.code_size = shader.size(); - info.entrypoint = "main"; - info.format = SDL_GPU_SHADERFORMAT_SPIRV; - info.stage = stage; - info.num_samplers = 0; - info.num_storage_buffers = 0; - info.num_storage_textures = 0; - info.num_uniform_buffers = num_uniform_buffers; - auto shader = SDL_CreateGPUShader(p_device, &info); - if (!shader) - { - SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); - return std::unexpected(GpuError::CREATE_SHADER_FAILED); - } - return shader; - }); - } - - auto release_shader(const std::expected& shader_expected) - { - m_device.and_then( - [&](auto device) - { - return shader_expected.and_then( - [&](auto shader) -> std::expected - { - SDL_ReleaseGPUShader(device, shader); - return std::monostate{}; - }); - }); + if (buffer) + { + SDL_ReleaseGPUBuffer(device(), buffer); + } + } + + [[nodiscard]] std::expected create_pipeline_wrapper(); + + std::expected + create_pipeline(const SDL_GPUGraphicsPipelineCreateInfo& create_info) + { + auto pipeline = SDL_CreateGPUGraphicsPipeline(device(), &create_info); + if (!pipeline) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); + return std::unexpected(GpuError::CREATE_PIPELINE_FAILED); + } + return pipeline; } auto release_pipeline(SDL_GPUGraphicsPipeline* graphics_pipeline) { - m_device.and_then( - [&](auto device) -> std::expected - { - SDL_ReleaseGPUGraphicsPipeline(device, graphics_pipeline); - return std::monostate{}; - }); - } - - auto acquire_window() { return m_window; } - - auto get_texture_format() - { - return m_device.and_then( - [&](auto device) -> std::expected - { - return m_window.and_then( - [&](auto window) -> std::expected - { - auto format = SDL_GetGPUSwapchainTextureFormat(device, window); - if (format == SDL_GPU_TEXTUREFORMAT_INVALID) - { - SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); - return std::unexpected(GpuError::GET_TEXTUREFORMAT_FAILED); - } - return format; - }); - }); + if (graphics_pipeline) + { + SDL_ReleaseGPUGraphicsPipeline(device(), graphics_pipeline); + } + } + + [[nodiscard]] auto create_shader(const std::vector& shader, SDL_GPUShaderStage stage, + uint32_t num_uniform_buffers) -> std::expected + { + SDL_GPUShaderCreateInfo info{}; + info.code = shader.data(); + info.code_size = shader.size(); + info.entrypoint = "main"; + info.format = SDL_GPU_SHADERFORMAT_SPIRV; + info.stage = stage; + info.num_samplers = 0; + info.num_storage_buffers = 0; + info.num_storage_textures = 0; + info.num_uniform_buffers = num_uniform_buffers; + auto s = SDL_CreateGPUShader(device(), &info); + if (!s) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); + return std::unexpected(GpuError::CREATE_SHADER_FAILED); + } + return s; + } + + auto release_shader(SDL_GPUShader* shader) + { + if (shader) + { + SDL_ReleaseGPUShader(device(), shader); + } + } + + [[nodiscard]] std::expected get_texture_format() const + { + auto format = SDL_GetGPUSwapchainTextureFormat(device(), window()); + if (format == SDL_GPU_TEXTUREFORMAT_INVALID) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); + return std::unexpected(GpuError::GET_TEXTUREFORMAT_FAILED); + } + return format; } }; } // namespace sopho diff --git a/sdl_wrapper/sdl_wrapper.pipeline.cpp b/sdl_wrapper/sdl_wrapper.pipeline.cpp index f0dbb9e..623bd56 100644 --- a/sdl_wrapper/sdl_wrapper.pipeline.cpp +++ b/sdl_wrapper/sdl_wrapper.pipeline.cpp @@ -1,197 +1,248 @@ -// +// sdl_wrapper.pipeline.cpp // Created by sophomore on 11/13/25. // module; #include #include +#include #include +#include + #include "SDL3/SDL_gpu.h" #include "SDL3/SDL_log.h" #include "shaderc/shaderc.hpp" module sdl_wrapper; import :pipeline; +import :gpu; namespace sopho { - /** - * @brief Constructs a PipelineWrapper and configures default pipeline state for the provided GPU device. - * - * Stores the provided GPU device wrapper for the wrapper's lifetime, configures the shaderc target environment, - * and initializes default vertex input state, primitive type, and color target description used when creating - * graphics pipelines. - * - * @param p_device Shared pointer to the GpuWrapper used to create and release shaders and graphics pipelines. - */ - PipelineWrapper::PipelineWrapper(std::shared_ptr<::sopho::GpuWrapper> p_device) : m_device(p_device) + // Helper: convert shaderc SPIR-V result (words) into a byte buffer. + static std::vector spv_result_to_bytes(const shaderc::SpvCompilationResult& result) + { + // shaderc::SpvCompilationResult is an iterable sequence of uint32_t words. + auto begin = result.cbegin(); + auto end = result.cend(); + const std::size_t word_count = static_cast(end - begin); + + std::vector bytes; + bytes.reserve(word_count * sizeof(std::uint32_t)); + + // Safely read each word and append its bytes. + for (auto it = begin; it != end; ++it) + { + std::uint32_t word = *it; + const auto* ptr = reinterpret_cast(&word); + bytes.insert(bytes.end(), ptr, ptr + sizeof(std::uint32_t)); + } + + return bytes; + } + + PipelineWrapper::PipelineWrapper(std::shared_ptr gpu, SDL_GPUTextureFormat swapchain_format) noexcept : + m_gpu(std::move(gpu)) { - options.SetTargetEnvironment(shaderc_target_env_vulkan, 0); - - m_vertex_buffer_description.emplace_back(0, 28, SDL_GPU_VERTEXINPUTRATE_VERTEX, 0); - m_pipeline_info = m_device->get_texture_format().transform( - [&](auto format) - { - SDL_GPUGraphicsPipelineCreateInfo pipeline_info{}; - - pipeline_info.vertex_input_state.vertex_buffer_descriptions = m_vertex_buffer_description.data(); - pipeline_info.vertex_input_state.num_vertex_buffers = m_vertex_buffer_description.size(); - - m_vertex_attribute.emplace_back(0, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, 0); - m_vertex_attribute.emplace_back(1, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4, sizeof(float) * 3); - pipeline_info.vertex_input_state.vertex_attributes = m_vertex_attribute.data(); - pipeline_info.vertex_input_state.num_vertex_attributes = m_vertex_attribute.size(); - - pipeline_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; - - m_color_target_description.emplace_back( - format, - SDL_GPUColorTargetBlendState{SDL_GPU_BLENDFACTOR_SRC_ALPHA, - SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, - SDL_GPU_BLENDOP_ADD, - SDL_GPU_BLENDFACTOR_SRC_ALPHA, - SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, - SDL_GPU_BLENDOP_ADD, - {}, - true}); - pipeline_info.target_info.color_target_descriptions = m_color_target_description.data(); - pipeline_info.target_info.num_color_targets = m_color_target_description.size(); - return pipeline_info; - }); + // Configure shaderc to target Vulkan/SPIR-V. + m_options.SetTargetEnvironment(shaderc_target_env_vulkan, 0); + + // Setup vertex buffer description. + SDL_GPUVertexBufferDescription vb_desc{}; + vb_desc.slot = 0; + vb_desc.pitch = 28; // 3 * float + 4 * float = 28 bytes + vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX; + vb_desc.instance_step_rate = 0; + m_vertex_buffer_descriptions.push_back(vb_desc); + + // Setup vertex attributes: position (float3) + color (float4). + SDL_GPUVertexAttribute attr_pos{}; + attr_pos.location = 0; + attr_pos.buffer_slot = 0; + attr_pos.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3; + attr_pos.offset = 0; + m_vertex_attributes.push_back(attr_pos); + + SDL_GPUVertexAttribute attr_color{}; + attr_color.location = 1; + attr_color.buffer_slot = 0; + attr_color.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4; + attr_color.offset = sizeof(float) * 3; + m_vertex_attributes.push_back(attr_color); + + // Setup color target blend state. + SDL_GPUColorTargetBlendState blend{}; + blend.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA; + blend.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + blend.color_blend_op = SDL_GPU_BLENDOP_ADD; + blend.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA; + blend.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA; + blend.alpha_blend_op = SDL_GPU_BLENDOP_ADD; + blend.enable_blend = true; + + SDL_GPUColorTargetDescription color_target{}; + color_target.format = swapchain_format; + color_target.blend_state = blend; + + m_color_target_descriptions.push_back(color_target); + // Initialize pipeline create info with default state. + SDL_GPUGraphicsPipelineCreateInfo info{}; + info.vertex_input_state.vertex_buffer_descriptions = m_vertex_buffer_descriptions.data(); + info.vertex_input_state.num_vertex_buffers = static_cast(m_vertex_buffer_descriptions.size()); + info.vertex_input_state.vertex_attributes = m_vertex_attributes.data(); + info.vertex_input_state.num_vertex_attributes = static_cast(m_vertex_attributes.size()); + info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; + + info.target_info.color_target_descriptions = m_color_target_descriptions.data(); + info.target_info.num_color_targets = static_cast(m_color_target_descriptions.size()); + + // Shaders will be filled in later by set_vertex_shader / set_fragment_shader. + info.vertex_shader = nullptr; + info.fragment_shader = nullptr; + + m_pipeline_info = info; m_modified = true; } - /** - * @brief Releases any GPU graphics pipeline owned by this wrapper. - * - * If a graphics pipeline is currently held, it is released using the associated - * device and the stored pipeline handle is cleared so the wrapper no longer - * references the pipeline. - */ - PipelineWrapper::~PipelineWrapper() + PipelineWrapper::~PipelineWrapper() noexcept { + if (!m_gpu) + { + return; + } + if (m_graphics_pipeline) { - m_device->release_pipeline(m_graphics_pipeline); + m_gpu->release_pipeline(m_graphics_pipeline); m_graphics_pipeline = nullptr; } - m_device->release_shader(m_vertex_shader); - m_device->release_shader(m_fragment_shader); + + if (m_vertex_shader) + { + m_gpu->release_shader(m_vertex_shader); + m_vertex_shader = nullptr; + } + + if (m_fragment_shader) + { + m_gpu->release_shader(m_fragment_shader); + m_fragment_shader = nullptr; + } } - /** - * @brief Rebuilds the GPU graphics pipeline when the wrapper is marked modified. - * - * If the wrapper's modified flag is set, this clears the flag, attempts to create a new - * graphics pipeline from the stored pipeline info and device, and on success replaces the - * current pipeline (releasing the previous pipeline first). If creation fails, an error - * is logged. - */ - void PipelineWrapper::submit() + + [[nodiscard]] std::expected PipelineWrapper::submit() { - if (m_modified) + if (!m_modified) + { + // Nothing changed, nothing to do. + return std::monostate{}; + } + + // Refresh pointers in case the underlying vectors were reallocated. + m_pipeline_info.vertex_input_state.vertex_buffer_descriptions = m_vertex_buffer_descriptions.data(); + m_pipeline_info.vertex_input_state.num_vertex_buffers = + static_cast(m_vertex_buffer_descriptions.size()); + + m_pipeline_info.vertex_input_state.vertex_attributes = m_vertex_attributes.data(); + m_pipeline_info.vertex_input_state.num_vertex_attributes = + static_cast(m_vertex_attributes.size()); + + m_pipeline_info.target_info.color_target_descriptions = m_color_target_descriptions.data(); + m_pipeline_info.target_info.num_color_targets = static_cast(m_color_target_descriptions.size()); + + // Attempt to create a new graphics pipeline. + auto pipeline_result = m_gpu->create_pipeline(m_pipeline_info); + if (!pipeline_result) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d failed to create graphics pipeline", __FILE__, __LINE__); + return std::unexpected(pipeline_result.error()); + } + + SDL_GPUGraphicsPipeline* new_pipeline = pipeline_result.value(); + + // Replace previous pipeline if any. + if (m_graphics_pipeline) { - m_modified = false; - m_pipeline_info = m_pipeline_info.transform( - [&](auto pipeline_info) - { - pipeline_info.vertex_input_state.vertex_buffer_descriptions = m_vertex_buffer_description.data(); - pipeline_info.vertex_input_state.vertex_attributes = m_vertex_attribute.data(); - pipeline_info.target_info.color_target_descriptions = m_color_target_description.data(); - return pipeline_info; - }); - auto new_graphics_pipeline = m_device->create_pipeline(m_pipeline_info); - if (new_graphics_pipeline) - { - if (m_graphics_pipeline) - { - m_device->release_pipeline(m_graphics_pipeline); - } - m_graphics_pipeline = new_graphics_pipeline.value(); - } - else - { - SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s get error:%s", __FUNCTION__, SDL_GetError()); - } + m_gpu->release_pipeline(m_graphics_pipeline); } + + m_graphics_pipeline = new_pipeline; + m_modified = false; + + return std::monostate{}; } - /** - * @brief Compile and install a new vertex shader from GLSL source. - * - * Compiles the provided GLSL vertex shader source to SPIR‑V, replaces any previously installed - * vertex shader on the device with the newly created shader, updates the pipeline's vertex - * shader reference, and marks the pipeline wrapper as modified so the pipeline will be rebuilt. - * - * @param p_source GLSL source code for the vertex shader. - * - * On compilation failure, a compilation error is logged and the previously installed shader is left unchanged. - */ - void PipelineWrapper::set_vertex_shader(const std::string& p_source) + [[nodiscard]] std::expected PipelineWrapper::set_vertex_shader(const std::string& source) { - auto result = compiler.CompileGlslToSpv(p_source, shaderc_glsl_vertex_shader, "vertex.glsl", options); + auto result = m_compiler.CompileGlslToSpv(source, shaderc_glsl_vertex_shader, "vertex.glsl", m_options); if (result.GetCompilationStatus() != shaderc_compilation_status_success) { SDL_LogError(SDL_LOG_CATEGORY_RENDER, "[shaderc] compile error in vertex.glsl: %s", - result.GetErrorMessage().data()); + result.GetErrorMessage().c_str()); + + // You may want to add this to your GpuError enum. + return std::unexpected(GpuError::COMPILE_VERTEX_SHADER_FAILED); + } + + // Convert SPIR-V words to a byte vector + std::vector code = spv_result_to_bytes(result); + + auto shader_result = m_gpu->create_shader(code, SDL_GPU_SHADERSTAGE_VERTEX, 1); + if (!shader_result) + { + return std::unexpected(shader_result.error()); } - else + + SDL_GPUShader* new_shader = shader_result.value(); + + // Release previous shader, if any + if (m_vertex_shader) { - m_device->release_shader(m_vertex_shader); - auto code_size = static_cast(result.cend() - result.cbegin()) * sizeof(uint32_t); - auto ptr = reinterpret_cast(result.cbegin()); - std::vector code{ptr, ptr + code_size}; - m_vertex_shader = m_device->create_shader(code, SDL_GPU_SHADERSTAGE_VERTEX, 1); - m_vertex_shader.and_then( - [&](auto shader) -> std::expected - { - m_pipeline_info = m_pipeline_info.transform( - [&](auto pipeline_info) - { - pipeline_info.vertex_shader = shader; - return pipeline_info; - }); - return std::monostate{}; - }); - m_modified = true; + m_gpu->release_shader(m_vertex_shader); } + + m_vertex_shader = new_shader; + m_pipeline_info.vertex_shader = new_shader; + m_modified = true; + + return std::monostate{}; } - /** - * @brief Compile and install a fragment shader from GLSL source. - * - * Compiles the provided GLSL fragment shader source to SPIR‑V; on compilation failure logs the error. - * On success, releases the previously installed fragment shader (if any), uploads the new SPIR‑V bytecode - * to the device as a fragment-stage shader, updates the internal pipeline description to reference it, - * and marks the wrapper as modified so the graphics pipeline will be rebuilt. - * - * @param p_source GLSL source code for the fragment shader. - */ - void PipelineWrapper::set_fragment_shader(const std::string& p_source) + + [[nodiscard]] std::expected + PipelineWrapper::set_fragment_shader(const std::string& source) { - auto result = compiler.CompileGlslToSpv(p_source, shaderc_glsl_fragment_shader, "fragment.glsl", options); + auto result = m_compiler.CompileGlslToSpv(source, shaderc_glsl_fragment_shader, "fragment.glsl", m_options); if (result.GetCompilationStatus() != shaderc_compilation_status_success) { - SDL_LogError(SDL_LOG_CATEGORY_RENDER, "[shaderc] compile error fragment.glsl: %s", - result.GetErrorMessage().data()); + SDL_LogError(SDL_LOG_CATEGORY_RENDER, "[shaderc] compile error in fragment.glsl: %s", + result.GetErrorMessage().c_str()); + + // You may want to add this to your GpuError enum. + return std::unexpected(GpuError::COMPILE_FRAGMENT_SHADER_FAILED); + } + + // Convert SPIR-V words to a byte vector + std::vector code = spv_result_to_bytes(result); + + auto shader_result = m_gpu->create_shader(code, SDL_GPU_SHADERSTAGE_FRAGMENT, 0); + if (!shader_result) + { + return std::unexpected(shader_result.error()); } - else + + SDL_GPUShader* new_shader = shader_result.value(); + + // Release previous shader, if any + if (m_fragment_shader) { - m_device->release_shader(m_fragment_shader); - auto code_size = static_cast(result.cend() - result.cbegin()) * sizeof(uint32_t); - auto ptr = reinterpret_cast(result.cbegin()); - std::vector code{ptr, ptr + code_size}; - m_fragment_shader = m_device->create_shader(code, SDL_GPU_SHADERSTAGE_FRAGMENT, 0); - m_fragment_shader.and_then( - [&](auto shader) -> std::expected - { - m_pipeline_info = m_pipeline_info.transform( - [&](auto pipeline_info) - { - pipeline_info.fragment_shader = shader; - return pipeline_info; - }); - return std::monostate{}; - }); - m_modified = true; + m_gpu->release_shader(m_fragment_shader); } + + m_fragment_shader = new_shader; + m_pipeline_info.fragment_shader = new_shader; + m_modified = true; + + return std::monostate{}; } + } // namespace sopho diff --git a/sdl_wrapper/sdl_wrapper.pipeline.ixx b/sdl_wrapper/sdl_wrapper.pipeline.ixx index bfc5d96..f353e2c 100644 --- a/sdl_wrapper/sdl_wrapper.pipeline.ixx +++ b/sdl_wrapper/sdl_wrapper.pipeline.ixx @@ -1,42 +1,71 @@ -// +// sdl_wrapper.pipeline.ixx // Created by sophomore on 11/11/25. // module; #include #include +#include +#include +#include + #include "SDL3/SDL_gpu.h" #include "shaderc/shaderc.hpp" export module sdl_wrapper:pipeline; -import :decl; +import :decl; // GpuError, forward declarations, etc. + export namespace sopho { class PipelineWrapper { + std::shared_ptr m_gpu{}; + SDL_GPUGraphicsPipeline* m_graphics_pipeline{}; - std::shared_ptr m_device{}; + SDL_GPUShader* m_vertex_shader{}; + SDL_GPUShader* m_fragment_shader{}; - std::expected m_vertex_shader{}; - std::expected m_fragment_shader{}; - std::vector m_vertex_buffer_description{}; - std::vector m_vertex_attribute{}; - std::vector m_color_target_description{}; - std::expected m_pipeline_info{}; + std::vector m_vertex_buffer_descriptions{}; + std::vector m_vertex_attributes{}; + std::vector m_color_target_descriptions{}; + SDL_GPUGraphicsPipelineCreateInfo m_pipeline_info{}; - shaderc::Compiler compiler{}; - shaderc::CompileOptions options{}; + shaderc::Compiler m_compiler{}; + shaderc::CompileOptions m_options{}; - bool m_modified = false; + bool m_modified{false}; + + // Internal constructor: assumes texture format is already known and valid. + PipelineWrapper(std::shared_ptr gpu, SDL_GPUTextureFormat swapchain_format) noexcept; - PipelineWrapper(std::shared_ptr p_device); public: - ~PipelineWrapper(); + PipelineWrapper(const PipelineWrapper&) = delete; + PipelineWrapper& operator=(const PipelineWrapper&) = delete; + PipelineWrapper(PipelineWrapper&&) noexcept = default; + PipelineWrapper& operator=(PipelineWrapper&&) = delete; + ~PipelineWrapper() noexcept; + + /// Returns the underlying SDL_GPUGraphicsPipeline*. + [[nodiscard]] SDL_GPUGraphicsPipeline* data() const noexcept { return m_graphics_pipeline; } + + /// Rebuilds the graphics pipeline if any state has been modified. + /// + /// On success, this replaces the previous pipeline (if any) with a newly created one. + /// On failure, an error is propagated to the caller. + [[nodiscard]] std::expected submit(); - auto data() { return m_graphics_pipeline; } + /// Compiles and sets the vertex shader from GLSL source. + /// + /// On success, this replaces the previous vertex shader (if any), updates the + /// pipeline create info, and marks the pipeline as modified. + /// On failure, a compile or creation error is returned. + [[nodiscard]] std::expected set_vertex_shader(const std::string& source); - void submit(); + /// Compiles and sets the fragment shader from GLSL source. + /// + /// On success, this replaces the previous fragment shader (if any), updates the + /// pipeline create info, and marks the pipeline as modified. + /// On failure, a compile or creation error is returned. + [[nodiscard]] std::expected set_fragment_shader(const std::string& source); - void set_vertex_shader(const std::string& p_source); - void set_fragment_shader(const std::string& p_source); - friend GpuWrapper; + friend class GpuWrapper; }; } // namespace sopho From d9014578a0c223a0df3db992317d616aa27f419d Mon Sep 17 00:00:00 2001 From: Sophomore <17792626+WSQS@users.noreply.github.com> Date: Sun, 16 Nov 2025 21:27:32 +0800 Subject: [PATCH 5/8] doc: change comment --- sdl_wrapper/sdl_wrapper.buffer.cpp | 1 - sdl_wrapper/sdl_wrapper.gpu.ixx | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/sdl_wrapper/sdl_wrapper.buffer.cpp b/sdl_wrapper/sdl_wrapper.buffer.cpp index 7eddcb2..7d2b61c 100644 --- a/sdl_wrapper/sdl_wrapper.buffer.cpp +++ b/sdl_wrapper/sdl_wrapper.buffer.cpp @@ -48,7 +48,6 @@ namespace sopho SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d buffer overflow: size=%u, offset=%u, buffer_size=%u", __FILE__, __LINE__, size, offset, m_vertex_buffer_size); - // NOTE: You should add this value to your GpuError enum. return std::unexpected(GpuError::BUFFER_OVERFLOW); } diff --git a/sdl_wrapper/sdl_wrapper.gpu.ixx b/sdl_wrapper/sdl_wrapper.gpu.ixx index 9e7a46c..e8e7137 100644 --- a/sdl_wrapper/sdl_wrapper.gpu.ixx +++ b/sdl_wrapper/sdl_wrapper.gpu.ixx @@ -200,6 +200,7 @@ export namespace sopho [](GpuContext ctx) { return std::shared_ptr(new GpuWrapper(std::move(ctx))); }); } + // Release resource in m_ctx ~GpuWrapper() = default; GpuWrapper(const GpuWrapper&) = delete; From 085c4d9faefc6b125cc18b5577d8cfee00dcafcf Mon Sep 17 00:00:00 2001 From: Sophomore <17792626+WSQS@users.noreply.github.com> Date: Sun, 16 Nov 2025 21:37:39 +0800 Subject: [PATCH 6/8] fix:add check for sdl init video --- sdl_wrapper/sdl_callback_implement.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sdl_wrapper/sdl_callback_implement.cpp b/sdl_wrapper/sdl_callback_implement.cpp index 150173f..cdef9f2 100644 --- a/sdl_wrapper/sdl_callback_implement.cpp +++ b/sdl_wrapper/sdl_callback_implement.cpp @@ -18,7 +18,11 @@ extern sopho::App* create_app(int argc, char** argv); */ SDL_AppResult SDL_AppInit(void** appstate, int argc, char** argv) { - SDL_Init(SDL_INIT_VIDEO); + if (!SDL_Init(SDL_INIT_VIDEO)) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d SDL_INIT_VIDEO:%s", __FILE__, __LINE__, SDL_GetError()); + return SDL_APP_FAILURE; + } auto app = create_app(argc, argv); if (!app) return SDL_APP_FAILURE; From 13172f990a5d270d18cad077d1d34bbb12b767d1 Mon Sep 17 00:00:00 2001 From: Sophomore <17792626+WSQS@users.noreply.github.com> Date: Sun, 16 Nov 2025 21:42:16 +0800 Subject: [PATCH 7/8] fix:early return for pipeline submit --- main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.cpp b/main.cpp index 4f29769..bcd95a2 100644 --- a/main.cpp +++ b/main.cpp @@ -313,6 +313,8 @@ void main() { SDL_LogError(SDL_LOG_CATEGORY_GPU, "Pipeline submit failed, error = %d", static_cast(pipeline_submit.error())); + // Upload pipeline failed, no need to draw. + return SDL_APP_CONTINUE; } SDL_GPUDevice* device = m_gpu->device(); From 4e0dd99bf43cf2f864fb47bc81b318b2789873ad Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Sun, 16 Nov 2025 13:49:24 +0000 Subject: [PATCH 8/8] style: format code with ClangFormat This commit fixes the style issues introduced in 13172f9 according to the output from ClangFormat. Details: https://github.com/WSQS/sdl_test/pull/32 --- main.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/main.cpp b/main.cpp index bcd95a2..c5bcb27 100644 --- a/main.cpp +++ b/main.cpp @@ -39,8 +39,10 @@ class UserApp : public sopho::App { // GPU + resources std::shared_ptr m_gpu{}; - std::expected m_vertex_buffer{std::unexpected(sopho::GpuError::UNINITIALIZED)}; - std::expected m_pipeline_wrapper{std::unexpected(sopho::GpuError::UNINITIALIZED)}; + std::expected m_vertex_buffer{ + std::unexpected(sopho::GpuError::UNINITIALIZED)}; + std::expected m_pipeline_wrapper{ + std::unexpected(sopho::GpuError::UNINITIALIZED)}; // camera state float yaw = 0.0f;