yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
background_buffer.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstdint>
5#include <vector>
6
9#include "util/log.h"
10
11namespace yaze::gfx {
12
14 : width_(width), height_(height) {}
15
17 const size_t total_tiles = static_cast<size_t>((width_ / 8) * (height_ / 8));
18 if (buffer_.size() != total_tiles) {
19 buffer_.assign(total_tiles, 0);
20 }
21}
22
24 const size_t total_pixels = static_cast<size_t>(width_ * height_);
25 if (priority_buffer_.size() != total_pixels) {
26 priority_buffer_.assign(total_pixels, 0xFF);
27 }
28}
29
31 const size_t total_pixels = static_cast<size_t>(width_ * height_);
32 if (coverage_buffer_.size() != total_pixels) {
33 coverage_buffer_.assign(total_pixels, 0);
34 }
35}
36
38 const size_t total_pixels = static_cast<size_t>(width_ * height_);
39 if (bg1_reveal_mask_buffer_.size() != total_pixels) {
40 bg1_reveal_mask_buffer_.assign(total_pixels, 0);
41 }
42}
43
48
53
58
59void BackgroundBuffer::SetTileAt(int x_pos, int y_pos, uint16_t value) {
60 if (x_pos < 0 || y_pos < 0) {
61 return;
62 }
63 int tiles_w = width_ / 8;
64 int tiles_h = height_ / 8;
65 if (x_pos >= tiles_w || y_pos >= tiles_h) {
66 return;
67 }
69 buffer_[y_pos * tiles_w + x_pos] = value;
70}
71
72uint16_t BackgroundBuffer::GetTileAt(int x_pos, int y_pos) const {
73 int tiles_w = width_ / 8;
74 int tiles_h = height_ / 8;
75 if (x_pos < 0 || y_pos < 0 || x_pos >= tiles_w || y_pos >= tiles_h) {
76 return 0;
77 }
78 const size_t index = static_cast<size_t>(y_pos * tiles_w + x_pos);
79 if (index >= buffer_.size()) {
80 return 0;
81 }
82 return buffer_[index];
83}
84
86 if (!buffer_.empty()) {
87 std::fill(buffer_.begin(), buffer_.end(), 0);
88 }
92}
93
95 // 0xFF indicates no priority set (transparent/empty pixel)
96 if (!priority_buffer_.empty()) {
97 std::fill(priority_buffer_.begin(), priority_buffer_.end(), 0xFF);
98 }
99}
100
102 // 0 indicates the layer never wrote here; 1 indicates it did.
103 if (!coverage_buffer_.empty()) {
104 std::fill(coverage_buffer_.begin(), coverage_buffer_.end(), 0);
105 }
106}
107
109 if (!bg1_reveal_mask_buffer_.empty()) {
110 std::fill(bg1_reveal_mask_buffer_.begin(), bg1_reveal_mask_buffer_.end(),
111 0);
112 }
113}
114
116 if (bg1_reveal_mask_buffer_.empty()) {
117 return;
118 }
119 const uint8_t keep_mask = static_cast<uint8_t>(~static_cast<uint8_t>(source));
120 for (auto& value : bg1_reveal_mask_buffer_) {
121 value &= keep_mask;
122 }
123}
124
126 int start_x, int start_y,
127 int width, int height) {
128 if (bg1_reveal_mask_buffer_.empty() || width <= 0 || height <= 0) {
129 return;
130 }
131 const uint8_t keep_mask = static_cast<uint8_t>(~static_cast<uint8_t>(source));
132 const int end_x = std::min(start_x + width, width_);
133 const int end_y = std::min(start_y + height, height_);
134 for (int y = std::max(start_y, 0); y < end_y; ++y) {
135 for (int x = std::max(start_x, 0); x < end_x; ++x) {
136 bg1_reveal_mask_buffer_[static_cast<size_t>(y * width_ + x)] &= keep_mask;
137 }
138 }
139}
140
142 int start_x, int start_y, int width,
143 int height) {
144 if (width <= 0 || height <= 0) {
145 return;
146 }
148 const uint8_t source_mask = static_cast<uint8_t>(source);
149 const int end_x = std::min(start_x + width, width_);
150 const int end_y = std::min(start_y + height, height_);
151 for (int y = std::max(start_y, 0); y < end_y; ++y) {
152 for (int x = std::max(start_x, 0); x < end_x; ++x) {
153 bg1_reveal_mask_buffer_[static_cast<size_t>(y * width_ + x)] |=
154 source_mask;
155 }
156 }
157}
158
159uint8_t BackgroundBuffer::GetPriorityAt(int x, int y) const {
160 if (x < 0 || y < 0 || x >= width_ || y >= height_) {
161 return 0xFF; // Out of bounds = no priority
162 }
163 return priority_buffer_[y * width_ + x];
164}
165
166void BackgroundBuffer::SetPriorityAt(int x, int y, uint8_t priority) {
167 if (x < 0 || y < 0 || x >= width_ || y >= height_) {
168 return;
169 }
171 priority_buffer_[y * width_ + x] = priority;
172}
173
175 if (!bitmap_.is_active() || bitmap_.width() == 0) {
176 // IMPORTANT: Initialize to 255 (transparent fill), NOT 0!
177 // In dungeon rendering, pixel value 0 represents palette[0] (actual color).
178 // Only 255 is treated as transparent by IsTransparent().
180 std::vector<uint8_t>(width_ * height_, 255));
181
182 // Fix: Set index 255 to be transparent so the background is actually transparent
183 // instead of white (default SDL palette index 255)
184 std::vector<SDL_Color> palette(256);
185 // Initialize with grayscale for debugging
186 for (int i = 0; i < 256; i++) {
187 palette[i] = {static_cast<Uint8>(i), static_cast<Uint8>(i),
188 static_cast<Uint8>(i), 255};
189 }
190 // Set index 255 to transparent
191 palette[255] = {0, 0, 0, 0};
192 bitmap_.SetPalette(palette);
193
194 // Enable blending
195 if (bitmap_.surface()) {
196 SDL_SetSurfaceBlendMode(bitmap_.surface(), SDL_BLENDMODE_BLEND);
197 }
198 }
199
202}
203
204void BackgroundBuffer::DrawTile(const TileInfo& tile, uint8_t* canvas,
205 const uint8_t* tiledata, int indexoffset) {
206 // tiledata is now 8BPP linear data (1 byte per pixel)
207 // Buffer size: 0x10000 (65536 bytes) = 64 tile rows max
208 constexpr int kGfxBufferSize = 0x10000;
209 constexpr int kMaxTileRow = 63; // 64 rows (0-63), each 1024 bytes
210
211 // Calculate tile position in the 8BPP buffer
212 int tile_col_idx = tile.id_ % 16;
213 int tile_row_idx = tile.id_ / 16;
214
215 // CRITICAL: Validate tile_row to prevent index out of bounds
216 if (tile_row_idx > kMaxTileRow) {
217 return; // Skip invalid tiles silently
218 }
219
220 int tile_base_x = tile_col_idx * 8; // 8 pixels wide (8 bytes)
221 int tile_base_y =
222 tile_row_idx * 1024; // 8 rows * 128 bytes stride (sheet width)
223
224 // Palette offset: tile palette field (3-bit, 0-7) is the CGRAM row index.
225 // SDL palette mirrors CGRAM directly: dungeon view loads banks 2-7 in
226 // Room::RenderRoomGraphics, leaving banks 0-1 as HUD placeholders.
227 // See object_drawer.cc for the canonical comment.
228 const uint8_t pal = tile.palette_ & 0x07;
229 const uint8_t palette_offset = static_cast<uint8_t>(pal * 16);
230
231 // Pre-calculate max valid destination index
232 int max_dest = width_ * height_;
233
234 // Get priority bit from tile (over_ = priority bit in SNES tilemap)
235 uint8_t priority = tile.over_ ? 1 : 0;
236
237 // Copy 8x8 pixels
238 for (int py = 0; py < 8; py++) {
239 int src_row = tile.vertical_mirror_ ? (7 - py) : py;
240
241 for (int px = 0; px < 8; px++) {
242 int src_col = tile.horizontal_mirror_ ? (7 - px) : px;
243
244 // Calculate source index
245 // Stride is 128 bytes (sheet width)
246 int src_index = (src_row * 128) + src_col + tile_base_x + tile_base_y;
247
248 // Bounds check source
249 if (src_index < 0 || src_index >= kGfxBufferSize)
250 continue;
251
252 uint8_t pixel = tiledata[src_index];
253
254 if (pixel != 0) {
255 // Pixel 0 is transparent (not written). Pixels 1-15 map to bank indices 1-15.
256 // With 16-color bank chunking: final_color = pixel + (bank * 16)
257 uint8_t final_color = pixel + palette_offset;
258 int dest_index = indexoffset + (py * width_) + px;
259
260 // Bounds check destination
261 if (dest_index >= 0 && dest_index < max_dest) {
262 canvas[dest_index] = final_color;
263 // Also store priority for this pixel
264 priority_buffer_[dest_index] = priority;
265 }
266 }
267 }
268 }
269}
270
271void BackgroundBuffer::DrawBackground(std::span<uint8_t> gfx16_data) {
272 int tiles_w = width_ / 8;
273 int tiles_h = height_ / 8;
275
276 // NEVER recreate bitmap here - it should be created by DrawFloor or
277 // initialized earlier. If bitmap doesn't exist, create it ONCE with 255 fill
278 // IMPORTANT: Use 255 (transparent), NOT 0! Pixel value 0 = palette[0] (actual color)
279 if (!bitmap_.is_active() || bitmap_.width() == 0) {
281 std::vector<uint8_t>(width_ * height_, 255));
282 }
283
285
286 // For each tile on the tile buffer
287 // int drawn_count = 0;
288 // int skipped_count = 0;
289 for (int yy = 0; yy < tiles_h; yy++) {
290 for (int xx = 0; xx < tiles_w; xx++) {
291 uint16_t word = buffer_[xx + yy * tiles_w];
292
293 // Skip empty tiles (0xFFFF) - these show the floor
294 if (word == 0xFFFF) {
295 // skipped_count++;
296 continue;
297 }
298
299 // Skip zero tiles - also show the floor
300 if (word == 0) {
301 // skipped_count++;
302 continue;
303 }
304
305 auto tile = gfx::WordToTileInfo(word);
306
307 // Skip floor tiles (0xEC-0xFD) - don't overwrite DrawFloor's work
308 // These are the animated floor tiles, already drawn by DrawFloor
309 // Skip floor tiles (0xEC-0xFD) - don't overwrite DrawFloor's work
310 // These are the animated floor tiles, already drawn by DrawFloor
311 // if (tile.id_ >= 0xEC && tile.id_ <= 0xFD) {
312 // skipped_count++;
313 // continue;
314 // }
315
316 // Calculate pixel offset for tile position (xx, yy) in the 512x512 bitmap
317 // Each tile is 8x8, so pixel Y = yy * 8, pixel X = xx * 8
318 // Linear offset = (pixel_y * width) + pixel_x = (yy * 8 * 512) + (xx * 8)
319 int tile_offset = (yy * 8 * width_) + (xx * 8);
320 DrawTile(tile, bitmap_.mutable_data().data(), gfx16_data.data(),
321 tile_offset);
322 // drawn_count++;
323 }
324 }
325 // CRITICAL: Sync bitmap data back to SDL surface!
326 // DrawTile() writes to bitmap_.mutable_data(), but the SDL surface needs
327 // updating
328 if (bitmap_.surface() && bitmap_.mutable_data().size() > 0) {
329 SDL_LockSurface(bitmap_.surface());
330 memcpy(bitmap_.surface()->pixels, bitmap_.mutable_data().data(),
331 bitmap_.mutable_data().size());
332 SDL_UnlockSurface(bitmap_.surface());
333 }
334}
335
336void BackgroundBuffer::DrawFloor(const std::vector<uint8_t>& rom_data,
337 int tile_address, int tile_address_floor,
338 uint8_t floor_graphics) {
339 // Create bitmap ONCE at the start if it doesn't exist
340 // IMPORTANT: Use 255 (transparent fill), NOT 0! Pixel value 0 = palette[0] (actual color)
341 if (!bitmap_.is_active() || bitmap_.width() == 0) {
342 LOG_DEBUG("[DrawFloor]", "Creating bitmap: %dx%d, active=%d, width=%d",
345 std::vector<uint8_t>(width_ * height_, 255));
346 LOG_DEBUG("[DrawFloor]", "After Create: active=%d, width=%d, height=%d",
348 } else {
349 LOG_DEBUG("[DrawFloor]",
350 "Bitmap already exists: active=%d, width=%d, height=%d",
352 }
353
354 auto floor_offset = static_cast<uint8_t>(floor_graphics << 4);
355
356 // Create floor tiles from ROM data
357 gfx::TileInfo floorTile1(rom_data[tile_address + floor_offset],
358 rom_data[tile_address + floor_offset + 1]);
359 gfx::TileInfo floorTile2(rom_data[tile_address + floor_offset + 2],
360 rom_data[tile_address + floor_offset + 3]);
361 gfx::TileInfo floorTile3(rom_data[tile_address + floor_offset + 4],
362 rom_data[tile_address + floor_offset + 5]);
363 gfx::TileInfo floorTile4(rom_data[tile_address + floor_offset + 6],
364 rom_data[tile_address + floor_offset + 7]);
365
366 gfx::TileInfo floorTile5(rom_data[tile_address_floor + floor_offset],
367 rom_data[tile_address_floor + floor_offset + 1]);
368 gfx::TileInfo floorTile6(rom_data[tile_address_floor + floor_offset + 2],
369 rom_data[tile_address_floor + floor_offset + 3]);
370 gfx::TileInfo floorTile7(rom_data[tile_address_floor + floor_offset + 4],
371 rom_data[tile_address_floor + floor_offset + 5]);
372 gfx::TileInfo floorTile8(rom_data[tile_address_floor + floor_offset + 6],
373 rom_data[tile_address_floor + floor_offset + 7]);
374
375 // Floor tiles specify which 8-color sub-palette from the 90-color dungeon
376 // palette e.g., palette 6 = colors 48-55 (6 * 8 = 48)
377
378 // Draw the floor tiles in a pattern
379 // Convert TileInfo to 16-bit words with palette information
380 uint16_t word1 = gfx::TileInfoToWord(floorTile1);
381 uint16_t word2 = gfx::TileInfoToWord(floorTile2);
382 uint16_t word3 = gfx::TileInfoToWord(floorTile3);
383 uint16_t word4 = gfx::TileInfoToWord(floorTile4);
384 uint16_t word5 = gfx::TileInfoToWord(floorTile5);
385 uint16_t word6 = gfx::TileInfoToWord(floorTile6);
386 uint16_t word7 = gfx::TileInfoToWord(floorTile7);
387 uint16_t word8 = gfx::TileInfoToWord(floorTile8);
388 for (int xx = 0; xx < 16; xx++) {
389 for (int yy = 0; yy < 32; yy++) {
390 SetTileAt((xx * 4), (yy * 2), word1);
391 SetTileAt((xx * 4) + 1, (yy * 2), word2);
392 SetTileAt((xx * 4) + 2, (yy * 2), word3);
393 SetTileAt((xx * 4) + 3, (yy * 2), word4);
394
395 SetTileAt((xx * 4), (yy * 2) + 1, word5);
396 SetTileAt((xx * 4) + 1, (yy * 2) + 1, word6);
397 SetTileAt((xx * 4) + 2, (yy * 2) + 1, word7);
398 SetTileAt((xx * 4) + 3, (yy * 2) + 1, word8);
399 }
400 }
401}
402
403} // namespace yaze::gfx
void DrawTile(const TileInfo &tile_info, uint8_t *canvas, const uint8_t *tiledata, int indexoffset)
void DrawBackground(std::span< uint8_t > gfx16_data)
void SetBG1RevealMaskRect(BG1RevealMaskSource source, int start_x, int start_y, int width, int height)
std::vector< uint8_t > & mutable_bg1_reveal_mask_data()
BackgroundBuffer(int width=512, int height=512)
std::vector< uint8_t > coverage_buffer_
void SetPriorityAt(int x, int y, uint8_t priority)
std::vector< uint8_t > & mutable_priority_data()
std::vector< uint8_t > bg1_reveal_mask_buffer_
uint8_t GetPriorityAt(int x, int y) const
void SetTileAt(int x, int y, uint16_t value)
void DrawFloor(const std::vector< uint8_t > &rom_data, int tile_address, int tile_address_floor, uint8_t floor_graphics)
std::vector< uint8_t > priority_buffer_
std::vector< uint8_t > & mutable_coverage_data()
void ClearBG1RevealMaskRect(BG1RevealMaskSource source, int start_x, int start_y, int width, int height)
uint16_t GetTileAt(int x, int y) const
std::vector< uint16_t > buffer_
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
Definition bitmap.cc:201
bool is_active() const
Definition bitmap.h:405
int height() const
Definition bitmap.h:395
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Definition bitmap.cc:384
int width() const
Definition bitmap.h:394
std::vector< uint8_t > & mutable_data()
Definition bitmap.h:399
SDL_Surface * surface() const
Definition bitmap.h:400
SNES 16-bit tile metadata container.
Definition snes_tile.h:52
#define LOG_DEBUG(category, format,...)
Definition log.h:103
Contains classes for handling graphical data.
Definition editor.h:28
uint16_t TileInfoToWord(TileInfo tile_info)
Definition snes_tile.cc:361
TileInfo WordToTileInfo(uint16_t word)
Definition snes_tile.cc:378