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 // Initialize buffer with size for SNES layers
16 const int total_tiles = (width / 8) * (height / 8);
17 buffer_.resize(total_tiles, 0);
18 // Initialize priority buffer for per-pixel priority tracking
19 // Uses 0xFF as "no priority set" (transparent/empty pixel)
20 priority_buffer_.resize(width * height, 0xFF);
21 // Note: bitmap_ is NOT initialized here to avoid circular dependency
22 // with Arena::Get(). Call EnsureBitmapInitialized() before accessing bitmap().
23}
24
25void BackgroundBuffer::SetTileAt(int x_pos, int y_pos, uint16_t value) {
26 if (x_pos < 0 || y_pos < 0) {
27 return;
28 }
29 int tiles_w = width_ / 8;
30 int tiles_h = height_ / 8;
31 if (x_pos >= tiles_w || y_pos >= tiles_h) {
32 return;
33 }
34 buffer_[y_pos * tiles_w + x_pos] = value;
35}
36
37uint16_t BackgroundBuffer::GetTileAt(int x_pos, int y_pos) const {
38 int tiles_w = width_ / 8;
39 int tiles_h = height_ / 8;
40 if (x_pos < 0 || y_pos < 0 || x_pos >= tiles_w || y_pos >= tiles_h) {
41 return 0;
42 }
43 return buffer_[y_pos * tiles_w + x_pos];
44}
45
47 std::ranges::fill(buffer_, 0);
49}
50
52 // 0xFF indicates no priority set (transparent/empty pixel)
53 std::ranges::fill(priority_buffer_, 0xFF);
54}
55
56uint8_t BackgroundBuffer::GetPriorityAt(int x, int y) const {
57 if (x < 0 || y < 0 || x >= width_ || y >= height_) {
58 return 0xFF; // Out of bounds = no priority
59 }
60 return priority_buffer_[y * width_ + x];
61}
62
63void BackgroundBuffer::SetPriorityAt(int x, int y, uint8_t priority) {
64 if (x < 0 || y < 0 || x >= width_ || y >= height_) {
65 return;
66 }
67 priority_buffer_[y * width_ + x] = priority;
68}
69
71 if (!bitmap_.is_active() || bitmap_.width() == 0) {
72 // IMPORTANT: Initialize to 255 (transparent fill), NOT 0!
73 // In dungeon rendering, pixel value 0 represents palette[0] (actual color).
74 // Only 255 is treated as transparent by IsTransparent().
76 std::vector<uint8_t>(width_ * height_, 255));
77
78 // Fix: Set index 255 to be transparent so the background is actually transparent
79 // instead of white (default SDL palette index 255)
80 std::vector<SDL_Color> palette(256);
81 // Initialize with grayscale for debugging
82 for (int i = 0; i < 256; i++) {
83 palette[i] = {static_cast<Uint8>(i), static_cast<Uint8>(i), static_cast<Uint8>(i), 255};
84 }
85 // Set index 255 to transparent
86 palette[255] = {0, 0, 0, 0};
87 bitmap_.SetPalette(palette);
88
89 // Enable blending
90 if (bitmap_.surface()) {
91 SDL_SetSurfaceBlendMode(bitmap_.surface(), SDL_BLENDMODE_BLEND);
92 }
93 }
94
95 // Ensure priority buffer is properly sized
96 if (priority_buffer_.size() != static_cast<size_t>(width_ * height_)) {
97 priority_buffer_.resize(width_ * height_, 0xFF);
98 }
99}
100
101void BackgroundBuffer::DrawTile(const TileInfo& tile, uint8_t* canvas,
102 const uint8_t* tiledata, int indexoffset) {
103 // tiledata is now 8BPP linear data (1 byte per pixel)
104 // Buffer size: 0x10000 (65536 bytes) = 64 tile rows max
105 constexpr int kGfxBufferSize = 0x10000;
106 constexpr int kMaxTileRow = 63; // 64 rows (0-63), each 1024 bytes
107
108 // Calculate tile position in the 8BPP buffer
109 int tile_col_idx = tile.id_ % 16;
110 int tile_row_idx = tile.id_ / 16;
111
112 // CRITICAL: Validate tile_row to prevent index out of bounds
113 if (tile_row_idx > kMaxTileRow) {
114 return; // Skip invalid tiles silently
115 }
116
117 int tile_base_x = tile_col_idx * 8; // 8 pixels wide (8 bytes)
118 int tile_base_y = tile_row_idx * 1024; // 8 rows * 128 bytes stride (sheet width)
119
120 // Palette offset calculation based on SNES CGRAM layout from ASM analysis:
121 // - PaletteLoad_UnderworldSet loads dungeon palette to CGRAM starting at 0x0042
122 // - CGRAM 0x42 = color index 33 = row 2, position 1
123 // - Dungeon tiles use palette bits 2-7, mapping to CGRAM rows 2-7
124 // - Each CGRAM row has 16 colors; ROM stores 15 colors per row (index 0 = transparent)
125 // - 6 rows × 15 colors = 90 colors in ROM, loaded to CGRAM indices 33-47, 49-63, etc.
126 //
127 // Tile palette bits → ROM palette offset:
128 // 2 → 0-14, 3 → 15-29, 4 → 30-44, 5 → 45-59, 6 → 60-74, 7 → 75-89
129 uint8_t pal = tile.palette_ & 0x07;
130 uint8_t palette_offset;
131 if (pal >= 2 && pal <= 7) {
132 // Dungeon BG tiles use palette bits 2-7, mapping to ROM indices 0-89
133 palette_offset = (pal - 2) * 15;
134 } else {
135 // Palette 0-1 are for HUD/other - fallback to first sub-palette
136 palette_offset = 0;
137 }
138
139 // Pre-calculate max valid destination index
140 int max_dest = width_ * height_;
141
142 // Get priority bit from tile (over_ = priority bit in SNES tilemap)
143 uint8_t priority = tile.over_ ? 1 : 0;
144
145 // Copy 8x8 pixels
146 for (int py = 0; py < 8; py++) {
147 int src_row = tile.vertical_mirror_ ? (7 - py) : py;
148
149 for (int px = 0; px < 8; px++) {
150 int src_col = tile.horizontal_mirror_ ? (7 - px) : px;
151
152 // Calculate source index
153 // Stride is 128 bytes (sheet width)
154 int src_index = (src_row * 128) + src_col + tile_base_x + tile_base_y;
155
156 // Bounds check source
157 if (src_index < 0 || src_index >= kGfxBufferSize) continue;
158
159 uint8_t pixel = tiledata[src_index];
160
161 if (pixel != 0) {
162 // Pixel 0 is transparent. Pixel 1 maps to palette index 0.
163 // This matches ObjectDrawer::DrawTileToBitmap formula.
164 uint8_t final_color = (pixel - 1) + palette_offset;
165 int dest_index = indexoffset + (py * width_) + px;
166
167 // Bounds check destination
168 if (dest_index >= 0 && dest_index < max_dest) {
169 canvas[dest_index] = final_color;
170 // Also store priority for this pixel
171 priority_buffer_[dest_index] = priority;
172 }
173 }
174 }
175 }
176}
177
178void BackgroundBuffer::DrawBackground(std::span<uint8_t> gfx16_data) {
179 int tiles_w = width_ / 8;
180 int tiles_h = height_ / 8;
181 if ((int)buffer_.size() < tiles_w * tiles_h) {
182 buffer_.resize(tiles_w * tiles_h);
183 }
184
185 // NEVER recreate bitmap here - it should be created by DrawFloor or
186 // initialized earlier. If bitmap doesn't exist, create it ONCE with 255 fill
187 // IMPORTANT: Use 255 (transparent), NOT 0! Pixel value 0 = palette[0] (actual color)
188 if (!bitmap_.is_active() || bitmap_.width() == 0) {
190 std::vector<uint8_t>(width_ * height_, 255));
191 }
192
193 // Ensure priority buffer is properly sized
194 if (priority_buffer_.size() != static_cast<size_t>(width_ * height_)) {
195 priority_buffer_.resize(width_ * height_, 0xFF);
196 }
197
198 // For each tile on the tile buffer
199 // int drawn_count = 0;
200 // int skipped_count = 0;
201 for (int yy = 0; yy < tiles_h; yy++) {
202 for (int xx = 0; xx < tiles_w; xx++) {
203 uint16_t word = buffer_[xx + yy * tiles_w];
204
205 // Skip empty tiles (0xFFFF) - these show the floor
206 if (word == 0xFFFF) {
207 // skipped_count++;
208 continue;
209 }
210
211 // Skip zero tiles - also show the floor
212 if (word == 0) {
213 // skipped_count++;
214 continue;
215 }
216
217 auto tile = gfx::WordToTileInfo(word);
218
219 // Skip floor tiles (0xEC-0xFD) - don't overwrite DrawFloor's work
220 // These are the animated floor tiles, already drawn by DrawFloor
221 // Skip floor tiles (0xEC-0xFD) - don't overwrite DrawFloor's work
222 // These are the animated floor tiles, already drawn by DrawFloor
223 // if (tile.id_ >= 0xEC && tile.id_ <= 0xFD) {
224 // skipped_count++;
225 // continue;
226 // }
227
228 // Calculate pixel offset for tile position (xx, yy) in the 512x512 bitmap
229 // Each tile is 8x8, so pixel Y = yy * 8, pixel X = xx * 8
230 // Linear offset = (pixel_y * width) + pixel_x = (yy * 8 * 512) + (xx * 8)
231 int tile_offset = (yy * 8 * width_) + (xx * 8);
232 DrawTile(tile, bitmap_.mutable_data().data(), gfx16_data.data(),
233 tile_offset);
234 // drawn_count++;
235 }
236 }
237 // CRITICAL: Sync bitmap data back to SDL surface!
238 // DrawTile() writes to bitmap_.mutable_data(), but the SDL surface needs
239 // updating
240 if (bitmap_.surface() && bitmap_.mutable_data().size() > 0) {
241 SDL_LockSurface(bitmap_.surface());
242 memcpy(bitmap_.surface()->pixels, bitmap_.mutable_data().data(),
243 bitmap_.mutable_data().size());
244 SDL_UnlockSurface(bitmap_.surface());
245 }
246}
247
248void BackgroundBuffer::DrawFloor(const std::vector<uint8_t>& rom_data,
249 int tile_address, int tile_address_floor,
250 uint8_t floor_graphics) {
251 // Create bitmap ONCE at the start if it doesn't exist
252 // IMPORTANT: Use 255 (transparent fill), NOT 0! Pixel value 0 = palette[0] (actual color)
253 if (!bitmap_.is_active() || bitmap_.width() == 0) {
254 LOG_DEBUG("[DrawFloor]", "Creating bitmap: %dx%d, active=%d, width=%d",
257 std::vector<uint8_t>(width_ * height_, 255));
258 LOG_DEBUG("[DrawFloor]", "After Create: active=%d, width=%d, height=%d",
260 } else {
261 LOG_DEBUG("[DrawFloor]",
262 "Bitmap already exists: active=%d, width=%d, height=%d",
264 }
265
266 auto floor_offset = static_cast<uint8_t>(floor_graphics << 4);
267
268 // Create floor tiles from ROM data
269 gfx::TileInfo floorTile1(rom_data[tile_address + floor_offset],
270 rom_data[tile_address + floor_offset + 1]);
271 gfx::TileInfo floorTile2(rom_data[tile_address + floor_offset + 2],
272 rom_data[tile_address + floor_offset + 3]);
273 gfx::TileInfo floorTile3(rom_data[tile_address + floor_offset + 4],
274 rom_data[tile_address + floor_offset + 5]);
275 gfx::TileInfo floorTile4(rom_data[tile_address + floor_offset + 6],
276 rom_data[tile_address + floor_offset + 7]);
277
278 gfx::TileInfo floorTile5(rom_data[tile_address_floor + floor_offset],
279 rom_data[tile_address_floor + floor_offset + 1]);
280 gfx::TileInfo floorTile6(rom_data[tile_address_floor + floor_offset + 2],
281 rom_data[tile_address_floor + floor_offset + 3]);
282 gfx::TileInfo floorTile7(rom_data[tile_address_floor + floor_offset + 4],
283 rom_data[tile_address_floor + floor_offset + 5]);
284 gfx::TileInfo floorTile8(rom_data[tile_address_floor + floor_offset + 6],
285 rom_data[tile_address_floor + floor_offset + 7]);
286
287 // Floor tiles specify which 8-color sub-palette from the 90-color dungeon
288 // palette e.g., palette 6 = colors 48-55 (6 * 8 = 48)
289
290 // Draw the floor tiles in a pattern
291 // Convert TileInfo to 16-bit words with palette information
292 uint16_t word1 = gfx::TileInfoToWord(floorTile1);
293 uint16_t word2 = gfx::TileInfoToWord(floorTile2);
294 uint16_t word3 = gfx::TileInfoToWord(floorTile3);
295 uint16_t word4 = gfx::TileInfoToWord(floorTile4);
296 uint16_t word5 = gfx::TileInfoToWord(floorTile5);
297 uint16_t word6 = gfx::TileInfoToWord(floorTile6);
298 uint16_t word7 = gfx::TileInfoToWord(floorTile7);
299 uint16_t word8 = gfx::TileInfoToWord(floorTile8);
300 for (int xx = 0; xx < 16; xx++) {
301 for (int yy = 0; yy < 32; yy++) {
302 SetTileAt((xx * 4), (yy * 2), word1);
303 SetTileAt((xx * 4) + 1, (yy * 2), word2);
304 SetTileAt((xx * 4) + 2, (yy * 2), word3);
305 SetTileAt((xx * 4) + 3, (yy * 2), word4);
306
307 SetTileAt((xx * 4), (yy * 2) + 1, word5);
308 SetTileAt((xx * 4) + 1, (yy * 2) + 1, word6);
309 SetTileAt((xx * 4) + 2, (yy * 2) + 1, word7);
310 SetTileAt((xx * 4) + 3, (yy * 2) + 1, word8);
311 }
312 }
313}
314
315} // 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)
BackgroundBuffer(int width=512, int height=512)
void SetPriorityAt(int x, int y, uint8_t priority)
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_
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:199
bool is_active() const
Definition bitmap.h:384
int height() const
Definition bitmap.h:374
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Definition bitmap.cc:382
int width() const
Definition bitmap.h:373
std::vector< uint8_t > & mutable_data()
Definition bitmap.h:378
SDL_Surface * surface() const
Definition bitmap.h:379
SNES 16-bit tile metadata container.
Definition snes_tile.h:50
#define LOG_DEBUG(category, format,...)
Definition log.h:103
Contains classes for handling graphical data.
Definition editor.h:26
uint16_t TileInfoToWord(TileInfo tile_info)
Definition snes_tile.cc:304
TileInfo WordToTileInfo(uint16_t word)
Definition snes_tile.cc:321