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