diff --git a/CMakeLists.txt b/CMakeLists.txt index 757c702..21cf4db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,12 +88,23 @@ add_library(imgui STATIC ${IMGUI_DIR}/backends/imgui_impl_sdlgpu3.cpp ${IMGUI_DIR}/misc/cpp/imgui_stdlib.cpp ) -target_include_directories(imgui PUBLIC +target_include_directories(imgui SYSTEM PUBLIC ${IMGUI_DIR} ${IMGUI_DIR}/backends ) target_link_libraries(imgui PUBLIC SDL3::SDL3) +FetchContent_Declare( + stb + GIT_REPOSITORY https://github.com/nothings/stb.git + GIT_TAG master + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE +) +FetchContent_MakeAvailable(stb) +add_library(stb INTERFACE) +target_include_directories(stb SYSTEM INTERFACE ${stb_SOURCE_DIR}) + FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git @@ -111,10 +122,26 @@ add_subdirectory(sdl_wrapper) add_subdirectory(glsl_reflector) add_executable(SDL_TEST main.cpp) -target_link_libraries(SDL_TEST PRIVATE imgui sdl_wrapper) - +target_link_libraries(SDL_TEST PRIVATE imgui sdl_wrapper stb) include(GNUInstallDirs) install(TARGETS SDL_TEST RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -) \ No newline at end of file +) + +set(ASSETS_DST_DIR ${CMAKE_BINARY_DIR}/assets) +file(MAKE_DIRECTORY ${ASSETS_DST_DIR}) + +set(TEST_TEXTURE_PATH ${ASSETS_DST_DIR}/test_texture.png) + +if (NOT EXISTS ${TEST_TEXTURE_PATH}) + file(DOWNLOAD + "https://opengameart.org/sites/default/files/slime_25.png" + "${TEST_TEXTURE_PATH}" + SHOW_PROGRESS + ) +endif() + +install(DIRECTORY ${ASSETS_DST_DIR}/ + DESTINATION ${CMAKE_INSTALL_BINDIR}/assets +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..2dde53b --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# SDL Test + +This is a personal learning project following [Learn OpenGL](https://learnopengl.com/) + +## Track + +- [ ] Getting started + - [x] OpenGL + - [x] Creating a window + - [x] Hello Window + - [x] Hello Triangle + - [x] Shaders + - [x] Textures + - [ ] Transformations + - [ ] Coordinate Systems + - [ ] Camera +- [ ] Lighting +- [ ] Model Loading +- [ ] Advanced OpenGL +- [ ] Advanced Lighting +- [ ] PBR +- [ ] In Practice diff --git a/data_type/modules/data_type.ixx b/data_type/modules/data_type.ixx index d8a3a2e..5b4948e 100644 --- a/data_type/modules/data_type.ixx +++ b/data_type/modules/data_type.ixx @@ -2,19 +2,26 @@ // Created by sophomore on 11/23/25. // module; -#include +#include +#include +#include +#include export module data_type; -export namespace sopho { - enum class GpuError { +export namespace sopho +{ + enum class GpuError + { UNINITIALIZED, CREATE_DEVICE_FAILED, CREATE_WINDOW_FAILED, CLAIM_WINDOW_FAILED, CREATE_GPU_BUFFER_FAILED, CREATE_TRANSFER_BUFFER_FAILED, + CREATE_TEXTURE_FAILED, CREATE_SHADER_FAILED, CREATE_PIPELINE_FAILED, + CREATE_SAMPLER_FAILED, GET_TEXTUREFORMAT_FAILED, BUFFER_OVERFLOW, MAP_TRANSFER_BUFFER_FAILED, @@ -26,6 +33,39 @@ export namespace sopho { }; using TError = GpuError; - template + template using checkable = std::expected; -} + + struct ImageData + { + int width{}; + int height{}; + int channels{}; + std::vector pixels{}; + }; + + enum class BasicType : std::uint8_t + { + NONE, + FLOAT + }; + + struct VertexInfo + { + std::uint32_t location{}; + std::string name{}; + BasicType basic_type{}; + int vector_size{}; + }; + + struct VertexReflection + { + std::vector inputs{}; + }; + + struct FragmentReflection + { + std::uint32_t sampler_count{}; + }; + +} // namespace sopho diff --git a/glsl_reflector/CMakeLists.txt b/glsl_reflector/CMakeLists.txt index 2b0dc45..5e0ec26 100644 --- a/glsl_reflector/CMakeLists.txt +++ b/glsl_reflector/CMakeLists.txt @@ -8,10 +8,9 @@ target_sources(glsl_reflector PUBLIC FILE_SET cxx_modules TYPE CXX_MODULES FILES modules/glsl_reflector.ixx - modules/glsl_reflector.type.ixx ) -target_link_libraries(glsl_reflector PUBLIC glslang glslang::glslang-default-resource-limits) +target_link_libraries(glsl_reflector PUBLIC glslang glslang::glslang-default-resource-limits data_type) add_executable(glslr test/test.cpp) diff --git a/glsl_reflector/modules/glsl_reflector.ixx b/glsl_reflector/modules/glsl_reflector.ixx index ad7e841..5d5f5ab 100644 --- a/glsl_reflector/modules/glsl_reflector.ixx +++ b/glsl_reflector/modules/glsl_reflector.ixx @@ -10,7 +10,7 @@ module; #include "glslang/Public/ResourceLimits.h" #include "glslang/Public/ShaderLang.h" export module glsl_reflector; -export import :type; +export import data_type; namespace sopho { @@ -82,6 +82,59 @@ namespace sopho .basic_type = to_basic_type(type->getBasicType()), .vector_size = type->getVectorSize()}); } + count = program.getNumUniformVariables(); + for (auto i = 0; i < count; i++) + { + auto var = program.getUniform(i); + } + return result; + } + + export auto reflect_fragment(const std::string& fragment_source) + { + GlslangProcessGuard glslang_initializer{}; + FragmentReflection result{}; + glslang::TShader shader(EShLangFragment); + std::vector sources{fragment_source.data()}; + shader.setStrings(sources.data(), sources.size()); + + int clientInputSemanticsVersion = 100; // not used for desktop GL + glslang::EShTargetClientVersion clientVersion = glslang::EShTargetOpenGL_450; + glslang::EShTargetLanguageVersion targetVersion = glslang::EShTargetSpv_1_0; + + shader.setEnvInput(glslang::EShSourceGlsl, EShLangFragment, glslang::EShClientOpenGL, + clientInputSemanticsVersion); + shader.setEnvClient(glslang::EShClientOpenGL, clientVersion); + shader.setEnvTarget(glslang::EShTargetSpv, targetVersion); + + EShMessages messages = (EShMessages)(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules); + + if (!shader.parse(GetDefaultResources(), 450, ENoProfile, false, false, messages)) + { + std::cerr << "Parse failed:\n" << shader.getInfoLog() << std::endl; + return result; + } + + glslang::TProgram program; + program.addShader(&shader); + if (!program.link(messages)) + { + std::cerr << "Link failed:\n" << program.getInfoLog() << std::endl; + return result; + } + program.buildReflection(); + auto count = program.getNumUniformVariables(); + for (auto i = 0; i < count; i++) + { + auto var = program.getUniform(i); + auto type = var.getType(); + auto basic_type = type->getBasicType(); + if (basic_type == glslang::EbtSampler) + { + result.sampler_count++; + } + } return result; } + } // namespace sopho diff --git a/glsl_reflector/modules/glsl_reflector.type.ixx b/glsl_reflector/modules/glsl_reflector.type.ixx deleted file mode 100644 index 29f83af..0000000 --- a/glsl_reflector/modules/glsl_reflector.type.ixx +++ /dev/null @@ -1,30 +0,0 @@ -// glsl_reflector.type.ixx -// Created by wsqsy on 11/20/2025. -// -module; -#include -#include -#include -export module glsl_reflector:type; - -export namespace sopho -{ - enum class BasicType : std::uint8_t - { - NONE, - FLOAT - }; - - struct VertexInfo - { - std::uint32_t location{}; - std::string name{}; - BasicType basic_type{}; - int vector_size{}; - }; - - struct VertexReflection - { - std::vector inputs{}; - }; -} // namespace sopho diff --git a/glsl_reflector/test/test.cpp b/glsl_reflector/test/test.cpp index 4bb8d00..0184eda 100644 --- a/glsl_reflector/test/test.cpp +++ b/glsl_reflector/test/test.cpp @@ -36,3 +36,41 @@ TEST(reflect_vertex, Basic) EXPECT_EQ(reflect_info.inputs[1].basic_type, sopho::BasicType::FLOAT); EXPECT_EQ(reflect_info.inputs[1].vector_size, 3); } + + +TEST(reflect_fragment, Basic) +{ + auto reflect_info = sopho::reflect_fragment(R"( +#version 460 + +layout (location = 0) in vec4 v_color; +layout (location = 0) out vec4 FragColor; + +layout(set = 2, binding = 0) uniform sampler2D uTexture; + +void main() +{ + FragColor = texture(uTexture, v_color.xy); +} + )"); + EXPECT_EQ(reflect_info.sampler_count, 1); +} + +TEST(reflect_fragment, Multi) +{ + auto reflect_info = sopho::reflect_fragment(R"( +#version 460 + +layout (location = 0) in vec4 v_color; +layout (location = 0) out vec4 FragColor; + +layout(set = 2, binding = 0) uniform sampler2D uTexture; +layout(set = 2, binding = 1) uniform sampler2D uTexture1; + +void main() +{ + FragColor = texture(uTexture, v_color.xy) + texture(uTexture1, v_color.xy); +} + )"); + EXPECT_EQ(reflect_info.sampler_count, 2); +} diff --git a/main.cpp b/main.cpp index 7b7f488..81eef1a 100644 --- a/main.cpp +++ b/main.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -20,15 +19,55 @@ #include "SDL3/SDL.h" #include "SDL3/SDL_gpu.h" #include "SDL3/SDL_keycode.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + import data_type; import glsl_reflector; import sdl_wrapper; +/** + * @brief Structure to hold camera transformation matrix data. + * + * Contains a 4x4 matrix (16 elements) representing the camera's view transformation. + */ struct CameraUniform { - std::array m{}; + std::array m{}; ///< 4x4 transformation matrix as a flat array }; +/** + * @brief Loads image data from the test texture file. + * + * Uses stb_image library to load a PNG file (assets/test_texture.png) into an ImageData structure. + * The image is flipped vertically on load and the pixel data is stored as a vector of bytes. + * + * @return sopho::ImageData Structure containing the loaded image dimensions, channels, and pixel data. + * Returns an empty structure if loading fails. + */ +sopho::ImageData load_image() +{ + stbi_set_flip_vertically_on_load(true); + std::string file_name{"assets/test_texture.png"}; + sopho::ImageData result; + auto data = stbi_load(file_name.data(), &result.width, &result.height, &result.channels, 4); + result.channels = 4; + + if (!data) + { + SDL_Log("stbi_load failed for %s: %s", file_name.data(), stbi_failure_reason()); + } + else + { + result.pixels.assign(reinterpret_cast(data), + reinterpret_cast(data) + result.width * result.height * result.channels); + stbi_image_free(data); + SDL_Log("stbi_load succeeded, w: %d h:%d ch:%d", result.width, result.height, result.channels); + } + return result; +} + class UserApp : public sopho::App { // GPU + resources @@ -36,6 +75,9 @@ class UserApp : public sopho::App std::shared_ptr m_renderable{}; + sopho::ImageData m_image_data; + std::shared_ptr m_texture_wrapper{}; + // camera state float yaw = 0.0f; float pitch = 0.0f; @@ -66,9 +108,11 @@ void main() layout (location = 0) in vec4 v_color; layout (location = 0) out vec4 FragColor; +layout(set = 2, binding = 0) uniform sampler2D uTexture; + void main() { - FragColor = v_color; + FragColor = texture(uTexture, v_color.xy); })WSQ"; public: @@ -116,7 +160,7 @@ void main() } // 3. Create vertex buffer. - auto render_data = std::move(m_gpu->create_data(pw_result.value(), 4)); + auto render_data = m_gpu->create_data(pw_result.value(), 4, 6); if (!render_data) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create vertex buffer, error = %d", @@ -125,7 +169,7 @@ void main() } // 5. Upload initial vertex data. - auto upload_result = render_data.and_then([&](auto& vertex_buffer) { return vertex_buffer.upload(); }); + auto upload_result = render_data.and_then([&](auto& vertex_buffer) { return vertex_buffer->upload(); }); if (!upload_result) { @@ -136,7 +180,7 @@ void main() m_renderable = std::make_shared(sopho::Renderable{ .m_render_procedural = std::make_shared(std::move(pw_result.value())), - .m_render_data = std::make_shared(std::move(render_data.value()))}); + .m_render_data = std::move(render_data.value())}); // 6. Initialize camera matrix to identity. { @@ -192,7 +236,18 @@ void main() init_info.PresentMode = SDL_GPU_PRESENTMODE_VSYNC; ImGui_ImplSDLGPU3_Init(&init_info); + m_image_data = load_image(); + auto texture = sopho::TextureWrapper::Builder{}.set_image_data(m_image_data).build(*m_gpu.get()); + if (texture) + { + m_texture_wrapper = std::make_shared(std::move(texture.value())); + } + else + { + SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "Failed to create texture: error = %d", + static_cast(texture.error())); + } return SDL_APP_CONTINUE; } @@ -294,8 +349,8 @@ void main() } else { - auto new_data = m_gpu->create_data(*m_renderable->procedural(), 3); - m_renderable->data() = std::make_shared(std::move(new_data.value())); + auto new_data = m_gpu->create_data(*m_renderable->procedural(), 4, 6); + m_renderable->data() = std::move(new_data.value()); m_renderable->data()->upload(); } } @@ -443,6 +498,15 @@ void main() SDL_BindGPUIndexBuffer(renderPass, &m_renderable->data()->get_index_buffer_binding(), SDL_GPU_INDEXELEMENTSIZE_32BIT); + if (m_texture_wrapper) + { + SDL_BindGPUFragmentSamplers(renderPass, 0, m_texture_wrapper->get(), 1); + } + else + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "Texture not available, skip binding sampler"); + } + SDL_DrawGPUIndexedPrimitives(renderPass, 6, 1, 0, 0, 0); diff --git a/sdl_wrapper/CMakeLists.txt b/sdl_wrapper/CMakeLists.txt index c2c6297..e58f90c 100644 --- a/sdl_wrapper/CMakeLists.txt +++ b/sdl_wrapper/CMakeLists.txt @@ -7,20 +7,25 @@ add_library(sdl_wrapper STATIC) target_sources(sdl_wrapper PUBLIC FILE_SET cxx_modules TYPE CXX_MODULES FILES - sdl_wrapper.ixx - sdl_wrapper.buffer.ixx - sdl_wrapper.app.ixx - sdl_wrapper.render_procedural.ixx - sdl_wrapper.render_data.ixx - sdl_wrapper.renderable.ixx - sdl_wrapper.gpu.ixx - sdl_wrapper.decl.ixx - sdl_wrapper.vertex_layout.ixx + modules/sdl_wrapper.ixx + modules/sdl_wrapper.buffer.ixx + modules/sdl_wrapper.app.ixx + modules/sdl_wrapper.render_procedural.ixx + modules/sdl_wrapper.render_data.ixx + modules/sdl_wrapper.render_data_impl.ixx + modules/sdl_wrapper.renderable.ixx + modules/sdl_wrapper.gpu.ixx + modules/sdl_wrapper.decl.ixx + modules/sdl_wrapper.vertex_layout.ixx + modules/sdl_wrapper.transfer_buffer.ixx + modules/sdl_wrapper.texture.ixx PRIVATE - sdl_wrapper.buffer.cpp - sdl_wrapper.render_procedural.cpp - sdl_wrapper.gpu.cpp - sdl_callback_implement.cpp + src/sdl_wrapper.buffer.cpp + src/sdl_wrapper.render_procedural.cpp + src/sdl_wrapper.gpu.cpp + src/sdl_callback_implement.cpp + src/sdl_wrapper.texture.cpp + src/sdl_wrapper.transfer_buffer.cpp ) target_link_libraries(sdl_wrapper PUBLIC SDL3::SDL3 shaderc glsl_reflector data_type) diff --git a/sdl_wrapper/sdl_wrapper.app.ixx b/sdl_wrapper/modules/sdl_wrapper.app.ixx similarity index 100% rename from sdl_wrapper/sdl_wrapper.app.ixx rename to sdl_wrapper/modules/sdl_wrapper.app.ixx diff --git a/sdl_wrapper/sdl_wrapper.buffer.ixx b/sdl_wrapper/modules/sdl_wrapper.buffer.ixx similarity index 64% rename from sdl_wrapper/sdl_wrapper.buffer.ixx rename to sdl_wrapper/modules/sdl_wrapper.buffer.ixx index 3467be4..8294662 100644 --- a/sdl_wrapper/sdl_wrapper.buffer.ixx +++ b/sdl_wrapper/modules/sdl_wrapper.buffer.ixx @@ -6,12 +6,13 @@ module; #include #include #include - #include "SDL3/SDL_gpu.h" +#include "SDL3/SDL_log.h" export module sdl_wrapper:buffer; import data_type; import :decl; +import :transfer_buffer; namespace sopho { @@ -19,19 +20,40 @@ namespace sopho { std::shared_ptr m_gpu{}; // Owns the device lifetime SDL_GPUBuffer* m_gpu_buffer{}; // Target GPU buffer - SDL_GPUTransferBuffer* m_transfer_buffer{}; // Staging/transfer buffer + TransferBufferWrapper m_transfer_buffer; // Staging/transfer buffer std::uint32_t m_buffer_size{}; // Total size of the GPU buffer std::vector m_cpu_buffer{}; // Only GpuWrapper is allowed to construct this type. - BufferWrapper(std::shared_ptr gpu, SDL_GPUBuffer* gpu_buffer, - SDL_GPUTransferBuffer* transfer_buffer, std::uint32_t size) noexcept : - m_gpu(std::move(gpu)), m_gpu_buffer(gpu_buffer), m_transfer_buffer(transfer_buffer), m_buffer_size(size) + BufferWrapper(std::shared_ptr gpu, SDL_GPUBuffer* gpu_buffer, TransferBufferWrapper transfer_buffer, + std::uint32_t size) noexcept : + m_gpu(std::move(gpu)), m_gpu_buffer(gpu_buffer), m_transfer_buffer(std::move(transfer_buffer)), + m_buffer_size(size) { m_cpu_buffer.resize(m_buffer_size); } public: + struct Builder + { + SDL_GPUBufferUsageFlags flag = SDL_GPU_BUFFERUSAGE_VERTEX; + std::uint32_t size = 0; + + Builder& set_flag(SDL_GPUBufferUsageFlags usage_flag) + { + flag = usage_flag; + return *this; + } + + Builder& set_size(std::uint32_t buffer_size) + { + size = buffer_size; + return *this; + } + + checkable build(GpuWrapper& gpu); + }; + BufferWrapper(const BufferWrapper&) = delete; BufferWrapper& operator=(const BufferWrapper&) = delete; BufferWrapper(BufferWrapper&&) = default; diff --git a/sdl_wrapper/sdl_wrapper.decl.ixx b/sdl_wrapper/modules/sdl_wrapper.decl.ixx similarity index 88% rename from sdl_wrapper/sdl_wrapper.decl.ixx rename to sdl_wrapper/modules/sdl_wrapper.decl.ixx index 1bacec0..646621f 100644 --- a/sdl_wrapper/sdl_wrapper.decl.ixx +++ b/sdl_wrapper/modules/sdl_wrapper.decl.ixx @@ -8,7 +8,6 @@ export namespace sopho { class App; class GpuWrapper; - class BufferWrapper; class RenderProcedural; class RenderData; } diff --git a/sdl_wrapper/sdl_wrapper.gpu.ixx b/sdl_wrapper/modules/sdl_wrapper.gpu.ixx similarity index 95% rename from sdl_wrapper/sdl_wrapper.gpu.ixx rename to sdl_wrapper/modules/sdl_wrapper.gpu.ixx index 572aa09..ea8c19a 100644 --- a/sdl_wrapper/sdl_wrapper.gpu.ixx +++ b/sdl_wrapper/modules/sdl_wrapper.gpu.ixx @@ -11,7 +11,6 @@ module; #include "shaderc/shaderc.hpp" export module sdl_wrapper:gpu; import :decl; -import :buffer; import :render_procedural; export namespace sopho { @@ -210,11 +209,8 @@ export namespace sopho GpuWrapper& operator=(GpuWrapper&&) = delete; [[nodiscard]] auto device() const { return m_ctx.device.raw; } - [[nodiscard]] SDL_Window *window() const { return m_ctx.window.raw; } - - [[nodiscard]] std::expected create_buffer(SDL_GPUBufferUsageFlags flag, uint32_t size); - [[nodiscard]] std::expected - create_data(const RenderProcedural& render_procedural, uint32_t vertex_count); + [[nodiscard]] SDL_Window* window() const { return m_ctx.window.raw; } + [[nodiscard]] checkable> create_data(const RenderProcedural& render_procedural, std::uint32_t vertex_count, std::uint32_t index_count); auto release_buffer(SDL_GPUBuffer* buffer) { @@ -247,7 +243,8 @@ export namespace sopho } [[nodiscard]] auto create_shader(const std::vector& shader, SDL_GPUShaderStage stage, - uint32_t num_uniform_buffers) -> std::expected + uint32_t num_uniform_buffers, uint32_t num_samplers) + -> std::expected { SDL_GPUShaderCreateInfo info{}; info.code = shader.data(); @@ -255,7 +252,7 @@ export namespace sopho info.entrypoint = "main"; info.format = SDL_GPU_SHADERFORMAT_SPIRV; info.stage = stage; - info.num_samplers = 0; + info.num_samplers = num_samplers; info.num_storage_buffers = 0; info.num_storage_textures = 0; info.num_uniform_buffers = num_uniform_buffers; diff --git a/sdl_wrapper/sdl_wrapper.ixx b/sdl_wrapper/modules/sdl_wrapper.ixx similarity index 91% rename from sdl_wrapper/sdl_wrapper.ixx rename to sdl_wrapper/modules/sdl_wrapper.ixx index 94a2470..430fbaf 100644 --- a/sdl_wrapper/sdl_wrapper.ixx +++ b/sdl_wrapper/modules/sdl_wrapper.ixx @@ -6,8 +6,8 @@ export module sdl_wrapper; export import :decl; export import :app; export import :gpu; -export import :buffer; export import :render_procedural; export import :renderable; export import :vertex_layout; export import :render_data; +export import :texture; diff --git a/sdl_wrapper/modules/sdl_wrapper.render_data.ixx b/sdl_wrapper/modules/sdl_wrapper.render_data.ixx new file mode 100644 index 0000000..50daeaf --- /dev/null +++ b/sdl_wrapper/modules/sdl_wrapper.render_data.ixx @@ -0,0 +1,35 @@ +// sdl_wrapper.render_data.ixx +// Created by sophomore on 11/18/25. +// +module; +#include +#include +#include +#include +#include +export module sdl_wrapper:render_data; +import :vertex_layout; +namespace sopho +{ + export class RenderData + { + public: + struct VertexView + { + VertexLayout layout{}; + size_t vertex_count{}; + std::byte* raw{}; + }; + struct IndexView + { + size_t index_count{}; + std::byte* raw{}; + }; + virtual ~RenderData() = default; + virtual std::vector& get_vertex_buffer_binding() = 0; + virtual SDL_GPUBufferBinding& get_index_buffer_binding() = 0; + virtual VertexView vertex_view() = 0; + virtual IndexView index_view() = 0; + virtual std::expected upload() = 0; + }; +} // namespace sopho diff --git a/sdl_wrapper/modules/sdl_wrapper.render_data_impl.ixx b/sdl_wrapper/modules/sdl_wrapper.render_data_impl.ixx new file mode 100644 index 0000000..09c0ae6 --- /dev/null +++ b/sdl_wrapper/modules/sdl_wrapper.render_data_impl.ixx @@ -0,0 +1,63 @@ +// sdl_wrapper.render_data_impl.ixx +// Created by wsqsy on 11/25/2025. +// +module; +#include +#include +#include +#include +#include +#include + +#include "SDL3/SDL_gpu.h" +export module sdl_wrapper:render_data_impl; +import :render_data; +import :buffer; +namespace sopho +{ + class RenderDataImpl : public RenderData + { + BufferWrapper m_vertex_buffer; + BufferWrapper m_index_buffer; + VertexLayout m_layouts{}; + size_t m_vertex_count{}; + size_t m_index_count{}; + std::vector m_bindings{}; + SDL_GPUBufferBinding m_index_binding{}; + + public: + RenderDataImpl(BufferWrapper&& vertex_buffer_wrapper, BufferWrapper&& index_buffer_wrapper, + const VertexLayout& layouts, size_t vertex_count, std::uint32_t index_count) : + m_vertex_buffer(std::move(vertex_buffer_wrapper)), m_index_buffer(std::move(index_buffer_wrapper)), + m_layouts(layouts), m_vertex_count(vertex_count), m_index_count(index_count) + { + m_bindings.emplace_back(m_vertex_buffer.gpu_buffer(), 0); + m_index_binding.buffer = m_index_buffer.gpu_buffer(); + m_index_binding.offset = 0; + } + RenderDataImpl(const RenderDataImpl&) = delete; + RenderDataImpl& operator=(const RenderDataImpl&) = delete; + RenderDataImpl(RenderDataImpl&&) = default; + RenderDataImpl& operator=(RenderDataImpl&&) = default; + virtual std::vector& get_vertex_buffer_binding() override { return m_bindings; } + virtual SDL_GPUBufferBinding& get_index_buffer_binding() override { return m_index_binding; } + virtual VertexView vertex_view() override + { + return VertexView{.layout = m_layouts, .vertex_count = m_vertex_count, .raw = m_vertex_buffer.cpu_buffer()}; + } + virtual IndexView index_view() override + { + return IndexView{.index_count = m_index_count, .raw = m_index_buffer.cpu_buffer()}; + } + virtual std::expected upload() override + { + auto result = m_vertex_buffer.upload(); + if (!result) + { + return result; + } + result = m_index_buffer.upload(); + return result; + } + }; +} // namespace sopho diff --git a/sdl_wrapper/sdl_wrapper.render_procedural.ixx b/sdl_wrapper/modules/sdl_wrapper.render_procedural.ixx similarity index 100% rename from sdl_wrapper/sdl_wrapper.render_procedural.ixx rename to sdl_wrapper/modules/sdl_wrapper.render_procedural.ixx diff --git a/sdl_wrapper/sdl_wrapper.renderable.ixx b/sdl_wrapper/modules/sdl_wrapper.renderable.ixx similarity index 100% rename from sdl_wrapper/sdl_wrapper.renderable.ixx rename to sdl_wrapper/modules/sdl_wrapper.renderable.ixx diff --git a/sdl_wrapper/modules/sdl_wrapper.texture.ixx b/sdl_wrapper/modules/sdl_wrapper.texture.ixx new file mode 100644 index 0000000..ec3a6db --- /dev/null +++ b/sdl_wrapper/modules/sdl_wrapper.texture.ixx @@ -0,0 +1,79 @@ +// sdl_wrapper.texture.ixx +// Created by wsqsy on 11/25/2025. +// + +module; +#include +#include +#include +#include +export module sdl_wrapper:texture; +import data_type; +import :decl; + +namespace sopho +{ + export class TextureWrapper + { + std::shared_ptr m_gpu{}; + SDL_GPUTexture* m_texture{}; + SDL_GPUSampler* m_sampler{}; + SDL_GPUTextureSamplerBinding m_tex_binding{}; + + TextureWrapper(std::shared_ptr gpu, SDL_GPUTexture* texture, SDL_GPUSampler* sampler) noexcept : + m_gpu(std::move(gpu)), m_texture(texture), m_sampler(sampler) + { + m_tex_binding.texture = m_texture; + m_tex_binding.sampler = m_sampler; + } + + public: + TextureWrapper(const TextureWrapper&) = delete; + TextureWrapper& operator=(const TextureWrapper&) = delete; + TextureWrapper(TextureWrapper&& other) noexcept : m_gpu(std::move(other.m_gpu)), m_texture(other.m_texture), m_sampler(other.m_sampler),m_tex_binding(other.m_tex_binding) + { + other.m_texture = nullptr; + other.m_sampler = nullptr; + other.m_tex_binding = {}; + } + + TextureWrapper& operator=(TextureWrapper&& other) noexcept + { + if (this != &other) + { + reset(); + m_gpu = std::move(other.m_gpu); + m_texture = other.m_texture; + other.m_texture = nullptr; + m_sampler = other.m_sampler; + other.m_sampler = nullptr; + other.m_tex_binding = {}; + m_tex_binding.texture = m_texture; + m_tex_binding.sampler = m_sampler; + } + return *this; + } + + [[nodiscard]] const SDL_GPUTextureSamplerBinding* get() const noexcept + { + return &m_tex_binding; + } + + void reset() noexcept; + + ~TextureWrapper() noexcept { reset(); } + + struct Builder + { + ImageData img_data{}; + + Builder& set_image_data(ImageData image_data) + { + img_data = std::move(image_data); + return *this; + } + + std::expected build(GpuWrapper& gpu); + }; + }; +} // namespace sopho diff --git a/sdl_wrapper/modules/sdl_wrapper.transfer_buffer.ixx b/sdl_wrapper/modules/sdl_wrapper.transfer_buffer.ixx new file mode 100644 index 0000000..c169fe7 --- /dev/null +++ b/sdl_wrapper/modules/sdl_wrapper.transfer_buffer.ixx @@ -0,0 +1,138 @@ +// sdl_wrapper.transfer_buffer.ixx +// Created by sophomore on 11/24/25. +// +/* + * @file sdl_wrapper.transfer_buffer.ixx + * @brief Transfer buffer wrapper module for SDL GPU operations. + * + * This module provides a wrapper for SDL GPU transfer buffers with a builder pattern + * that allows specifying usage limits for automatic resource management. + */ +module; +#include +#include +#include +#include +#include +#include "SDL3/SDL_gpu.h" +#include "SDL3/SDL_log.h" +export module sdl_wrapper:transfer_buffer; +import data_type; +import :decl; +namespace sopho +{ + + /* + * @brief Wrapper class for SDL GPU transfer buffer operations. + */ + class TransferBufferWrapper + { + private: + std::shared_ptr m_gpu{}; // Owns the device lifetime + SDL_GPUTransferBuffer* m_transfer_buffer{}; // The actual transfer buffer + std::int32_t m_usage_limit{}; // Current usage count + std::uint32_t m_size{}; // Size of the transfer buffer in bytes + + // Private constructor to ensure only Builder can create instances + TransferBufferWrapper(std::shared_ptr gpu, SDL_GPUTransferBuffer* transfer_buffer, + std::int32_t usage_limit, std::uint32_t size) noexcept : + m_gpu(std::move(gpu)), m_transfer_buffer(transfer_buffer), m_usage_limit(usage_limit), m_size(size) + { + } + + public: + TransferBufferWrapper(const TransferBufferWrapper&) = delete; + TransferBufferWrapper& operator=(const TransferBufferWrapper&) = delete; + TransferBufferWrapper(TransferBufferWrapper&& other) noexcept : + m_gpu(std::move(other.m_gpu)), m_transfer_buffer(other.m_transfer_buffer), + m_usage_limit(other.m_usage_limit), m_size(other.m_size) + { + other.m_transfer_buffer = nullptr; + } + + TransferBufferWrapper& operator=(TransferBufferWrapper&& other) noexcept; + + /* + * @brief Destructor that releases the transfer buffer. + */ + ~TransferBufferWrapper() noexcept { reset(); } + + /* + * @brief Releases the underlying transfer buffer and resets state. + */ + void reset() noexcept; + + /* + * @brief Returns the underlying SDL_GPUTransferBuffer pointer. + */ + [[nodiscard]] SDL_GPUTransferBuffer* raw() const noexcept { return m_transfer_buffer; } + + /* + * @brief Returns the size of the transfer buffer in bytes. + */ + [[nodiscard]] std::uint32_t size() const noexcept { return m_size; } + + checkable submit(const void* data_source); + + /* + * @brief Builder pattern for creating TransferBufferWrapper instances. + * + * Provides a fluent interface for configuring transfer buffer properties + * such as usage limit, buffer type, and size. + */ + struct Builder + { + /* + * @brief Maximum number of uploads allowed before the buffer is recycled. + * When this limit is reached, the buffer will be automatically recycled. + */ + std::int32_t usage_limit{1}; + + /* @brief The usage type for the transfer buffer (e.g., upload/download). */ + SDL_GPUTransferBufferUsage usage{SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD}; + + /* @brief Size of the transfer buffer in bytes. */ + std::uint32_t size{1024}; + + /* + * @brief Sets the usage limit for the transfer buffer. + * @param limit Maximum number of uploads before the buffer is recycled + * @return Reference to this Builder for chaining + */ + Builder& set_usage_limit(std::int32_t limit) + { + usage_limit = limit; + return *this; + } + + /* + * @brief Sets the usage type for the transfer buffer. + * @param buffer_usage The SDL GPU transfer buffer usage type + * @return Reference to this Builder for chaining + */ + Builder& set_usage(SDL_GPUTransferBufferUsage buffer_usage) + { + usage = buffer_usage; + return *this; + } + + /* + * @brief Sets the size of the transfer buffer. + * @param buffer_size Size in bytes + * @return Reference to this Builder for chaining + */ + Builder& set_size(std::uint32_t buffer_size) + { + size = buffer_size; + return *this; + } + + /* + * @brief Builds the TransferBufferWrapper instance. + * @param gpu The GPU wrapper to use for creating the transfer buffer + * @return A checkable containing the TransferBufferWrapper on success, or an error on failure + */ + checkable build(GpuWrapper& gpu); + }; + }; +} // namespace sopho diff --git a/sdl_wrapper/sdl_wrapper.vertex_layout.ixx b/sdl_wrapper/modules/sdl_wrapper.vertex_layout.ixx similarity index 100% rename from sdl_wrapper/sdl_wrapper.vertex_layout.ixx rename to sdl_wrapper/modules/sdl_wrapper.vertex_layout.ixx diff --git a/sdl_wrapper/sdl_wrapper.render_data.ixx b/sdl_wrapper/sdl_wrapper.render_data.ixx deleted file mode 100644 index 9f220d4..0000000 --- a/sdl_wrapper/sdl_wrapper.render_data.ixx +++ /dev/null @@ -1,68 +0,0 @@ -// sdl_wrapper.render_data.ixx -// Created by sophomore on 11/18/25. -// -module; -#include -#include -#include -export module sdl_wrapper:render_data; -import :buffer; -import :vertex_layout; -namespace sopho -{ - export class RenderData - { - BufferWrapper m_vertex_buffer; - BufferWrapper m_index_buffer; - VertexLayout m_layouts{}; - size_t m_vertex_count{}; - std::vector m_bindings{}; - SDL_GPUBufferBinding m_index_binding{}; - - public: - explicit RenderData(BufferWrapper&& vertex_buffer_wrapper, BufferWrapper&& index_buffer_wrapper, - const VertexLayout& layouts, size_t vertex_count) : - m_vertex_buffer(std::move(vertex_buffer_wrapper)), m_index_buffer(std::move(index_buffer_wrapper)), - m_layouts(layouts), m_vertex_count(vertex_count) - { - m_bindings.emplace_back(m_vertex_buffer.gpu_buffer(), 0); - m_index_binding.buffer = m_index_buffer.gpu_buffer(); - m_index_binding.offset = 0; - } - - public: - struct VertexView - { - VertexLayout layout{}; - size_t vertex_count{}; - std::byte* raw{}; - }; - struct IndexView - { - size_t index_count{}; - std::byte* raw{}; - }; - RenderData(const RenderData&) = delete; - RenderData& operator=(const RenderData&) = delete; - RenderData(RenderData&&) = default; - RenderData& operator=(RenderData&&) = default; - auto& buffer() { return m_vertex_buffer; } - auto& get_vertex_buffer_binding() { return m_bindings; } - auto& get_index_buffer_binding() { return m_index_binding; } - auto vertex_view() - { - return VertexView{.layout = m_layouts, .vertex_count = m_vertex_count, .raw = m_vertex_buffer.cpu_buffer()}; - } - auto index_view() { return IndexView{.index_count = m_vertex_count, .raw = m_index_buffer.cpu_buffer()}; } - auto upload() - { - auto result = m_vertex_buffer.upload(); - if (!result) - { - return result; - } - result = m_index_buffer.upload(); - return result; - } - }; -} // namespace sopho diff --git a/sdl_wrapper/sdl_callback_implement.cpp b/sdl_wrapper/src/sdl_callback_implement.cpp similarity index 100% rename from sdl_wrapper/sdl_callback_implement.cpp rename to sdl_wrapper/src/sdl_callback_implement.cpp diff --git a/sdl_wrapper/sdl_wrapper.buffer.cpp b/sdl_wrapper/src/sdl_wrapper.buffer.cpp similarity index 73% rename from sdl_wrapper/sdl_wrapper.buffer.cpp rename to sdl_wrapper/src/sdl_wrapper.buffer.cpp index 78e9323..6454a93 100644 --- a/sdl_wrapper/sdl_wrapper.buffer.cpp +++ b/sdl_wrapper/src/sdl_wrapper.buffer.cpp @@ -35,13 +35,6 @@ namespace sopho m_gpu->release_buffer(m_gpu_buffer); m_gpu_buffer = nullptr; } - - // Release transfer buffer - if (m_transfer_buffer) - { - SDL_ReleaseGPUTransferBuffer(m_gpu->device(), m_transfer_buffer); - m_transfer_buffer = nullptr; - } } /** @@ -63,19 +56,14 @@ namespace sopho auto* device = m_gpu->device(); - // 2. Map the transfer buffer and copy data into it. - void* dst = SDL_MapGPUTransferBuffer(device, m_transfer_buffer, false); - if (!dst) + auto rst = m_transfer_buffer.submit(src_data); + if (!rst) { 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); + return std::unexpected(rst.error()); } - 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) @@ -97,7 +85,7 @@ namespace sopho } SDL_GPUTransferBufferLocation location{}; - location.transfer_buffer = m_transfer_buffer; + location.transfer_buffer = m_transfer_buffer.raw(); location.offset = 0; SDL_GPUBufferRegion region{}; @@ -117,4 +105,27 @@ namespace sopho return std::monostate{}; } + checkable BufferWrapper::Builder::build(GpuWrapper& gpu) + { + SDL_GPUBufferCreateInfo create_info{.usage = flag, .size = size}; + auto gpu_buffer = SDL_CreateGPUBuffer(gpu.device(), &create_info); + if (!gpu_buffer) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); + return std::unexpected(GpuError::CREATE_GPU_BUFFER_FAILED); + } + auto transfer_buffer = TransferBufferWrapper::Builder{} + .set_size(size) + .set_usage(SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD) + .set_usage_limit(-1) + .build(gpu); + if (!transfer_buffer) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); + gpu.release_buffer(gpu_buffer); + return std::unexpected(transfer_buffer.error()); + } + return BufferWrapper{gpu.shared_from_this(), gpu_buffer, (std::move(transfer_buffer.value())), size}; + } + } // namespace sopho diff --git a/sdl_wrapper/sdl_wrapper.gpu.cpp b/sdl_wrapper/src/sdl_wrapper.gpu.cpp similarity index 51% rename from sdl_wrapper/sdl_wrapper.gpu.cpp rename to sdl_wrapper/src/sdl_wrapper.gpu.cpp index be90b62..f23f665 100644 --- a/sdl_wrapper/sdl_wrapper.gpu.cpp +++ b/sdl_wrapper/src/sdl_wrapper.gpu.cpp @@ -3,44 +3,20 @@ // module; #include +#include #include "SDL3/SDL_gpu.h" #include "SDL3/SDL_log.h" module sdl_wrapper; import :gpu; import :render_procedural; +import :buffer; import :render_data; import :vertex_layout; +import :transfer_buffer; +import :render_data_impl; namespace sopho { - /** - * @brief Create a GPU buffer and its associated upload transfer buffer. - * - * @param flag Usage flags for the GPU buffer. - * @param size Size in bytes of the buffer to allocate. - * @return std::expected BufferWrapper containing the created GPU buffer, its transfer - * buffer, and the buffer size on success; `std::unexpected` with a `GpuError` on failure. - */ - std::expected GpuWrapper::create_buffer(SDL_GPUBufferUsageFlags flag, uint32_t size) - { - SDL_GPUBufferCreateInfo create_info{.usage = flag, .size = size}; - auto gpu_buffer = SDL_CreateGPUBuffer(device(), &create_info); - if (!gpu_buffer) - { - SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); - return std::unexpected(GpuError::CREATE_GPU_BUFFER_FAILED); - } - SDL_GPUTransferBufferCreateInfo transfer_info{}; - transfer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; - transfer_info.size = size; - transfer_info.props = 0; - auto transfer_buffer = SDL_CreateGPUTransferBuffer(device(), &transfer_info); - if (!transfer_buffer) - { - SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); - return std::unexpected(GpuError::CREATE_TRANSFER_BUFFER_FAILED); - } - return BufferWrapper{shared_from_this(), gpu_buffer, transfer_buffer, size}; - } + /** * @brief Create RenderData for a procedural render setup and given vertex count. * @@ -51,25 +27,31 @@ namespace sopho * * @param render_procedural Source procedural that provides the vertex layout. * @param vertex_count Number of vertices the allocated buffer must hold. + * @param index_count * @return RenderData RenderData containing the allocated vertex buffer, the vertex layout, and `vertex_count`. */ - std::expected GpuWrapper::create_data(const RenderProcedural& render_procedural, - uint32_t vertex_count) + checkable> GpuWrapper::create_data(const RenderProcedural& render_procedural, + std::uint32_t vertex_count, + std::uint32_t index_count) { auto size = render_procedural.vertex_layout().get_stride() * vertex_count; - auto vertex_buffer = create_buffer(SDL_GPU_BUFFERUSAGE_VERTEX, size); - auto index_buffer = create_buffer(SDL_GPU_BUFFERUSAGE_INDEX, 6 * sizeof(int)); + auto vertex_buffer = BufferWrapper::Builder{}.set_flag(SDL_GPU_BUFFERUSAGE_VERTEX).set_size(size).build(*this); if (!vertex_buffer) { return std::unexpected(vertex_buffer.error()); } + auto index_buffer = BufferWrapper::Builder{} + .set_flag(SDL_GPU_BUFFERUSAGE_INDEX) + .set_size(index_count * sizeof(int)) + .build(*this); if (!index_buffer) { return std::unexpected(index_buffer.error()); } - return RenderData{std::move(vertex_buffer.value()), std::move(index_buffer.value()), - render_procedural.vertex_layout(), vertex_count}; + return std::make_shared(std::move(vertex_buffer.value()), std::move(index_buffer.value()), + render_procedural.vertex_layout(), vertex_count, index_count); } + /** * @brief Create a RenderProcedural configured for the device's texture format. * diff --git a/sdl_wrapper/sdl_wrapper.render_procedural.cpp b/sdl_wrapper/src/sdl_wrapper.render_procedural.cpp similarity index 98% rename from sdl_wrapper/sdl_wrapper.render_procedural.cpp rename to sdl_wrapper/src/sdl_wrapper.render_procedural.cpp index 9558bdc..448163e 100644 --- a/sdl_wrapper/sdl_wrapper.render_procedural.cpp +++ b/sdl_wrapper/src/sdl_wrapper.render_procedural.cpp @@ -219,7 +219,7 @@ namespace sopho // 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); + auto shader_result = m_gpu->create_shader(code, SDL_GPU_SHADERSTAGE_VERTEX, 1, 0); if (!shader_result) { return std::unexpected(shader_result.error()); @@ -270,7 +270,9 @@ namespace sopho // 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); + auto reflect_result = reflect_fragment(source); + auto shader_result = m_gpu->create_shader(code, SDL_GPU_SHADERSTAGE_FRAGMENT, 0, reflect_result.sampler_count); + if (!shader_result) { return std::unexpected(shader_result.error()); diff --git a/sdl_wrapper/src/sdl_wrapper.texture.cpp b/sdl_wrapper/src/sdl_wrapper.texture.cpp new file mode 100644 index 0000000..24f6b7c --- /dev/null +++ b/sdl_wrapper/src/sdl_wrapper.texture.cpp @@ -0,0 +1,123 @@ +// sdl_wrapper.texture.cpp +// Created by sophomore on 11/25/25. +// +module; +#include +#include +#include "SDL3/SDL_gpu.h" +#include "SDL3/SDL_log.h" +module sdl_wrapper; +import :texture; +import :transfer_buffer; +import :gpu; +namespace sopho +{ + + void TextureWrapper::reset() noexcept + { + if (m_texture && m_gpu) + { + SDL_ReleaseGPUTexture(m_gpu->device(), m_texture); + m_texture = nullptr; + } + if (m_sampler && m_gpu) + { + SDL_ReleaseGPUSampler(m_gpu->device(), m_sampler); + m_sampler = nullptr; + } + } + + std::expected TextureWrapper::Builder::build(GpuWrapper& gpu) + { + SDL_GPUTextureCreateInfo create_info{.type = SDL_GPU_TEXTURETYPE_2D, + .format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, + .usage = SDL_GPU_TEXTUREUSAGE_SAMPLER, + .width = static_cast(img_data.width), + .height = static_cast(img_data.height), + .layer_count_or_depth = 1, + .num_levels = 1, + .sample_count = SDL_GPU_SAMPLECOUNT_1, + .props = 0}; + + auto* texture = SDL_CreateGPUTexture(gpu.device(), &create_info); + if (!texture) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to create GPU texture: %s", SDL_GetError()); + return std::unexpected(GpuError::CREATE_TEXTURE_FAILED); + } + + auto c_tb = + TransferBufferWrapper::Builder{} + .set_usage_limit(2) + .set_size(static_cast(img_data.width) * static_cast(img_data.height) * 4) + .set_usage(SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD) + .build(gpu); + + auto submit_result = c_tb.and_then([&](auto& tb) { return tb.submit(img_data.pixels.data()); }); + if (!submit_result) + { + SDL_ReleaseGPUTexture(gpu.device(), texture); + return std::unexpected{submit_result.error()}; + } + SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(gpu.device()); + if (!cmd) + { + SDL_Log("SDL_AcquireGPUCommandBuffer failed: %s", SDL_GetError()); + return std::unexpected{GpuError::ACQUIRE_COMMAND_BUFFER_FAILED}; + } + + SDL_GPUCopyPass* copy_pass = SDL_BeginGPUCopyPass(cmd); + if (!copy_pass) + { + SDL_SubmitGPUCommandBuffer(cmd); + SDL_ReleaseGPUTexture(gpu.device(), texture); + SDL_Log("SDL_BeginGPUCopyPass failed: %s", SDL_GetError()); + return std::unexpected{GpuError::BEGIN_COPY_PASS_FAILED}; + } + + SDL_GPUTextureTransferInfo src{}; + src.transfer_buffer = c_tb.value().raw(); + src.offset = 0; + src.pixels_per_row = 0; + src.rows_per_layer = 0; + + SDL_GPUTextureRegion dst{}; + dst.texture = texture; + dst.mip_level = 0; + dst.layer = 0; + dst.x = 0; + dst.y = 0; + dst.z = 0; + dst.w = static_cast(img_data.width); + dst.h = static_cast(img_data.height); + dst.d = 1; + + SDL_UploadToGPUTexture(copy_pass, &src, &dst, false); + + SDL_EndGPUCopyPass(copy_pass); + SDL_SubmitGPUCommandBuffer(cmd); + + SDL_GPUSamplerCreateInfo info{}; + info.min_filter = SDL_GPU_FILTER_LINEAR; + info.mag_filter = SDL_GPU_FILTER_LINEAR; + info.max_anisotropy = 1.f; + info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR; + info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_REPEAT; + info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_REPEAT; + info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_REPEAT; + info.enable_anisotropy = true; + info.enable_compare = false; + info.compare_op = SDL_GPU_COMPAREOP_ALWAYS; + info.props = 0; + + auto sampler = SDL_CreateGPUSampler(gpu.device(), &info); + if (!sampler) + { + SDL_Log("SDL_CreateGPUSampler failed: %s", SDL_GetError()); + SDL_ReleaseGPUTexture(gpu.device(), texture); + return std::unexpected{GpuError::CREATE_SAMPLER_FAILED}; + } + + return TextureWrapper{gpu.shared_from_this(), texture, sampler}; + } +} // namespace sopho diff --git a/sdl_wrapper/src/sdl_wrapper.transfer_buffer.cpp b/sdl_wrapper/src/sdl_wrapper.transfer_buffer.cpp new file mode 100644 index 0000000..b11e8e2 --- /dev/null +++ b/sdl_wrapper/src/sdl_wrapper.transfer_buffer.cpp @@ -0,0 +1,78 @@ +// sdl_wrapper.transfer_buffer.cpp +// Created by sophomore on 11/25/25. +// +module; +#include +#include +#include +#include + +#include "SDL3/SDL_gpu.h" +#include "SDL3/SDL_log.h" +module sdl_wrapper; +import :transfer_buffer; +import :gpu; +namespace sopho +{ + TransferBufferWrapper& TransferBufferWrapper::operator=(TransferBufferWrapper&& other) noexcept + { + if (this != &other) + { + reset(); + m_gpu = std::move(other.m_gpu); + m_transfer_buffer = other.m_transfer_buffer; + m_usage_limit = other.m_usage_limit; + m_size = other.m_size; + other.m_transfer_buffer = nullptr; + } + return *this; + } + void TransferBufferWrapper::reset() noexcept + { + if (m_transfer_buffer && m_gpu && m_gpu->device()) + { + SDL_ReleaseGPUTransferBuffer(m_gpu->device(), m_transfer_buffer); + m_transfer_buffer = nullptr; + } + } + + checkable TransferBufferWrapper::submit(const void* data_source) + { + assert(m_usage_limit != 0); + void* dst = SDL_MapGPUTransferBuffer(m_gpu->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, data_source, m_size); + SDL_UnmapGPUTransferBuffer(m_gpu->device(), m_transfer_buffer); + if (m_usage_limit != -1) + { + m_usage_limit--; + if (m_usage_limit == 0) + { + reset(); + } + } + return {}; + } + checkable TransferBufferWrapper::Builder::build(GpuWrapper& gpu) + { + SDL_GPUTransferBufferCreateInfo create_info{}; + create_info.usage = usage; + create_info.size = size; + + auto* transfer_buffer = SDL_CreateGPUTransferBuffer(gpu.device(), &create_info); + if (!transfer_buffer) + { + SDL_LogError(SDL_LOG_CATEGORY_GPU, "%s:%d %s", __FILE__, __LINE__, SDL_GetError()); + return std::unexpected(GpuError::CREATE_TRANSFER_BUFFER_FAILED); + } + + return TransferBufferWrapper{gpu.shared_from_this(), transfer_buffer, usage_limit, size}; + } +} // namespace sopho