yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
canvas_rendering.cc
Go to the documentation of this file.
1#include "canvas_rendering.h"
2
3#include <algorithm>
4#include <cmath>
5
9#include "util/log.h"
10
11namespace yaze {
12namespace gui {
13
14namespace {
15
16// Diagnostic for the silent guard inside RenderBitmapOnCanvas. The renderer
17// drops a draw when the bitmap has no SDL texture (typical for a first-frame
18// composite whose CREATE command is queued but not yet processed). Without
19// this log the symptom is a blank canvas with no breadcrumb. The metadata
20// purpose tag pinned in slice 3 is what makes this log actionable: the call
21// site already declared what the bitmap IS, so the log can name it.
22inline const char* PurposeLabel(gfx::Bitmap::BitmapPurpose purpose) {
23 switch (purpose) {
25 return "preview";
27 return "editable";
29 return "selection-source";
31 return "composite-output";
32 default:
33 return "unspecified";
34 }
35}
36
37inline void LogBitmapDropIfDebug(const gfx::Bitmap& bitmap, const char* site) {
38 LOG_DEBUG("CanvasRender",
39 "Skipped %s: bitmap purpose=%s active=%d size=%dx%d (no texture)",
40 site, PurposeLabel(bitmap.metadata().purpose), bitmap.is_active(),
41 bitmap.width(), bitmap.height());
42}
43
44} // namespace
45
46void RenderCanvasBackground(ImDrawList* draw_list,
47 const CanvasGeometry& geometry) {
48 const auto& theme = ThemeManager::Get().GetCurrentTheme();
49
50 // Draw primary background color from theme
51 draw_list->AddRectFilled(
52 geometry.canvas_p0, geometry.canvas_p1,
53 ImGui::GetColorU32(ConvertColorToImVec4(theme.editor_background)));
54
55 // Add a subtle pattern to the background (e.g., very faint dots)
56 if (theme.enable_glow_effects) {
57 const float dot_spacing = 32.0f;
58 const uint32_t dot_color =
59 ImGui::GetColorU32(ConvertColorToImVec4(theme.editor_grid));
60
61 for (float x =
62 geometry.canvas_p0.x + fmodf(geometry.scrolling.x, dot_spacing);
63 x < geometry.canvas_p1.x; x += dot_spacing) {
64 for (float y =
65 geometry.canvas_p0.y + fmodf(geometry.scrolling.y, dot_spacing);
66 y < geometry.canvas_p1.y; y += dot_spacing) {
67 draw_list->AddCircleFilled(ImVec2(x, y), 1.0f, dot_color);
68 }
69 }
70 }
71
72 // Draw theme-aware border
73 draw_list->AddRect(
74 geometry.canvas_p0, geometry.canvas_p1,
75 ImGui::GetColorU32(ConvertColorToImVec4(theme.border)), 0.0f,
76 theme.window_border_size > 0 ? theme.window_border_size : 1.0f);
77}
78
79void RenderCanvasGrid(ImDrawList* draw_list, const CanvasGeometry& geometry,
80 const CanvasConfig& config, int highlight_tile_id) {
81 if (!config.enable_grid) {
82 return;
83 }
84
85 // Create render context for utility functions (extracted from
86 // Canvas::DrawGrid)
88 .draw_list = draw_list,
89 .canvas_p0 = geometry.canvas_p0,
90 .canvas_p1 = geometry.canvas_p1,
91 .scrolling = geometry.scrolling,
92 .global_scale = config.global_scale,
93 .enable_grid = config.enable_grid,
94 .enable_hex_labels = config.enable_hex_labels,
95 .grid_step = config.grid_step};
96
97 // Use high-level utility function
98 CanvasUtils::DrawCanvasGrid(ctx, highlight_tile_id);
99}
100
101void RenderCanvasOverlay(ImDrawList* draw_list, const CanvasGeometry& geometry,
102 const CanvasConfig& config,
103 const ImVector<ImVec2>& points,
104 const ImVector<ImVec2>& selected_points) {
105 // Create render context for utility functions (extracted from
106 // Canvas::DrawOverlay)
108 .draw_list = draw_list,
109 .canvas_p0 = geometry.canvas_p0,
110 .canvas_p1 = geometry.canvas_p1,
111 .scrolling = geometry.scrolling,
112 .global_scale = config.global_scale,
113 .enable_grid = config.enable_grid,
114 .enable_hex_labels = config.enable_hex_labels,
115 .grid_step = config.grid_step};
116
117 // Use high-level utility function
118 CanvasUtils::DrawCanvasOverlay(ctx, points, selected_points);
119}
120
121void RenderCanvasLabels(ImDrawList* draw_list, const CanvasGeometry& geometry,
122 const CanvasConfig& config,
123 const ImVector<ImVector<std::string>>& labels,
124 int current_labels, int tile_id_offset) {
125 if (!config.enable_custom_labels || current_labels >= labels.size()) {
126 return;
127 }
128
129 // Push clip rect to prevent drawing outside canvas
130 draw_list->PushClipRect(geometry.canvas_p0, geometry.canvas_p1, true);
131
132 // Create render context for utility functions
134 .draw_list = draw_list,
135 .canvas_p0 = geometry.canvas_p0,
136 .canvas_p1 = geometry.canvas_p1,
137 .scrolling = geometry.scrolling,
138 .global_scale = config.global_scale,
139 .enable_grid = config.enable_grid,
140 .enable_hex_labels = config.enable_hex_labels,
141 .grid_step = config.grid_step};
142
143 // Use high-level utility function (extracted from Canvas::DrawInfoGrid)
144 CanvasUtils::DrawCanvasLabels(ctx, labels, current_labels, tile_id_offset);
145
146 draw_list->PopClipRect();
147}
148
149void RenderBitmapOnCanvas(ImDrawList* draw_list, const CanvasGeometry& geometry,
150 gfx::Bitmap& bitmap, int /*border_offset*/,
151 float scale) {
152 if (!bitmap.is_active() || !bitmap.texture()) {
153 LogBitmapDropIfDebug(bitmap, "RenderBitmapOnCanvas(border)");
154 return;
155 }
156
157 // Extracted from Canvas::DrawBitmap (border offset variant)
158 ScopedNearestSampling nearest_sampling(draw_list);
159 draw_list->AddImage((ImTextureID)(intptr_t)bitmap.texture(),
160 ImVec2(geometry.canvas_p0.x, geometry.canvas_p0.y),
161 ImVec2(geometry.canvas_p0.x + (bitmap.width() * scale),
162 geometry.canvas_p0.y + (bitmap.height() * scale)),
163 ImVec2(0, 0), ImVec2(1, 1),
164 ImGui::GetColorU32(ImVec4(1, 1, 1, 1)));
165 // NOTE: Canvas border is drawn once by RenderCanvasBackground().
166 // Do NOT draw border here - this function is called per-bitmap, which would
167 // cause N stacked border rectangles (e.g., 64 in overworld editor).
168}
169
170void RenderBitmapOnCanvas(ImDrawList* draw_list, const CanvasGeometry& geometry,
171 gfx::Bitmap& bitmap, int x_offset, int y_offset,
172 float scale, int alpha) {
173 if (!bitmap.is_active() || !bitmap.texture()) {
174 LogBitmapDropIfDebug(bitmap, "RenderBitmapOnCanvas(offset)");
175 return;
176 }
177
178 // Calculate the actual rendered size including scale and offsets
179 // CRITICAL: Use scale parameter (NOT global_scale_) for per-bitmap scaling
180 // Extracted from Canvas::DrawBitmap (x/y offset variant)
181 ImVec2 rendered_size(bitmap.width() * scale, bitmap.height() * scale);
182
183 // CRITICAL FIX: Draw bitmap WITHOUT additional global_scale multiplication
184 // The scale parameter already contains the correct scale factor
185 // The scrolling should NOT be scaled - it's already in screen space
186 ScopedNearestSampling nearest_sampling(draw_list);
187 draw_list->AddImage(
188 (ImTextureID)(intptr_t)bitmap.texture(),
189 ImVec2(geometry.canvas_p0.x + x_offset + geometry.scrolling.x,
190 geometry.canvas_p0.y + y_offset + geometry.scrolling.y),
191 ImVec2(geometry.canvas_p0.x + x_offset + geometry.scrolling.x +
192 rendered_size.x,
193 geometry.canvas_p0.y + y_offset + geometry.scrolling.y +
194 rendered_size.y),
195 ImVec2(0, 0), ImVec2(1, 1),
196 ImGui::GetColorU32(IM_COL32(255, 255, 255, alpha)));
197}
198
199void RenderBitmapOnCanvas(ImDrawList* draw_list, const CanvasGeometry& geometry,
200 gfx::Bitmap& bitmap, ImVec2 dest_pos,
201 ImVec2 dest_size, ImVec2 src_pos, ImVec2 src_size) {
202 if (!bitmap.is_active() || !bitmap.texture()) {
203 LogBitmapDropIfDebug(bitmap, "RenderBitmapOnCanvas(custom-uv)");
204 return;
205 }
206
207 // Extracted from Canvas::DrawBitmap (custom source/dest regions variant)
208 ScopedNearestSampling nearest_sampling(draw_list);
209 draw_list->AddImage(
210 (ImTextureID)(intptr_t)bitmap.texture(),
211 ImVec2(geometry.canvas_p0.x + dest_pos.x,
212 geometry.canvas_p0.y + dest_pos.y),
213 ImVec2(geometry.canvas_p0.x + dest_pos.x + dest_size.x,
214 geometry.canvas_p0.y + dest_pos.y + dest_size.y),
215 ImVec2(src_pos.x / bitmap.width(), src_pos.y / bitmap.height()),
216 ImVec2((src_pos.x + src_size.x) / bitmap.width(),
217 (src_pos.y + src_size.y) / bitmap.height()),
218 ImGui::GetColorU32(ImVec4(1, 1, 1, 1)));
219}
220
221void RenderBitmapGroup(ImDrawList* draw_list, const CanvasGeometry& geometry,
222 std::vector<int>& group, gfx::Tilemap& tilemap,
223 int tile_size, float scale, int local_map_size,
224 ImVec2 total_map_size) {
225 // Extracted from Canvas::DrawBitmapGroup (lines 1148-1264)
226 // This is used for multi-tile selection preview in overworld editor
227
228 if (group.empty()) {
229 return;
230 }
231
232 // OPTIMIZATION: Use optimized rendering for large groups to improve
233 // performance
234 bool use_optimized_rendering = group.size() > 128;
235
236 // Pre-calculate common values to avoid repeated computation
237 const float tile_scale = tile_size * scale;
238 const int atlas_tiles_per_row = tilemap.atlas.width() / tilemap.tile_size.x;
239
240 // Get selected points (note: this assumes selected_points are available in
241 // context) For now, we'll just render tiles at their grid positions The full
242 // implementation would need the selected_points passed in
243
244 // One scope for the whole loop: the atlas tiles share a texture, so their
245 // images stay in one draw command between the two sampler callbacks.
246 ScopedNearestSampling nearest_sampling(draw_list);
247 int i = 0;
248 for (const auto tile_id : group) {
249 // Calculate grid position for this tile
250 int tiles_per_row = 32; // Default for standard maps
251 int x = i % tiles_per_row;
252 int y = i / tiles_per_row;
253
254 int tile_pos_x = x * tile_size * scale;
255 int tile_pos_y = y * tile_size * scale;
256
257 // Check if tile_id is within the range
258 auto tilemap_size = tilemap.map_size.x;
259 if (tile_id >= 0 && tile_id < tilemap_size) {
260 if (tilemap.atlas.is_active() && tilemap.atlas.texture() &&
261 atlas_tiles_per_row > 0) {
262 int atlas_tile_x =
263 (tile_id % atlas_tiles_per_row) * tilemap.tile_size.x;
264 int atlas_tile_y =
265 (tile_id / atlas_tiles_per_row) * tilemap.tile_size.y;
266
267 // Simple bounds check
268 if (atlas_tile_x >= 0 && atlas_tile_x < tilemap.atlas.width() &&
269 atlas_tile_y >= 0 && atlas_tile_y < tilemap.atlas.height()) {
270 // Calculate UV coordinates once for efficiency
271 const float atlas_width = static_cast<float>(tilemap.atlas.width());
272 const float atlas_height = static_cast<float>(tilemap.atlas.height());
273 ImVec2 uv0 =
274 ImVec2(atlas_tile_x / atlas_width, atlas_tile_y / atlas_height);
275 ImVec2 uv1 =
276 ImVec2((atlas_tile_x + tilemap.tile_size.x) / atlas_width,
277 (atlas_tile_y + tilemap.tile_size.y) / atlas_height);
278
279 // Calculate screen positions
280 float screen_x =
281 geometry.canvas_p0.x + geometry.scrolling.x + tile_pos_x;
282 float screen_y =
283 geometry.canvas_p0.y + geometry.scrolling.y + tile_pos_y;
284 float screen_w = tilemap.tile_size.x * scale;
285 float screen_h = tilemap.tile_size.y * scale;
286
287 // Use higher alpha for large selections to make them more visible
288 uint32_t alpha_color = use_optimized_rendering
289 ? IM_COL32(255, 255, 255, 200)
290 : IM_COL32(255, 255, 255, 150);
291
292 // Draw from atlas texture with optimized parameters
293 draw_list->AddImage((ImTextureID)(intptr_t)tilemap.atlas.texture(),
294 ImVec2(screen_x, screen_y),
295 ImVec2(screen_x + screen_w, screen_y + screen_h),
296 uv0, uv1, ImGui::GetColorU32(alpha_color));
297 }
298 }
299 }
300 i++;
301 }
302}
303
304} // namespace gui
305} // namespace yaze
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:70
TextureHandle texture() const
Definition bitmap.h:410
BitmapPurpose
How a bitmap is used in the editor pipeline.
Definition bitmap.h:364
BitmapMetadata & metadata()
Definition bitmap.h:395
bool is_active() const
Definition bitmap.h:414
int height() const
Definition bitmap.h:399
int width() const
Definition bitmap.h:398
RAII scope that samples the images drawn inside it with nearest-neighbor filtering.
const Theme & GetCurrentTheme() const
static ThemeManager & Get()
#define LOG_DEBUG(category, format,...)
Definition log.h:133
void DrawCanvasLabels(const CanvasRenderContext &ctx, const ImVector< ImVector< std::string > > &labels, int current_labels, int tile_id_offset)
void DrawCanvasOverlay(const CanvasRenderContext &ctx, const ImVector< ImVec2 > &points, const ImVector< ImVec2 > &selected_points)
void DrawCanvasGrid(const CanvasRenderContext &ctx, int highlight_tile_id)
void LogBitmapDropIfDebug(const gfx::Bitmap &bitmap, const char *site)
const char * PurposeLabel(gfx::Bitmap::BitmapPurpose purpose)
ImVec4 ConvertColorToImVec4(const Color &color)
Definition color.h:140
void RenderBitmapGroup(ImDrawList *draw_list, const CanvasGeometry &geometry, std::vector< int > &group, gfx::Tilemap &tilemap, int tile_size, float scale, int local_map_size, ImVec2 total_map_size)
Render group of bitmaps from tilemap.
void RenderCanvasLabels(ImDrawList *draw_list, const CanvasGeometry &geometry, const CanvasConfig &config, const ImVector< ImVector< std::string > > &labels, int current_labels, int tile_id_offset)
Render canvas labels on grid.
void RenderCanvasBackground(ImDrawList *draw_list, const CanvasGeometry &geometry)
Render canvas background and border.
void RenderCanvasGrid(ImDrawList *draw_list, const CanvasGeometry &geometry, const CanvasConfig &config, int highlight_tile_id)
Render canvas grid with optional highlighting.
void RenderBitmapOnCanvas(ImDrawList *draw_list, const CanvasGeometry &geometry, gfx::Bitmap &bitmap, int, float scale)
Render bitmap on canvas (border offset variant)
void RenderCanvasOverlay(ImDrawList *draw_list, const CanvasGeometry &geometry, const CanvasConfig &config, const ImVector< ImVec2 > &points, const ImVector< ImVec2 > &selected_points)
Render canvas overlay (hover and selection points)
int y
Y coordinate or height.
Definition tilemap.h:21
int x
X coordinate or width.
Definition tilemap.h:20
Tilemap structure for SNES tile-based graphics management.
Definition tilemap.h:118
Pair tile_size
Size of individual tiles (8x8 or 16x16)
Definition tilemap.h:123
Pair map_size
Size of tilemap in tiles.
Definition tilemap.h:124
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:119
Unified configuration for canvas display and interaction.
Canvas geometry calculated per-frame.