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
41
46
47void BackgroundBuffer::SetTileAt(int x_pos, int y_pos, uint16_t value) {
48 if (x_pos < 0 || y_pos < 0) {
49 return;
50 }
51 int tiles_w = width_ / 8;
52 int tiles_h = height_ / 8;
53 if (x_pos >= tiles_w || y_pos >= tiles_h) {
54 return;
55 }
57 buffer_[y_pos * tiles_w + x_pos] = value;
58}
59
60uint16_t BackgroundBuffer::GetTileAt(int x_pos, int y_pos) const {
61 int tiles_w = width_ / 8;
62 int tiles_h = height_ / 8;
63 if (x_pos < 0 || y_pos < 0 || x_pos >= tiles_w || y_pos >= tiles_h) {
64 return 0;
65 }
66 const size_t index = static_cast<size_t>(y_pos * tiles_w + x_pos);
67 if (index >= buffer_.size()) {
68 return 0;
69 }
70 return buffer_[index];
71}
72
74 if (!buffer_.empty()) {
75 std::fill(buffer_.begin(), buffer_.end(), 0);
76 }
79}
80
82 // 0xFF indicates no priority set (transparent/empty pixel)
83 if (!priority_buffer_.empty()) {
84 std::fill(priority_buffer_.begin(), priority_buffer_.end(), 0xFF);
85 }
86}
87
89 // 0 indicates the layer never wrote here; 1 indicates it did.
90 if (!coverage_buffer_.empty()) {
91 std::fill(coverage_buffer_.begin(), coverage_buffer_.end(), 0);
92 }
93}
94
95uint8_t BackgroundBuffer::GetPriorityAt(int x, int y) const {
96 if (x < 0 || y < 0 || x >= width_ || y >= height_) {
97 return 0xFF; // Out of bounds = no priority
98 }
99 return priority_buffer_[y * width_ + x];
100}
101
102void BackgroundBuffer::SetPriorityAt(int x, int y, uint8_t priority) {
103 if (x < 0 || y < 0 || x >= width_ || y >= height_) {
104 return;
105 }
107 priority_buffer_[y * width_ + x] = priority;
108}
109
111 if (!bitmap_.is_active() || bitmap_.width() == 0) {
112 // IMPORTANT: Initialize to 255 (transparent fill), NOT 0!
113 // In dungeon rendering, pixel value 0 represents palette[0] (actual color).
114 // Only 255 is treated as transparent by IsTransparent().
116 std::vector<uint8_t>(width_ * height_, 255));
117
118 // Fix: Set index 255 to be transparent so the background is actually transparent
119 // instead of white (default SDL palette index 255)
120 std::vector<SDL_Color> palette(256);
121 // Initialize with grayscale for debugging
122 for (int i = 0; i < 256; i++) {
123 palette[i] = {static_cast<Uint8>(i), static_cast<Uint8>(i),
124 static_cast<Uint8>(i), 255};
125 }
126 // Set index 255 to transparent
127 palette[255] = {0, 0, 0, 0};
128 bitmap_.SetPalette(palette);
129
130 // Enable blending
131 if (bitmap_.surface()) {
132 SDL_SetSurfaceBlendMode(bitmap_.surface(), SDL_BLENDMODE_BLEND);
133 }
134 }
135
138}
139
140void BackgroundBuffer::DrawTile(const TileInfo& tile, uint8_t* canvas,
141 const uint8_t* tiledata, int indexoffset) {
142 // tiledata is now 8BPP linear data (1 byte per pixel)
143 // Buffer size: 0x10000 (65536 bytes) = 64 tile rows max
144 constexpr int kGfxBufferSize = 0x10000;
145 constexpr int kMaxTileRow = 63; // 64 rows (0-63), each 1024 bytes
146
147 // Calculate tile position in the 8BPP buffer
148 int tile_col_idx = tile.id_ % 16;
149 int tile_row_idx = tile.id_ / 16;
150
151 // CRITICAL: Validate tile_row to prevent index out of bounds
152 if (tile_row_idx > kMaxTileRow) {
153 return; // Skip invalid tiles silently
154 }
155
156 int tile_base_x = tile_col_idx * 8; // 8 pixels wide (8 bytes)
157 int tile_base_y =
158 tile_row_idx * 1024; // 8 rows * 128 bytes stride (sheet width)
159
160 // Palette offset: tile palette field (3-bit, 0-7) is the CGRAM row index.
161 // SDL palette mirrors CGRAM directly: dungeon view loads banks 2-7 in
162 // Room::RenderRoomGraphics, leaving banks 0-1 as HUD placeholders.
163 // See object_drawer.cc for the canonical comment.
164 const uint8_t pal = tile.palette_ & 0x07;
165 const uint8_t palette_offset = static_cast<uint8_t>(pal * 16);
166
167 // Pre-calculate max valid destination index
168 int max_dest = width_ * height_;
169
170 // Get priority bit from tile (over_ = priority bit in SNES tilemap)
171 uint8_t priority = tile.over_ ? 1 : 0;
172
173 // Copy 8x8 pixels
174 for (int py = 0; py < 8; py++) {
175 int src_row = tile.vertical_mirror_ ? (7 - py) : py;
176
177 for (int px = 0; px < 8; px++) {
178 int src_col = tile.horizontal_mirror_ ? (7 - px) : px;
179
180 // Calculate source index
181 // Stride is 128 bytes (sheet width)
182 int src_index = (src_row * 128) + src_col + tile_base_x + tile_base_y;
183
184 // Bounds check source
185 if (src_index < 0 || src_index >= kGfxBufferSize)
186 continue;
187
188 uint8_t pixel = tiledata[src_index];
189
190 if (pixel != 0) {
191 // Pixel 0 is transparent (not written). Pixels 1-15 map to bank indices 1-15.
192 // With 16-color bank chunking: final_color = pixel + (bank * 16)
193 uint8_t final_color = pixel + palette_offset;
194 int dest_index = indexoffset + (py * width_) + px;
195
196 // Bounds check destination
197 if (dest_index >= 0 && dest_index < max_dest) {
198 canvas[dest_index] = final_color;
199 // Also store priority for this pixel
200 priority_buffer_[dest_index] = priority;
201 }
202 }
203 }
204 }
205}
206
207void BackgroundBuffer::DrawBackground(std::span<uint8_t> gfx16_data) {
208 int tiles_w = width_ / 8;
209 int tiles_h = height_ / 8;
211
212 // NEVER recreate bitmap here - it should be created by DrawFloor or
213 // initialized earlier. If bitmap doesn't exist, create it ONCE with 255 fill
214 // IMPORTANT: Use 255 (transparent), NOT 0! Pixel value 0 = palette[0] (actual color)
215 if (!bitmap_.is_active() || bitmap_.width() == 0) {
217 std::vector<uint8_t>(width_ * height_, 255));
218 }
219
221
222 // For each tile on the tile buffer
223 // int drawn_count = 0;
224 // int skipped_count = 0;
225 for (int yy = 0; yy < tiles_h; yy++) {
226 for (int xx = 0; xx < tiles_w; xx++) {
227 uint16_t word = buffer_[xx + yy * tiles_w];
228
229 // Skip empty tiles (0xFFFF) - these show the floor
230 if (word == 0xFFFF) {
231 // skipped_count++;
232 continue;
233 }
234
235 // Skip zero tiles - also show the floor
236 if (word == 0) {
237 // skipped_count++;
238 continue;
239 }
240
241 auto tile = gfx::WordToTileInfo(word);
242
243 // Skip floor tiles (0xEC-0xFD) - don't overwrite DrawFloor's work
244 // These are the animated floor tiles, already drawn by DrawFloor
245 // Skip floor tiles (0xEC-0xFD) - don't overwrite DrawFloor's work
246 // These are the animated floor tiles, already drawn by DrawFloor
247 // if (tile.id_ >= 0xEC && tile.id_ <= 0xFD) {
248 // skipped_count++;
249 // continue;
250 // }
251
252 // Calculate pixel offset for tile position (xx, yy) in the 512x512 bitmap
253 // Each tile is 8x8, so pixel Y = yy * 8, pixel X = xx * 8
254 // Linear offset = (pixel_y * width) + pixel_x = (yy * 8 * 512) + (xx * 8)
255 int tile_offset = (yy * 8 * width_) + (xx * 8);
256 DrawTile(tile, bitmap_.mutable_data().data(), gfx16_data.data(),
257 tile_offset);
258 // drawn_count++;
259 }
260 }
261 // CRITICAL: Sync bitmap data back to SDL surface!
262 // DrawTile() writes to bitmap_.mutable_data(), but the SDL surface needs
263 // updating
264 if (bitmap_.surface() && bitmap_.mutable_data().size() > 0) {
265 SDL_LockSurface(bitmap_.surface());
266 memcpy(bitmap_.surface()->pixels, bitmap_.mutable_data().data(),
267 bitmap_.mutable_data().size());
268 SDL_UnlockSurface(bitmap_.surface());
269 }
270}
271
272void BackgroundBuffer::DrawFloor(const std::vector<uint8_t>& rom_data,
273 int tile_address, int tile_address_floor,
274 uint8_t floor_graphics) {
275 // Create bitmap ONCE at the start if it doesn't exist
276 // IMPORTANT: Use 255 (transparent fill), NOT 0! Pixel value 0 = palette[0] (actual color)
277 if (!bitmap_.is_active() || bitmap_.width() == 0) {
278 LOG_DEBUG("[DrawFloor]", "Creating bitmap: %dx%d, active=%d, width=%d",
281 std::vector<uint8_t>(width_ * height_, 255));
282 LOG_DEBUG("[DrawFloor]", "After Create: active=%d, width=%d, height=%d",
284 } else {
285 LOG_DEBUG("[DrawFloor]",
286 "Bitmap already exists: active=%d, width=%d, height=%d",
288 }
289
290 auto floor_offset = static_cast<uint8_t>(floor_graphics << 4);
291
292 // Create floor tiles from ROM data
293 gfx::TileInfo floorTile1(rom_data[tile_address + floor_offset],
294 rom_data[tile_address + floor_offset + 1]);
295 gfx::TileInfo floorTile2(rom_data[tile_address + floor_offset + 2],
296 rom_data[tile_address + floor_offset + 3]);
297 gfx::TileInfo floorTile3(rom_data[tile_address + floor_offset + 4],
298 rom_data[tile_address + floor_offset + 5]);
299 gfx::TileInfo floorTile4(rom_data[tile_address + floor_offset + 6],
300 rom_data[tile_address + floor_offset + 7]);
301
302 gfx::TileInfo floorTile5(rom_data[tile_address_floor + floor_offset],
303 rom_data[tile_address_floor + floor_offset + 1]);
304 gfx::TileInfo floorTile6(rom_data[tile_address_floor + floor_offset + 2],
305 rom_data[tile_address_floor + floor_offset + 3]);
306 gfx::TileInfo floorTile7(rom_data[tile_address_floor + floor_offset + 4],
307 rom_data[tile_address_floor + floor_offset + 5]);
308 gfx::TileInfo floorTile8(rom_data[tile_address_floor + floor_offset + 6],
309 rom_data[tile_address_floor + floor_offset + 7]);
310
311 // Floor tiles specify which 8-color sub-palette from the 90-color dungeon
312 // palette e.g., palette 6 = colors 48-55 (6 * 8 = 48)
313
314 // Draw the floor tiles in a pattern
315 // Convert TileInfo to 16-bit words with palette information
316 uint16_t word1 = gfx::TileInfoToWord(floorTile1);
317 uint16_t word2 = gfx::TileInfoToWord(floorTile2);
318 uint16_t word3 = gfx::TileInfoToWord(floorTile3);
319 uint16_t word4 = gfx::TileInfoToWord(floorTile4);
320 uint16_t word5 = gfx::TileInfoToWord(floorTile5);
321 uint16_t word6 = gfx::TileInfoToWord(floorTile6);
322 uint16_t word7 = gfx::TileInfoToWord(floorTile7);
323 uint16_t word8 = gfx::TileInfoToWord(floorTile8);
324 for (int xx = 0; xx < 16; xx++) {
325 for (int yy = 0; yy < 32; yy++) {
326 SetTileAt((xx * 4), (yy * 2), word1);
327 SetTileAt((xx * 4) + 1, (yy * 2), word2);
328 SetTileAt((xx * 4) + 2, (yy * 2), word3);
329 SetTileAt((xx * 4) + 3, (yy * 2), word4);
330
331 SetTileAt((xx * 4), (yy * 2) + 1, word5);
332 SetTileAt((xx * 4) + 1, (yy * 2) + 1, word6);
333 SetTileAt((xx * 4) + 2, (yy * 2) + 1, word7);
334 SetTileAt((xx * 4) + 3, (yy * 2) + 1, word8);
335 }
336 }
337}
338
339} // 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)
std::vector< uint8_t > coverage_buffer_
void SetPriorityAt(int x, int y, uint8_t priority)
std::vector< uint8_t > & mutable_priority_data()
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()
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: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:384
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:52
#define LOG_DEBUG(category, format,...)
Definition log.h:103
Contains classes for handling graphical data.
Definition editor.h:27
uint16_t TileInfoToWord(TileInfo tile_info)
Definition snes_tile.cc:361
TileInfo WordToTileInfo(uint16_t word)
Definition snes_tile.cc:378