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
7#include "app/gfx/bitmap.h"
8#include "app/gfx/snes_tile.h"
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}
19
20void BackgroundBuffer::SetTileAt(int x, int y, uint16_t value) {
21 if (x < 0 || y < 0) return;
22 int tiles_w = width_ / 8;
23 int tiles_h = height_ / 8;
24 if (x >= tiles_w || y >= tiles_h) return;
25 buffer_[y * tiles_w + x] = value;
26}
27
28uint16_t BackgroundBuffer::GetTileAt(int x, int y) const {
29 int tiles_w = width_ / 8;
30 int tiles_h = height_ / 8;
31 if (x < 0 || y < 0 || x >= tiles_w || y >= tiles_h) return 0;
32 return buffer_[y * tiles_w + x];
33}
34
35void BackgroundBuffer::ClearBuffer() { std::ranges::fill(buffer_, 0); }
36
37void BackgroundBuffer::DrawTile(const TileInfo& tile, uint8_t* canvas,
38 const uint8_t* tiledata, int indexoffset) {
39 // tiledata is a 128-pixel-wide indexed bitmap (16 tiles/row * 8 pixels/tile)
40 // Calculate tile position in the tilesheet
41 int tile_x = (tile.id_ % 16) * 8; // 16 tiles per row, 8 pixels per tile
42 int tile_y = (tile.id_ / 16) * 8; // Each row is 16 tiles
43
44 // DEBUG: For floor tiles, check what we're actually reading
45 static int debug_count = 0;
46 if (debug_count < 4 && (tile.id_ == 0xEC || tile.id_ == 0xED || tile.id_ == 0xFC || tile.id_ == 0xFD)) {
47 LOG_DEBUG("[DrawTile]", "Floor tile 0x%02X at sheet pos (%d,%d), palette=%d, mirror=(%d,%d)",
48 tile.id_, tile_x, tile_y, tile.palette_, tile.horizontal_mirror_, tile.vertical_mirror_);
49 LOG_DEBUG("[DrawTile]", "First row (8 pixels): ");
50 for (int i = 0; i < 8; i++) {
51 int src_index = tile_y * 128 + (tile_x + i);
52 LOG_DEBUG("[DrawTile]", "%d ", tiledata[src_index]);
53 }
54 LOG_DEBUG("[DrawTile]", "Second row (8 pixels): ");
55 for (int i = 0; i < 8; i++) {
56 int src_index = (tile_y + 1) * 128 + (tile_x + i);
57 LOG_DEBUG("[DrawTile]", "%d ", tiledata[src_index]);
58 }
59 debug_count++;
60 }
61
62 // Dungeon graphics are 3BPP: 8 colors per palette (0-7, 8-15, 16-23, etc.)
63 // NOT 4BPP which would be 16 colors per palette!
64 // Clamp palette to 0-10 (90 colors / 8 = 11.25, so max palette is 10)
65 uint8_t clamped_palette = tile.palette_ & 0x0F;
66 if (clamped_palette > 10) {
67 clamped_palette = clamped_palette % 11;
68 }
69
70 // For 3BPP: palette offset = palette * 8 (not * 16!)
71 uint8_t palette_offset = (uint8_t)(clamped_palette * 8);
72
73 // Copy 8x8 pixels from tiledata to canvas
74 for (int py = 0; py < 8; py++) {
75 for (int px = 0; px < 8; px++) {
76 // Apply mirroring
77 int src_x = tile.horizontal_mirror_ ? (7 - px) : px;
78 int src_y = tile.vertical_mirror_ ? (7 - py) : py;
79
80 // Read pixel from tiledata (128-pixel-wide bitmap)
81 int src_index = (tile_y + src_y) * 128 + (tile_x + src_x);
82 uint8_t pixel_index = tiledata[src_index];
83
84 // Apply palette offset and write to canvas
85 // For 3BPP: final color = base_pixel (0-7) + palette_offset (0, 8, 16, 24, ...)
86 if (pixel_index == 0) {
87 continue;
88 }
89 uint8_t final_color = pixel_index + palette_offset;
90 int dest_index = indexoffset + (py * width_) + px;
91 canvas[dest_index] = final_color;
92 }
93 }
94}
95
96void BackgroundBuffer::DrawBackground(std::span<uint8_t> gfx16_data) {
97 int tiles_w = width_ / 8;
98 int tiles_h = height_ / 8;
99 if ((int)buffer_.size() < tiles_w * tiles_h) {
100 buffer_.resize(tiles_w * tiles_h);
101 }
102
103 // NEVER recreate bitmap here - it should be created by DrawFloor or initialized earlier
104 // If bitmap doesn't exist, create it ONCE with zeros
105 if (!bitmap_.is_active() || bitmap_.width() == 0) {
106 bitmap_.Create(width_, height_, 8, std::vector<uint8_t>(width_ * height_, 0));
107 }
108
109 // For each tile on the tile buffer
110 int drawn_count = 0;
111 int skipped_count = 0;
112 for (int yy = 0; yy < tiles_h; yy++) {
113 for (int xx = 0; xx < tiles_w; xx++) {
114 uint16_t word = buffer_[xx + yy * tiles_w];
115
116 // Skip empty tiles (0xFFFF) - these show the floor
117 if (word == 0xFFFF) {
118 skipped_count++;
119 continue;
120 }
121
122 // Skip zero tiles - also show the floor
123 if (word == 0) {
124 skipped_count++;
125 continue;
126 }
127
128 auto tile = gfx::WordToTileInfo(word);
129
130 // Skip floor tiles (0xEC-0xFD) - don't overwrite DrawFloor's work
131 // These are the animated floor tiles, already drawn by DrawFloor
132 if (tile.id_ >= 0xEC && tile.id_ <= 0xFD) {
133 skipped_count++;
134 continue;
135 }
136
137 // Calculate pixel offset for tile position (xx, yy) in the 512x512 bitmap
138 // Each tile is 8x8, so pixel Y = yy * 8, pixel X = xx * 8
139 // Linear offset = (pixel_y * width) + pixel_x = (yy * 8 * 512) + (xx * 8)
140 int tile_offset = (yy * 8 * width_) + (xx * 8);
141 DrawTile(tile, bitmap_.mutable_data().data(), gfx16_data.data(), tile_offset);
142 drawn_count++;
143 }
144 }
145 // CRITICAL: Sync bitmap data back to SDL surface!
146 // DrawTile() writes to bitmap_.mutable_data(), but the SDL surface needs updating
147 if (bitmap_.surface() && bitmap_.mutable_data().size() > 0) {
148 SDL_LockSurface(bitmap_.surface());
149 memcpy(bitmap_.surface()->pixels, bitmap_.mutable_data().data(), bitmap_.mutable_data().size());
150 SDL_UnlockSurface(bitmap_.surface());
151 }
152}
153
154void BackgroundBuffer::DrawFloor(const std::vector<uint8_t>& rom_data,
155 int tile_address, int tile_address_floor,
156 uint8_t floor_graphics) {
157 // Create bitmap ONCE at the start if it doesn't exist
158 if (!bitmap_.is_active() || bitmap_.width() == 0) {
159 LOG_DEBUG("[DrawFloor]", "Creating bitmap: %dx%d, active=%d, width=%d",
161 bitmap_.Create(width_, height_, 8, std::vector<uint8_t>(width_ * height_, 0));
162 LOG_DEBUG("[DrawFloor]", "After Create: active=%d, width=%d, height=%d",
164 } else {
165 LOG_DEBUG("[DrawFloor]", "Bitmap already exists: active=%d, width=%d, height=%d",
167 }
168
169 auto f = (uint8_t)(floor_graphics << 4);
170
171 // Create floor tiles from ROM data
172 gfx::TileInfo floorTile1(rom_data[tile_address + f],
173 rom_data[tile_address + f + 1]);
174 gfx::TileInfo floorTile2(rom_data[tile_address + f + 2],
175 rom_data[tile_address + f + 3]);
176 gfx::TileInfo floorTile3(rom_data[tile_address + f + 4],
177 rom_data[tile_address + f + 5]);
178 gfx::TileInfo floorTile4(rom_data[tile_address + f + 6],
179 rom_data[tile_address + f + 7]);
180
181 gfx::TileInfo floorTile5(rom_data[tile_address_floor + f],
182 rom_data[tile_address_floor + f + 1]);
183 gfx::TileInfo floorTile6(rom_data[tile_address_floor + f + 2],
184 rom_data[tile_address_floor + f + 3]);
185 gfx::TileInfo floorTile7(rom_data[tile_address_floor + f + 4],
186 rom_data[tile_address_floor + f + 5]);
187 gfx::TileInfo floorTile8(rom_data[tile_address_floor + f + 6],
188 rom_data[tile_address_floor + f + 7]);
189
190 // Floor tiles specify which 8-color sub-palette from the 90-color dungeon palette
191 // e.g., palette 6 = colors 48-55 (6 * 8 = 48)
192
193 // Draw the floor tiles in a pattern
194 // Convert TileInfo to 16-bit words with palette information
195 uint16_t word1 = gfx::TileInfoToWord(floorTile1);
196 uint16_t word2 = gfx::TileInfoToWord(floorTile2);
197 uint16_t word3 = gfx::TileInfoToWord(floorTile3);
198 uint16_t word4 = gfx::TileInfoToWord(floorTile4);
199 uint16_t word5 = gfx::TileInfoToWord(floorTile5);
200 uint16_t word6 = gfx::TileInfoToWord(floorTile6);
201 uint16_t word7 = gfx::TileInfoToWord(floorTile7);
202 uint16_t word8 = gfx::TileInfoToWord(floorTile8);
203 for (int xx = 0; xx < 16; xx++) {
204 for (int yy = 0; yy < 32; yy++) {
205 SetTileAt((xx * 4), (yy * 2), word1);
206 SetTileAt((xx * 4) + 1, (yy * 2), word2);
207 SetTileAt((xx * 4) + 2, (yy * 2), word3);
208 SetTileAt((xx * 4) + 3, (yy * 2), word4);
209
210 SetTileAt((xx * 4), (yy * 2) + 1, word5);
211 SetTileAt((xx * 4) + 1, (yy * 2) + 1, word6);
212 SetTileAt((xx * 4) + 2, (yy * 2) + 1, word7);
213 SetTileAt((xx * 4) + 3, (yy * 2) + 1, word8);
214 }
215 }
216}
217
218} // 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 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)
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:162
bool is_active() const
Definition bitmap.h:264
int height() const
Definition bitmap.h:254
int width() const
Definition bitmap.h:253
std::vector< uint8_t > & mutable_data()
Definition bitmap.h:258
SDL_Surface * surface() const
Definition bitmap.h:259
SNES 16-bit tile metadata container.
Definition snes_tile.h:50
#define LOG_DEBUG(category, format,...)
Definition log.h:104
Contains classes for handling graphical data.
Definition emulator.h:16
uint16_t TileInfoToWord(TileInfo tile_info)
Definition snes_tile.cc:291
TileInfo WordToTileInfo(uint16_t word)
Definition snes_tile.cc:308