Flint Engine
Modular C++20 2D game engine powered by SDL3
Loading...
Searching...
No Matches
gpu-texture.h
1// SPDX-License-Identifier: MIT
2// SPDX-FileCopyrightText: 2025-2026 Manuel Osorio
3#pragma once
4#include "flint/core/log.h"
5#include "flint/graphics/gpu/gpu-tracker.h"
6
7#include <SDL3/SDL_gpu.h>
8#include <SDL3_image/SDL_image.h>
9
10#include <cstddef>
11#include <cstring>
12
13namespace Flint {
20 class GPUTexture {
21 public:
22 GPUTexture() = default;
23
24 virtual ~GPUTexture() {
25 free();
26 }
27
28 GPUTexture(const GPUTexture &) = delete;
29 GPUTexture &operator=(const GPUTexture &) = delete;
30
32 GPUTexture(GPUTexture &&other) noexcept
33 : m_device(other.m_device),
34 m_texture(other.m_texture),
35 m_sampler(other.m_sampler),
36 m_width(other.m_width),
37 m_height(other.m_height) {
38 other.m_device = nullptr;
39 other.m_texture = nullptr;
40 other.m_sampler = nullptr;
41 other.m_width = 0;
42 other.m_height = 0;
43 }
44
46 GPUTexture &operator=(GPUTexture &&other) noexcept {
47 if (this != &other) {
48 free();
49 m_device = other.m_device;
50 m_texture = other.m_texture;
51 m_sampler = other.m_sampler;
52 m_width = other.m_width;
53 m_height = other.m_height;
54
55 other.m_device = nullptr;
56 other.m_texture = nullptr;
57 other.m_sampler = nullptr;
58 other.m_width = 0;
59 other.m_height = 0;
60 }
61 return *this;
62 }
63
78 bool loadFromMemory(SDL_GPUDevice *device, const void *data, size_t size) {
79 m_device = device;
80
81 SDL_IOStream *rw = SDL_IOFromConstMem(data, static_cast<int>(size));
82 if (!rw) {
83 FLINT_ENGINE_ERROR("Failed to create IO stream from memory");
84 return false;
85 }
86
87 SDL_Surface *loaded = IMG_Load_IO(rw, true);
88 if (!loaded) {
89 FLINT_ENGINE_ERROR("Failed to load image: {}", SDL_GetError());
90 return false;
91 }
92
93 SDL_Surface *surface = loaded;
94 if (loaded->format != SDL_PIXELFORMAT_RGBA32) {
95 surface = SDL_ConvertSurface(loaded, SDL_PIXELFORMAT_RGBA32);
96 SDL_DestroySurface(loaded);
97 if (!surface) {
98 FLINT_ENGINE_ERROR("Failed to convert surface to RGBA32: {}", SDL_GetError());
99 return false;
100 }
101 }
102
103 m_width = surface->w;
104 m_height = surface->h;
105
106 const Uint32 bytesPerRow = static_cast<Uint32>(m_width) * 4;
107 const Uint32 dataSize = bytesPerRow * static_cast<Uint32>(m_height);
108
109 SDL_GPUTextureCreateInfo textureInfo = {};
110 textureInfo.type = SDL_GPU_TEXTURETYPE_2D;
111 textureInfo.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
112 textureInfo.width = m_width;
113 textureInfo.height = m_height;
114 textureInfo.layer_count_or_depth = 1;
115 textureInfo.num_levels = 1;
116 textureInfo.sample_count = SDL_GPU_SAMPLECOUNT_1;
117 textureInfo.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
118
119 m_texture = SDL_CreateGPUTexture(m_device, &textureInfo);
120 if (!m_texture) {
121 FLINT_ENGINE_ERROR("Failed to create GPU texture: {}", SDL_GetError());
122 SDL_DestroySurface(surface);
123 return false;
124 }
125
126 SDL_GPUCommandBuffer *uploadCmd = SDL_AcquireGPUCommandBuffer(m_device);
127 if (!uploadCmd) {
128 FLINT_ENGINE_ERROR("Failed to acquire command buffer for upload");
129 SDL_DestroySurface(surface);
130 GPU_RELEASE_TEXTURE(m_device, m_texture);
131 return false;
132 }
133
134 SDL_GPUCopyPass *copyPass = SDL_BeginGPUCopyPass(uploadCmd);
135 if (!copyPass) {
136 FLINT_ENGINE_ERROR("Failed to begin GPU copy pass: {}", SDL_GetError());
137 return false;
138 }
139
140 SDL_GPUTransferBufferCreateInfo transferBufferInfo = {};
141 transferBufferInfo.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
142 transferBufferInfo.size = dataSize;
143
144 SDL_GPUTransferBuffer *transferBuffer = SDL_CreateGPUTransferBuffer(m_device,
145 &transferBufferInfo);
146 if (!transferBuffer) {
147 FLINT_ENGINE_ERROR("Failed to create transfer buffer");
148 SDL_DestroySurface(surface);
149 SDL_EndGPUCopyPass(copyPass);
150 SDL_SubmitGPUCommandBuffer(uploadCmd);
151 return false;
152 }
153
154 void *mapped = SDL_MapGPUTransferBuffer(m_device, transferBuffer, false);
155 if (!mapped) {
156 FLINT_ENGINE_ERROR("Failed to map transfer buffer: {}", SDL_GetError());
157 SDL_EndGPUCopyPass(copyPass);
158 SDL_SubmitGPUCommandBuffer(uploadCmd);
159 SDL_ReleaseGPUTransferBuffer(m_device, transferBuffer);
160 GPU_RELEASE_TEXTURE(m_device, m_texture);
161 SDL_DestroySurface(surface);
162 return false;
163 }
164 auto *dstBytes = static_cast<Uint8 *>(mapped);
165 auto *srcBytes = static_cast<const Uint8 *>(surface->pixels);
166 for (int y = 0; y < m_height; ++y) {
167 memcpy(dstBytes + y * bytesPerRow, srcBytes + y * surface->pitch, bytesPerRow);
168 }
169 SDL_UnmapGPUTransferBuffer(m_device, transferBuffer);
170
171 SDL_GPUTextureTransferInfo uploadInfo = {};
172 uploadInfo.transfer_buffer = transferBuffer;
173 uploadInfo.offset = 0;
174 uploadInfo.pixels_per_row = m_width;
175 uploadInfo.rows_per_layer = m_height;
176
177 SDL_GPUTextureRegion textureRegion = {};
178 textureRegion.texture = m_texture;
179 textureRegion.w = m_width;
180 textureRegion.h = m_height;
181 textureRegion.d = 1;
182
183 SDL_UploadToGPUTexture(copyPass, &uploadInfo, &textureRegion, false);
184
185 SDL_EndGPUCopyPass(copyPass);
186 SDL_SubmitGPUCommandBuffer(uploadCmd);
187
188 SDL_ReleaseGPUTransferBuffer(m_device, transferBuffer);
189 SDL_DestroySurface(surface);
190
191 SDL_GPUSamplerCreateInfo samplerInfo = {};
192 samplerInfo.min_filter = SDL_GPU_FILTER_NEAREST;
193 samplerInfo.mag_filter = SDL_GPU_FILTER_NEAREST;
194 samplerInfo.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
195 samplerInfo.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
196 samplerInfo.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
197 samplerInfo.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
198
199 m_sampler = SDL_CreateGPUSampler(m_device, &samplerInfo);
200 if (!m_sampler) {
201 FLINT_ENGINE_ERROR("Failed to create GPU sampler: {}", SDL_GetError());
202 GPU_RELEASE_TEXTURE(m_device, m_texture);
203 return false;
204 }
205
206 FLINT_ENGINE_INFO("GPU texture loaded successfully ({}x{})", m_width, m_height);
207 return true;
208 }
209
210 void free() {
211 FLINT_ENGINE_DEBUG("Releasing GPUTexture resources");
212 FLINT_ENGINE_DEBUG("Device pointer: {}", std::uintptr_t(m_device));
213 if (m_sampler) {
214 SDL_ReleaseGPUSampler(m_device, m_sampler);
215 m_sampler = nullptr;
216 }
217 FLINT_ENGINE_DEBUG("Sampler released");
218 FLINT_ENGINE_DEBUG("Texture pointer: {}", std::uintptr_t(m_texture));
219 if (m_texture) {
220 GPU_RELEASE_TEXTURE(m_device, m_texture);
221 m_texture = nullptr;
222 }
223 FLINT_ENGINE_DEBUG("Texture released");
224 m_width = 0;
225 m_height = 0;
226 FLINT_ENGINE_DEBUG("GPUTexture resources released");
227 }
228
230 SDL_GPUTexture *getTexture() const {
231 return m_texture;
232 }
233
235 SDL_GPUSampler *getSampler() const {
236 return m_sampler;
237 }
238
240 int getWidth() const {
241 return m_width;
242 }
243
245 int getHeight() const {
246 return m_height;
247 }
248
258 static GPUTexture fromRenderer(SDL_GPUDevice *device, const void *data, size_t size) {
259 GPUTexture gpuTexture;
260 if (gpuTexture.loadFromMemory(device, data, size)) {
261 return gpuTexture;
262 } else {
263 throw std::runtime_error("Failed to create GPUTexture from memory");
264 }
265 }
266
267 private:
268 SDL_GPUDevice *m_device = nullptr;
269 SDL_GPUTexture *m_texture = nullptr;
270 SDL_GPUSampler *m_sampler = nullptr;
271 int m_width = 0, m_height = 0;
272 };
273}
GPUTexture(GPUTexture &&other) noexcept
Move constructor; transfers GPU ownership from the other.
Definition gpu-texture.h:32
int getWidth() const
Return the texture width in pixels.
bool loadFromMemory(SDL_GPUDevice *device, const void *data, size_t size)
Upload an image from memory to the GPU.
Definition gpu-texture.h:78
GPUTexture & operator=(GPUTexture &&other) noexcept
Move-assignment; releases current resources then steals from the other.
Definition gpu-texture.h:46
SDL_GPUTexture * getTexture() const
Return the underlying SDL GPU texture handle.
static GPUTexture fromRenderer(SDL_GPUDevice *device, const void *data, size_t size)
Factory: create a GPUTexture by loading from memory.
int getHeight() const
Return the texture height in pixels.
SDL_GPUSampler * getSampler() const
Return the sampler created alongside this texture.
Owns an SDL GPU texture and its associated sampler.
Definition gpu-texture.h:20