Flint Engine
Modular C++20 2D game engine powered by SDL3
Loading...
Searching...
No Matches
gpu-tracker.h
1// SPDX-License-Identifier: MIT
2// SPDX-FileCopyrightText: 2025-2026 Manuel Osorio
3#pragma once
4#include "flint/core/log.h"
5
6#include <SDL3/SDL_gpu.h>
7
8#include <string>
9#include <unordered_map>
10
11#ifdef FLINT_ENABLE_GPU_TRACKER
22class GPUResourceTracker {
23 public:
27 static GPUResourceTracker &instance() {
28 static GPUResourceTracker tracker;
29 return tracker;
30 }
31
38 void trackBuffer(SDL_GPUBuffer *buffer, const std::string &location) {
39 if (!buffer) {
40 return;
41 }
42 m_buffers[buffer] = location;
43 FLINT_ENGINE_INFO("[GPU TRACK] Buffer {} CREATED at {}", static_cast<void *>(buffer),
44 location);
45 }
46
55 void releaseBuffer(SDL_GPUBuffer *buffer, const std::string &location) {
56 if (!buffer) {
57 return;
58 }
59 auto it = m_buffers.find(buffer);
60 if (it != m_buffers.end()) {
61 FLINT_ENGINE_DEBUG("[GPU TRACK] Buffer {} RELEASED at {} (was created at {})",
62 static_cast<void *>(buffer), location, it->second);
63 m_buffers.erase(it);
64 } else {
65 FLINT_ENGINE_DEBUG(
66 "[GPU TRACK] Buffer {} RELEASED at {} but was NOT TRACKED (SDL internal?)",
67 static_cast<void *>(buffer), location);
68 }
69 }
70
77 void trackTexture(SDL_GPUTexture *texture, const std::string &location) {
78 if (!texture) {
79 return;
80 }
81 m_textures[texture] = location;
82 FLINT_ENGINE_INFO("[GPU TRACK] Texture {} CREATED at {}", static_cast<void *>(texture),
83 location);
84 }
85
92 void releaseTexture(SDL_GPUTexture *texture, const std::string &location) {
93 if (!texture) {
94 return;
95 }
96 auto it = m_textures.find(texture);
97 if (it != m_textures.end()) {
98 FLINT_ENGINE_DEBUG("[GPU TRACK] Texture {} RELEASED at {} (was created at {})",
99 static_cast<void *>(texture), location, it->second);
100 m_textures.erase(it);
101 } else {
102 FLINT_ENGINE_DEBUG("[GPU TRACK] Texture {} RELEASED at {} but was NOT TRACKED",
103 static_cast<void *>(texture), location);
104 }
105 }
106
113 void trackSampler(SDL_GPUSampler *sampler, const std::string &location) {
114 if (!sampler) {
115 return;
116 }
117 m_samplers[sampler] = location;
118 FLINT_ENGINE_DEBUG("[GPU TRACK] Sampler {} CREATED at {}", static_cast<void *>(sampler),
119 location);
120 }
121
128 void releaseSampler(SDL_GPUSampler *sampler, const std::string &location) {
129 if (!sampler) {
130 return;
131 }
132 auto it = m_samplers.find(sampler);
133 if (it != m_samplers.end()) {
134 FLINT_ENGINE_DEBUG("[GPU TRACK] Sampler {} RELEASED at {} (was created at {})",
135 static_cast<void *>(sampler), location, it->second);
136 m_samplers.erase(it);
137 } else {
138 FLINT_ENGINE_DEBUG("[GPU TRACK] Sampler {} RELEASED at {} but was NOT TRACKED",
139 static_cast<void *>(sampler), location);
140 }
141 }
142
149 void trackShader(SDL_GPUShader *shader, const std::string &location) {
150 if (!shader) {
151 return;
152 }
153 m_shaders[shader] = location;
154 FLINT_ENGINE_DEBUG("[GPU TRACK] Shader {} CREATED at {}", static_cast<void *>(shader),
155 location);
156 }
157
164 void releaseShader(SDL_GPUShader *shader, const std::string &location) {
165 if (!shader) {
166 return;
167 }
168 auto it = m_shaders.find(shader);
169 if (it != m_shaders.end()) {
170 FLINT_ENGINE_DEBUG("[GPU TRACK] Shader {} RELEASED at {} (was created at {})",
171 static_cast<void *>(shader), location, it->second);
172 m_shaders.erase(it);
173 } else {
174 FLINT_ENGINE_DEBUG("[GPU TRACK] Shader {} RELEASED at {} but was NOT TRACKED",
175 static_cast<void *>(shader), location);
176 }
177 }
178
189 void reportLeaks() {
190 bool hasLeaks = false;
191
192 if (!m_buffers.empty()) {
193 hasLeaks = true;
194 FLINT_ENGINE_ERROR("╔═══════════════════════════════════════════╗");
195 FLINT_ENGINE_ERROR("║ GPU BUFFER LEAKS DETECTED: {} ║", m_buffers.size());
196 FLINT_ENGINE_ERROR("╚═══════════════════════════════════════════╝");
197 for (const auto &[buffer, location] : m_buffers) {
198 FLINT_ENGINE_ERROR(" → Buffer {} created at {}", static_cast<void *>(buffer), location);
199 }
200 }
201
202 if (!m_textures.empty()) {
203 hasLeaks = true;
204 FLINT_ENGINE_ERROR("╔═══════════════════════════════════════════╗");
205 FLINT_ENGINE_ERROR("║ GPU TEXTURE LEAKS DETECTED: {} ║", m_textures.size());
206 FLINT_ENGINE_ERROR("╚═══════════════════════════════════════════╝");
207 for (const auto &[texture, location] : m_textures) {
208 FLINT_ENGINE_ERROR(" → Texture {} created at {}", static_cast<void *>(texture),
209 location);
210 }
211 }
212
213 if (!m_samplers.empty()) {
214 hasLeaks = true;
215 FLINT_ENGINE_ERROR("╔═══════════════════════════════════════════╗");
216 FLINT_ENGINE_ERROR("║ GPU SAMPLER LEAKS DETECTED: {} ║", m_samplers.size());
217 FLINT_ENGINE_ERROR("╚═══════════════════════════════════════════╝");
218 for (const auto &[sampler, location] : m_samplers) {
219 FLINT_ENGINE_ERROR(" → Sampler {} created at {}", static_cast<void *>(sampler),
220 location);
221 }
222 }
223
224 if (!m_shaders.empty()) {
225 hasLeaks = true;
226 FLINT_ENGINE_ERROR("╔═══════════════════════════════════════════╗");
227 FLINT_ENGINE_ERROR("║ GPU SHADER LEAKS DETECTED: {} ║", m_shaders.size());
228 FLINT_ENGINE_ERROR("╚═══════════════════════════════════════════╝");
229 for (const auto &[shader, location] : m_shaders) {
230 FLINT_ENGINE_ERROR(" → Shader {} created at {}", static_cast<void *>(shader), location);
231 }
232 }
233
234 if (!hasLeaks) {
235 FLINT_ENGINE_INFO("╔═══════════════════════════════════════════╗");
236 FLINT_ENGINE_INFO("║ ✓ NO GPU RESOURCE LEAKS DETECTED! ║");
237 FLINT_ENGINE_INFO("╚═══════════════════════════════════════════╝");
238 } else {
239 FLINT_ENGINE_ERROR("");
240 FLINT_ENGINE_ERROR("NOTE: If Vulkan validation shows leaks but tracker doesn't,");
241 FLINT_ENGINE_ERROR(" those are SDL3/driver internal resources, not your code!");
242 }
243 }
244
245 private:
246 std::unordered_map<SDL_GPUBuffer *, std::string> m_buffers;
247 std::unordered_map<SDL_GPUTexture *, std::string> m_textures;
248 std::unordered_map<SDL_GPUSampler *, std::string> m_samplers;
249 std::unordered_map<SDL_GPUShader *, std::string> m_shaders;
250};
251
252 #define GPU_TRACK_LOCATION (std::string(__FILE__) + ":" + std::to_string(__LINE__))
253
254 #define GPU_TRACK_BUFFER(buffer, name) \
255 GPUResourceTracker::instance().trackBuffer(buffer, GPU_TRACK_LOCATION)
256
257 #define GPU_RELEASE_BUFFER(device, buffer) \
258 do { \
259 GPUResourceTracker::instance().releaseBuffer(buffer, GPU_TRACK_LOCATION); \
260 SDL_ReleaseGPUBuffer(device, buffer); \
261 } while (0)
262
263 #define GPU_TRACK_TEXTURE(texture, name) \
264 GPUResourceTracker::instance().trackTexture(texture, GPU_TRACK_LOCATION)
265
266 #define GPU_RELEASE_TEXTURE(device, texture) \
267 do { \
268 GPUResourceTracker::instance().releaseTexture(texture, GPU_TRACK_LOCATION); \
269 SDL_ReleaseGPUTexture(device, texture); \
270 } while (0)
271
272 #define GPU_TRACK_SAMPLER(sampler, name) \
273 GPUResourceTracker::instance().trackSampler(sampler, GPU_TRACK_LOCATION)
274
275 #define GPU_RELEASE_SAMPLER(device, sampler) \
276 do { \
277 GPUResourceTracker::instance().releaseSampler(sampler, GPU_TRACK_LOCATION); \
278 SDL_ReleaseGPUSampler(device, sampler); \
279 } while (0)
280
281 #define GPU_TRACK_SHADER(shader, name) \
282 GPUResourceTracker::instance().trackShader(shader, GPU_TRACK_LOCATION)
283
284 #define GPU_RELEASE_SHADER(device, shader) \
285 do { \
286 GPUResourceTracker::instance().releaseShader(shader, GPU_TRACK_LOCATION); \
287 SDL_ReleaseGPUShader(device, shader); \
288 } while (0)
289
290 #define GPU_REPORT_LEAKS() GPUResourceTracker::instance().reportLeaks()
291
292#else
293
294 #define GPU_TRACK_BUFFER(buffer, name)
295 #define GPU_RELEASE_BUFFER(device, buffer) SDL_ReleaseGPUBuffer(device, buffer)
296 #define GPU_TRACK_TEXTURE(texture, name)
297 #define GPU_RELEASE_TEXTURE(device, texture) SDL_ReleaseGPUTexture(device, texture)
298 #define GPU_TRACK_SAMPLER(sampler, name)
299 #define GPU_RELEASE_SAMPLER(device, sampler) SDL_ReleaseGPUSampler(device, sampler)
300 #define GPU_TRACK_SHADER(shader, name)
301 #define GPU_RELEASE_SHADER(device, shader) SDL_ReleaseGPUShader(device, shader)
302 #define GPU_REPORT_LEAKS()
303
304#endif