#include "gpu_texture.hpp" #include #include #include // for std::array #include // memcpy #include // stb_image is compiled in texture.cpp (STB_IMAGE_IMPLEMENTATION defined there) #include "external/stb_image.h" #include "resource_manager.hpp" // --------------------------------------------------------------------------- // Public interface // --------------------------------------------------------------------------- auto GpuTexture::fromFile(SDL_GPUDevice* device, const std::string& file_path) -> bool { 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; int h = 0; int orig = 0; unsigned char* pixels = stbi_load_from_memory( resource_data, static_cast(resource_size), &w, &h, &orig, STBI_rgb_alpha); delete[] resource_data; if (pixels == nullptr) { 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; } 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 == nullptr) { 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; } 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(w); info.height = static_cast(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_ == nullptr) { 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); } auto GpuTexture::createWhite(SDL_GPUDevice* device) -> bool { destroy(device); // 1×1 white RGBA pixel constexpr std::array 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 == nullptr) { return; } if (sampler_ != nullptr) { SDL_ReleaseGPUSampler(device, sampler_); sampler_ = nullptr; } if (texture_ != nullptr) { SDL_ReleaseGPUTexture(device, texture_); texture_ = nullptr; } width_ = height_ = 0; } // --------------------------------------------------------------------------- // Private helpers // --------------------------------------------------------------------------- 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(w); tex_info.height = static_cast(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_ == nullptr) { SDL_Log("GpuTexture: SDL_CreateGPUTexture failed: %s", SDL_GetError()); return false; } // Create transfer buffer and upload pixels auto data_size = static_cast(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 == nullptr) { 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 == nullptr) { 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(w); src.rows_per_layer = static_cast(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(w); dst.h = static_cast(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; } 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.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_ == nullptr) { SDL_Log("GpuTexture: SDL_CreateGPUSampler failed: %s", SDL_GetError()); return false; } return true; }