- Infraestructura GPU: GpuContext, GpuPipeline, GpuSpriteBatch, GpuTexture - Engine::render() migrat a 2-pass: sprites → offscreen R8G8B8A8 → swapchain + vignette - UI/text via software renderer (SDL3_ttf) + upload com a textura overlay GPU - CMakeLists.txt actualitzat per incloure subsistema gpu/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
209 lines
7.1 KiB
C++
209 lines
7.1 KiB
C++
#include "gpu_texture.hpp"
|
||
|
||
#include <SDL3/SDL_log.h>
|
||
#include <SDL3/SDL_pixels.h>
|
||
#include <cstring> // memcpy
|
||
#include <string>
|
||
|
||
// stb_image is compiled in texture.cpp (STB_IMAGE_IMPLEMENTATION defined there)
|
||
#include "external/stb_image.h"
|
||
#include "resource_manager.hpp"
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Public interface
|
||
// ---------------------------------------------------------------------------
|
||
|
||
bool GpuTexture::fromFile(SDL_GPUDevice* device, const std::string& file_path) {
|
||
unsigned char* resource_data = nullptr;
|
||
size_t resource_size = 0;
|
||
|
||
if (!ResourceManager::loadResource(file_path, resource_data, resource_size)) {
|
||
SDL_Log("GpuTexture: can't load resource '%s'", file_path.c_str());
|
||
return false;
|
||
}
|
||
|
||
int w = 0, h = 0, orig = 0;
|
||
unsigned char* pixels = stbi_load_from_memory(
|
||
resource_data, static_cast<int>(resource_size),
|
||
&w, &h, &orig, STBI_rgb_alpha);
|
||
delete[] resource_data;
|
||
|
||
if (!pixels) {
|
||
SDL_Log("GpuTexture: stbi decode failed for '%s': %s",
|
||
file_path.c_str(), stbi_failure_reason());
|
||
return false;
|
||
}
|
||
|
||
destroy(device);
|
||
bool ok = uploadPixels(device, pixels, w, h, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
||
stbi_image_free(pixels);
|
||
|
||
if (ok) {
|
||
ok = createSampler(device, true /*nearest = pixel-perfect sprites*/);
|
||
}
|
||
return ok;
|
||
}
|
||
|
||
bool GpuTexture::fromSurface(SDL_GPUDevice* device, SDL_Surface* surface, bool nearest) {
|
||
if (!surface) 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) {
|
||
SDL_Log("GpuTexture: SDL_ConvertSurface failed: %s", SDL_GetError());
|
||
return false;
|
||
}
|
||
need_free = true;
|
||
}
|
||
|
||
destroy(device);
|
||
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);
|
||
return ok;
|
||
}
|
||
|
||
bool GpuTexture::createRenderTarget(SDL_GPUDevice* device, int w, int h,
|
||
SDL_GPUTextureFormat format) {
|
||
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.layer_count_or_depth = 1;
|
||
info.num_levels = 1;
|
||
info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||
|
||
texture_ = SDL_CreateGPUTexture(device, &info);
|
||
if (!texture_) {
|
||
SDL_Log("GpuTexture: createRenderTarget failed: %s", SDL_GetError());
|
||
return false;
|
||
}
|
||
width_ = w;
|
||
height_ = h;
|
||
|
||
// Render targets are sampled with linear filter (postfx reads them)
|
||
return createSampler(device, false);
|
||
}
|
||
|
||
bool GpuTexture::createWhite(SDL_GPUDevice* device) {
|
||
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);
|
||
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; }
|
||
width_ = height_ = 0;
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Private helpers
|
||
// ---------------------------------------------------------------------------
|
||
|
||
bool GpuTexture::uploadPixels(SDL_GPUDevice* device, const void* pixels,
|
||
int w, int h, SDL_GPUTextureFormat format) {
|
||
// 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.layer_count_or_depth = 1;
|
||
tex_info.num_levels = 1;
|
||
tex_info.sample_count = SDL_GPU_SAMPLECOUNT_1;
|
||
|
||
texture_ = SDL_CreateGPUTexture(device, &tex_info);
|
||
if (!texture_) {
|
||
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
|
||
|
||
SDL_GPUTransferBufferCreateInfo tb_info = {};
|
||
tb_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||
tb_info.size = data_size;
|
||
|
||
SDL_GPUTransferBuffer* transfer = SDL_CreateGPUTransferBuffer(device, &tb_info);
|
||
if (!transfer) {
|
||
SDL_Log("GpuTexture: transfer buffer creation failed: %s", SDL_GetError());
|
||
SDL_ReleaseGPUTexture(device, texture_);
|
||
texture_ = nullptr;
|
||
return false;
|
||
}
|
||
|
||
void* mapped = SDL_MapGPUTransferBuffer(device, transfer, false);
|
||
if (!mapped) {
|
||
SDL_Log("GpuTexture: map failed: %s", SDL_GetError());
|
||
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
||
SDL_ReleaseGPUTexture(device, texture_);
|
||
texture_ = nullptr;
|
||
return false;
|
||
}
|
||
memcpy(mapped, pixels, data_size);
|
||
SDL_UnmapGPUTransferBuffer(device, transfer);
|
||
|
||
// Upload via command buffer
|
||
SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device);
|
||
SDL_GPUCopyPass* copy = SDL_BeginGPUCopyPass(cmd);
|
||
|
||
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);
|
||
|
||
SDL_GPUTextureRegion dst = {};
|
||
dst.texture = texture_;
|
||
dst.mip_level = 0;
|
||
dst.layer = 0;
|
||
dst.x = dst.y = dst.z = 0;
|
||
dst.w = static_cast<Uint32>(w);
|
||
dst.h = static_cast<Uint32>(h);
|
||
dst.d = 1;
|
||
|
||
SDL_UploadToGPUTexture(copy, &src, &dst, false);
|
||
SDL_EndGPUCopyPass(copy);
|
||
SDL_SubmitGPUCommandBuffer(cmd);
|
||
|
||
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
||
width_ = w;
|
||
height_ = h;
|
||
return true;
|
||
}
|
||
|
||
bool GpuTexture::createSampler(SDL_GPUDevice* device, bool nearest) {
|
||
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.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_) {
|
||
SDL_Log("GpuTexture: SDL_CreateGPUSampler failed: %s", SDL_GetError());
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|