yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
atlas_renderer.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cmath>
5
7
8namespace yaze {
9namespace gfx {
10
12 static AtlasRenderer instance;
13 return instance;
14}
15
16void AtlasRenderer::Initialize(IRenderer* renderer, int initial_size) {
17 renderer_ = renderer;
20
21 // Clear any existing atlases
22 Clear();
23
24 // Create initial atlas
26}
27
28int AtlasRenderer::AddBitmap(const Bitmap& bitmap) {
29 if (!bitmap.is_active() || !bitmap.texture()) {
30 return -1; // Invalid bitmap
31 }
32
33 ScopedTimer timer("atlas_add_bitmap");
34
35 // Try to pack into current atlas
36 SDL_Rect uv_rect;
37 if (PackBitmap(*atlases_[current_atlas_], bitmap, uv_rect)) {
38 int atlas_id = next_atlas_id_++;
39 auto& atlas = *atlases_[current_atlas_];
40
41 // Copy bitmap data to atlas texture
42 renderer_->SetRenderTarget(atlas.texture);
43 renderer_->RenderCopy(bitmap.texture(), nullptr, &uv_rect);
44 renderer_->SetRenderTarget(nullptr);
45
46 return atlas_id;
47 }
48
49 // Current atlas is full, create new one
51 if (PackBitmap(*atlases_[current_atlas_], bitmap, uv_rect)) {
52 int atlas_id = next_atlas_id_++;
53 auto& atlas = *atlases_[current_atlas_];
54
56 bitmap.vector(), bitmap.width(), bitmap.height());
57 atlas.entries.emplace_back(atlas_id, uv_rect, bitmap.texture(), bpp_format,
58 bitmap.width(), bitmap.height());
59 atlas_lookup_[atlas_id] = &atlas.entries.back();
60
61 // Copy bitmap data to atlas texture
62 renderer_->SetRenderTarget(atlas.texture);
63 renderer_->RenderCopy(bitmap.texture(), nullptr, &uv_rect);
64 renderer_->SetRenderTarget(nullptr);
65
66 return atlas_id;
67 }
68
69 return -1; // Failed to add
70}
71
73 BppFormat target_bpp) {
74 if (!bitmap.is_active() || !bitmap.texture()) {
75 return -1; // Invalid bitmap
76 }
77
78 ScopedTimer timer("atlas_add_bitmap_bpp_optimized");
79
80 // Detect current BPP format
82 bitmap.vector(), bitmap.width(), bitmap.height());
83
84 // If formats match, use standard addition
85 if (current_bpp == target_bpp) {
86 return AddBitmap(bitmap);
87 }
88
89 // Convert bitmap to target BPP format
90 auto converted_data = BppFormatManager::Get().ConvertFormat(
91 bitmap.vector(), current_bpp, target_bpp, bitmap.width(),
92 bitmap.height());
93
94 // Create temporary bitmap with converted data
95 Bitmap converted_bitmap(bitmap.width(), bitmap.height(), bitmap.depth(),
96 converted_data, bitmap.palette());
97 converted_bitmap.CreateTexture();
98
99 // Add converted bitmap to atlas
100 return AddBitmap(converted_bitmap);
101}
102
103void AtlasRenderer::RemoveBitmap(int atlas_id) {
104 auto it = atlas_lookup_.find(atlas_id);
105 if (it == atlas_lookup_.end()) {
106 return;
107 }
108
109 AtlasEntry* entry = it->second;
110 entry->in_use = false;
111
112 // Mark region as free
113 for (auto& atlas : atlases_) {
114 for (auto& atlas_entry : atlas->entries) {
115 if (atlas_entry.atlas_id == atlas_id) {
116 MarkRegionUsed(*atlas, atlas_entry.uv_rect, false);
117 break;
118 }
119 }
120 }
121
122 atlas_lookup_.erase(it);
123}
124
125void AtlasRenderer::UpdateBitmap(int atlas_id, const Bitmap& bitmap) {
126 auto it = atlas_lookup_.find(atlas_id);
127 if (it == atlas_lookup_.end()) {
128 return;
129 }
130
131 AtlasEntry* entry = it->second;
132 entry->texture = bitmap.texture();
133
134 // Update UV coordinates if size changed
135 if (bitmap.width() != entry->uv_rect.w ||
136 bitmap.height() != entry->uv_rect.h) {
137 // Remove old entry and add new one
138 RemoveBitmap(atlas_id);
139 AddBitmap(bitmap);
140 }
141}
142
144 const std::vector<RenderCommand>& render_commands) {
145 if (render_commands.empty()) {
146 return;
147 }
148
149 ScopedTimer timer("atlas_batch_render");
150
151 // Group commands by atlas for efficient rendering
152 std::unordered_map<int, std::vector<const RenderCommand*>> atlas_groups;
153
154 for (const auto& cmd : render_commands) {
155 auto it = atlas_lookup_.find(cmd.atlas_id);
156 if (it != atlas_lookup_.end() && it->second->in_use) {
157 // Find which atlas contains this entry
158 for (size_t i = 0; i < atlases_.size(); ++i) {
159 for (const auto& entry : atlases_[i]->entries) {
160 if (entry.atlas_id == cmd.atlas_id) {
161 atlas_groups[i].push_back(&cmd);
162 break;
163 }
164 }
165 }
166 }
167 }
168
169 // Render each atlas group
170 for (const auto& [atlas_index, commands] : atlas_groups) {
171 if (commands.empty())
172 continue;
173
174 auto& atlas = *atlases_[atlas_index];
175
176 // Set atlas texture
177 // SDL_SetTextureBlendMode(atlas.texture, SDL_BLENDMODE_BLEND);
178
179 // Render all commands for this atlas
180 for (const auto* cmd : commands) {
181 auto it = atlas_lookup_.find(cmd->atlas_id);
182 if (it == atlas_lookup_.end())
183 continue;
184
185 AtlasEntry* entry = it->second;
186
187 // Calculate destination rectangle
188 SDL_Rect dest_rect = {static_cast<int>(cmd->x), static_cast<int>(cmd->y),
189 static_cast<int>(entry->uv_rect.w * cmd->scale_x),
190 static_cast<int>(entry->uv_rect.h * cmd->scale_y)};
191
192 // Apply rotation if needed
193 if (std::abs(cmd->rotation) > 0.001F) {
194 // For rotation, we'd need to use SDL_RenderCopyEx
195 // This is a simplified version
196 renderer_->RenderCopy(atlas.texture, &entry->uv_rect, &dest_rect);
197 } else {
198 renderer_->RenderCopy(atlas.texture, &entry->uv_rect, &dest_rect);
199 }
200 }
201 }
202}
203
205 const std::vector<RenderCommand>& render_commands,
206 const std::unordered_map<BppFormat, std::vector<int>>& bpp_groups) {
207 if (render_commands.empty()) {
208 return;
209 }
210
211 ScopedTimer timer("atlas_batch_render_bpp_optimized");
212
213 // Render each BPP group separately for optimal performance
214 for (const auto& [bpp_format, command_indices] : bpp_groups) {
215 if (command_indices.empty())
216 continue;
217
218 // Group commands by atlas for this BPP format
219 std::unordered_map<int, std::vector<const RenderCommand*>> atlas_groups;
220
221 for (int cmd_index : command_indices) {
222 if (cmd_index >= 0 &&
223 cmd_index < static_cast<int>(render_commands.size())) {
224 const auto& cmd = render_commands[cmd_index];
225 auto it = atlas_lookup_.find(cmd.atlas_id);
226 if (it != atlas_lookup_.end() && it->second->in_use &&
227 it->second->bpp_format == bpp_format) {
228 // Find which atlas contains this entry
229 for (size_t i = 0; i < atlases_.size(); ++i) {
230 for (const auto& entry : atlases_[i]->entries) {
231 if (entry.atlas_id == cmd.atlas_id) {
232 atlas_groups[i].push_back(&cmd);
233 break;
234 }
235 }
236 }
237 }
238 }
239 }
240
241 // Render each atlas group for this BPP format
242 for (const auto& [atlas_index, commands] : atlas_groups) {
243 if (commands.empty())
244 continue;
245
246 auto& atlas = *atlases_[atlas_index];
247
248 // Set atlas texture with BPP-specific blend mode
249 // SDL_SetTextureBlendMode(atlas.texture, SDL_BLENDMODE_BLEND);
250
251 // Render all commands for this atlas and BPP format
252 for (const auto* cmd : commands) {
253 auto it = atlas_lookup_.find(cmd->atlas_id);
254 if (it == atlas_lookup_.end())
255 continue;
256
257 AtlasEntry* entry = it->second;
258
259 // Calculate destination rectangle
260 SDL_Rect dest_rect = {
261 static_cast<int>(cmd->x), static_cast<int>(cmd->y),
262 static_cast<int>(entry->uv_rect.w * cmd->scale_x),
263 static_cast<int>(entry->uv_rect.h * cmd->scale_y)};
264
265 // Apply rotation if needed
266 if (std::abs(cmd->rotation) > 0.001F) {
267 renderer_->RenderCopy(atlas.texture, &entry->uv_rect, &dest_rect);
268 } else {
269 renderer_->RenderCopy(atlas.texture, &entry->uv_rect, &dest_rect);
270 }
271 }
272 }
273 }
274}
275
277 AtlasStats stats;
278
279 stats.total_atlases = atlases_.size();
280
281 for (const auto& atlas : atlases_) {
282 stats.total_entries += atlas->entries.size();
283 stats.used_entries +=
284 std::count_if(atlas->entries.begin(), atlas->entries.end(),
285 [](const AtlasEntry& entry) { return entry.in_use; });
286
287 // Calculate memory usage (simplified)
288 stats.total_memory += atlas->size * atlas->size * 4; // RGBA8888
289 }
290
291 if (stats.total_entries > 0) {
292 stats.utilization_percent =
293 (static_cast<float>(stats.used_entries) / stats.total_entries) * 100.0F;
294 }
295
296 return stats;
297}
298
300 ScopedTimer timer("atlas_defragment");
301
302 for (auto& atlas : atlases_) {
303 // Remove unused entries
304 atlas->entries.erase(
305 std::remove_if(atlas->entries.begin(), atlas->entries.end(),
306 [](const AtlasEntry& entry) { return !entry.in_use; }),
307 atlas->entries.end());
308
309 // Rebuild atlas texture
310 RebuildAtlas(*atlas);
311 }
312}
313
315 // Clean up SDL textures
316 for (auto& atlas : atlases_) {
317 if (atlas->texture) {
318 renderer_->DestroyTexture(atlas->texture);
319 }
320 }
321
322 atlases_.clear();
323 atlas_lookup_.clear();
324 next_atlas_id_ = 0;
325 current_atlas_ = 0;
326}
327
331
332void AtlasRenderer::RenderBitmap(int atlas_id, float x, float y, float scale_x,
333 float scale_y) {
334 auto it = atlas_lookup_.find(atlas_id);
335 if (it == atlas_lookup_.end() || !it->second->in_use) {
336 return;
337 }
338
339 AtlasEntry* entry = it->second;
340
341 // Find which atlas contains this entry
342 for (auto& atlas : atlases_) {
343 for (const auto& atlas_entry : atlas->entries) {
344 if (atlas_entry.atlas_id == atlas_id) {
345 // Calculate destination rectangle
346 SDL_Rect dest_rect = {static_cast<int>(x), static_cast<int>(y),
347 static_cast<int>(entry->uv_rect.w * scale_x),
348 static_cast<int>(entry->uv_rect.h * scale_y)};
349
350 // Render using atlas texture
351 // SDL_SetTextureBlendMode(atlas->texture, SDL_BLENDMODE_BLEND);
352 renderer_->RenderCopy(atlas->texture, &entry->uv_rect, &dest_rect);
353 return;
354 }
355 }
356 }
357}
358
359SDL_Rect AtlasRenderer::GetUVCoordinates(int atlas_id) const {
360 auto it = atlas_lookup_.find(atlas_id);
361 if (it == atlas_lookup_.end() || !it->second->in_use) {
362 return {0, 0, 0, 0};
363 }
364
365 return it->second->uv_rect;
366}
367
368bool AtlasRenderer::PackBitmap(Atlas& atlas, const Bitmap& bitmap,
369 SDL_Rect& uv_rect) {
370 int width = bitmap.width();
371 int height = bitmap.height();
372
373 // Find free region
374 SDL_Rect free_rect = FindFreeRegion(atlas, width, height);
375 if (free_rect.w == 0 || free_rect.h == 0) {
376 return false; // No space available
377 }
378
379 // Mark region as used
380 MarkRegionUsed(atlas, free_rect, true);
381
382 // Set UV coordinates (normalized to 0-1 range)
383 uv_rect = {free_rect.x, free_rect.y, width, height};
384
385 return true;
386}
387
389 int size = 1024; // Default size
390 if (!atlases_.empty()) {
391 size = atlases_.back()->size * 2; // Double size for new atlas
392 }
393
394 atlases_.push_back(std::make_unique<Atlas>(size));
395 current_atlas_ = atlases_.size() - 1;
396
397 // Create SDL texture for the atlas
398 auto& atlas = *atlases_[current_atlas_];
399 atlas.texture = renderer_->CreateTexture(size, size);
400
401 if (!atlas.texture) {
402 SDL_Log("Failed to create atlas texture: %s", SDL_GetError());
403 }
404}
405
407 // Clear used regions
408 std::fill(atlas.used_regions.begin(), atlas.used_regions.end(), false);
409
410 // Rebuild atlas texture by copying from source textures
412 renderer_->SetDrawColor({0, 0, 0, 0});
413 renderer_->Clear();
414
415 for (auto& entry : atlas.entries) {
416 if (entry.in_use && entry.texture) {
417 renderer_->RenderCopy(entry.texture, nullptr, &entry.uv_rect);
418 MarkRegionUsed(atlas, entry.uv_rect, true);
419 }
420 }
421
422 renderer_->SetRenderTarget(nullptr);
423}
424
425SDL_Rect AtlasRenderer::FindFreeRegion(Atlas& atlas, int width, int height) {
426 // Simple first-fit algorithm
427 for (int y = 0; y <= atlas.size - height; ++y) {
428 for (int x = 0; x <= atlas.size - width; ++x) {
429 bool can_fit = true;
430
431 // Check if region is free
432 for (int dy = 0; dy < height && can_fit; ++dy) {
433 for (int dx = 0; dx < width && can_fit; ++dx) {
434 int index = (y + dy) * atlas.size + (x + dx);
435 if (index >= static_cast<int>(atlas.used_regions.size()) ||
436 atlas.used_regions[index]) {
437 can_fit = false;
438 }
439 }
440 }
441
442 if (can_fit) {
443 return {x, y, width, height};
444 }
445 }
446 }
447
448 return {0, 0, 0, 0}; // No space found
449}
450
451void AtlasRenderer::MarkRegionUsed(Atlas& atlas, const SDL_Rect& rect,
452 bool used) {
453 for (int y = rect.y; y < rect.y + rect.h; ++y) {
454 for (int x = rect.x; x < rect.x + rect.w; ++x) {
455 int index = y * atlas.size + x;
456 if (index >= 0 && index < static_cast<int>(atlas.used_regions.size())) {
457 atlas.used_regions[index] = used;
458 }
459 }
460 }
461}
462
463} // namespace gfx
464} // namespace yaze
Atlas-based rendering system for efficient graphics operations.
SDL_Rect FindFreeRegion(Atlas &atlas, int width, int height)
AtlasStats GetStats() const
Get atlas statistics.
bool PackBitmap(Atlas &atlas, const Bitmap &bitmap, SDL_Rect &uv_rect)
void UpdateBitmap(int atlas_id, const Bitmap &bitmap)
Update a bitmap in the atlas.
std::vector< std::unique_ptr< Atlas > > atlases_
void RebuildAtlas(Atlas &atlas)
void Clear()
Clear all atlases.
int AddBitmapWithBppOptimization(const Bitmap &bitmap, BppFormat target_bpp)
Add a bitmap to the atlas with BPP format optimization.
void RenderBitmap(int atlas_id, float x, float y, float scale_x=1.0f, float scale_y=1.0f)
Render a single bitmap using atlas (convenience method)
void MarkRegionUsed(Atlas &atlas, const SDL_Rect &rect, bool used)
std::unordered_map< int, AtlasEntry * > atlas_lookup_
void RenderBatch(const std::vector< RenderCommand > &render_commands)
Render multiple bitmaps in a single draw call.
void RemoveBitmap(int atlas_id)
Remove a bitmap from the atlas.
void Defragment()
Defragment the atlas to reclaim space.
SDL_Rect GetUVCoordinates(int atlas_id) const
Get UV coordinates for a bitmap in the atlas.
int AddBitmap(const Bitmap &bitmap)
Add a bitmap to the atlas.
static AtlasRenderer & Get()
void Initialize(IRenderer *renderer, int initial_size=1024)
Initialize the atlas renderer.
void RenderBatchWithBppOptimization(const std::vector< RenderCommand > &render_commands, const std::unordered_map< BppFormat, std::vector< int > > &bpp_groups)
Render multiple bitmaps with BPP-aware batching.
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
const SnesPalette & palette() const
Definition bitmap.h:368
TextureHandle texture() const
Definition bitmap.h:380
const std::vector< uint8_t > & vector() const
Definition bitmap.h:381
void CreateTexture()
Creates the underlying SDL_Texture to be displayed.
Definition bitmap.cc:291
bool is_active() const
Definition bitmap.h:384
int height() const
Definition bitmap.h:374
int width() const
Definition bitmap.h:373
int depth() const
Definition bitmap.h:375
BppFormat DetectFormat(const std::vector< uint8_t > &data, int width, int height)
Detect BPP format from bitmap data.
std::vector< uint8_t > ConvertFormat(const std::vector< uint8_t > &data, BppFormat from_format, BppFormat to_format, int width, int height)
Convert bitmap data between BPP formats.
static BppFormatManager & Get()
Defines an abstract interface for all rendering operations.
Definition irenderer.h:40
virtual TextureHandle CreateTexture(int width, int height)=0
Creates a new, empty texture.
virtual void RenderCopy(TextureHandle texture, const SDL_Rect *srcrect, const SDL_Rect *dstrect)=0
Copies a portion of a texture to the current render target.
virtual void SetDrawColor(SDL_Color color)=0
Sets the color used for drawing operations (e.g., Clear).
virtual void Clear()=0
Clears the entire render target with the current draw color.
virtual void DestroyTexture(TextureHandle texture)=0
Destroys a texture and frees its associated resources.
virtual void SetRenderTarget(TextureHandle texture)=0
Sets the render target for subsequent drawing operations.
RAII timer for automatic timing management.
BppFormat
BPP format enumeration for SNES graphics.
std::vector< AtlasEntry > entries
Atlas usage statistics.