| 9120e874 Andy McCall |
1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
|
| 18 |
#include "imgui.h" |
| 19 |
#include "debug_ui_panels.h" |
| 20 |
#include "debug_ui_bridge.h" |
| 21 |
#include "debug_ui.h" |
| 22 |
#include "debug_ui_widgets.h" |
| 23 |
#include "sprite_multiplex_tab.h" |
| 24 |
|
| 25 |
#include <SDL.h> |
| 26 |
#include <stdint.h> |
| 27 |
#include <string.h> |
| 28 |
|
| 29 |
namespace { |
| 30 |
|
| 31 |
constexpr uint32_t ADDR_PALETTE = 0x1FA00; |
| 32 |
constexpr uint32_t ADDR_SPRITE_ATTR = 0x1FC00; |
| 33 |
constexpr int NUM_SPRITES = 128; |
| 34 |
|
| 35 |
inline int clampi(int v, int lo, int hi) { return v < lo ? lo : (v > hi ? hi : v); } |
| 36 |
|
| 37 |
|
| 38 |
|
| 39 |
inline uint8_t expand4(uint8_t v) { return (uint8_t)((v << 4) | v); } |
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
void |
| 44 |
build_palette(uint32_t out[256]) |
| 45 |
{ |
| 46 |
for (int i = 0; i < 256; ++i) { |
| 47 |
uint8_t b0 = video_space_read(ADDR_PALETTE + i * 2); |
| 48 |
uint8_t b1 = video_space_read(ADDR_PALETTE + i * 2 + 1); |
| 49 |
uint8_t r = expand4(b1 & 0x0f); |
| 50 |
uint8_t g = expand4((b0 >> 4) & 0x0f); |
| 51 |
uint8_t b = expand4(b0 & 0x0f); |
| 52 |
out[i] = 0xFF000000u | ((uint32_t)b << 16) | ((uint32_t)g << 8) | (uint32_t)r; |
| 53 |
} |
| 54 |
} |
| 55 |
|
| 56 |
|
| 57 |
|
| 58 |
struct GfxTexture { |
| 59 |
SDL_Texture *tex = nullptr; |
| 60 |
int w = 0; |
| 61 |
int h = 0; |
| 62 |
|
| 63 |
bool ensure(int width, int height) |
| 64 |
{ |
| 65 |
if (width <= 0 || height <= 0) |
| 66 |
return false; |
| 67 |
if (tex && w == width && h == height) |
| 68 |
return true; |
| 69 |
if (tex) { |
| 70 |
SDL_DestroyTexture(tex); |
| 71 |
tex = nullptr; |
| 72 |
} |
| 73 |
SDL_Renderer *ren = debug_ui_get_renderer(); |
| 74 |
if (!ren) |
| 75 |
return false; |
| 76 |
tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ABGR8888, |
| 77 |
SDL_TEXTUREACCESS_STREAMING, width, height); |
| 78 |
if (!tex) |
| 79 |
return false; |
| 80 |
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND); |
| 81 |
SDL_SetTextureScaleMode(tex, SDL_ScaleModeNearest); |
| 82 |
w = width; |
| 83 |
h = height; |
| 84 |
return true; |
| 85 |
} |
| 86 |
|
| 87 |
void update(const uint32_t *pixels) |
| 88 |
{ |
| 89 |
if (tex) |
| 90 |
SDL_UpdateTexture(tex, nullptr, pixels, w * (int)sizeof(uint32_t)); |
| 91 |
} |
| 92 |
|
| 93 |
ImTextureID id() const { return (ImTextureID)(intptr_t)tex; } |
| 94 |
}; |
| 95 |
|
| 96 |
|
| 97 |
struct PixelBuffer { |
| 98 |
uint32_t *data = nullptr; |
| 99 |
size_t cap = 0; |
| 100 |
|
| 101 |
uint32_t *ensure(size_t count) |
| 102 |
{ |
| 103 |
if (count > cap) { |
| 104 |
free(data); |
| 105 |
data = (uint32_t *)malloc(count * sizeof(uint32_t)); |
| 106 |
cap = data ? count : 0; |
| 107 |
} |
| 108 |
return data; |
| 109 |
} |
| 110 |
}; |
| 111 |
|
| 112 |
|
| 113 |
|
| 114 |
inline uint32_t |
| 115 |
resolve_color(const uint32_t pal[256], int idx, int palette_offset, bool transparent0) |
| 116 |
{ |
| 117 |
if (idx == 0 && transparent0) |
| 118 |
return 0; |
| 119 |
int ci = idx; |
| 120 |
if (ci > 0 && ci < 16) |
| 121 |
ci += palette_offset; |
| 122 |
return pal[ci & 0xff]; |
| 123 |
} |
| 124 |
|
| 125 |
|
| 126 |
|
| 127 |
|
| 128 |
void |
| 129 |
draw_palette_tab() |
| 130 |
{ |
| 131 |
ImGui::TextUnformatted("VERA palette — $1FA00..$1FBFF (256 entries, 12-bit RGB)"); |
| 132 |
ImGui::Spacing(); |
| 133 |
|
| 134 |
uint32_t pal[256]; |
| 135 |
build_palette(pal); |
| 136 |
|
| 137 |
const ImGuiColorEditFlags flags = |
| 138 |
ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoAlpha | |
| 139 |
ImGuiColorEditFlags_NoDragDrop | ImGuiColorEditFlags_NoBorder; |
| 140 |
const ImVec2 sw(20.0f, 20.0f); |
| 141 |
|
| 142 |
for (int i = 0; i < 256; ++i) { |
| 143 |
ImGui::PushID(i); |
| 144 |
uint32_t c = pal[i]; |
| 145 |
uint8_t r = c & 0xff, g = (c >> 8) & 0xff, b = (c >> 16) & 0xff; |
| 146 |
ImVec4 col(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f); |
| 147 |
ImGui::ColorButton("##sw", col, flags, sw); |
| 148 |
if (ImGui::IsItemHovered()) { |
| 149 |
uint8_t b0 = video_space_read(ADDR_PALETTE + i * 2); |
| 150 |
uint8_t b1 = video_space_read(ADDR_PALETTE + i * 2 + 1); |
| 151 |
ImGui::SetTooltip("Index %3d ($%02X)\nRGB $%X%X%X (%d, %d, %d)\nVRAM $%05X: $%02X $%02X", |
| 152 |
i, i, (b1 & 0x0f), (b0 >> 4) & 0x0f, b0 & 0x0f, r, g, b, |
| 153 |
ADDR_PALETTE + i * 2, b0, b1); |
| 154 |
} |
| 155 |
if ((i % 16) != 15) |
| 156 |
ImGui::SameLine(); |
| 157 |
ImGui::PopID(); |
| 158 |
} |
| 159 |
} |
| 160 |
|
| 161 |
|
| 162 |
|
| 163 |
|
| 164 |
void |
| 165 |
draw_tiles_tab(const uint32_t pal[256]) |
| 166 |
{ |
| 167 |
static int base = 0x00000; |
| 168 |
static int bpp_idx = 2; |
| 169 |
static int tw_idx = 0; |
| 170 |
static int th_idx = 0; |
| 171 |
static int cols = 16; |
| 172 |
static int rows = 16; |
| 173 |
static int pal_off = 0; |
| 174 |
static float zoom = 2.0f; |
| 175 |
static int selected = -1; |
| 176 |
|
| 177 |
static const int bpp_vals[4] = { 1, 2, 4, 8 }; |
| 178 |
static const char *bpp_names = "1 bpp\0" "2 bpp\0" "4 bpp\0" "8 bpp\0"; |
| 179 |
static const char *dim_names = "8\0" "16\0"; |
| 180 |
|
| 181 |
|
| 182 |
|
| 183 |
ImGui::TextUnformatted("Base $"); |
| 184 |
ImGui::SameLine(0.0f, 2.0f); |
| 185 |
ImGui::PushItemWidth(90); |
| 186 |
ImGui::InputScalar("##tilebase", ImGuiDataType_S32, &base, nullptr, nullptr, "%05X", |
| 187 |
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase); |
| 188 |
ImGui::SameLine(); |
| 189 |
ImGui::Combo("Depth", &bpp_idx, bpp_names); |
| 190 |
ImGui::SameLine(); |
| 191 |
ImGui::Combo("W", &tw_idx, dim_names); |
| 192 |
ImGui::SameLine(); |
| 193 |
ImGui::Combo("H", &th_idx, dim_names); |
| 194 |
ImGui::PopItemWidth(); |
| 195 |
|
| 196 |
|
| 197 |
const int bpp = bpp_vals[bpp_idx]; |
| 198 |
const int tw = tw_idx ? 16 : 8; |
| 199 |
const int th = th_idx ? 16 : 8; |
| 200 |
const int bytes_per_tile = tw * th * bpp / 8; |
| 201 |
cols = clampi(cols, 1, 64); |
| 202 |
rows = clampi(rows, 1, 64); |
| 203 |
const int row_step = cols * bytes_per_tile; |
| 204 |
const int page_step = cols * rows * bytes_per_tile; |
| 205 |
|
| 206 |
base = clampi(base, 0, 0x1FFFF); |
| 207 |
|
| 208 |
|
| 209 |
int tile_no = bytes_per_tile ? base / bytes_per_tile : 0; |
| 210 |
ImGui::PushItemWidth(110); |
| 211 |
if (ImGui::InputInt("Tile #", &tile_no, 1, 16)) |
| 212 |
base = clampi(tile_no * bytes_per_tile, 0, 0x1FFFF); |
| 213 |
ImGui::PopItemWidth(); |
| 214 |
ImGui::SameLine(); |
| 215 |
if (ImGui::SmallButton("Align")) |
| 216 |
base -= base % bytes_per_tile; |
| 217 |
ImGui::SameLine(); |
| 218 |
if (ImGui::SmallButton("Align $800")) |
| 219 |
base &= ~0x7FF; |
| 220 |
ImGui::SameLine(); |
| 221 |
ImGui::TextDisabled("(?)"); |
| 222 |
if (ImGui::IsItemHovered()) |
| 223 |
ImGui::SetTooltip("Type a hex address in Base, or a tile index in Tile #, to jump.\n" |
| 224 |
"Step with the buttons below, or drag the scrollbar for fast scrolling.\n" |
| 225 |
"-1/+1 nudge by one byte; Align snaps to the tile stride ($800 = VERA tilebase)."); |
| 226 |
|
| 227 |
|
| 228 |
if (ImGui::SmallButton("|< Home")) base = 0; |
| 229 |
ImGui::SameLine(); if (ImGui::SmallButton("<< Page")) base -= page_step; |
| 230 |
ImGui::SameLine(); if (ImGui::SmallButton("< Row")) base -= row_step; |
| 231 |
ImGui::SameLine(); if (ImGui::SmallButton("< Tile")) base -= bytes_per_tile; |
| 232 |
ImGui::SameLine(); if (ImGui::SmallButton("-1")) base -= 1; |
| 233 |
ImGui::SameLine(); if (ImGui::SmallButton("+1")) base += 1; |
| 234 |
ImGui::SameLine(); if (ImGui::SmallButton("Tile >")) base += bytes_per_tile; |
| 235 |
ImGui::SameLine(); if (ImGui::SmallButton("Row >")) base += row_step; |
| 236 |
ImGui::SameLine(); if (ImGui::SmallButton("Page >>")) base += page_step; |
| 237 |
base = clampi(base, 0, 0x1FFFF); |
| 238 |
|
| 239 |
|
| 240 |
ImGui::PushItemWidth(-1); |
| 241 |
if (ImGui::SliderInt("##tilescroll", &base, 0, 0x1FFFF, "scroll -> $%05X")) |
| 242 |
base -= base % bytes_per_tile; |
| 243 |
ImGui::PopItemWidth(); |
| 244 |
base = clampi(base, 0, 0x1FFFF); |
| 245 |
|
| 246 |
|
| 247 |
ImGui::PushItemWidth(110); |
| 248 |
ImGui::SliderInt("Columns", &cols, 1, 64); |
| 249 |
ImGui::SameLine(); |
| 250 |
ImGui::SliderInt("Rows", &rows, 1, 64); |
| 251 |
ImGui::SameLine(); |
| 252 |
ImGui::SliderInt("Pal offset", &pal_off, 0, 15); |
| 253 |
ImGui::SameLine(); |
| 254 |
ImGui::SliderFloat("Zoom", &zoom, 1.0f, 8.0f, "%.0fx"); |
| 255 |
ImGui::PopItemWidth(); |
| 256 |
|
| 257 |
cols = clampi(cols, 1, 64); |
| 258 |
rows = clampi(rows, 1, 64); |
| 259 |
const int img_w = cols * tw; |
| 260 |
const int img_h = rows * th; |
| 261 |
const int mask = (1 << bpp) - 1; |
| 262 |
|
| 263 |
static PixelBuffer buf; |
| 264 |
uint32_t *px = buf.ensure((size_t)img_w * img_h); |
| 265 |
if (!px) |
| 266 |
return; |
| 267 |
|
| 268 |
for (int t = 0; t < cols * rows; ++t) { |
| 269 |
const int tx = t % cols; |
| 270 |
const int ty = t / cols; |
| 271 |
const uint32_t tile_addr = (uint32_t)base + (uint32_t)t * bytes_per_tile; |
| 272 |
for (int y = 0; y < th; ++y) { |
| 273 |
for (int x = 0; x < tw; ++x) { |
| 274 |
const uint32_t bitpos = (uint32_t)(y * tw + x) * bpp; |
| 275 |
const uint8_t byte = video_space_read(tile_addr + (bitpos >> 3)); |
| 276 |
const int shift = 8 - bpp - (int)(bitpos & 7); |
| 277 |
const int idx = (byte >> shift) & mask; |
| 278 |
const uint32_t color = resolve_color(pal, idx, pal_off << 4, false); |
| 279 |
px[(size_t)(ty * th + y) * img_w + (tx * tw + x)] = color; |
| 280 |
} |
| 281 |
} |
| 282 |
} |
| 283 |
|
| 284 |
static GfxTexture gt; |
| 285 |
if (!gt.ensure(img_w, img_h)) |
| 286 |
return; |
| 287 |
gt.update(px); |
| 288 |
|
| 289 |
ImGui::Separator(); |
| 290 |
const ImVec2 img_pos = ImGui::GetCursorScreenPos(); |
| 291 |
ImGui::Image(gt.id(), ImVec2(img_w * zoom, img_h * zoom)); |
| 292 |
|
| 293 |
int hovered = -1; |
| 294 |
if (ImGui::IsItemHovered()) { |
| 295 |
const ImVec2 m = ImGui::GetIO().MousePos; |
| 296 |
const int lx = (int)((m.x - img_pos.x) / zoom); |
| 297 |
const int ly = (int)((m.y - img_pos.y) / zoom); |
| 298 |
const int htx = lx / tw; |
| 299 |
const int hty = ly / th; |
| 300 |
if (htx >= 0 && htx < cols && hty >= 0 && hty < rows) { |
| 301 |
hovered = hty * cols + htx; |
| 302 |
const uint32_t a = (uint32_t)base + (uint32_t)hovered * bytes_per_tile; |
| 303 |
ImGui::SetTooltip("Tile %d (col %d, row %d)\nVRAM $%05X..$%05X", |
| 304 |
hovered, htx, hty, a, a + bytes_per_tile - 1); |
| 305 |
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) |
| 306 |
selected = hovered; |
| 307 |
} |
| 308 |
} |
| 309 |
|
| 310 |
if (selected >= 0 && selected < cols * rows) { |
| 311 |
const uint32_t a = (uint32_t)base + (uint32_t)selected * bytes_per_tile; |
| 312 |
ImGui::Text("Selected tile %d — VRAM $%05X (%d bytes/tile)", selected, a, bytes_per_tile); |
| 313 |
} else { |
| 314 |
ImGui::TextUnformatted("Hover a tile for its address; click to select."); |
| 315 |
} |
| 316 |
} |
| 317 |
|
| 318 |
|
| 319 |
|
| 320 |
|
| 321 |
struct SpriteInfo { |
| 322 |
uint32_t address; |
| 323 |
int mode; |
| 324 |
int w, h; |
| 325 |
int x, y; |
| 326 |
int zdepth; |
| 327 |
int pal_off; |
| 328 |
bool hflip, vflip; |
| 329 |
int collision; |
| 330 |
}; |
| 331 |
|
| 332 |
SpriteInfo |
| 333 |
decode_sprite_attr(int i) |
| 334 |
{ |
| 335 |
uint8_t a[8]; |
| 336 |
for (int b = 0; b < 8; ++b) |
| 337 |
a[b] = video_space_read(ADDR_SPRITE_ATTR + i * 8 + b); |
| 338 |
|
| 339 |
SpriteInfo s; |
| 340 |
s.address = ((uint32_t)a[0] << 5) | ((uint32_t)(a[1] & 0x0f) << 13); |
| 341 |
s.mode = (a[1] >> 7) & 1; |
| 342 |
s.w = 1 << (((a[7] >> 4) & 3) + 3); |
| 343 |
s.h = 1 << (((a[7] >> 6) & 3) + 3); |
| 344 |
s.pal_off = (a[7] & 0x0f) << 4; |
| 345 |
s.zdepth = (a[6] >> 2) & 3; |
| 346 |
s.collision = a[6] & 0xf0; |
| 347 |
s.hflip = a[6] & 1; |
| 348 |
s.vflip = (a[6] >> 1) & 1; |
| 349 |
s.x = a[2] | ((a[3] & 3) << 8); |
| 350 |
s.y = a[4] | ((a[5] & 3) << 8); |
| 351 |
if (s.x >= 0x400 - s.w) |
| 352 |
s.x -= 0x400; |
| 353 |
if (s.y >= 0x400 - s.h) |
| 354 |
s.y -= 0x400; |
| 355 |
return s; |
| 356 |
} |
| 357 |
|
| 358 |
|
| 359 |
|
| 360 |
void |
| 361 |
decode_sprite_pixels(const SpriteInfo &s, const uint32_t pal[256], |
| 362 |
uint32_t *atlas, int atlas_w, int cx, int cy) |
| 363 |
{ |
| 364 |
for (int y = 0; y < s.h; ++y) { |
| 365 |
for (int x = 0; x < s.w; ++x) { |
| 366 |
int idx; |
| 367 |
if (s.mode) { |
| 368 |
idx = video_space_read(s.address + (uint32_t)y * s.w + x); |
| 369 |
} else { |
| 370 |
uint8_t byte = video_space_read(s.address + (uint32_t)y * (s.w / 2) + (x / 2)); |
| 371 |
idx = (x & 1) ? (byte & 0x0f) : (byte >> 4); |
| 372 |
} |
| 373 |
atlas[(size_t)(cy + y) * atlas_w + (cx + x)] = |
| 374 |
resolve_color(pal, idx, s.pal_off, true); |
| 375 |
} |
| 376 |
} |
| 377 |
} |
| 378 |
|
| 379 |
void |
| 380 |
draw_sprites_tab(const uint32_t pal[256]) |
| 381 |
{ |
| 382 |
static bool hide_disabled = false; |
| 383 |
static float zoom = 2.0f; |
| 384 |
|
| 385 |
ImGui::Checkbox("Hide disabled (z=0)", &hide_disabled); |
| 386 |
ImGui::SameLine(); |
| 387 |
ImGui::PushItemWidth(120); |
| 388 |
ImGui::SliderFloat("Preview zoom", &zoom, 1.0f, 4.0f, "%.0fx"); |
| 389 |
ImGui::PopItemWidth(); |
| 390 |
|
| 391 |
|
| 392 |
constexpr int CELL = 64; |
| 393 |
constexpr int ATLAS_COLS = 16; |
| 394 |
constexpr int ATLAS_ROWS = 8; |
| 395 |
constexpr int ATLAS_W = CELL * ATLAS_COLS; |
| 396 |
constexpr int ATLAS_H = CELL * ATLAS_ROWS; |
| 397 |
|
| 398 |
static PixelBuffer buf; |
| 399 |
uint32_t *atlas = buf.ensure((size_t)ATLAS_W * ATLAS_H); |
| 400 |
if (!atlas) |
| 401 |
return; |
| 402 |
memset(atlas, 0, (size_t)ATLAS_W * ATLAS_H * sizeof(uint32_t)); |
| 403 |
|
| 404 |
SpriteInfo info[NUM_SPRITES]; |
| 405 |
for (int i = 0; i < NUM_SPRITES; ++i) { |
| 406 |
info[i] = decode_sprite_attr(i); |
| 407 |
const int cx = (i % ATLAS_COLS) * CELL; |
| 408 |
const int cy = (i / ATLAS_COLS) * CELL; |
| 409 |
decode_sprite_pixels(info[i], pal, atlas, ATLAS_W, cx, cy); |
| 410 |
} |
| 411 |
|
| 412 |
static GfxTexture gt; |
| 413 |
if (!gt.ensure(ATLAS_W, ATLAS_H)) |
| 414 |
return; |
| 415 |
gt.update(atlas); |
| 416 |
|
| 417 |
ImGui::Separator(); |
| 418 |
const ImGuiTableFlags tflags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | |
| 419 |
ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingFixedFit | |
| 420 |
DBGUI_TABLE_FLAGS_RESIZABLE; |
| 421 |
if (ImGui::BeginTable("sprites", 9, tflags, ImVec2(0, 0))) { |
| 422 |
ImGui::TableSetupScrollFreeze(0, 1); |
| 423 |
ImGui::TableSetupColumn("#"); |
| 424 |
ImGui::TableSetupColumn("Image"); |
| 425 |
ImGui::TableSetupColumn("Addr"); |
| 426 |
ImGui::TableSetupColumn("Mode"); |
| 427 |
ImGui::TableSetupColumn("Size"); |
| 428 |
ImGui::TableSetupColumn("X,Y"); |
| 429 |
ImGui::TableSetupColumn("Z"); |
| 430 |
ImGui::TableSetupColumn("PalOff"); |
| 431 |
ImGui::TableSetupColumn("Flip"); |
| 432 |
ImGui::TableHeadersRow(); |
| 433 |
|
| 434 |
for (int i = 0; i < NUM_SPRITES; ++i) { |
| 435 |
const SpriteInfo &s = info[i]; |
| 436 |
if (hide_disabled && s.zdepth == 0) |
| 437 |
continue; |
| 438 |
|
| 439 |
ImGui::TableNextRow(); |
| 440 |
ImGui::TableNextColumn(); |
| 441 |
ImGui::Text("%d", i); |
| 442 |
|
| 443 |
ImGui::TableNextColumn(); |
| 444 |
const int cx = (i % ATLAS_COLS) * CELL; |
| 445 |
const int cy = (i / ATLAS_COLS) * CELL; |
| 446 |
ImVec2 uv0((float)cx / ATLAS_W, (float)cy / ATLAS_H); |
| 447 |
ImVec2 uv1((float)(cx + s.w) / ATLAS_W, (float)(cy + s.h) / ATLAS_H); |
| 448 |
ImGui::Image(gt.id(), ImVec2(s.w * zoom, s.h * zoom), uv0, uv1); |
| 449 |
|
| 450 |
ImGui::TableNextColumn(); |
| 451 |
ImGui::Text("$%05X", s.address); |
| 452 |
ImGui::TableNextColumn(); |
| 453 |
ImGui::TextUnformatted(s.mode ? "8bpp" : "4bpp"); |
| 454 |
ImGui::TableNextColumn(); |
| 455 |
ImGui::Text("%dx%d", s.w, s.h); |
| 456 |
ImGui::TableNextColumn(); |
| 457 |
ImGui::Text("%d,%d", s.x, s.y); |
| 458 |
ImGui::TableNextColumn(); |
| 459 |
ImGui::Text("%d", s.zdepth); |
| 460 |
ImGui::TableNextColumn(); |
| 461 |
ImGui::Text("$%02X", s.pal_off); |
| 462 |
ImGui::TableNextColumn(); |
| 463 |
ImGui::TextUnformatted(s.hflip && s.vflip ? "HV" : s.hflip ? "H" : s.vflip ? "V" : "-"); |
| 464 |
} |
| 465 |
ImGui::EndTable(); |
| 466 |
} |
| 467 |
} |
| 468 |
|
| 469 |
|
| 470 |
|
| 471 |
|
| 472 |
struct LayerRegs { |
| 473 |
int color_depth; |
| 474 |
int bpp; |
| 475 |
bool bitmap_mode; |
| 476 |
bool text_mode; |
| 477 |
bool text_256c; |
| 478 |
uint32_t map_base; |
| 479 |
uint32_t tile_base; |
| 480 |
int mapw, maph; |
| 481 |
int tilew, tileh; |
| 482 |
int tile_size_log2; |
| 483 |
int bitmap_w; |
| 484 |
int bitmap_pal_off; |
| 485 |
int hscroll, vscroll; |
| 486 |
}; |
| 487 |
|
| 488 |
LayerRegs |
| 489 |
decode_layer_regs(const uint8_t r[7]) |
| 490 |
{ |
| 491 |
uint8_t r0 = r[0], r1 = r[1], r2 = r[2], r3 = r[3], r4 = r[4], r5 = r[5], r6 = r[6]; |
| 492 |
|
| 493 |
LayerRegs L; |
| 494 |
L.color_depth = r0 & 3; |
| 495 |
L.bpp = 1 << L.color_depth; |
| 496 |
L.bitmap_mode = (r0 & 4) != 0; |
| 497 |
L.text_mode = (L.color_depth == 0) && !L.bitmap_mode; |
| 498 |
L.text_256c = (r0 & 8) != 0; |
| 499 |
L.map_base = (uint32_t)r1 << 9; |
| 500 |
L.tile_base = (uint32_t)(r2 & 0xFC) << 9; |
| 501 |
L.mapw = 1 << (5 + ((r0 >> 4) & 3)); |
| 502 |
L.maph = 1 << (5 + ((r0 >> 6) & 3)); |
| 503 |
L.tilew = 1 << (3 + (r2 & 1)); |
| 504 |
L.tileh = 1 << (3 + ((r2 >> 1) & 1)); |
| 505 |
L.tile_size_log2 = (3 + (r2 & 1)) + (3 + ((r2 >> 1) & 1)) + L.color_depth - 3; |
| 506 |
L.bitmap_w = (r2 & 1) ? 640 : 320; |
| 507 |
L.bitmap_pal_off = (r4 & 0x0f) << 4; |
| 508 |
L.hscroll = r3 | ((r4 & 0x0f) << 8); |
| 509 |
L.vscroll = r5 | ((r6 & 0x0f) << 8); |
| 510 |
return L; |
| 511 |
} |
| 512 |
|
| 513 |
LayerRegs |
| 514 |
read_layer_regs(int layer) |
| 515 |
{ |
| 516 |
const uint8_t base = layer == 0 ? 0x0D : 0x14; |
| 517 |
uint8_t r[7]; |
| 518 |
for (int i = 0; i < 7; ++i) |
| 519 |
r[i] = video_read(base + i, true); |
| 520 |
return decode_layer_regs(r); |
| 521 |
} |
| 522 |
|
| 523 |
|
| 524 |
|
| 525 |
bool |
| 526 |
same_layer_geometry(const LayerRegs &a, const LayerRegs &b) |
| 527 |
{ |
| 528 |
return a.color_depth == b.color_depth && a.bitmap_mode == b.bitmap_mode && |
| 529 |
a.text_mode == b.text_mode && a.text_256c == b.text_256c && |
| 530 |
a.mapw == b.mapw && a.maph == b.maph && |
| 531 |
a.tilew == b.tilew && a.tileh == b.tileh; |
| 532 |
} |
| 533 |
|
| 534 |
|
| 535 |
|
| 536 |
|
| 537 |
|
| 538 |
|
| 539 |
|
| 540 |
|
| 541 |
|
| 542 |
|
| 543 |
|
| 544 |
|
| 545 |
|
| 546 |
|
| 547 |
constexpr int MAX_LAYER_ROWS = 256 * 16; |
| 548 |
|
| 549 |
struct RasterRowRegs { |
| 550 |
uint8_t regs[MAX_LAYER_ROWS][7]; |
| 551 |
bool valid[MAX_LAYER_ROWS]; |
| 552 |
bool any_split; |
| 553 |
bool any_valid; |
| 554 |
}; |
| 555 |
|
| 556 |
void |
| 557 |
build_raster_row_regs(int layer, const LayerRegs &live, RasterRowRegs &out) |
| 558 |
{ |
| 559 |
memset(out.valid, 0, sizeof(out.valid)); |
| 560 |
out.any_split = false; |
| 561 |
out.any_valid = false; |
| 562 |
|
| 563 |
const uint16_t lines = video_get_scanline_count(); |
| 564 |
bool have_first = false; |
| 565 |
uint8_t first[7] = {0}; |
| 566 |
|
| 567 |
for (uint16_t line = 0; line < lines; ++line) { |
| 568 |
uint8_t r[7]; |
| 569 |
bool enabled = false; |
| 570 |
uint16_t layer_row = 0; |
| 571 |
if (!video_get_layer_line_state((uint8_t)layer, line, r, nullptr, &enabled, &layer_row)) |
| 572 |
continue; |
| 573 |
if (!enabled) |
| 574 |
continue; |
| 575 |
|
| 576 |
if (!have_first) { |
| 577 |
memcpy(first, r, sizeof(first)); |
| 578 |
have_first = true; |
| 579 |
} else if (memcmp(first, r, sizeof(first)) != 0) { |
| 580 |
out.any_split = true; |
| 581 |
} |
| 582 |
|
| 583 |
|
| 584 |
|
| 585 |
const LayerRegs LL = decode_layer_regs(r); |
| 586 |
if (!same_layer_geometry(LL, live)) |
| 587 |
continue; |
| 588 |
|
| 589 |
|
| 590 |
|
| 591 |
|
| 592 |
|
| 593 |
|
| 594 |
|
| 595 |
const int ly = (int)layer_row; |
| 596 |
if (ly < 0 || ly >= MAX_LAYER_ROWS || out.valid[ly]) |
| 597 |
continue; |
| 598 |
|
| 599 |
memcpy(out.regs[ly], r, sizeof(out.regs[ly])); |
| 600 |
out.valid[ly] = true; |
| 601 |
out.any_valid = true; |
| 602 |
} |
| 603 |
} |
| 604 |
|
| 605 |
|
| 606 |
|
| 607 |
|
| 608 |
void |
| 609 |
draw_bitmap_tab(const uint32_t pal[256]) |
| 610 |
{ |
| 611 |
static int layer = 0; |
| 612 |
static int height = 240; |
| 613 |
static float zoom = 1.0f; |
| 614 |
static bool follow_raster = true; |
| 615 |
static bool auto_fit = true; |
| 616 |
|
| 617 |
|
| 618 |
|
| 619 |
|
| 620 |
int fit_h = 0; |
| 621 |
video_get_active_layer_size(nullptr, &fit_h); |
| 622 |
if (auto_fit && fit_h > 0) |
| 623 |
height = clampi(fit_h, 1, 480); |
| 624 |
|
| 625 |
ImGui::PushItemWidth(120); |
| 626 |
ImGui::Combo("Layer", &layer, "Layer 0\0Layer 1\0"); |
| 627 |
ImGui::SameLine(); |
| 628 |
|
| 629 |
|
| 630 |
if (ImGui::DragInt("Height", &height, 1.0f, 8, 480)) |
| 631 |
auto_fit = false; |
| 632 |
ImGui::SameLine(); |
| 633 |
ImGui::SliderFloat("Zoom", &zoom, 1.0f, 4.0f, "%.1fx"); |
| 634 |
ImGui::PopItemWidth(); |
| 635 |
ImGui::SameLine(); |
| 636 |
if (ImGui::Button("Fit")) |
| 637 |
auto_fit = true; |
| 638 |
if (ImGui::IsItemHovered()) |
| 639 |
ImGui::SetTooltip("Match the active video mode (%d lines)", fit_h); |
| 640 |
|
| 641 |
LayerRegs L = read_layer_regs(layer); |
| 642 |
ImGui::Text("depth=%dbpp bitmap=%s tile_base(data)=$%05X width=%d pal_off=$%02X", |
| 643 |
L.bpp, L.bitmap_mode ? "yes" : "no", L.tile_base, L.bitmap_w, L.bitmap_pal_off); |
| 644 |
if (!L.bitmap_mode) { |
| 645 |
ImGui::TextColored(ImVec4(1, 0.7f, 0.2f, 1), |
| 646 |
"Layer %d is not in bitmap mode; showing raw decode anyway.", layer); |
| 647 |
} |
| 648 |
|
| 649 |
ImGui::Checkbox("Follow raster", &follow_raster); |
| 650 |
ImGui::SameLine(); |
| 651 |
ImGui::TextDisabled("(decode each row with the registers that rendered it)"); |
| 652 |
|
| 653 |
static RasterRowRegs rr; |
| 654 |
build_raster_row_regs(layer, L, rr); |
| 655 |
const bool raster_active = follow_raster && rr.any_valid; |
| 656 |
|
| 657 |
if (rr.any_split) { |
| 658 |
ImGui::TextColored(raster_active ? ImVec4(0.4f, 1, 0.4f, 1) : ImVec4(1, 0.7f, 0.2f, 1), |
| 659 |
raster_active ? "Raster split detected - rows decoded per scanline." |
| 660 |
: "Raster split detected - enable \"Follow raster\"."); |
| 661 |
} |
| 662 |
|
| 663 |
const int img_w = L.bitmap_w; |
| 664 |
const int img_h = clampi(height, 1, 480); |
| 665 |
const int mask = (1 << L.bpp) - 1; |
| 666 |
|
| 667 |
static PixelBuffer buf; |
| 668 |
uint32_t *px = buf.ensure((size_t)img_w * img_h); |
| 669 |
if (!px) |
| 670 |
return; |
| 671 |
|
| 672 |
for (int y = 0; y < img_h; ++y) { |
| 673 |
|
| 674 |
LayerRegs LR = L; |
| 675 |
if (raster_active && y < MAX_LAYER_ROWS && rr.valid[y]) |
| 676 |
LR = decode_layer_regs(rr.regs[y]); |
| 677 |
|
| 678 |
const uint32_t row_base = LR.tile_base + (uint32_t)((y * img_w * LR.bpp) >> 3); |
| 679 |
for (int x = 0; x < img_w; ++x) { |
| 680 |
const uint32_t bitpos = (uint32_t)x * LR.bpp; |
| 681 |
const uint8_t byte = video_space_read(row_base + (bitpos >> 3)); |
| 682 |
const int shift = 8 - LR.bpp - (int)(bitpos & 7); |
| 683 |
const int idx = (byte >> shift) & mask; |
| 684 |
px[(size_t)y * img_w + x] = resolve_color(pal, idx, LR.bitmap_pal_off, false); |
| 685 |
} |
| 686 |
} |
| 687 |
|
| 688 |
static GfxTexture gt; |
| 689 |
if (!gt.ensure(img_w, img_h)) |
| 690 |
return; |
| 691 |
gt.update(px); |
| 692 |
|
| 693 |
ImGui::Separator(); |
| 694 |
ImGui::BeginChild("bmp", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); |
| 695 |
ImGui::Image(gt.id(), ImVec2(img_w * zoom, img_h * zoom)); |
| 696 |
ImGui::EndChild(); |
| 697 |
} |
| 698 |
|
| 699 |
|
| 700 |
|
| 701 |
|
| 702 |
void |
| 703 |
draw_tilemap_tab(const uint32_t pal[256]) |
| 704 |
{ |
| 705 |
static int layer = 0; |
| 706 |
static int max_cols = 32; |
| 707 |
static int max_rows = 32; |
| 708 |
static float zoom = 1.0f; |
| 709 |
static bool follow_raster = true; |
| 710 |
static bool auto_fit = true; |
| 711 |
|
| 712 |
ImGui::PushItemWidth(120); |
| 713 |
ImGui::Combo("Layer", &layer, "Layer 0\0Layer 1\0"); |
| 714 |
ImGui::PopItemWidth(); |
| 715 |
|
| 716 |
LayerRegs L = read_layer_regs(layer); |
| 717 |
|
| 718 |
|
| 719 |
|
| 720 |
|
| 721 |
int fit_w = 0, fit_h = 0; |
| 722 |
video_get_active_layer_size(&fit_w, &fit_h); |
| 723 |
const int fit_cols = L.tilew > 0 ? (fit_w + L.tilew - 1) / L.tilew : 0; |
| 724 |
const int fit_rows = L.tileh > 0 ? (fit_h + L.tileh - 1) / L.tileh : 0; |
| 725 |
if (auto_fit && fit_cols > 0 && fit_rows > 0) { |
| 726 |
max_cols = clampi(fit_cols, 1, 128); |
| 727 |
max_rows = clampi(fit_rows, 1, 128); |
| 728 |
} |
| 729 |
|
| 730 |
ImGui::PushItemWidth(120); |
| 731 |
|
| 732 |
|
| 733 |
if (ImGui::DragInt("Max cols", &max_cols, 0.25f, 1, 128)) |
| 734 |
auto_fit = false; |
| 735 |
ImGui::SameLine(); |
| 736 |
if (ImGui::DragInt("Max rows", &max_rows, 0.25f, 1, 128)) |
| 737 |
auto_fit = false; |
| 738 |
ImGui::SameLine(); |
| 739 |
ImGui::SliderFloat("Zoom", &zoom, 1.0f, 4.0f, "%.1fx"); |
| 740 |
ImGui::PopItemWidth(); |
| 741 |
ImGui::SameLine(); |
| 742 |
if (ImGui::Button("Fit")) |
| 743 |
auto_fit = true; |
| 744 |
if (ImGui::IsItemHovered()) |
| 745 |
ImGui::SetTooltip("Match the active video mode (%dx%d px = %dx%d tiles)", |
| 746 |
fit_w, fit_h, fit_cols, fit_rows); |
| 747 |
|
| 748 |
ImGui::Text("mode=%s depth=%dbpp map_base=$%05X tile_base=$%05X map=%dx%d tile=%dx%d", |
| 749 |
L.bitmap_mode ? "bitmap" : (L.text_mode ? (L.text_256c ? "text256" : "text") : "tile"), |
| 750 |
L.bpp, L.map_base, L.tile_base, L.mapw, L.maph, L.tilew, L.tileh); |
| 751 |
|
| 752 |
if (L.bitmap_mode) { |
| 753 |
ImGui::TextColored(ImVec4(1, 0.7f, 0.2f, 1), |
| 754 |
"Layer %d is in bitmap mode — use the Bitmap tab.", layer); |
| 755 |
return; |
| 756 |
} |
| 757 |
|
| 758 |
ImGui::Checkbox("Follow raster", &follow_raster); |
| 759 |
ImGui::SameLine(); |
| 760 |
ImGui::TextDisabled("(decode each row with the registers that rendered it)"); |
| 761 |
|
| 762 |
static RasterRowRegs rr; |
| 763 |
build_raster_row_regs(layer, L, rr); |
| 764 |
const bool raster_active = follow_raster && rr.any_valid; |
| 765 |
|
| 766 |
if (rr.any_split) { |
| 767 |
if (raster_active) |
| 768 |
ImGui::TextColored(ImVec4(0.4f, 1, 0.4f, 1), |
| 769 |
"Raster split detected - rows decoded per scanline."); |
| 770 |
else |
| 771 |
ImGui::TextColored(ImVec4(1, 0.7f, 0.2f, 1), |
| 772 |
"Raster split detected - rows outside the active band will " |
| 773 |
"decode incorrectly. Enable \"Follow raster\"."); |
| 774 |
} |
| 775 |
|
| 776 |
|
| 777 |
int cols = clampi(max_cols < L.mapw ? max_cols : L.mapw, 1, 128); |
| 778 |
int rows = clampi(max_rows < L.maph ? max_rows : L.maph, 1, 128); |
| 779 |
while ((long)cols * L.tilew > 2048) |
| 780 |
--cols; |
| 781 |
while ((long)rows * L.tileh > 2048) |
| 782 |
--rows; |
| 783 |
|
| 784 |
const int img_w = cols * L.tilew; |
| 785 |
const int img_h = rows * L.tileh; |
| 786 |
const int mask = (1 << L.bpp) - 1; |
| 787 |
|
| 788 |
static PixelBuffer buf; |
| 789 |
uint32_t *px = buf.ensure((size_t)img_w * img_h); |
| 790 |
if (!px) |
| 791 |
return; |
| 792 |
|
| 793 |
for (int ty = 0; ty < rows; ++ty) { |
| 794 |
for (int y = 0; y < L.tileh; ++y) { |
| 795 |
|
| 796 |
const int layer_row = ty * L.tileh + y; |
| 797 |
LayerRegs LR = L; |
| 798 |
if (raster_active && layer_row < MAX_LAYER_ROWS && rr.valid[layer_row]) |
| 799 |
LR = decode_layer_regs(rr.regs[layer_row]); |
| 800 |
|
| 801 |
uint32_t *dst = px + (size_t)(ty * L.tileh + y) * img_w; |
| 802 |
|
| 803 |
for (int tx = 0; tx < cols; ++tx) { |
| 804 |
const uint32_t map_addr = LR.map_base + (uint32_t)(ty * LR.mapw + tx) * 2; |
| 805 |
const uint8_t byte0 = video_space_read(map_addr); |
| 806 |
const uint8_t byte1 = video_space_read(map_addr + 1); |
| 807 |
|
| 808 |
if (LR.text_mode) { |
| 809 |
|
| 810 |
const int fg = LR.text_256c ? byte1 : (byte1 & 0x0f); |
| 811 |
const int bg = LR.text_256c ? 0 : ((byte1 >> 4) & 0x0f); |
| 812 |
const uint32_t glyph_base = LR.tile_base + (uint32_t)byte0 * (LR.tilew * LR.tileh / 8); |
| 813 |
for (int x = 0; x < LR.tilew; ++x) { |
| 814 |
const uint32_t bitpos = (uint32_t)(y * LR.tilew + x); |
| 815 |
const uint8_t b = video_space_read(glyph_base + (bitpos >> 3)); |
| 816 |
const int on = (b >> (7 - (bitpos & 7))) & 1; |
| 817 |
dst[tx * L.tilew + x] = pal[on ? fg : bg]; |
| 818 |
} |
| 819 |
} else { |
| 820 |
|
| 821 |
const int tile_index = byte0 | ((byte1 & 3) << 8); |
| 822 |
const int pal_off = byte1 & 0xf0; |
| 823 |
const bool vflip = (byte1 >> 3) & 1; |
| 824 |
const bool hflip = (byte1 >> 2) & 1; |
| 825 |
const uint32_t tile_start = LR.tile_base + ((uint32_t)tile_index << LR.tile_size_log2); |
| 826 |
const int sy = vflip ? (LR.tileh - 1 - y) : y; |
| 827 |
for (int x = 0; x < LR.tilew; ++x) { |
| 828 |
const int sx = hflip ? (LR.tilew - 1 - x) : x; |
| 829 |
const uint32_t bitpos = (uint32_t)(sy * LR.tilew + sx) * LR.bpp; |
| 830 |
const uint8_t byte = video_space_read(tile_start + (bitpos >> 3)); |
| 831 |
const int shift = 8 - LR.bpp - (int)(bitpos & 7); |
| 832 |
const int idx = (byte >> shift) & mask; |
| 833 |
dst[tx * L.tilew + x] = resolve_color(pal, idx, pal_off, false); |
| 834 |
} |
| 835 |
} |
| 836 |
} |
| 837 |
} |
| 838 |
} |
| 839 |
|
| 840 |
static GfxTexture gt; |
| 841 |
if (!gt.ensure(img_w, img_h)) |
| 842 |
return; |
| 843 |
gt.update(px); |
| 844 |
|
| 845 |
if (cols < L.mapw || rows < L.maph) |
| 846 |
ImGui::TextDisabled("Showing %dx%d of %dx%d tiles (raise Max cols/rows).", |
| 847 |
cols, rows, L.mapw, L.maph); |
| 848 |
|
| 849 |
ImGui::Separator(); |
| 850 |
ImGui::BeginChild("tmap", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); |
| 851 |
ImGui::Image(gt.id(), ImVec2(img_w * zoom, img_h * zoom)); |
| 852 |
ImGui::EndChild(); |
| 853 |
} |
| 854 |
|
| 855 |
|
| 856 |
|
| 857 |
|
| 858 |
|
| 859 |
static const char * |
| 860 |
vera_reg_name(int reg, int dcsel) |
| 861 |
{ |
| 862 |
static const char *fixed[0x20] = { |
| 863 |
"ADDR_L", "ADDR_M", "ADDR_H", "DATA0", "DATA1", "CTRL", "IEN", "ISR", |
| 864 |
"IRQLINE_L", nullptr, nullptr, nullptr, nullptr, |
| 865 |
"L0_CONFIG", "L0_MAPBASE", "L0_TILEBASE", "L0_HSCROLL_L", "L0_HSCROLL_H", |
| 866 |
"L0_VSCROLL_L", "L0_VSCROLL_H", "L1_CONFIG", "L1_MAPBASE", "L1_TILEBASE", |
| 867 |
"L1_HSCROLL_L", "L1_HSCROLL_H", "L1_VSCROLL_L", "L1_VSCROLL_H", |
| 868 |
"AUDIO_CTRL", "AUDIO_RATE", "AUDIO_DATA", "SPI_DATA", "SPI_CTRL" |
| 869 |
}; |
| 870 |
if (reg >= 0x09 && reg <= 0x0C) { |
| 871 |
static const char *dc0[4] = {"DC_VIDEO", "DC_HSCALE", "DC_VSCALE", "DC_BORDER"}; |
| 872 |
static const char *dc1[4] = {"DC_HSTART", "DC_HSTOP", "DC_VSTART", "DC_VSTOP"}; |
| 873 |
if (dcsel == 0) |
| 874 |
return dc0[reg - 0x09]; |
| 875 |
if (dcsel == 1) |
| 876 |
return dc1[reg - 0x09]; |
| 877 |
return "FX"; |
| 878 |
} |
| 879 |
return (reg >= 0 && reg < 0x20 && fixed[reg]) ? fixed[reg] : "?"; |
| 880 |
} |
| 881 |
|
| 882 |
static void |
| 883 |
draw_registers_tab() |
| 884 |
{ |
| 885 |
|
| 886 |
uint8_t ctrl = video_read(0x05, true); |
| 887 |
int addrsel = ctrl & 1; |
| 888 |
int dcsel = (ctrl >> 1) & 0x3f; |
| 889 |
|
| 890 |
ImGui::Text("CTRL $%02X ADDRSEL = %d DCSEL = %d", ctrl, addrsel, dcsel); |
| 891 |
ImGui::TextDisabled("VERA external registers $9F20-$9F3F. Hover a value for hex/dec/bin."); |
| 892 |
ImGui::Separator(); |
| 893 |
|
| 894 |
ImGuiTableFlags tflags = ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerV | |
| 895 |
ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY | |
| 896 |
DBGUI_TABLE_FLAGS_RESIZABLE; |
| 897 |
if (ImGui::BeginTable("vera_regs", 4, tflags, ImVec2(0, 300))) { |
| 898 |
ImGui::TableSetupScrollFreeze(0, 1); |
| 899 |
ImGui::TableSetupColumn("Reg"); |
| 900 |
ImGui::TableSetupColumn("Name"); |
| 901 |
ImGui::TableSetupColumn("Hex"); |
| 902 |
ImGui::TableSetupColumn("Binary"); |
| 903 |
ImGui::TableHeadersRow(); |
| 904 |
|
| 905 |
for (int r = 0; r < 0x20; r++) { |
| 906 |
|
| 907 |
|
| 908 |
bool fx_wo = (r >= 0x09 && r <= 0x0C && dcsel >= 2); |
| 909 |
|
| 910 |
|
| 911 |
|
| 912 |
|
| 913 |
|
| 914 |
|
| 915 |
ImGui::TableNextRow(); |
| 916 |
ImGui::TableSetColumnIndex(0); |
| 917 |
ImGui::Text("$9F%02X", 0x20 + r); |
| 918 |
ImGui::TableSetColumnIndex(1); |
| 919 |
ImGui::TextUnformatted(vera_reg_name(r, dcsel)); |
| 920 |
|
| 921 |
if (fx_wo) { |
| 922 |
ImGui::TableSetColumnIndex(2); |
| 923 |
ImGui::TextDisabled("--"); |
| 924 |
ImGui::TableSetColumnIndex(3); |
| 925 |
ImGui::TextDisabled("(write-only)"); |
| 926 |
} else { |
| 927 |
uint8_t v = video_read((uint8_t)r, true); |
| 928 |
ImGui::TableSetColumnIndex(2); |
| 929 |
ImGui::Text("$%02X", v); |
| 930 |
dbgui_hover_value_tooltip(vera_reg_name(r, dcsel), v, 1); |
| 931 |
ImGui::TableSetColumnIndex(3); |
| 932 |
char bin[12]; |
| 933 |
dbgui_format_binary(bin, sizeof bin, v, 8); |
| 934 |
ImGui::TextUnformatted(bin); |
| 935 |
} |
| 936 |
} |
| 937 |
ImGui::EndTable(); |
| 938 |
} |
| 939 |
|
| 940 |
ImGui::Separator(); |
| 941 |
if (ImGui::CollapsingHeader("Decoded", ImGuiTreeNodeFlags_DefaultOpen)) { |
| 942 |
|
| 943 |
uint8_t al = video_read(0x00, true); |
| 944 |
uint8_t am = video_read(0x01, true); |
| 945 |
uint8_t ah = video_read(0x02, true); |
| 946 |
uint32_t addr = ((uint32_t)(ah & 1) << 16) | ((uint32_t)am << 8) | al; |
| 947 |
ImGui::Text("Data port %d address: $%05X (increment code %d)", addrsel, addr, (ah >> 3) & 0x1f); |
| 948 |
|
| 949 |
if (dcsel == 0) { |
| 950 |
uint8_t dcv = video_read(0x09, true); |
| 951 |
static const char *omode[4] = {"disabled", "VGA", "NTSC", "RGB/component"}; |
| 952 |
ImGui::Text("DC_VIDEO $%02X: output=%s layer0=%s layer1=%s sprites=%s", dcv, |
| 953 |
omode[dcv & 3], (dcv & 0x10) ? "ON" : "off", |
| 954 |
(dcv & 0x20) ? "ON" : "off", (dcv & 0x40) ? "ON" : "off"); |
| 955 |
uint8_t hs = video_read(0x0A, true); |
| 956 |
uint8_t vs = video_read(0x0B, true); |
| 957 |
ImGui::Text("Scale: H=%d (%.2fx) V=%d (%.2fx) Border color=%d", hs, hs / 128.0f, |
| 958 |
vs, vs / 128.0f, video_read(0x0C, true)); |
| 959 |
} else if (dcsel == 1) { |
| 960 |
ImGui::Text("Active area: H %d..%d, V %d..%d", video_read(0x09, true) << 2, |
| 961 |
video_read(0x0A, true) << 2, video_read(0x0B, true) << 1, |
| 962 |
video_read(0x0C, true) << 1); |
| 963 |
} else { |
| 964 |
ImGui::TextDisabled("DCSEL=%d selects FX registers; set DCSEL=0 to see DC_VIDEO etc.", dcsel); |
| 965 |
} |
| 966 |
|
| 967 |
static const char *depth[4] = {"1bpp", "2bpp", "4bpp", "8bpp"}; |
| 968 |
for (int layer = 0; layer < 2; layer++) { |
| 969 |
int base = layer == 0 ? 0x0D : 0x14; |
| 970 |
uint8_t cfg = video_read((uint8_t)base, true); |
| 971 |
uint8_t mapb = video_read((uint8_t)(base + 1), true); |
| 972 |
uint8_t tileb = video_read((uint8_t)(base + 2), true); |
| 973 |
ImGui::Text("Layer %d $%02X: %s%s%s map %dx%d mapbase $%05X tilebase $%05X tile %dx%d", |
| 974 |
layer, cfg, depth[cfg & 3], (cfg & 4) ? " bitmap" : "", |
| 975 |
(cfg & 8) ? " T256C" : "", 32 << ((cfg >> 4) & 3), 32 << ((cfg >> 6) & 3), |
| 976 |
(uint32_t)mapb << 9, (uint32_t)(tileb & 0xFC) << 9, 8 << (tileb & 1), |
| 977 |
8 << ((tileb >> 1) & 1)); |
| 978 |
} |
| 979 |
} |
| 980 |
} |
| 981 |
|
| 982 |
|
| 983 |
|
| 984 |
|
| 985 |
void |
| 986 |
vera_panel_render(bool *p_open) |
| 987 |
{ |
| 988 |
if (ImGui::Begin("VERA", p_open)) { |
| 989 |
dbgui_window_zoom("vera"); |
| 990 |
if (debug_ui_get_renderer() == nullptr) { |
| 991 |
ImGui::TextUnformatted("Debugger renderer unavailable."); |
| 992 |
dbgui_window_end(); |
| 993 |
return; |
| 994 |
} |
| 995 |
|
| 996 |
uint32_t pal[256]; |
| 997 |
build_palette(pal); |
| 998 |
|
| 999 |
if (ImGui::BeginTabBar("vera_tabs", ImGuiTabBarFlags_DrawSelectedOverline)) { |
| 1000 |
if (ImGui::BeginTabItem("Registers")) { |
| 1001 |
draw_registers_tab(); |
| 1002 |
ImGui::EndTabItem(); |
| 1003 |
} |
| 1004 |
if (ImGui::BeginTabItem("Palette")) { |
| 1005 |
draw_palette_tab(); |
| 1006 |
ImGui::EndTabItem(); |
| 1007 |
} |
| 1008 |
if (ImGui::BeginTabItem("Tiles")) { |
| 1009 |
draw_tiles_tab(pal); |
| 1010 |
ImGui::EndTabItem(); |
| 1011 |
} |
| 1012 |
if (ImGui::BeginTabItem("Sprites")) { |
| 1013 |
draw_sprites_tab(pal); |
| 1014 |
ImGui::EndTabItem(); |
| 1015 |
} |
| 1016 |
|
| 1017 |
|
| 1018 |
|
| 1019 |
if (ImGui::BeginTabItem("Multiplex")) { |
| 1020 |
draw_sprite_multiplex_tab(pal); |
| 1021 |
ImGui::EndTabItem(); |
| 1022 |
} |
| 1023 |
if (ImGui::BeginTabItem("Bitmap")) { |
| 1024 |
draw_bitmap_tab(pal); |
| 1025 |
ImGui::EndTabItem(); |
| 1026 |
} |
| 1027 |
if (ImGui::BeginTabItem("Tilemap")) { |
| 1028 |
draw_tilemap_tab(pal); |
| 1029 |
ImGui::EndTabItem(); |
| 1030 |
} |
| 1031 |
ImGui::EndTabBar(); |
| 1032 |
} |
| 1033 |
} |
| 1034 |
dbgui_window_end(); |
| 1035 |
} |
| 1036 |
|
| 1037 |
} |
| 1038 |
|
| 1039 |
static DebugPanelRegistration s_reg("VERA", vera_panel_render, true); |