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