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