Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
404 changes: 249 additions & 155 deletions main.cpp

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion sdl_wrapper/sdl_callback_implement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
135 changes: 95 additions & 40 deletions sdl_wrapper/sdl_wrapper.buffer.cpp
Original file line number Diff line number Diff line change
@@ -1,82 +1,137 @@
//
// sdl_wrapper.buffer.cpp
// Created by sophomore on 11/12/25.
//
module;
#include <cstdint>
#include <expected>
#include <memory>
#include <variant>

#include "SDL3/SDL_gpu.h"
#include "SDL3/SDL_log.h"
#include "SDL3/SDL_stdinc.h"
module sdl_wrapper;
import :buffer;
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
{
SDL_ReleaseGPUBuffer(m_gpu->data(), m_vertex_buffer);
m_vertex_buffer = nullptr;
if (!m_gpu)
{
return;
}

// 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(), 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<std::monostate, GpuError>
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);

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(), 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(), &transfer_info);

m_transfer_buffer_size = transfer_info.size;
}

auto data = SDL_MapGPUTransferBuffer(m_gpu->data(), m_transfer_buffer, false);
SDL_memcpy(data, p_data, p_size);
SDL_UnmapGPUTransferBuffer(m_gpu->data(), 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);
}

// TODO: Delay submit command in collect
auto command_buffer = SDL_AcquireGPUCommandBuffer(m_gpu->data());
auto copy_pass = SDL_BeginGPUCopyPass(command_buffer);
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);

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;
location.offset = 0;

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, &region, 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
47 changes: 36 additions & 11 deletions sdl_wrapper/sdl_wrapper.buffer.ixx
Original file line number Diff line number Diff line change
@@ -1,32 +1,57 @@
//
// sdl_wrapper.buffer.ixx
// Created by sophomore on 11/8/25.
//

module;

#include <expected>
#include <memory>
#include <variant>

#include "SDL3/SDL_gpu.h"

export module sdl_wrapper:buffer;
import :decl;

export namespace sopho
{
class BufferWrapper
{
std::shared_ptr<GpuWrapper> m_gpu{};
SDL_GPUBuffer* m_vertex_buffer{};
SDL_GPUTransferBuffer* m_transfer_buffer{};
uint32_t m_transfer_buffer_size{};
std::shared_ptr<GpuWrapper> 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<GpuWrapper> gpu, SDL_GPUBuffer* buffer, std::uint32_t size) noexcept :
m_gpu(std::move(gpu)), m_vertex_buffer(buffer), m_vertex_buffer_size(size)
{
}

public:
void upload(void* p_data, uint32_t p_size, uint32_t p_offset);
BufferWrapper(const BufferWrapper&) = delete;
BufferWrapper& operator=(const BufferWrapper&) = delete;
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<std::monostate, GpuError> 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
21 changes: 21 additions & 0 deletions sdl_wrapper/sdl_wrapper.decl.ixx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,27 @@ export module sdl_wrapper:decl;

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;
class GpuWrapper;
class BufferWrapper;
Expand Down
27 changes: 19 additions & 8 deletions sdl_wrapper/sdl_wrapper.gpu.cpp
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
//
// sdl_wrapper.gpu.cpp
// Created by wsqsy on 11/14/2025.
//
module;
#include <SDL3/SDL_gpu.h>
#include <expected>
#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<BufferWrapper, GpuError> 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;
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<PipelineWrapper, GpuError> 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}; });
}
PipelineWrapper GpuWrapper::create_pipeline() { return PipelineWrapper{shared_from_this()}; }
} // namespace sopho
Loading