style: aplicar fixes de clang-tidy (todo excepto uppercase-literal-suffix)
Corregidos ~2570 issues automáticamente con clang-tidy --fix-errors más ajustes manuales posteriores: - modernize: designated-initializers, trailing-return-type, use-auto, avoid-c-arrays (→ std::array<>), use-ranges, use-emplace, deprecated-headers, use-equals-default, pass-by-value, return-braced-init-list, use-default-member-init - readability: math-missing-parentheses, implicit-bool-conversion, braces-around-statements, isolate-declaration, use-std-min-max, identifier-naming, else-after-return, redundant-casting, convert-member-functions-to-static, make-member-function-const, static-accessed-through-instance - performance: avoid-endl, unnecessary-value-param, type-promotion, inefficient-vector-operation - dead code: XOR_KEY (orphan tras eliminar encryptData/decryptData), dead stores en engine.cpp y png_shape.cpp - NOLINT justificado en 10 funciones con alta complejidad cognitiva (initialize, render, main, processEvents, update×3, performDemoAction, randomizeOnDemoStart, renderDebugHUD, AppLogo::update) Compilación: gcc -Wall sin warnings. clang-tidy: 0 issues. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,19 @@
|
||||
#include "gpu_ball_buffer.hpp"
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
|
||||
#include <algorithm> // std::min
|
||||
#include <cstring> // memcpy
|
||||
|
||||
bool GpuBallBuffer::init(SDL_GPUDevice* device) {
|
||||
auto GpuBallBuffer::init(SDL_GPUDevice* device) -> bool {
|
||||
Uint32 buf_size = static_cast<Uint32>(MAX_BALLS) * sizeof(BallGPUData);
|
||||
|
||||
// GPU vertex buffer (instance-rate data read by the ball instanced shader)
|
||||
SDL_GPUBufferCreateInfo buf_info = {};
|
||||
buf_info.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
|
||||
buf_info.size = buf_size;
|
||||
buf_info.size = buf_size;
|
||||
gpu_buf_ = SDL_CreateGPUBuffer(device, &buf_info);
|
||||
if (!gpu_buf_) {
|
||||
if (gpu_buf_ == nullptr) {
|
||||
SDL_Log("GpuBallBuffer: GPU buffer creation failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -20,34 +21,45 @@ bool GpuBallBuffer::init(SDL_GPUDevice* device) {
|
||||
// Transfer buffer (upload staging, cycled every frame)
|
||||
SDL_GPUTransferBufferCreateInfo tb_info = {};
|
||||
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||
tb_info.size = buf_size;
|
||||
tb_info.size = buf_size;
|
||||
transfer_buf_ = SDL_CreateGPUTransferBuffer(device, &tb_info);
|
||||
if (!transfer_buf_) {
|
||||
if (transfer_buf_ == nullptr) {
|
||||
SDL_Log("GpuBallBuffer: transfer buffer creation failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_Log("GpuBallBuffer: initialized (capacity %d balls, %.1f MB VRAM)",
|
||||
MAX_BALLS, buf_size / (1024.0f * 1024.0f));
|
||||
MAX_BALLS,
|
||||
buf_size / (1024.0f * 1024.0f));
|
||||
return true;
|
||||
}
|
||||
|
||||
void GpuBallBuffer::destroy(SDL_GPUDevice* device) {
|
||||
if (!device) return;
|
||||
if (transfer_buf_) { SDL_ReleaseGPUTransferBuffer(device, transfer_buf_); transfer_buf_ = nullptr; }
|
||||
if (gpu_buf_) { SDL_ReleaseGPUBuffer(device, gpu_buf_); gpu_buf_ = nullptr; }
|
||||
if (device == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (transfer_buf_ != nullptr) {
|
||||
SDL_ReleaseGPUTransferBuffer(device, transfer_buf_);
|
||||
transfer_buf_ = nullptr;
|
||||
}
|
||||
if (gpu_buf_ != nullptr) {
|
||||
SDL_ReleaseGPUBuffer(device, gpu_buf_);
|
||||
gpu_buf_ = nullptr;
|
||||
}
|
||||
count_ = 0;
|
||||
}
|
||||
|
||||
bool GpuBallBuffer::upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd,
|
||||
const BallGPUData* data, int count) {
|
||||
if (!data || count <= 0) { count_ = 0; return false; }
|
||||
auto GpuBallBuffer::upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd, const BallGPUData* data, int count) -> bool {
|
||||
if ((data == nullptr) || count <= 0) {
|
||||
count_ = 0;
|
||||
return false;
|
||||
}
|
||||
count = std::min(count, MAX_BALLS);
|
||||
|
||||
Uint32 upload_size = static_cast<Uint32>(count) * sizeof(BallGPUData);
|
||||
|
||||
void* ptr = SDL_MapGPUTransferBuffer(device, transfer_buf_, true /* cycle */);
|
||||
if (!ptr) {
|
||||
if (ptr == nullptr) {
|
||||
SDL_Log("GpuBallBuffer: transfer buffer map failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -55,8 +67,8 @@ bool GpuBallBuffer::upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd,
|
||||
SDL_UnmapGPUTransferBuffer(device, transfer_buf_);
|
||||
|
||||
SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd);
|
||||
SDL_GPUTransferBufferLocation src = { transfer_buf_, 0 };
|
||||
SDL_GPUBufferRegion dst = { gpu_buf_, 0, upload_size };
|
||||
SDL_GPUTransferBufferLocation src = {transfer_buf_, 0};
|
||||
SDL_GPUBufferRegion dst = {gpu_buf_, 0, upload_size};
|
||||
SDL_UploadToGPUBuffer(copy, &src, &dst, true /* cycle */);
|
||||
SDL_EndGPUCopyPass(copy);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -12,9 +13,9 @@
|
||||
// r,g,b,a: RGBA in [0,1]
|
||||
// ---------------------------------------------------------------------------
|
||||
struct BallGPUData {
|
||||
float cx, cy; // NDC center
|
||||
float hw, hh; // NDC half-size (positive)
|
||||
float r, g, b, a; // RGBA color [0,1]
|
||||
float cx, cy; // NDC center
|
||||
float hw, hh; // NDC half-size (positive)
|
||||
float r, g, b, a; // RGBA color [0,1]
|
||||
};
|
||||
static_assert(sizeof(BallGPUData) == 32, "BallGPUData must be 32 bytes");
|
||||
|
||||
@@ -26,22 +27,21 @@ static_assert(sizeof(BallGPUData) == 32, "BallGPUData must be 32 bytes");
|
||||
// // Then in render pass: bind buffer, SDL_DrawGPUPrimitives(pass, 6, count, 0, 0)
|
||||
// ============================================================================
|
||||
class GpuBallBuffer {
|
||||
public:
|
||||
static constexpr int MAX_BALLS = 500000;
|
||||
public:
|
||||
static constexpr int MAX_BALLS = 500000;
|
||||
|
||||
bool init(SDL_GPUDevice* device);
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
bool init(SDL_GPUDevice* device);
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
|
||||
// Upload ball array to GPU via an internal copy pass.
|
||||
// count is clamped to MAX_BALLS. Returns false on error or empty input.
|
||||
bool upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd,
|
||||
const BallGPUData* data, int count);
|
||||
// Upload ball array to GPU via an internal copy pass.
|
||||
// count is clamped to MAX_BALLS. Returns false on error or empty input.
|
||||
bool upload(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd, const BallGPUData* data, int count);
|
||||
|
||||
SDL_GPUBuffer* buffer() const { return gpu_buf_; }
|
||||
int count() const { return count_; }
|
||||
SDL_GPUBuffer* buffer() const { return gpu_buf_; }
|
||||
int count() const { return count_; }
|
||||
|
||||
private:
|
||||
SDL_GPUBuffer* gpu_buf_ = nullptr;
|
||||
SDL_GPUTransferBuffer* transfer_buf_ = nullptr;
|
||||
int count_ = 0;
|
||||
private:
|
||||
SDL_GPUBuffer* gpu_buf_ = nullptr;
|
||||
SDL_GPUTransferBuffer* transfer_buf_ = nullptr;
|
||||
int count_ = 0;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#include "gpu_context.hpp"
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
bool GpuContext::init(SDL_Window* window) {
|
||||
auto GpuContext::init(SDL_Window* window) -> bool {
|
||||
window_ = window;
|
||||
|
||||
// Create GPU device: Metal on Apple, Vulkan elsewhere
|
||||
@@ -13,15 +14,15 @@ bool GpuContext::init(SDL_Window* window) {
|
||||
SDL_GPUShaderFormat preferred = SDL_GPU_SHADERFORMAT_SPIRV;
|
||||
#endif
|
||||
device_ = SDL_CreateGPUDevice(preferred, false, nullptr);
|
||||
if (!device_) {
|
||||
std::cerr << "GpuContext: SDL_CreateGPUDevice failed: " << SDL_GetError() << std::endl;
|
||||
if (device_ == nullptr) {
|
||||
std::cerr << "GpuContext: SDL_CreateGPUDevice failed: " << SDL_GetError() << '\n';
|
||||
return false;
|
||||
}
|
||||
std::cout << "GpuContext: driver = " << SDL_GetGPUDeviceDriver(device_) << std::endl;
|
||||
std::cout << "GpuContext: driver = " << SDL_GetGPUDeviceDriver(device_) << '\n';
|
||||
|
||||
// Claim the window so the GPU device owns its swapchain
|
||||
if (!SDL_ClaimWindowForGPUDevice(device_, window_)) {
|
||||
std::cerr << "GpuContext: SDL_ClaimWindowForGPUDevice failed: " << SDL_GetError() << std::endl;
|
||||
std::cerr << "GpuContext: SDL_ClaimWindowForGPUDevice failed: " << SDL_GetError() << '\n';
|
||||
SDL_DestroyGPUDevice(device_);
|
||||
device_ = nullptr;
|
||||
return false;
|
||||
@@ -29,17 +30,15 @@ bool GpuContext::init(SDL_Window* window) {
|
||||
|
||||
// Query swapchain format (Metal: typically B8G8R8A8_UNORM or R8G8B8A8_UNORM)
|
||||
swapchain_format_ = SDL_GetGPUSwapchainTextureFormat(device_, window_);
|
||||
std::cout << "GpuContext: swapchain format = " << static_cast<int>(swapchain_format_) << std::endl;
|
||||
std::cout << "GpuContext: swapchain format = " << static_cast<int>(swapchain_format_) << '\n';
|
||||
|
||||
// Default: VSync ON
|
||||
SDL_SetGPUSwapchainParameters(device_, window_,
|
||||
SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
|
||||
SDL_GPU_PRESENTMODE_VSYNC);
|
||||
SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, SDL_GPU_PRESENTMODE_VSYNC);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GpuContext::destroy() {
|
||||
if (device_) {
|
||||
if (device_ != nullptr) {
|
||||
SDL_WaitForGPUIdle(device_);
|
||||
SDL_ReleaseWindowFromGPUDevice(device_, window_);
|
||||
SDL_DestroyGPUDevice(device_);
|
||||
@@ -48,16 +47,17 @@ void GpuContext::destroy() {
|
||||
window_ = nullptr;
|
||||
}
|
||||
|
||||
SDL_GPUCommandBuffer* GpuContext::acquireCommandBuffer() {
|
||||
auto GpuContext::acquireCommandBuffer() -> SDL_GPUCommandBuffer* {
|
||||
SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device_);
|
||||
if (!cmd) {
|
||||
if (cmd == nullptr) {
|
||||
SDL_Log("GpuContext: SDL_AcquireGPUCommandBuffer failed: %s", SDL_GetError());
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
SDL_GPUTexture* GpuContext::acquireSwapchainTexture(SDL_GPUCommandBuffer* cmd_buf,
|
||||
Uint32* out_w, Uint32* out_h) {
|
||||
auto GpuContext::acquireSwapchainTexture(SDL_GPUCommandBuffer* cmd_buf,
|
||||
Uint32* out_w,
|
||||
Uint32* out_h) -> SDL_GPUTexture* {
|
||||
SDL_GPUTexture* tex = nullptr;
|
||||
if (!SDL_AcquireGPUSwapchainTexture(cmd_buf, window_, &tex, out_w, out_h)) {
|
||||
SDL_Log("GpuContext: SDL_AcquireGPUSwapchainTexture failed: %s", SDL_GetError());
|
||||
@@ -71,10 +71,8 @@ void GpuContext::submit(SDL_GPUCommandBuffer* cmd_buf) {
|
||||
SDL_SubmitGPUCommandBuffer(cmd_buf);
|
||||
}
|
||||
|
||||
bool GpuContext::setVSync(bool enabled) {
|
||||
auto GpuContext::setVSync(bool enabled) -> bool {
|
||||
SDL_GPUPresentMode mode = enabled ? SDL_GPU_PRESENTMODE_VSYNC
|
||||
: SDL_GPU_PRESENTMODE_IMMEDIATE;
|
||||
return SDL_SetGPUSwapchainParameters(device_, window_,
|
||||
SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
|
||||
mode);
|
||||
return SDL_SetGPUSwapchainParameters(device_, window_, SDL_GPU_SWAPCHAINCOMPOSITION_SDR, mode);
|
||||
}
|
||||
|
||||
@@ -8,26 +8,27 @@
|
||||
// Replaces SDL_Renderer as the main rendering backend.
|
||||
// ============================================================================
|
||||
class GpuContext {
|
||||
public:
|
||||
bool init(SDL_Window* window);
|
||||
void destroy();
|
||||
public:
|
||||
bool init(SDL_Window* window);
|
||||
void destroy();
|
||||
|
||||
SDL_GPUDevice* device() const { return device_; }
|
||||
SDL_Window* window() const { return window_; }
|
||||
SDL_GPUTextureFormat swapchainFormat() const { return swapchain_format_; }
|
||||
SDL_GPUDevice* device() const { return device_; }
|
||||
SDL_Window* window() const { return window_; }
|
||||
SDL_GPUTextureFormat swapchainFormat() const { return swapchain_format_; }
|
||||
|
||||
// Per-frame helpers
|
||||
SDL_GPUCommandBuffer* acquireCommandBuffer();
|
||||
// Returns nullptr if window is minimized (swapchain not available).
|
||||
SDL_GPUTexture* acquireSwapchainTexture(SDL_GPUCommandBuffer* cmd_buf,
|
||||
Uint32* out_w, Uint32* out_h);
|
||||
void submit(SDL_GPUCommandBuffer* cmd_buf);
|
||||
// Per-frame helpers
|
||||
SDL_GPUCommandBuffer* acquireCommandBuffer();
|
||||
// Returns nullptr if window is minimized (swapchain not available).
|
||||
SDL_GPUTexture* acquireSwapchainTexture(SDL_GPUCommandBuffer* cmd_buf,
|
||||
Uint32* out_w,
|
||||
Uint32* out_h);
|
||||
static void submit(SDL_GPUCommandBuffer* cmd_buf);
|
||||
|
||||
// VSync control (call after init)
|
||||
bool setVSync(bool enabled);
|
||||
// VSync control (call after init)
|
||||
bool setVSync(bool enabled);
|
||||
|
||||
private:
|
||||
SDL_GPUDevice* device_ = nullptr;
|
||||
SDL_Window* window_ = nullptr;
|
||||
SDL_GPUTextureFormat swapchain_format_ = SDL_GPU_TEXTUREFORMAT_INVALID;
|
||||
private:
|
||||
SDL_GPUDevice* device_ = nullptr;
|
||||
SDL_Window* window_ = nullptr;
|
||||
SDL_GPUTextureFormat swapchain_format_ = SDL_GPU_TEXTUREFORMAT_INVALID;
|
||||
};
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
#include "gpu_pipeline.hpp"
|
||||
#include "gpu_sprite_batch.hpp" // for GpuVertex layout
|
||||
#include "gpu_ball_buffer.hpp" // for BallGPUData layout
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
|
||||
#include <array> // for std::array
|
||||
#include <cstddef> // offsetof
|
||||
#include <cstring> // strlen
|
||||
|
||||
#include "gpu_ball_buffer.hpp" // for BallGPUData layout
|
||||
#include "gpu_sprite_batch.hpp" // for GpuVertex layout
|
||||
|
||||
#ifndef __APPLE__
|
||||
// Generated at build time by CMake + glslc (see cmake/spv_to_header.cmake)
|
||||
#include "sprite_vert_spv.h"
|
||||
#include "sprite_frag_spv.h"
|
||||
#include "postfx_vert_spv.h"
|
||||
#include "postfx_frag_spv.h"
|
||||
#include "ball_vert_spv.h"
|
||||
#include "postfx_frag_spv.h"
|
||||
#include "postfx_vert_spv.h"
|
||||
#include "sprite_frag_spv.h"
|
||||
#include "sprite_vert_spv.h"
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
@@ -198,15 +201,15 @@ vertex BallVOut ball_instanced_vs(BallInstance inst [[stage_in]],
|
||||
return out;
|
||||
}
|
||||
)";
|
||||
#endif // __APPLE__
|
||||
#endif // __APPLE__
|
||||
|
||||
// ============================================================================
|
||||
// GpuPipeline implementation
|
||||
// ============================================================================
|
||||
|
||||
bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
SDL_GPUTextureFormat target_format,
|
||||
SDL_GPUTextureFormat offscreen_format) {
|
||||
auto GpuPipeline::init(SDL_GPUDevice* device,
|
||||
SDL_GPUTextureFormat target_format,
|
||||
SDL_GPUTextureFormat offscreen_format) -> bool {
|
||||
SDL_GPUShaderFormat supported = SDL_GetGPUShaderFormats(device);
|
||||
#ifdef __APPLE__
|
||||
if (!(supported & SDL_GPU_SHADERFORMAT_MSL)) {
|
||||
@@ -214,7 +217,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (!(supported & SDL_GPU_SHADERFORMAT_SPIRV)) {
|
||||
if ((supported & SDL_GPU_SHADERFORMAT_SPIRV) == 0u) {
|
||||
SDL_Log("GpuPipeline: SPIRV not supported (format mask=%u)", supported);
|
||||
return false;
|
||||
}
|
||||
@@ -224,81 +227,81 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
// Sprite pipeline
|
||||
// ----------------------------------------------------------------
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* sprite_vert = createShader(device, kSpriteVertMSL, "sprite_vs",
|
||||
SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* sprite_frag = createShader(device, kSpriteFragMSL, "sprite_fs",
|
||||
SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
SDL_GPUShader* sprite_vert = createShader(device, kSpriteVertMSL, "sprite_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* sprite_frag = createShader(device, kSpriteFragMSL, "sprite_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
#else
|
||||
SDL_GPUShader* sprite_vert = createShaderSPIRV(device, ksprite_vert_spv, ksprite_vert_spv_size,
|
||||
"main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* sprite_frag = createShaderSPIRV(device, ksprite_frag_spv, ksprite_frag_spv_size,
|
||||
"main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
SDL_GPUShader* sprite_vert = createShaderSPIRV(device, ksprite_vert_spv, ksprite_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* sprite_frag = createShaderSPIRV(device, ksprite_frag_spv, ksprite_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
#endif
|
||||
if (!sprite_vert || !sprite_frag) {
|
||||
if ((sprite_vert == nullptr) || (sprite_frag == nullptr)) {
|
||||
SDL_Log("GpuPipeline: failed to create sprite shaders");
|
||||
if (sprite_vert) SDL_ReleaseGPUShader(device, sprite_vert);
|
||||
if (sprite_frag) SDL_ReleaseGPUShader(device, sprite_frag);
|
||||
if (sprite_vert != nullptr) {
|
||||
SDL_ReleaseGPUShader(device, sprite_vert);
|
||||
}
|
||||
if (sprite_frag != nullptr) {
|
||||
SDL_ReleaseGPUShader(device, sprite_frag);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vertex input: GpuVertex layout
|
||||
SDL_GPUVertexBufferDescription vb_desc = {};
|
||||
vb_desc.slot = 0;
|
||||
vb_desc.pitch = sizeof(GpuVertex);
|
||||
vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
|
||||
vb_desc.slot = 0;
|
||||
vb_desc.pitch = sizeof(GpuVertex);
|
||||
vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
|
||||
vb_desc.instance_step_rate = 0;
|
||||
|
||||
SDL_GPUVertexAttribute attrs[3] = {};
|
||||
attrs[0].location = 0;
|
||||
std::array<SDL_GPUVertexAttribute, 3> attrs = {};
|
||||
attrs[0].location = 0;
|
||||
attrs[0].buffer_slot = 0;
|
||||
attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
attrs[0].offset = static_cast<Uint32>(offsetof(GpuVertex, x));
|
||||
attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
attrs[0].offset = static_cast<Uint32>(offsetof(GpuVertex, x));
|
||||
|
||||
attrs[1].location = 1;
|
||||
attrs[1].location = 1;
|
||||
attrs[1].buffer_slot = 0;
|
||||
attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
attrs[1].offset = static_cast<Uint32>(offsetof(GpuVertex, u));
|
||||
attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
attrs[1].offset = static_cast<Uint32>(offsetof(GpuVertex, u));
|
||||
|
||||
attrs[2].location = 2;
|
||||
attrs[2].location = 2;
|
||||
attrs[2].buffer_slot = 0;
|
||||
attrs[2].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
|
||||
attrs[2].offset = static_cast<Uint32>(offsetof(GpuVertex, r));
|
||||
attrs[2].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
|
||||
attrs[2].offset = static_cast<Uint32>(offsetof(GpuVertex, r));
|
||||
|
||||
SDL_GPUVertexInputState vertex_input = {};
|
||||
vertex_input.vertex_buffer_descriptions = &vb_desc;
|
||||
vertex_input.num_vertex_buffers = 1;
|
||||
vertex_input.vertex_attributes = attrs;
|
||||
vertex_input.num_vertex_attributes = 3;
|
||||
vertex_input.num_vertex_buffers = 1;
|
||||
vertex_input.vertex_attributes = attrs.data();
|
||||
vertex_input.num_vertex_attributes = 3;
|
||||
|
||||
// Alpha blend state (SRC_ALPHA, ONE_MINUS_SRC_ALPHA)
|
||||
SDL_GPUColorTargetBlendState blend = {};
|
||||
blend.enable_blend = true;
|
||||
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_ONE;
|
||||
blend.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
blend.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||
blend.enable_color_write_mask = false; // write all channels
|
||||
blend.enable_blend = true;
|
||||
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_ONE;
|
||||
blend.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
blend.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||
blend.enable_color_write_mask = false; // write all channels
|
||||
|
||||
SDL_GPUColorTargetDescription color_target_desc = {};
|
||||
color_target_desc.format = offscreen_format;
|
||||
color_target_desc.format = offscreen_format;
|
||||
color_target_desc.blend_state = blend;
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo sprite_pipe_info = {};
|
||||
sprite_pipe_info.vertex_shader = sprite_vert;
|
||||
sprite_pipe_info.fragment_shader = sprite_frag;
|
||||
sprite_pipe_info.vertex_shader = sprite_vert;
|
||||
sprite_pipe_info.fragment_shader = sprite_frag;
|
||||
sprite_pipe_info.vertex_input_state = vertex_input;
|
||||
sprite_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
sprite_pipe_info.target_info.num_color_targets = 1;
|
||||
sprite_pipe_info.target_info.color_target_descriptions = &color_target_desc;
|
||||
sprite_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
sprite_pipe_info.target_info.num_color_targets = 1;
|
||||
sprite_pipe_info.target_info.color_target_descriptions = &color_target_desc;
|
||||
|
||||
sprite_pipeline_ = SDL_CreateGPUGraphicsPipeline(device, &sprite_pipe_info);
|
||||
|
||||
SDL_ReleaseGPUShader(device, sprite_vert);
|
||||
SDL_ReleaseGPUShader(device, sprite_frag);
|
||||
|
||||
if (!sprite_pipeline_) {
|
||||
if (sprite_pipeline_ == nullptr) {
|
||||
SDL_Log("GpuPipeline: sprite pipeline creation failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -310,59 +313,59 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
// Targets: offscreen (same as sprite pipeline)
|
||||
// ----------------------------------------------------------------
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* ball_vert = createShader(device, kBallInstancedVertMSL, "ball_instanced_vs",
|
||||
SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* ball_frag = createShader(device, kSpriteFragMSL, "sprite_fs",
|
||||
SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
SDL_GPUShader* ball_vert = createShader(device, kBallInstancedVertMSL, "ball_instanced_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* ball_frag = createShader(device, kSpriteFragMSL, "sprite_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
#else
|
||||
SDL_GPUShader* ball_vert = createShaderSPIRV(device, kball_vert_spv, kball_vert_spv_size,
|
||||
"main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* ball_frag = createShaderSPIRV(device, ksprite_frag_spv, ksprite_frag_spv_size,
|
||||
"main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
SDL_GPUShader* ball_vert = createShaderSPIRV(device, kball_vert_spv, kball_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* ball_frag = createShaderSPIRV(device, ksprite_frag_spv, ksprite_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0);
|
||||
#endif
|
||||
if (!ball_vert || !ball_frag) {
|
||||
if ((ball_vert == nullptr) || (ball_frag == nullptr)) {
|
||||
SDL_Log("GpuPipeline: failed to create ball instanced shaders");
|
||||
if (ball_vert) SDL_ReleaseGPUShader(device, ball_vert);
|
||||
if (ball_frag) SDL_ReleaseGPUShader(device, ball_frag);
|
||||
if (ball_vert != nullptr) {
|
||||
SDL_ReleaseGPUShader(device, ball_vert);
|
||||
}
|
||||
if (ball_frag != nullptr) {
|
||||
SDL_ReleaseGPUShader(device, ball_frag);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vertex input: BallGPUData as per-instance data (step rate = 1 instance)
|
||||
SDL_GPUVertexBufferDescription ball_vb_desc = {};
|
||||
ball_vb_desc.slot = 0;
|
||||
ball_vb_desc.pitch = sizeof(BallGPUData);
|
||||
ball_vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE;
|
||||
ball_vb_desc.slot = 0;
|
||||
ball_vb_desc.pitch = sizeof(BallGPUData);
|
||||
ball_vb_desc.input_rate = SDL_GPU_VERTEXINPUTRATE_INSTANCE;
|
||||
ball_vb_desc.instance_step_rate = 1;
|
||||
|
||||
SDL_GPUVertexAttribute ball_attrs[3] = {};
|
||||
std::array<SDL_GPUVertexAttribute, 3> ball_attrs = {};
|
||||
// attr 0: center (float2) at offset 0
|
||||
ball_attrs[0].location = 0;
|
||||
ball_attrs[0].location = 0;
|
||||
ball_attrs[0].buffer_slot = 0;
|
||||
ball_attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
ball_attrs[0].offset = static_cast<Uint32>(offsetof(BallGPUData, cx));
|
||||
ball_attrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
ball_attrs[0].offset = static_cast<Uint32>(offsetof(BallGPUData, cx));
|
||||
// attr 1: half-size (float2) at offset 8
|
||||
ball_attrs[1].location = 1;
|
||||
ball_attrs[1].location = 1;
|
||||
ball_attrs[1].buffer_slot = 0;
|
||||
ball_attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
ball_attrs[1].offset = static_cast<Uint32>(offsetof(BallGPUData, hw));
|
||||
ball_attrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
|
||||
ball_attrs[1].offset = static_cast<Uint32>(offsetof(BallGPUData, hw));
|
||||
// attr 2: color (float4) at offset 16
|
||||
ball_attrs[2].location = 2;
|
||||
ball_attrs[2].location = 2;
|
||||
ball_attrs[2].buffer_slot = 0;
|
||||
ball_attrs[2].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
|
||||
ball_attrs[2].offset = static_cast<Uint32>(offsetof(BallGPUData, r));
|
||||
ball_attrs[2].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4;
|
||||
ball_attrs[2].offset = static_cast<Uint32>(offsetof(BallGPUData, r));
|
||||
|
||||
SDL_GPUVertexInputState ball_vertex_input = {};
|
||||
ball_vertex_input.vertex_buffer_descriptions = &ball_vb_desc;
|
||||
ball_vertex_input.num_vertex_buffers = 1;
|
||||
ball_vertex_input.vertex_attributes = ball_attrs;
|
||||
ball_vertex_input.num_vertex_attributes = 3;
|
||||
ball_vertex_input.num_vertex_buffers = 1;
|
||||
ball_vertex_input.vertex_attributes = ball_attrs.data();
|
||||
ball_vertex_input.num_vertex_attributes = 3;
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo ball_pipe_info = {};
|
||||
ball_pipe_info.vertex_shader = ball_vert;
|
||||
ball_pipe_info.fragment_shader = ball_frag;
|
||||
ball_pipe_info.vertex_shader = ball_vert;
|
||||
ball_pipe_info.fragment_shader = ball_frag;
|
||||
ball_pipe_info.vertex_input_state = ball_vertex_input;
|
||||
ball_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
ball_pipe_info.target_info.num_color_targets = 1;
|
||||
ball_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
ball_pipe_info.target_info.num_color_targets = 1;
|
||||
ball_pipe_info.target_info.color_target_descriptions = &color_target_desc;
|
||||
|
||||
ball_pipeline_ = SDL_CreateGPUGraphicsPipeline(device, &ball_pipe_info);
|
||||
@@ -370,7 +373,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
SDL_ReleaseGPUShader(device, ball_vert);
|
||||
SDL_ReleaseGPUShader(device, ball_frag);
|
||||
|
||||
if (!ball_pipeline_) {
|
||||
if (ball_pipeline_ == nullptr) {
|
||||
SDL_Log("GpuPipeline: ball instanced pipeline creation failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -389,20 +392,20 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
// PostFX pipeline
|
||||
// ----------------------------------------------------------------
|
||||
#ifdef __APPLE__
|
||||
SDL_GPUShader* postfx_vert = createShader(device, kPostFXVertMSL, "postfx_vs",
|
||||
SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* postfx_frag = createShader(device, kPostFXFragMSL, "postfx_fs",
|
||||
SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
SDL_GPUShader* postfx_vert = createShader(device, kPostFXVertMSL, "postfx_vs", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* postfx_frag = createShader(device, kPostFXFragMSL, "postfx_fs", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#else
|
||||
SDL_GPUShader* postfx_vert = createShaderSPIRV(device, kpostfx_vert_spv, kpostfx_vert_spv_size,
|
||||
"main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* postfx_frag = createShaderSPIRV(device, kpostfx_frag_spv, kpostfx_frag_spv_size,
|
||||
"main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
SDL_GPUShader* postfx_vert = createShaderSPIRV(device, kpostfx_vert_spv, kpostfx_vert_spv_size, "main", SDL_GPU_SHADERSTAGE_VERTEX, 0, 0);
|
||||
SDL_GPUShader* postfx_frag = createShaderSPIRV(device, kpostfx_frag_spv, kpostfx_frag_spv_size, "main", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1);
|
||||
#endif
|
||||
if (!postfx_vert || !postfx_frag) {
|
||||
if ((postfx_vert == nullptr) || (postfx_frag == nullptr)) {
|
||||
SDL_Log("GpuPipeline: failed to create postfx shaders");
|
||||
if (postfx_vert) SDL_ReleaseGPUShader(device, postfx_vert);
|
||||
if (postfx_frag) SDL_ReleaseGPUShader(device, postfx_frag);
|
||||
if (postfx_vert != nullptr) {
|
||||
SDL_ReleaseGPUShader(device, postfx_vert);
|
||||
}
|
||||
if (postfx_frag != nullptr) {
|
||||
SDL_ReleaseGPUShader(device, postfx_frag);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -412,17 +415,17 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
no_blend.enable_color_write_mask = false;
|
||||
|
||||
SDL_GPUColorTargetDescription postfx_target_desc = {};
|
||||
postfx_target_desc.format = target_format;
|
||||
postfx_target_desc.format = target_format;
|
||||
postfx_target_desc.blend_state = no_blend;
|
||||
|
||||
SDL_GPUVertexInputState no_input = {};
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo postfx_pipe_info = {};
|
||||
postfx_pipe_info.vertex_shader = postfx_vert;
|
||||
postfx_pipe_info.fragment_shader = postfx_frag;
|
||||
postfx_pipe_info.vertex_shader = postfx_vert;
|
||||
postfx_pipe_info.fragment_shader = postfx_frag;
|
||||
postfx_pipe_info.vertex_input_state = no_input;
|
||||
postfx_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
postfx_pipe_info.target_info.num_color_targets = 1;
|
||||
postfx_pipe_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
postfx_pipe_info.target_info.num_color_targets = 1;
|
||||
postfx_pipe_info.target_info.color_target_descriptions = &postfx_target_desc;
|
||||
|
||||
postfx_pipeline_ = SDL_CreateGPUGraphicsPipeline(device, &postfx_pipe_info);
|
||||
@@ -430,7 +433,7 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
SDL_ReleaseGPUShader(device, postfx_vert);
|
||||
SDL_ReleaseGPUShader(device, postfx_frag);
|
||||
|
||||
if (!postfx_pipeline_) {
|
||||
if (postfx_pipeline_ == nullptr) {
|
||||
SDL_Log("GpuPipeline: postfx pipeline creation failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -440,55 +443,65 @@ bool GpuPipeline::init(SDL_GPUDevice* device,
|
||||
}
|
||||
|
||||
void GpuPipeline::destroy(SDL_GPUDevice* device) {
|
||||
if (sprite_pipeline_) { SDL_ReleaseGPUGraphicsPipeline(device, sprite_pipeline_); sprite_pipeline_ = nullptr; }
|
||||
if (ball_pipeline_) { SDL_ReleaseGPUGraphicsPipeline(device, ball_pipeline_); ball_pipeline_ = nullptr; }
|
||||
if (postfx_pipeline_) { SDL_ReleaseGPUGraphicsPipeline(device, postfx_pipeline_); postfx_pipeline_ = nullptr; }
|
||||
if (sprite_pipeline_ != nullptr) {
|
||||
SDL_ReleaseGPUGraphicsPipeline(device, sprite_pipeline_);
|
||||
sprite_pipeline_ = nullptr;
|
||||
}
|
||||
if (ball_pipeline_ != nullptr) {
|
||||
SDL_ReleaseGPUGraphicsPipeline(device, ball_pipeline_);
|
||||
ball_pipeline_ = nullptr;
|
||||
}
|
||||
if (postfx_pipeline_ != nullptr) {
|
||||
SDL_ReleaseGPUGraphicsPipeline(device, postfx_pipeline_);
|
||||
postfx_pipeline_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_GPUShader* GpuPipeline::createShaderSPIRV(SDL_GPUDevice* device,
|
||||
const uint8_t* spv_code,
|
||||
size_t spv_size,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers,
|
||||
Uint32 num_storage_buffers) {
|
||||
auto GpuPipeline::createShaderSPIRV(SDL_GPUDevice* device,
|
||||
const uint8_t* spv_code,
|
||||
size_t spv_size,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers,
|
||||
Uint32 num_storage_buffers) -> SDL_GPUShader* {
|
||||
SDL_GPUShaderCreateInfo info = {};
|
||||
info.code = spv_code;
|
||||
info.code_size = spv_size;
|
||||
info.entrypoint = entrypoint;
|
||||
info.format = SDL_GPU_SHADERFORMAT_SPIRV;
|
||||
info.stage = stage;
|
||||
info.num_samplers = num_samplers;
|
||||
info.code = spv_code;
|
||||
info.code_size = spv_size;
|
||||
info.entrypoint = entrypoint;
|
||||
info.format = SDL_GPU_SHADERFORMAT_SPIRV;
|
||||
info.stage = stage;
|
||||
info.num_samplers = num_samplers;
|
||||
info.num_storage_textures = 0;
|
||||
info.num_storage_buffers = num_storage_buffers;
|
||||
info.num_uniform_buffers = num_uniform_buffers;
|
||||
info.num_storage_buffers = num_storage_buffers;
|
||||
info.num_uniform_buffers = num_uniform_buffers;
|
||||
SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info);
|
||||
if (!shader)
|
||||
if (shader == nullptr) {
|
||||
SDL_Log("GpuPipeline: SPIRV shader '%s' failed: %s", entrypoint, SDL_GetError());
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
|
||||
SDL_GPUShader* GpuPipeline::createShader(SDL_GPUDevice* device,
|
||||
const char* msl_source,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers,
|
||||
Uint32 num_storage_buffers) {
|
||||
auto GpuPipeline::createShader(SDL_GPUDevice* device,
|
||||
const char* msl_source,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers,
|
||||
Uint32 num_storage_buffers) -> SDL_GPUShader* {
|
||||
SDL_GPUShaderCreateInfo info = {};
|
||||
info.code = reinterpret_cast<const Uint8*>(msl_source);
|
||||
info.code_size = static_cast<size_t>(strlen(msl_source) + 1);
|
||||
info.entrypoint = entrypoint;
|
||||
info.format = SDL_GPU_SHADERFORMAT_MSL;
|
||||
info.stage = stage;
|
||||
info.num_samplers = num_samplers;
|
||||
info.code = reinterpret_cast<const Uint8*>(msl_source);
|
||||
info.code_size = static_cast<size_t>(strlen(msl_source) + 1);
|
||||
info.entrypoint = entrypoint;
|
||||
info.format = SDL_GPU_SHADERFORMAT_MSL;
|
||||
info.stage = stage;
|
||||
info.num_samplers = num_samplers;
|
||||
info.num_storage_textures = 0;
|
||||
info.num_storage_buffers = num_storage_buffers;
|
||||
info.num_uniform_buffers = num_uniform_buffers;
|
||||
info.num_storage_buffers = num_storage_buffers;
|
||||
info.num_uniform_buffers = num_uniform_buffers;
|
||||
|
||||
SDL_GPUShader* shader = SDL_CreateGPUShader(device, &info);
|
||||
if (!shader) {
|
||||
if (shader == nullptr) {
|
||||
SDL_Log("GpuPipeline: shader '%s' failed: %s", entrypoint, SDL_GetError());
|
||||
}
|
||||
return shader;
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
// MSL binding: constant PostFXUniforms& u [[buffer(0)]]
|
||||
// ============================================================================
|
||||
struct PostFXUniforms {
|
||||
float vignette_strength; // 0 = none, 0.8 = default subtle
|
||||
float chroma_strength; // 0 = off, 0.2 = default chromatic aberration
|
||||
float scanline_strength; // 0 = off, 1 = full scanlines
|
||||
float screen_height; // logical render target height (px), for resolution-independent scanlines
|
||||
float vignette_strength; // 0 = none, 0.8 = default subtle
|
||||
float chroma_strength; // 0 = off, 0.2 = default chromatic aberration
|
||||
float scanline_strength; // 0 = off, 1 = full scanlines
|
||||
float screen_height; // logical render target height (px), for resolution-independent scanlines
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
@@ -27,37 +27,37 @@ struct PostFXUniforms {
|
||||
// Accepts PostFXUniforms via fragment uniform buffer slot 0.
|
||||
// ============================================================================
|
||||
class GpuPipeline {
|
||||
public:
|
||||
// target_format: pass SDL_GetGPUSwapchainTextureFormat() result.
|
||||
// offscreen_format: format of the offscreen render target.
|
||||
bool init(SDL_GPUDevice* device,
|
||||
SDL_GPUTextureFormat target_format,
|
||||
SDL_GPUTextureFormat offscreen_format);
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
public:
|
||||
// target_format: pass SDL_GetGPUSwapchainTextureFormat() result.
|
||||
// offscreen_format: format of the offscreen render target.
|
||||
bool init(SDL_GPUDevice* device,
|
||||
SDL_GPUTextureFormat target_format,
|
||||
SDL_GPUTextureFormat offscreen_format);
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
|
||||
SDL_GPUGraphicsPipeline* spritePipeline() const { return sprite_pipeline_; }
|
||||
SDL_GPUGraphicsPipeline* ballPipeline() const { return ball_pipeline_; }
|
||||
SDL_GPUGraphicsPipeline* postfxPipeline() const { return postfx_pipeline_; }
|
||||
SDL_GPUGraphicsPipeline* spritePipeline() const { return sprite_pipeline_; }
|
||||
SDL_GPUGraphicsPipeline* ballPipeline() const { return ball_pipeline_; }
|
||||
SDL_GPUGraphicsPipeline* postfxPipeline() const { return postfx_pipeline_; }
|
||||
|
||||
private:
|
||||
SDL_GPUShader* createShader(SDL_GPUDevice* device,
|
||||
const char* msl_source,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers,
|
||||
Uint32 num_storage_buffers = 0);
|
||||
private:
|
||||
static SDL_GPUShader* createShader(SDL_GPUDevice* device,
|
||||
const char* msl_source,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers,
|
||||
Uint32 num_storage_buffers = 0);
|
||||
|
||||
SDL_GPUShader* createShaderSPIRV(SDL_GPUDevice* device,
|
||||
const uint8_t* spv_code,
|
||||
size_t spv_size,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers,
|
||||
Uint32 num_storage_buffers = 0);
|
||||
static SDL_GPUShader* createShaderSPIRV(SDL_GPUDevice* device,
|
||||
const uint8_t* spv_code,
|
||||
size_t spv_size,
|
||||
const char* entrypoint,
|
||||
SDL_GPUShaderStage stage,
|
||||
Uint32 num_samplers,
|
||||
Uint32 num_uniform_buffers,
|
||||
Uint32 num_storage_buffers = 0);
|
||||
|
||||
SDL_GPUGraphicsPipeline* sprite_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* ball_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* postfx_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* sprite_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* ball_pipeline_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* postfx_pipeline_ = nullptr;
|
||||
};
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
#include "gpu_sprite_batch.hpp"
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
|
||||
#include <cstring> // memcpy
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public interface
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
|
||||
auto GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) -> bool {
|
||||
max_sprites_ = max_sprites;
|
||||
// Pre-allocate GPU buffers large enough for (max_sprites_ + 2) quads.
|
||||
// The +2 reserves one slot for the background quad and one for the fullscreen overlay.
|
||||
Uint32 max_verts = static_cast<Uint32>(max_sprites_ + 2) * 4;
|
||||
Uint32 max_verts = static_cast<Uint32>(max_sprites_ + 2) * 4;
|
||||
Uint32 max_indices = static_cast<Uint32>(max_sprites_ + 2) * 6;
|
||||
|
||||
Uint32 vb_size = max_verts * sizeof(GpuVertex);
|
||||
Uint32 vb_size = max_verts * sizeof(GpuVertex);
|
||||
Uint32 ib_size = max_indices * sizeof(uint32_t);
|
||||
|
||||
// Vertex buffer
|
||||
SDL_GPUBufferCreateInfo vb_info = {};
|
||||
vb_info.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
|
||||
vb_info.size = vb_size;
|
||||
vb_info.size = vb_size;
|
||||
vertex_buf_ = SDL_CreateGPUBuffer(device, &vb_info);
|
||||
if (!vertex_buf_) {
|
||||
if (vertex_buf_ == nullptr) {
|
||||
SDL_Log("GpuSpriteBatch: vertex buffer creation failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -30,9 +31,9 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
|
||||
// Index buffer
|
||||
SDL_GPUBufferCreateInfo ib_info = {};
|
||||
ib_info.usage = SDL_GPU_BUFFERUSAGE_INDEX;
|
||||
ib_info.size = ib_size;
|
||||
ib_info.size = ib_size;
|
||||
index_buf_ = SDL_CreateGPUBuffer(device, &ib_info);
|
||||
if (!index_buf_) {
|
||||
if (index_buf_ == nullptr) {
|
||||
SDL_Log("GpuSpriteBatch: index buffer creation failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -41,16 +42,16 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
|
||||
SDL_GPUTransferBufferCreateInfo tb_info = {};
|
||||
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||
|
||||
tb_info.size = vb_size;
|
||||
tb_info.size = vb_size;
|
||||
vertex_transfer_ = SDL_CreateGPUTransferBuffer(device, &tb_info);
|
||||
if (!vertex_transfer_) {
|
||||
if (vertex_transfer_ == nullptr) {
|
||||
SDL_Log("GpuSpriteBatch: vertex transfer buffer failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
tb_info.size = ib_size;
|
||||
tb_info.size = ib_size;
|
||||
index_transfer_ = SDL_CreateGPUTransferBuffer(device, &tb_info);
|
||||
if (!index_transfer_) {
|
||||
if (index_transfer_ == nullptr) {
|
||||
SDL_Log("GpuSpriteBatch: index transfer buffer failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -61,67 +62,84 @@ bool GpuSpriteBatch::init(SDL_GPUDevice* device, int max_sprites) {
|
||||
}
|
||||
|
||||
void GpuSpriteBatch::destroy(SDL_GPUDevice* device) {
|
||||
if (!device) return;
|
||||
if (vertex_transfer_) { SDL_ReleaseGPUTransferBuffer(device, vertex_transfer_); vertex_transfer_ = nullptr; }
|
||||
if (index_transfer_) { SDL_ReleaseGPUTransferBuffer(device, index_transfer_); index_transfer_ = nullptr; }
|
||||
if (vertex_buf_) { SDL_ReleaseGPUBuffer(device, vertex_buf_); vertex_buf_ = nullptr; }
|
||||
if (index_buf_) { SDL_ReleaseGPUBuffer(device, index_buf_); index_buf_ = nullptr; }
|
||||
if (device == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (vertex_transfer_ != nullptr) {
|
||||
SDL_ReleaseGPUTransferBuffer(device, vertex_transfer_);
|
||||
vertex_transfer_ = nullptr;
|
||||
}
|
||||
if (index_transfer_ != nullptr) {
|
||||
SDL_ReleaseGPUTransferBuffer(device, index_transfer_);
|
||||
index_transfer_ = nullptr;
|
||||
}
|
||||
if (vertex_buf_ != nullptr) {
|
||||
SDL_ReleaseGPUBuffer(device, vertex_buf_);
|
||||
vertex_buf_ = nullptr;
|
||||
}
|
||||
if (index_buf_ != nullptr) {
|
||||
SDL_ReleaseGPUBuffer(device, index_buf_);
|
||||
index_buf_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void GpuSpriteBatch::beginFrame() {
|
||||
vertices_.clear();
|
||||
indices_.clear();
|
||||
bg_index_count_ = 0;
|
||||
sprite_index_offset_ = 0;
|
||||
sprite_index_count_ = 0;
|
||||
bg_index_count_ = 0;
|
||||
sprite_index_offset_ = 0;
|
||||
sprite_index_count_ = 0;
|
||||
overlay_index_offset_ = 0;
|
||||
overlay_index_count_ = 0;
|
||||
overlay_index_count_ = 0;
|
||||
}
|
||||
|
||||
void GpuSpriteBatch::addBackground(float screen_w, float screen_h,
|
||||
float top_r, float top_g, float top_b,
|
||||
float bot_r, float bot_g, float bot_b) {
|
||||
void GpuSpriteBatch::addBackground(float screen_w, float screen_h, float top_r, float top_g, float top_b, float bot_r, float bot_g, float bot_b) {
|
||||
// Background is the full screen quad, corners:
|
||||
// TL(-1, 1) TR(1, 1) → top color
|
||||
// BL(-1,-1) BR(1,-1) → bottom color
|
||||
// We push it as 4 separate vertices (different colors per row).
|
||||
uint32_t vi = static_cast<uint32_t>(vertices_.size());
|
||||
auto vi = static_cast<uint32_t>(vertices_.size());
|
||||
|
||||
// Top-left
|
||||
vertices_.push_back({ -1.0f, 1.0f, 0.0f, 0.0f, top_r, top_g, top_b, 1.0f });
|
||||
vertices_.push_back({-1.0f, 1.0f, 0.0f, 0.0f, top_r, top_g, top_b, 1.0f});
|
||||
// Top-right
|
||||
vertices_.push_back({ 1.0f, 1.0f, 1.0f, 0.0f, top_r, top_g, top_b, 1.0f });
|
||||
vertices_.push_back({1.0f, 1.0f, 1.0f, 0.0f, top_r, top_g, top_b, 1.0f});
|
||||
// Bottom-right
|
||||
vertices_.push_back({ 1.0f, -1.0f, 1.0f, 1.0f, bot_r, bot_g, bot_b, 1.0f });
|
||||
vertices_.push_back({1.0f, -1.0f, 1.0f, 1.0f, bot_r, bot_g, bot_b, 1.0f});
|
||||
// Bottom-left
|
||||
vertices_.push_back({ -1.0f, -1.0f, 0.0f, 1.0f, bot_r, bot_g, bot_b, 1.0f });
|
||||
vertices_.push_back({-1.0f, -1.0f, 0.0f, 1.0f, bot_r, bot_g, bot_b, 1.0f});
|
||||
|
||||
// Two triangles: TL-TR-BR, BR-BL-TL
|
||||
indices_.push_back(vi + 0); indices_.push_back(vi + 1); indices_.push_back(vi + 2);
|
||||
indices_.push_back(vi + 2); indices_.push_back(vi + 3); indices_.push_back(vi + 0);
|
||||
indices_.push_back(vi + 0);
|
||||
indices_.push_back(vi + 1);
|
||||
indices_.push_back(vi + 2);
|
||||
indices_.push_back(vi + 2);
|
||||
indices_.push_back(vi + 3);
|
||||
indices_.push_back(vi + 0);
|
||||
|
||||
bg_index_count_ = 6;
|
||||
bg_index_count_ = 6;
|
||||
sprite_index_offset_ = 6;
|
||||
|
||||
(void)screen_w; (void)screen_h; // unused — bg always covers full NDC
|
||||
(void)screen_w;
|
||||
(void)screen_h; // unused — bg always covers full NDC
|
||||
}
|
||||
|
||||
void GpuSpriteBatch::addSprite(float x, float y, float w, float h,
|
||||
float r, float g, float b, float a,
|
||||
float scale,
|
||||
float screen_w, float screen_h) {
|
||||
void GpuSpriteBatch::addSprite(float x, float y, float w, float h, float r, float g, float b, float a, float scale, float screen_w, float screen_h) {
|
||||
// Apply scale around the sprite centre
|
||||
float scaled_w = w * scale;
|
||||
float scaled_h = h * scale;
|
||||
float offset_x = (w - scaled_w) * 0.5f;
|
||||
float offset_y = (h - scaled_h) * 0.5f;
|
||||
float scaled_w = w * scale;
|
||||
float scaled_h = h * scale;
|
||||
float offset_x = (w - scaled_w) * 0.5f;
|
||||
float offset_y = (h - scaled_h) * 0.5f;
|
||||
|
||||
float px0 = x + offset_x;
|
||||
float py0 = y + offset_y;
|
||||
float px1 = px0 + scaled_w;
|
||||
float py1 = py0 + scaled_h;
|
||||
|
||||
float ndx0, ndy0, ndx1, ndy1;
|
||||
float ndx0;
|
||||
float ndy0;
|
||||
float ndx1;
|
||||
float ndy1;
|
||||
toNDC(px0, py0, screen_w, screen_h, ndx0, ndy0);
|
||||
toNDC(px1, py1, screen_w, screen_h, ndx1, ndy1);
|
||||
|
||||
@@ -133,42 +151,54 @@ void GpuSpriteBatch::addFullscreenOverlay() {
|
||||
// El overlay es un slot reservado fuera del espacio de max_sprites_, igual que el background.
|
||||
// Escribe directamente sin pasar por el guard de pushQuad().
|
||||
overlay_index_offset_ = static_cast<int>(indices_.size());
|
||||
uint32_t vi = static_cast<uint32_t>(vertices_.size());
|
||||
vertices_.push_back({ -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f });
|
||||
vertices_.push_back({ 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f });
|
||||
vertices_.push_back({ 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f });
|
||||
vertices_.push_back({ -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f });
|
||||
indices_.push_back(vi + 0); indices_.push_back(vi + 1); indices_.push_back(vi + 2);
|
||||
indices_.push_back(vi + 2); indices_.push_back(vi + 3); indices_.push_back(vi + 0);
|
||||
auto vi = static_cast<uint32_t>(vertices_.size());
|
||||
vertices_.push_back({-1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f});
|
||||
vertices_.push_back({1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f});
|
||||
vertices_.push_back({1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f});
|
||||
vertices_.push_back({-1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f});
|
||||
indices_.push_back(vi + 0);
|
||||
indices_.push_back(vi + 1);
|
||||
indices_.push_back(vi + 2);
|
||||
indices_.push_back(vi + 2);
|
||||
indices_.push_back(vi + 3);
|
||||
indices_.push_back(vi + 0);
|
||||
overlay_index_count_ = 6;
|
||||
}
|
||||
|
||||
bool GpuSpriteBatch::uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd_buf) {
|
||||
if (vertices_.empty()) return false;
|
||||
auto GpuSpriteBatch::uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd_buf) -> bool {
|
||||
if (vertices_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Uint32 vb_size = static_cast<Uint32>(vertices_.size() * sizeof(GpuVertex));
|
||||
Uint32 ib_size = static_cast<Uint32>(indices_.size() * sizeof(uint32_t));
|
||||
auto vb_size = static_cast<Uint32>(vertices_.size() * sizeof(GpuVertex));
|
||||
auto ib_size = static_cast<Uint32>(indices_.size() * sizeof(uint32_t));
|
||||
|
||||
// Map → write → unmap transfer buffers
|
||||
void* vp = SDL_MapGPUTransferBuffer(device, vertex_transfer_, true /* cycle */);
|
||||
if (!vp) { SDL_Log("GpuSpriteBatch: vertex map failed"); return false; }
|
||||
if (vp == nullptr) {
|
||||
SDL_Log("GpuSpriteBatch: vertex map failed");
|
||||
return false;
|
||||
}
|
||||
memcpy(vp, vertices_.data(), vb_size);
|
||||
SDL_UnmapGPUTransferBuffer(device, vertex_transfer_);
|
||||
|
||||
void* ip = SDL_MapGPUTransferBuffer(device, index_transfer_, true /* cycle */);
|
||||
if (!ip) { SDL_Log("GpuSpriteBatch: index map failed"); return false; }
|
||||
if (ip == nullptr) {
|
||||
SDL_Log("GpuSpriteBatch: index map failed");
|
||||
return false;
|
||||
}
|
||||
memcpy(ip, indices_.data(), ib_size);
|
||||
SDL_UnmapGPUTransferBuffer(device, index_transfer_);
|
||||
|
||||
// Upload via copy pass
|
||||
SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd_buf);
|
||||
|
||||
SDL_GPUTransferBufferLocation v_src = { vertex_transfer_, 0 };
|
||||
SDL_GPUBufferRegion v_dst = { vertex_buf_, 0, vb_size };
|
||||
SDL_GPUTransferBufferLocation v_src = {vertex_transfer_, 0};
|
||||
SDL_GPUBufferRegion v_dst = {vertex_buf_, 0, vb_size};
|
||||
SDL_UploadToGPUBuffer(copy, &v_src, &v_dst, true /* cycle */);
|
||||
|
||||
SDL_GPUTransferBufferLocation i_src = { index_transfer_, 0 };
|
||||
SDL_GPUBufferRegion i_dst = { index_buf_, 0, ib_size };
|
||||
SDL_GPUTransferBufferLocation i_src = {index_transfer_, 0};
|
||||
SDL_GPUBufferRegion i_dst = {index_buf_, 0, ib_size};
|
||||
SDL_UploadToGPUBuffer(copy, &i_src, &i_dst, true /* cycle */);
|
||||
|
||||
SDL_EndGPUCopyPass(copy);
|
||||
@@ -179,26 +209,28 @@ bool GpuSpriteBatch::uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cm
|
||||
// Private helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void GpuSpriteBatch::toNDC(float px, float py,
|
||||
float screen_w, float screen_h,
|
||||
float& ndx, float& ndy) const {
|
||||
void GpuSpriteBatch::toNDC(float px, float py, float screen_w, float screen_h, float& ndx, float& ndy) {
|
||||
ndx = (px / screen_w) * 2.0f - 1.0f;
|
||||
ndy = 1.0f - (py / screen_h) * 2.0f;
|
||||
}
|
||||
|
||||
void GpuSpriteBatch::pushQuad(float ndx0, float ndy0, float ndx1, float ndy1,
|
||||
float u0, float v0, float u1, float v1,
|
||||
float r, float g, float b, float a) {
|
||||
void GpuSpriteBatch::pushQuad(float ndx0, float ndy0, float ndx1, float ndy1, float u0, float v0, float u1, float v1, float r, float g, float b, float a) {
|
||||
// +1 reserva el slot del background que ya entró sin pasar por este guard.
|
||||
if (vertices_.size() + 4 > static_cast<size_t>(max_sprites_ + 1) * 4) return;
|
||||
uint32_t vi = static_cast<uint32_t>(vertices_.size());
|
||||
if (vertices_.size() + 4 > static_cast<size_t>(max_sprites_ + 1) * 4) {
|
||||
return;
|
||||
}
|
||||
auto vi = static_cast<uint32_t>(vertices_.size());
|
||||
|
||||
// TL, TR, BR, BL
|
||||
vertices_.push_back({ ndx0, ndy0, u0, v0, r, g, b, a });
|
||||
vertices_.push_back({ ndx1, ndy0, u1, v0, r, g, b, a });
|
||||
vertices_.push_back({ ndx1, ndy1, u1, v1, r, g, b, a });
|
||||
vertices_.push_back({ ndx0, ndy1, u0, v1, r, g, b, a });
|
||||
vertices_.push_back({ndx0, ndy0, u0, v0, r, g, b, a});
|
||||
vertices_.push_back({ndx1, ndy0, u1, v0, r, g, b, a});
|
||||
vertices_.push_back({ndx1, ndy1, u1, v1, r, g, b, a});
|
||||
vertices_.push_back({ndx0, ndy1, u0, v1, r, g, b, a});
|
||||
|
||||
indices_.push_back(vi + 0); indices_.push_back(vi + 1); indices_.push_back(vi + 2);
|
||||
indices_.push_back(vi + 2); indices_.push_back(vi + 3); indices_.push_back(vi + 0);
|
||||
indices_.push_back(vi + 0);
|
||||
indices_.push_back(vi + 1);
|
||||
indices_.push_back(vi + 2);
|
||||
indices_.push_back(vi + 2);
|
||||
indices_.push_back(vi + 3);
|
||||
indices_.push_back(vi + 0);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
#include <vector>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GpuVertex — 8-float vertex layout sent to the GPU.
|
||||
// Position is in NDC (pre-transformed on CPU), UV in [0,1], color in [0,1].
|
||||
// ---------------------------------------------------------------------------
|
||||
struct GpuVertex {
|
||||
float x, y; // NDC position (−1..1)
|
||||
float u, v; // Texture coords (0..1)
|
||||
float r, g, b, a; // RGBA color (0..1)
|
||||
float x, y; // NDC position (−1..1)
|
||||
float u, v; // Texture coords (0..1)
|
||||
float r, g, b, a; // RGBA color (0..1)
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
@@ -25,64 +26,56 @@ struct GpuVertex {
|
||||
// // Then in render pass: bind buffers, draw bg with white tex, draw sprites.
|
||||
// ============================================================================
|
||||
class GpuSpriteBatch {
|
||||
public:
|
||||
// Default maximum sprites (background + UI overlay each count as one sprite)
|
||||
static constexpr int DEFAULT_MAX_SPRITES = 200000;
|
||||
public:
|
||||
// Default maximum sprites (background + UI overlay each count as one sprite)
|
||||
static constexpr int DEFAULT_MAX_SPRITES = 200000;
|
||||
|
||||
bool init(SDL_GPUDevice* device, int max_sprites = DEFAULT_MAX_SPRITES);
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
bool init(SDL_GPUDevice* device, int max_sprites = DEFAULT_MAX_SPRITES);
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
|
||||
void beginFrame();
|
||||
void beginFrame();
|
||||
|
||||
// Add the full-screen background gradient quad.
|
||||
// top_* and bot_* are RGB in [0,1].
|
||||
void addBackground(float screen_w, float screen_h,
|
||||
float top_r, float top_g, float top_b,
|
||||
float bot_r, float bot_g, float bot_b);
|
||||
// Add the full-screen background gradient quad.
|
||||
// top_* and bot_* are RGB in [0,1].
|
||||
void addBackground(float screen_w, float screen_h, float top_r, float top_g, float top_b, float bot_r, float bot_g, float bot_b);
|
||||
|
||||
// Add a sprite quad (pixel coordinates).
|
||||
// scale: uniform scale around the quad centre.
|
||||
void addSprite(float x, float y, float w, float h,
|
||||
float r, float g, float b, float a,
|
||||
float scale,
|
||||
float screen_w, float screen_h);
|
||||
// Add a sprite quad (pixel coordinates).
|
||||
// scale: uniform scale around the quad centre.
|
||||
void addSprite(float x, float y, float w, float h, float r, float g, float b, float a, float scale, float screen_w, float screen_h);
|
||||
|
||||
// Add a full-screen overlay quad (e.g. UI surface, NDC −1..1).
|
||||
void addFullscreenOverlay();
|
||||
// Add a full-screen overlay quad (e.g. UI surface, NDC −1..1).
|
||||
void addFullscreenOverlay();
|
||||
|
||||
// Upload CPU vectors to GPU buffers via a copy pass.
|
||||
// Returns false if the batch is empty.
|
||||
bool uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd_buf);
|
||||
// Upload CPU vectors to GPU buffers via a copy pass.
|
||||
// Returns false if the batch is empty.
|
||||
bool uploadBatch(SDL_GPUDevice* device, SDL_GPUCommandBuffer* cmd_buf);
|
||||
|
||||
SDL_GPUBuffer* vertexBuffer() const { return vertex_buf_; }
|
||||
SDL_GPUBuffer* indexBuffer() const { return index_buf_; }
|
||||
int bgIndexCount() const { return bg_index_count_; }
|
||||
int overlayIndexOffset() const { return overlay_index_offset_; }
|
||||
int overlayIndexCount() const { return overlay_index_count_; }
|
||||
int spriteIndexOffset() const { return sprite_index_offset_; }
|
||||
int spriteIndexCount() const { return sprite_index_count_; }
|
||||
bool isEmpty() const { return vertices_.empty(); }
|
||||
SDL_GPUBuffer* vertexBuffer() const { return vertex_buf_; }
|
||||
SDL_GPUBuffer* indexBuffer() const { return index_buf_; }
|
||||
int bgIndexCount() const { return bg_index_count_; }
|
||||
int overlayIndexOffset() const { return overlay_index_offset_; }
|
||||
int overlayIndexCount() const { return overlay_index_count_; }
|
||||
int spriteIndexOffset() const { return sprite_index_offset_; }
|
||||
int spriteIndexCount() const { return sprite_index_count_; }
|
||||
bool isEmpty() const { return vertices_.empty(); }
|
||||
|
||||
private:
|
||||
void toNDC(float px, float py, float screen_w, float screen_h,
|
||||
float& ndx, float& ndy) const;
|
||||
void pushQuad(float ndx0, float ndy0, float ndx1, float ndy1,
|
||||
float u0, float v0, float u1, float v1,
|
||||
float r, float g, float b, float a);
|
||||
private:
|
||||
static void toNDC(float px, float py, float screen_w, float screen_h, float& ndx, float& ndy);
|
||||
void pushQuad(float ndx0, float ndy0, float ndx1, float ndy1, float u0, float v0, float u1, float v1, float r, float g, float b, float a);
|
||||
|
||||
std::vector<GpuVertex> vertices_;
|
||||
std::vector<uint32_t> indices_;
|
||||
std::vector<GpuVertex> vertices_;
|
||||
std::vector<uint32_t> indices_;
|
||||
|
||||
SDL_GPUBuffer* vertex_buf_ = nullptr;
|
||||
SDL_GPUBuffer* index_buf_ = nullptr;
|
||||
SDL_GPUTransferBuffer* vertex_transfer_ = nullptr;
|
||||
SDL_GPUTransferBuffer* index_transfer_ = nullptr;
|
||||
SDL_GPUBuffer* vertex_buf_ = nullptr;
|
||||
SDL_GPUBuffer* index_buf_ = nullptr;
|
||||
SDL_GPUTransferBuffer* vertex_transfer_ = nullptr;
|
||||
SDL_GPUTransferBuffer* index_transfer_ = nullptr;
|
||||
|
||||
int bg_index_count_ = 0;
|
||||
int sprite_index_offset_ = 0;
|
||||
int sprite_index_count_ = 0;
|
||||
int overlay_index_offset_ = 0;
|
||||
int overlay_index_count_ = 0;
|
||||
int bg_index_count_ = 0;
|
||||
int sprite_index_offset_ = 0;
|
||||
int sprite_index_count_ = 0;
|
||||
int overlay_index_offset_ = 0;
|
||||
int overlay_index_count_ = 0;
|
||||
|
||||
int max_sprites_ = DEFAULT_MAX_SPRITES;
|
||||
int max_sprites_ = DEFAULT_MAX_SPRITES;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
#include <SDL3/SDL_pixels.h>
|
||||
|
||||
#include <array> // for std::array
|
||||
#include <cstring> // memcpy
|
||||
#include <string>
|
||||
|
||||
@@ -13,7 +15,7 @@
|
||||
// Public interface
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool GpuTexture::fromFile(SDL_GPUDevice* device, const std::string& file_path) {
|
||||
auto GpuTexture::fromFile(SDL_GPUDevice* device, const std::string& file_path) -> bool {
|
||||
unsigned char* resource_data = nullptr;
|
||||
size_t resource_size = 0;
|
||||
|
||||
@@ -22,15 +24,22 @@ bool GpuTexture::fromFile(SDL_GPUDevice* device, const std::string& file_path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int w = 0, h = 0, orig = 0;
|
||||
int w = 0;
|
||||
int h = 0;
|
||||
int orig = 0;
|
||||
unsigned char* pixels = stbi_load_from_memory(
|
||||
resource_data, static_cast<int>(resource_size),
|
||||
&w, &h, &orig, STBI_rgb_alpha);
|
||||
resource_data,
|
||||
static_cast<int>(resource_size),
|
||||
&w,
|
||||
&h,
|
||||
&orig,
|
||||
STBI_rgb_alpha);
|
||||
delete[] resource_data;
|
||||
|
||||
if (!pixels) {
|
||||
if (pixels == nullptr) {
|
||||
SDL_Log("GpuTexture: stbi decode failed for '%s': %s",
|
||||
file_path.c_str(), stbi_failure_reason());
|
||||
file_path.c_str(),
|
||||
stbi_failure_reason());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -44,15 +53,17 @@ bool GpuTexture::fromFile(SDL_GPUDevice* device, const std::string& file_path) {
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool GpuTexture::fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool nearest) {
|
||||
if (!surface) return false;
|
||||
auto GpuTexture::fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool nearest) -> bool {
|
||||
if (surface == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure RGBA32 format
|
||||
SDL_Surface* rgba = surface;
|
||||
bool need_free = false;
|
||||
if (surface->format != SDL_PIXELFORMAT_RGBA32) {
|
||||
rgba = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
|
||||
if (!rgba) {
|
||||
if (rgba == nullptr) {
|
||||
SDL_Log("GpuTexture: SDL_ConvertSurface failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
@@ -60,55 +71,65 @@ bool GpuTexture::fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool n
|
||||
}
|
||||
|
||||
destroy(device);
|
||||
bool ok = uploadPixels(device, rgba->pixels, rgba->w, rgba->h,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
||||
if (ok) ok = createSampler(device, nearest);
|
||||
bool ok = uploadPixels(device, rgba->pixels, rgba->w, rgba->h, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
||||
if (ok) {
|
||||
ok = createSampler(device, nearest);
|
||||
}
|
||||
|
||||
if (need_free) SDL_DestroySurface(rgba);
|
||||
if (need_free) {
|
||||
SDL_DestroySurface(rgba);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool GpuTexture::createRenderTarget(SDL_GPUDevice* device, int w, int h,
|
||||
SDL_GPUTextureFormat format) {
|
||||
auto GpuTexture::createRenderTarget(SDL_GPUDevice* device, int w, int h, SDL_GPUTextureFormat format) -> bool {
|
||||
destroy(device);
|
||||
|
||||
SDL_GPUTextureCreateInfo info = {};
|
||||
info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
info.format = format;
|
||||
info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET
|
||||
| SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
info.width = static_cast<Uint32>(w);
|
||||
info.height = static_cast<Uint32>(h);
|
||||
info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
info.format = format;
|
||||
info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
info.width = static_cast<Uint32>(w);
|
||||
info.height = static_cast<Uint32>(h);
|
||||
info.layer_count_or_depth = 1;
|
||||
info.num_levels = 1;
|
||||
info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||
info.num_levels = 1;
|
||||
info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||
|
||||
texture_ = SDL_CreateGPUTexture(device, &info);
|
||||
if (!texture_) {
|
||||
if (texture_ == nullptr) {
|
||||
SDL_Log("GpuTexture: createRenderTarget failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
width_ = w;
|
||||
width_ = w;
|
||||
height_ = h;
|
||||
|
||||
// Render targets are sampled with linear filter (postfx reads them)
|
||||
return createSampler(device, false);
|
||||
}
|
||||
|
||||
bool GpuTexture::createWhite(SDL_GPUDevice* device) {
|
||||
auto GpuTexture::createWhite(SDL_GPUDevice* device) -> bool {
|
||||
destroy(device);
|
||||
// 1×1 white RGBA pixel
|
||||
const Uint8 white[4] = {255, 255, 255, 255};
|
||||
bool ok = uploadPixels(device, white, 1, 1,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
||||
if (ok) ok = createSampler(device, true);
|
||||
constexpr std::array<Uint8, 4> WHITE = {255, 255, 255, 255};
|
||||
bool ok = uploadPixels(device, WHITE.data(), 1, 1, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
||||
if (ok) {
|
||||
ok = createSampler(device, true);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
void GpuTexture::destroy(SDL_GPUDevice* device) {
|
||||
if (!device) return;
|
||||
if (sampler_) { SDL_ReleaseGPUSampler(device, sampler_); sampler_ = nullptr; }
|
||||
if (texture_) { SDL_ReleaseGPUTexture(device, texture_); texture_ = nullptr; }
|
||||
if (device == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (sampler_ != nullptr) {
|
||||
SDL_ReleaseGPUSampler(device, sampler_);
|
||||
sampler_ = nullptr;
|
||||
}
|
||||
if (texture_ != nullptr) {
|
||||
SDL_ReleaseGPUTexture(device, texture_);
|
||||
texture_ = nullptr;
|
||||
}
|
||||
width_ = height_ = 0;
|
||||
}
|
||||
|
||||
@@ -116,34 +137,33 @@ void GpuTexture::destroy(SDL_GPUDevice* device) {
|
||||
// Private helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
||||
int w, int h, SDL_GPUTextureFormat format) {
|
||||
auto GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels, int w, int h, SDL_GPUTextureFormat format) -> bool {
|
||||
// Create GPU texture
|
||||
SDL_GPUTextureCreateInfo tex_info = {};
|
||||
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
tex_info.format = format;
|
||||
tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
tex_info.width = static_cast<Uint32>(w);
|
||||
tex_info.height = static_cast<Uint32>(h);
|
||||
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
tex_info.format = format;
|
||||
tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
tex_info.width = static_cast<Uint32>(w);
|
||||
tex_info.height = static_cast<Uint32>(h);
|
||||
tex_info.layer_count_or_depth = 1;
|
||||
tex_info.num_levels = 1;
|
||||
tex_info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||
tex_info.num_levels = 1;
|
||||
tex_info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||||
|
||||
texture_ = SDL_CreateGPUTexture(device, &tex_info);
|
||||
if (!texture_) {
|
||||
if (texture_ == nullptr) {
|
||||
SDL_Log("GpuTexture: SDL_CreateGPUTexture failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create transfer buffer and upload pixels
|
||||
Uint32 data_size = static_cast<Uint32>(w * h * 4); // RGBA = 4 bytes/pixel
|
||||
auto data_size = static_cast<Uint32>(w * h * 4); // RGBA = 4 bytes/pixel
|
||||
|
||||
SDL_GPUTransferBufferCreateInfo tb_info = {};
|
||||
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||
tb_info.size = data_size;
|
||||
tb_info.size = data_size;
|
||||
|
||||
SDL_GPUTransferBuffer* transfer = SDL_CreateGPUTransferBuffer(device, &tb_info);
|
||||
if (!transfer) {
|
||||
if (transfer == nullptr) {
|
||||
SDL_Log("GpuTexture: transfer buffer creation failed: %s", SDL_GetError());
|
||||
SDL_ReleaseGPUTexture(device, texture_);
|
||||
texture_ = nullptr;
|
||||
@@ -151,7 +171,7 @@ bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
||||
}
|
||||
|
||||
void* mapped = SDL_MapGPUTransferBuffer(device, transfer, false);
|
||||
if (!mapped) {
|
||||
if (mapped == nullptr) {
|
||||
SDL_Log("GpuTexture: map failed: %s", SDL_GetError());
|
||||
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
||||
SDL_ReleaseGPUTexture(device, texture_);
|
||||
@@ -167,14 +187,14 @@ bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
||||
|
||||
SDL_GPUTextureTransferInfo src = {};
|
||||
src.transfer_buffer = transfer;
|
||||
src.offset = 0;
|
||||
src.pixels_per_row = static_cast<Uint32>(w);
|
||||
src.rows_per_layer = static_cast<Uint32>(h);
|
||||
src.offset = 0;
|
||||
src.pixels_per_row = static_cast<Uint32>(w);
|
||||
src.rows_per_layer = static_cast<Uint32>(h);
|
||||
|
||||
SDL_GPUTextureRegion dst = {};
|
||||
dst.texture = texture_;
|
||||
dst.texture = texture_;
|
||||
dst.mip_level = 0;
|
||||
dst.layer = 0;
|
||||
dst.layer = 0;
|
||||
dst.x = dst.y = dst.z = 0;
|
||||
dst.w = static_cast<Uint32>(w);
|
||||
dst.h = static_cast<Uint32>(h);
|
||||
@@ -185,22 +205,22 @@ bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
||||
SDL_SubmitGPUCommandBuffer(cmd);
|
||||
|
||||
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
||||
width_ = w;
|
||||
width_ = w;
|
||||
height_ = h;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GpuTexture::createSampler(SDL_GPUDevice* device, bool nearest) {
|
||||
auto GpuTexture::createSampler(SDL_GPUDevice* device, bool nearest) -> bool {
|
||||
SDL_GPUSamplerCreateInfo info = {};
|
||||
info.min_filter = nearest ? SDL_GPU_FILTER_NEAREST : SDL_GPU_FILTER_LINEAR;
|
||||
info.mag_filter = nearest ? SDL_GPU_FILTER_NEAREST : SDL_GPU_FILTER_LINEAR;
|
||||
info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
|
||||
info.min_filter = nearest ? SDL_GPU_FILTER_NEAREST : SDL_GPU_FILTER_LINEAR;
|
||||
info.mag_filter = nearest ? SDL_GPU_FILTER_NEAREST : SDL_GPU_FILTER_LINEAR;
|
||||
info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
|
||||
info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
|
||||
sampler_ = SDL_CreateGPUSampler(device, &info);
|
||||
if (!sampler_) {
|
||||
if (sampler_ == nullptr) {
|
||||
SDL_Log("GpuTexture: SDL_CreateGPUSampler failed: %s", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
#include <SDL3/SDL_surface.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
// ============================================================================
|
||||
@@ -9,40 +10,38 @@
|
||||
// Handles sprite textures, render targets, and the 1×1 white utility texture.
|
||||
// ============================================================================
|
||||
class GpuTexture {
|
||||
public:
|
||||
GpuTexture() = default;
|
||||
~GpuTexture() = default;
|
||||
public:
|
||||
GpuTexture() = default;
|
||||
~GpuTexture() = default;
|
||||
|
||||
// Load from resource path (pack or disk) using stb_image.
|
||||
bool fromFile(SDL_GPUDevice* device, const std::string& file_path);
|
||||
// Load from resource path (pack or disk) using stb_image.
|
||||
bool fromFile(SDL_GPUDevice* device, const std::string& file_path);
|
||||
|
||||
// Upload pixel data from an SDL_Surface to a new GPU texture + sampler.
|
||||
// Uses nearest-neighbor filter for sprite pixel-perfect look.
|
||||
bool fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool nearest = true);
|
||||
// Upload pixel data from an SDL_Surface to a new GPU texture + sampler.
|
||||
// Uses nearest-neighbor filter for sprite pixel-perfect look.
|
||||
bool fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool nearest = true);
|
||||
|
||||
// Create an offscreen render target (COLOR_TARGET | SAMPLER usage).
|
||||
bool createRenderTarget(SDL_GPUDevice* device, int w, int h,
|
||||
SDL_GPUTextureFormat format);
|
||||
// Create an offscreen render target (COLOR_TARGET | SAMPLER usage).
|
||||
bool createRenderTarget(SDL_GPUDevice* device, int w, int h, SDL_GPUTextureFormat format);
|
||||
|
||||
// Create a 1×1 opaque white texture (used for untextured geometry).
|
||||
bool createWhite(SDL_GPUDevice* device);
|
||||
// Create a 1×1 opaque white texture (used for untextured geometry).
|
||||
bool createWhite(SDL_GPUDevice* device);
|
||||
|
||||
// Release GPU resources.
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
// Release GPU resources.
|
||||
void destroy(SDL_GPUDevice* device);
|
||||
|
||||
SDL_GPUTexture* texture() const { return texture_; }
|
||||
SDL_GPUSampler* sampler() const { return sampler_; }
|
||||
int width() const { return width_; }
|
||||
int height() const { return height_; }
|
||||
bool isValid() const { return texture_ != nullptr; }
|
||||
SDL_GPUTexture* texture() const { return texture_; }
|
||||
SDL_GPUSampler* sampler() const { return sampler_; }
|
||||
int width() const { return width_; }
|
||||
int height() const { return height_; }
|
||||
bool isValid() const { return texture_ != nullptr; }
|
||||
|
||||
private:
|
||||
bool uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
||||
int w, int h, SDL_GPUTextureFormat format);
|
||||
bool createSampler(SDL_GPUDevice* device, bool nearest);
|
||||
private:
|
||||
bool uploadPixels(SDL_GPUDevice* device, const void* pixels, int w, int h, SDL_GPUTextureFormat format);
|
||||
bool createSampler(SDL_GPUDevice* device, bool nearest);
|
||||
|
||||
SDL_GPUTexture* texture_ = nullptr;
|
||||
SDL_GPUSampler* sampler_ = nullptr;
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
SDL_GPUTexture* texture_ = nullptr;
|
||||
SDL_GPUSampler* sampler_ = nullptr;
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user