| e8555c89 Andy McCall |
1 |
#include "ImGuiCharacter.hh" |
| 2 |
|
| 3 |
#include "ImGuiCpp.hh" |
| 4 |
#include "ImGuiManager.hh" |
| 5 |
#include "ImGuiPalette.hh" |
| 6 |
#include "ImGuiUtils.hh" |
| 7 |
|
| 8 |
#include "DisplayMode.hh" |
| 9 |
#include "MSXMotherBoard.hh" |
| 10 |
#include "VDP.hh" |
| 11 |
#include "VDPVRAM.hh" |
| 12 |
|
| 13 |
#include "one_of.hh" |
| 14 |
#include "ranges.hh" |
| 15 |
|
| 16 |
#include <imgui.h> |
| 17 |
|
| 18 |
#include <ranges> |
| 19 |
|
| 20 |
namespace openmsx { |
| 21 |
|
| 22 |
using namespace std::literals; |
| 23 |
|
| 24 |
ImGuiCharacter::ImGuiCharacter(ImGuiManager& manager_, size_t index) |
| 25 |
: ImGuiPart(manager_) |
| 26 |
, title(strCat("Tile viewer", strCat_if(index, " (", index + 1, ')'))) |
| 27 |
{ |
| 28 |
} |
| 29 |
|
| 30 |
void ImGuiCharacter::save(ImGuiTextBuffer& buf) |
| 31 |
{ |
| 32 |
savePersistent(buf, *this, persistentElements); |
| 33 |
} |
| 34 |
|
| 35 |
void ImGuiCharacter::loadLine(std::string_view name, zstring_view value) |
| 36 |
{ |
| 37 |
loadOnePersistent(name, value, *this, persistentElements); |
| 38 |
} |
| 39 |
|
| 40 |
void ImGuiCharacter::initHexDigits() |
| 41 |
{ |
| 42 |
smallHexDigits = gl::Texture(false, false); |
| 43 |
|
| 44 |
|
| 45 |
static constexpr int charWidth = 5; |
| 46 |
static constexpr int charHeight = 8; |
| 47 |
static constexpr int totalWidth = 16 * charWidth; |
| 48 |
static constexpr int totalSize = totalWidth * charHeight; |
| 49 |
static constexpr std::string_view glyphs = |
| 50 |
" ... ... ................................................ ........ .........." |
| 51 |
".MMM...M. .MMM..MMM..M.M..MMM..MMM..MMM..MMM..MMM..MMM..MM....MM..MM...MMM..MMM." |
| 52 |
".M.M..MM. ...M....M..M.M..M....M......M..M.M..M.M..M.M..M.M..M....M.M..M....M..." |
| 53 |
".M.M...M. .MM..MMM..M.M..MMM..MMM. .M..MMM..MMM..MMM..MM...M. .M.M..MMM..MMM." |
| 54 |
".M.M. .M. .M.. ...M..MMM....M..M.M. .M..M.M....M..M.M..M.M..M. .M.M..M....M..." |
| 55 |
".M.M. .M. .M......M....M....M..M.M. .M..M.M....M..M.M..M.M..M....M.M..M....M. " |
| 56 |
".MMM. .M. .MMM..MMM. .M..MMM..MMM. .M..MMM..MMM..M.M..MM....MM..MM...MMM..M. " |
| 57 |
" ... ... .......... ............. ...................... ........ ........ "; |
| 58 |
static_assert(glyphs.size() == totalSize); |
| 59 |
|
| 60 |
|
| 61 |
std::array<uint32_t, totalSize> pixels; |
| 62 |
for (auto [c, p] : std::views::zip(glyphs, pixels)) { |
| 63 |
p = (c == ' ') ? ImColor(0.0f, 0.0f, 0.0f, 0.0f) |
| 64 |
: (c == '.') ? ImColor(0.0f, 0.0f, 0.0f, 0.7f) |
| 65 |
: ImColor(1.0f, 1.0f, 1.0f, 0.7f); |
| 66 |
} |
| 67 |
|
| 68 |
|
| 69 |
smallHexDigits.bind(); |
| 70 |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, totalWidth, charHeight, 0, |
| 71 |
GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); |
| 72 |
} |
| 73 |
|
| 74 |
void ImGuiCharacter::paint(MSXMotherBoard* motherBoard) |
| 75 |
{ |
| 76 |
if (!show || !motherBoard) return; |
| 77 |
ImGui::SetNextWindowSize({692, 886}, ImGuiCond_FirstUseEver); |
| 78 |
im::Window(title.c_str(), &show, [&]{ |
| 79 |
auto* vdp = dynamic_cast<VDP*>(motherBoard->findDevice("VDP")); |
| 80 |
if (!vdp) return; |
| 81 |
const auto& vram = vdp->getVRAM().getData(); |
| 82 |
|
| 83 |
int vdpMode = [&] { |
| 84 |
auto base = vdp->getDisplayMode().getBase(); |
| 85 |
if (base == DisplayMode::TEXT1) return TEXT40; |
| 86 |
if (base == DisplayMode::TEXT2) return TEXT80; |
| 87 |
if (base == DisplayMode::GRAPHIC1) return SCR1; |
| 88 |
if (base == DisplayMode::GRAPHIC2) return SCR2; |
| 89 |
if (base == DisplayMode::GRAPHIC3) return SCR4; |
| 90 |
if (base == DisplayMode::MULTICOLOR) return SCR3; |
| 91 |
return OTHER; |
| 92 |
}(); |
| 93 |
auto modeToStr = [](int mode) { |
| 94 |
if (mode == TEXT40) return "screen 0, width 40"; |
| 95 |
if (mode == TEXT80) return "screen 0, width 80"; |
| 96 |
if (mode == SCR1) return "screen 1"; |
| 97 |
if (mode == SCR2) return "screen 2"; |
| 98 |
if (mode == SCR3) return "screen 3"; |
| 99 |
if (mode == SCR4) return "screen 4"; |
| 100 |
if (mode == OTHER) return "non-character"; |
| 101 |
assert(false); return "ERROR"; |
| 102 |
}; |
| 103 |
auto patMult = [](int mode) { return 1 << (mode == one_of(SCR2, SCR4) ? 13 : 11); }; |
| 104 |
auto colMult = [](int mode) { return 1 << (mode == one_of(SCR2, SCR4) ? 13 : |
| 105 |
mode == TEXT80 ? 9 : 6); }; |
| 106 |
auto namMult = [](int mode) { return 1 << (mode == TEXT80 ? 12 : 10); }; |
| 107 |
int vdpFgCol = vdp->getForegroundColor() & 15; |
| 108 |
int vdpBgCol = vdp->getBackgroundColor() & 15; |
| 109 |
int vdpFgBlink = vdp->getBlinkForegroundColor() & 15; |
| 110 |
int vdpBgBlink = vdp->getBlinkBackgroundColor() & 15; |
| 111 |
bool vdpBlink = vdp->getBlinkState(0); |
| 112 |
int vdpPatBase = vdp->getPatternTableBase() & ~(patMult(vdpMode) - 1); |
| 113 |
int vdpColBase = vdp->getColorTableBase() & ~(colMult(vdpMode) - 1); |
| 114 |
int vdpNamBase = vdp->getNameTableBase() & ~(namMult(vdpMode) - 1); |
| 115 |
int vdpLines = vdp->getNumberOfLines(); |
| 116 |
int vdpColor0 = vdp->getTransparency() ? vdpBgCol |
| 117 |
: 16; |
| 118 |
auto vramSize = std::min(vdp->getVRAM().getSize(), 0x20000u); |
| 119 |
bool isMSX1 = vdp->isMSX1VDP(); |
| 120 |
|
| 121 |
bool manMode = overrideAll || overrideMode; |
| 122 |
bool manFgCol = overrideAll || overrideFgCol; |
| 123 |
bool manBgCol = overrideAll || overrideBgCol; |
| 124 |
bool manFgBlink = overrideAll || overrideFgBlink; |
| 125 |
bool manBgBlink = overrideAll || overrideBgBlink; |
| 126 |
bool manBlink = overrideAll || overrideBlink; |
| 127 |
bool manPat = overrideAll || overridePat; |
| 128 |
bool manCol = overrideAll || overrideCol; |
| 129 |
bool manNam = overrideAll || overrideNam; |
| 130 |
bool manRows = overrideAll || overrideRows; |
| 131 |
bool manColor0 = overrideAll || overrideColor0; |
| 132 |
|
| 133 |
int mode = manMode ? manualMode : vdpMode; |
| 134 |
if (mode == SCR4) mode = SCR2; |
| 135 |
|
| 136 |
auto rasterBeamPos = vdp->getMSXPos(vdp->getCurrentTime()); |
| 137 |
if (mode == one_of(TEXT40, TEXT80)) rasterBeamPos.x -= (512 - (6 * 80)) / 2; |
| 138 |
if (mode != TEXT80) rasterBeamPos.x /= 2; |
| 139 |
|
| 140 |
im::TreeNode("Settings", ImGuiTreeNodeFlags_DefaultOpen, [&]{ |
| 141 |
static const char* const color0Str = "0\0001\0002\0003\0004\0005\0006\0007\0008\0009\00010\00011\00012\00013\00014\00015\000none\000"; |
| 142 |
im::Group([&]{ |
| 143 |
ImGui::TextUnformatted("VDP settings"); |
| 144 |
auto pos = ImGui::GetCursorPos(); |
| 145 |
ImGui::Dummy(ImGui::CalcTextSize("Screen mode: screen 0, width 80")); |
| 146 |
ImGui::SetCursorPos(pos); |
| 147 |
im::Disabled(manMode, [&]{ |
| 148 |
ImGui::AlignTextToFramePadding(); |
| 149 |
ImGui::StrCat("Screen mode: ", modeToStr(vdpMode)); |
| 150 |
}); |
| 151 |
im::Disabled(manFgCol, [&]{ |
| 152 |
ImGui::AlignTextToFramePadding(); |
| 153 |
ImGui::StrCat("Foreground color: ", vdpFgCol); |
| 154 |
}); |
| 155 |
im::Disabled(manBgCol, [&]{ |
| 156 |
ImGui::AlignTextToFramePadding(); |
| 157 |
ImGui::StrCat("Background color: ", vdpBgCol); |
| 158 |
}); |
| 159 |
im::Disabled(manFgBlink, [&]{ |
| 160 |
ImGui::AlignTextToFramePadding(); |
| 161 |
ImGui::StrCat("Foreground blink color: ", vdpFgBlink); |
| 162 |
}); |
| 163 |
im::Disabled(manBgBlink, [&]{ |
| 164 |
ImGui::AlignTextToFramePadding(); |
| 165 |
ImGui::StrCat("Background blink color: ", vdpBgBlink); |
| 166 |
}); |
| 167 |
im::Disabled(manBlink, [&]{ |
| 168 |
ImGui::AlignTextToFramePadding(); |
| 169 |
ImGui::StrCat("Blink: ", vdpBlink ? "enabled"sv : "disabled"sv); |
| 170 |
}); |
| 171 |
im::Disabled(manPat, [&]{ |
| 172 |
ImGui::AlignTextToFramePadding(); |
| 173 |
ImGui::StrCat("Pattern table: 0x", hex_string<5>(vdpPatBase)); |
| 174 |
}); |
| 175 |
im::Disabled(manCol, [&]{ |
| 176 |
ImGui::AlignTextToFramePadding(); |
| 177 |
ImGui::StrCat("Color table: 0x", hex_string<5>(vdpColBase)); |
| 178 |
}); |
| 179 |
im::Disabled(manNam, [&]{ |
| 180 |
ImGui::AlignTextToFramePadding(); |
| 181 |
ImGui::StrCat("Name table: 0x", hex_string<5>(vdpNamBase)); |
| 182 |
}); |
| 183 |
im::Disabled(manRows, [&]{ |
| 184 |
ImGui::AlignTextToFramePadding(); |
| 185 |
ImGui::StrCat("Visible rows: ", (vdpLines == 192) ? "24"sv : "26.5"sv); |
| 186 |
}); |
| 187 |
im::Disabled(manColor0, [&]{ |
| 188 |
ImGui::AlignTextToFramePadding(); |
| 189 |
ImGui::StrCat("Replace color 0: ", getComboString(vdpColor0, color0Str)); |
| 190 |
}); |
| 191 |
}); |
| 192 |
ImGui::SameLine(); |
| 193 |
im::Group([&]{ |
| 194 |
ImGui::Checkbox("Manual override", &overrideAll); |
| 195 |
im::Group([&]{ |
| 196 |
im::Disabled(overrideAll, [&]{ |
| 197 |
ImGui::Checkbox("##o-mode", overrideAll ? &overrideAll : &overrideMode); |
| 198 |
ImGui::Checkbox("##o-fgCol", overrideAll ? &overrideAll : &overrideFgCol); |
| 199 |
ImGui::Checkbox("##o-bgCol", overrideAll ? &overrideAll : &overrideBgCol); |
| 200 |
ImGui::Checkbox("##o-fgBlink", overrideAll ? &overrideAll : &overrideFgBlink); |
| 201 |
ImGui::Checkbox("##o-bgBlink", overrideAll ? &overrideAll : &overrideBgBlink); |
| 202 |
ImGui::Checkbox("##o-blink", overrideAll ? &overrideAll : &overrideBlink); |
| 203 |
ImGui::Checkbox("##o-pat", overrideAll ? &overrideAll : &overridePat); |
| 204 |
ImGui::Checkbox("##o-col", overrideAll ? &overrideAll : &overrideCol); |
| 205 |
ImGui::Checkbox("##o-nam", overrideAll ? &overrideAll : &overrideNam); |
| 206 |
ImGui::Checkbox("##o-rows", overrideAll ? &overrideAll : &overrideRows); |
| 207 |
ImGui::Checkbox("##o-color0", overrideAll ? &overrideAll : &overrideColor0); |
| 208 |
}); |
| 209 |
}); |
| 210 |
ImGui::SameLine(); |
| 211 |
im::Group([&]{ |
| 212 |
im::ItemWidth(ImGui::GetFontSize() * 9.0f, [&]{ |
| 213 |
im::Disabled(!manMode, [&]{ |
| 214 |
if (isMSX1 && (manualMode == one_of(TEXT80, SCR4))) manualMode = TEXT40; |
| 215 |
im::Combo("##mode", modeToStr(manualMode), [&]{ |
| 216 |
if (ImGui::Selectable("screen 0,40")) manualMode = TEXT40; |
| 217 |
if (!isMSX1 && ImGui::Selectable("screen 0,80")) manualMode = TEXT80; |
| 218 |
if (ImGui::Selectable("screen 1")) manualMode = SCR1; |
| 219 |
if (ImGui::Selectable("screen 2")) manualMode = SCR2; |
| 220 |
if (ImGui::Selectable("screen 3")) manualMode = SCR3; |
| 221 |
if (!isMSX1 && ImGui::Selectable("screen 4")) manualMode = SCR4; |
| 222 |
}); |
| 223 |
}); |
| 224 |
static const char* const range0_15 = "0\0001\0002\0003\0004\0005\0006\0007\0008\0009\00010\00011\00012\00013\00014\00015\000"; |
| 225 |
im::Disabled(manualMode != one_of(TEXT40, TEXT80), [&]{ |
| 226 |
im::Disabled(!manFgCol, [&]{ |
| 227 |
ImGui::Combo("##fgCol", &manualFgCol, range0_15); |
| 228 |
}); |
| 229 |
im::Disabled(!manBgCol, [&]{ |
| 230 |
ImGui::Combo("##bgCol", &manualBgCol, range0_15); |
| 231 |
}); |
| 232 |
}); |
| 233 |
im::Disabled(manualMode != TEXT80, [&]{ |
| 234 |
im::Disabled(!manFgBlink, [&]{ |
| 235 |
ImGui::Combo("##fgBlink", &manualFgBlink, range0_15); |
| 236 |
}); |
| 237 |
im::Disabled(!manBgBlink, [&]{ |
| 238 |
ImGui::Combo("##bgBlink", &manualBgBlink, range0_15); |
| 239 |
}); |
| 240 |
im::Disabled(!manBlink, [&]{ |
| 241 |
ImGui::Combo("##blink", &manualBlink, "disabled\000enabled\000"); |
| 242 |
}); |
| 243 |
}); |
| 244 |
im::Disabled(!manPat, [&]{ |
| 245 |
comboHexSequence<5>("##pattern", &manualPatBase, patMult(manualMode), vramSize, 0); |
| 246 |
}); |
| 247 |
im::Disabled(manualMode == one_of(TEXT40, SCR3), [&]{ |
| 248 |
im::Disabled(!manCol, [&]{ |
| 249 |
comboHexSequence<5>("##color", &manualColBase, colMult(manualMode), vramSize, 0); |
| 250 |
}); |
| 251 |
}); |
| 252 |
im::Disabled(!manNam, [&]{ |
| 253 |
comboHexSequence<5>("##name", &manualNamBase, namMult(manualMode), vramSize, 0); |
| 254 |
}); |
| 255 |
im::Disabled(!manRows, [&]{ |
| 256 |
ImGui::Combo("##rows", &manualRows, "24\00026.5\00032\000"); |
| 257 |
}); |
| 258 |
im::Disabled(!manColor0, [&]{ |
| 259 |
ImGui::Combo("##Color 0 replacement", &manualColor0, color0Str); |
| 260 |
}); |
| 261 |
}); |
| 262 |
}); |
| 263 |
}); |
| 264 |
|
| 265 |
ImGui::SameLine(); |
| 266 |
ImGui::Dummy(ImVec2(25, 1)); |
| 267 |
ImGui::SameLine(); |
| 268 |
im::Group([&]{ |
| 269 |
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10.0f); |
| 270 |
ImGui::Combo("Palette", &manager.palette->whichPalette, "VDP\000Custom\000Fixed\000"); |
| 271 |
if (ImGui::Button("Open palette editor")) { |
| 272 |
manager.palette->window.raise(); |
| 273 |
} |
| 274 |
ImGui::Separator(); |
| 275 |
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.0f); |
| 276 |
ImGui::Combo("Zoom", &zoom, "1x\0002x\0003x\0004x\0005x\0006x\0007x\0008x\000"); |
| 277 |
ImGui::Checkbox("grid", &grid); |
| 278 |
ImGui::SameLine(); |
| 279 |
im::Disabled(!grid, [&]{ |
| 280 |
ImGui::ColorEdit4("Grid color", gridColor.data(), |
| 281 |
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar); |
| 282 |
}); |
| 283 |
ImGui::Checkbox("Name table overlay", &nameTableOverlay); |
| 284 |
|
| 285 |
ImGui::Checkbox("Override color table", &overrideColorTable); |
| 286 |
HelpMarker("Ignore the content of the color table and instead use this value for everything.\n" |
| 287 |
"Could be useful to visualize the patterns when the color table has the same value for fg and bg."); |
| 288 |
const char* col0_15 = "0\0001\0002\0003\0004\0005\0006\0007\000" |
| 289 |
"8\0009\00010\00011\00012\00013\00014\00015\000"; |
| 290 |
im::DisabledIndent(!overrideColorTable, [&]{ |
| 291 |
int fg = (overrideColorValue >> 4) & 0x0f; |
| 292 |
int bg = (overrideColorValue >> 0) & 0x0f; |
| 293 |
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.0f); |
| 294 |
if (ImGui::Combo("fg", &fg, col0_15)) { |
| 295 |
overrideColorValue = (fg << 4) | bg; |
| 296 |
} |
| 297 |
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.0f); |
| 298 |
ImGui::SameLine(); |
| 299 |
if (ImGui::Combo("bg", &bg, col0_15)) { |
| 300 |
overrideColorValue = (fg << 4) | bg; |
| 301 |
} |
| 302 |
}); |
| 303 |
|
| 304 |
ImGui::Separator(); |
| 305 |
ImGui::Checkbox("beam", &rasterBeam); |
| 306 |
ImGui::SameLine(); |
| 307 |
im::Disabled(!rasterBeam, [&]{ |
| 308 |
ImGui::ColorEdit4("raster beam color", rasterBeamColor.data(), |
| 309 |
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar); |
| 310 |
ImGui::SameLine(); |
| 311 |
im::Font(manager.fontMono, [&]{ |
| 312 |
ImGui::StrCat('(', dec_string<4>(rasterBeamPos.x), |
| 313 |
',', dec_string<4>(rasterBeamPos.y), ')'); |
| 314 |
}); |
| 315 |
}); |
| 316 |
HelpMarker("Position of the raster beam, expressed in MSX coordinates.\n" |
| 317 |
"Left/top border have negative x/y-coordinates.\n" |
| 318 |
"Only practically useful when emulation is paused."); |
| 319 |
}); |
| 320 |
}); |
| 321 |
int manualLines = (manualRows == 0) ? 192 |
| 322 |
: (manualRows == 1) ? 212 |
| 323 |
: 256; |
| 324 |
|
| 325 |
int lines = manRows ? manualLines : vdpLines; |
| 326 |
int color0 = manColor0 ? manualColor0 : vdpColor0; |
| 327 |
|
| 328 |
VramTable patTable(vram); |
| 329 |
unsigned patReg = (manPat ? (manualPatBase | (patMult(manualMode) - 1)) : vdp->getPatternTableBase()) >> 11; |
| 330 |
patTable.setRegister(patReg, 11); |
| 331 |
|
| 332 |
VramTable colTable(vram); |
| 333 |
unsigned colReg = (manCol ? (manualColBase | (colMult(manualMode) - 1)) : vdp->getColorTableBase()) >> 6; |
| 334 |
colTable.setRegister(colReg, 6); |
| 335 |
|
| 336 |
auto colorTabVal = uint8_t(overrideColorValue); |
| 337 |
if (overrideColorTable) { |
| 338 |
colTable = VramTable(std::span(&colorTabVal, 1)); |
| 339 |
colTable.setRegister(0, 0); |
| 340 |
} |
| 341 |
|
| 342 |
VramTable namTable(vram); |
| 343 |
unsigned namReg = (manNam ? (manualNamBase | (namMult(manualMode) - 1)) : vdp->getNameTableBase()) >> 10; |
| 344 |
namTable.setRegister(namReg, 10); |
| 345 |
namTable.setIndexSize((mode == TEXT80) ? 12 : 10); |
| 346 |
|
| 347 |
auto palette = manager.palette->getPalette(vdp); |
| 348 |
if (color0 < 16) palette[0] = palette[color0]; |
| 349 |
|
| 350 |
auto fgCol = manFgCol ? manualFgCol : vdpFgCol; |
| 351 |
auto bgCol = manBgCol ? manualBgCol : vdpBgCol; |
| 352 |
auto fgBlink = manFgBlink ? manualFgBlink : vdpFgBlink; |
| 353 |
auto bgBlink = manBgBlink ? manualBgBlink : vdpBgBlink; |
| 354 |
auto blink = manBlink ? bool(manualBlink) : vdpBlink; |
| 355 |
|
| 356 |
bool narrow = mode == TEXT80; |
| 357 |
int zx = (1 + zoom) * (narrow ? 1 : 2); |
| 358 |
int zy = (1 + zoom) * 2; |
| 359 |
gl::vec2 zm{float(zx), float(zy)}; |
| 360 |
|
| 361 |
|
| 362 |
auto patternTexSize = [&] -> gl::ivec2 { |
| 363 |
if (mode == TEXT40) return {192, 64}; |
| 364 |
if (mode == TEXT80) return {192, 128}; |
| 365 |
if (mode == SCR1) return {256, 64}; |
| 366 |
if (mode == SCR2) return {256, lines == 192 ? 192 : 256}; |
| 367 |
if (mode == SCR3) return { 64, 64}; |
| 368 |
return {1, 1}; |
| 369 |
}(); |
| 370 |
auto patternTexChars = [&] -> gl::vec2 { |
| 371 |
if (mode == SCR3) return {32.0f, 32.0f}; |
| 372 |
if (mode == SCR2) return {32.0f, lines == 192 ? 24.0f : 32.0f}; |
| 373 |
if (mode == TEXT80) return {32.0f, 16.0f}; |
| 374 |
return {32.0f, 8.0f}; |
| 375 |
}(); |
| 376 |
auto recipPatTexChars = recip(patternTexChars); |
| 377 |
auto patternDisplaySize = [&] -> gl::ivec2 { |
| 378 |
if (mode == TEXT40) return {192, 64}; |
| 379 |
if (mode == TEXT80) return {192, 128}; |
| 380 |
if (mode == SCR2) return {256, lines == 192 ? 192 : 256}; |
| 381 |
if (mode == SCR3) return {256, 256}; |
| 382 |
return {256, 64}; |
| 383 |
}(); |
| 384 |
std::array<uint32_t, 256 * 256> pixels; |
| 385 |
renderPatterns(mode, palette, fgCol, bgCol, fgBlink, bgBlink, patTable, colTable, lines, pixels); |
| 386 |
if (!patternTex.get()) { |
| 387 |
patternTex = gl::Texture(false, false); |
| 388 |
} |
| 389 |
patternTex.bind(); |
| 390 |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, patternTexSize.x, patternTexSize.y, 0, |
| 391 |
GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); |
| 392 |
|
| 393 |
|
| 394 |
auto charWidth = mode == one_of(TEXT40, TEXT80) ? 6 : 8; |
| 395 |
auto charSize = gl::vec2{float(charWidth), 8.0f}; |
| 396 |
auto charZoom = zm * charSize; |
| 397 |
auto zoomCharSize = gl::vec2(narrow ? 6.0f : 12.0f, 12.0f) * charSize; |
| 398 |
if (grid) { |
| 399 |
auto gColor = ImGui::ColorConvertFloat4ToU32(gridColor); |
| 400 |
auto gridWidth = charWidth * zx; |
| 401 |
auto gridHeight = 8 * zy; |
| 402 |
for (auto y : xrange(gridHeight)) { |
| 403 |
auto* line = &pixels[y * gridWidth]; |
| 404 |
for (auto x : xrange(gridWidth)) { |
| 405 |
line[x] = (x == 0 || y == 0) ? gColor : 0; |
| 406 |
} |
| 407 |
} |
| 408 |
if (!gridTex.get()) { |
| 409 |
gridTex = gl::Texture(false, true); |
| 410 |
} |
| 411 |
gridTex.bind(); |
| 412 |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gridWidth, gridHeight, 0, |
| 413 |
GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); |
| 414 |
} |
| 415 |
if (nameTableOverlay && !smallHexDigits.get()) { |
| 416 |
initHexDigits(); |
| 417 |
} |
| 418 |
|
| 419 |
auto printPatternNr = [](unsigned pat) { |
| 420 |
ImGui::StrCat("Pattern: ", pat, " (0x", hex_string<3>(pat), ')'); |
| 421 |
}; |
| 422 |
auto printAddressName = [](std::string_view name) { |
| 423 |
ImGui::StrCat(name, " address:"); |
| 424 |
}; |
| 425 |
auto printAddress = [&](std::string_view name, unsigned address) { |
| 426 |
printAddressName(name); |
| 427 |
ImGui::StrCat(" 0x", hex_string<5>(address)); |
| 428 |
}; |
| 429 |
auto printAddressRange8 = [&](std::string_view name, unsigned address) { |
| 430 |
printAddressName(name); |
| 431 |
ImGui::StrCat(" 0x", hex_string<5>(address), "-0x", hex_string<5>(address + 7)); |
| 432 |
}; |
| 433 |
auto printPatternColorAddress = [&](unsigned pattern) { |
| 434 |
printAddressRange8("Pattern", patTable.getAddress(8 * pattern)); |
| 435 |
if (mode == one_of(SCR1, SCR2, SCR4)) { |
| 436 |
printAddressRange8("Color", colTable.getAddress((mode == SCR1) ? (pattern / 8) : (8 * pattern))); |
| 437 |
} |
| 438 |
}; |
| 439 |
auto formatBinaryData = [&](unsigned address) { |
| 440 |
return formatToString([&](unsigned addr){ return vram[addr]; }, address, address + 7, {}, 1, {}, ", ", "%02X", manager.getInterpreter()); |
| 441 |
}; |
| 442 |
auto getPatternFromGrid = [&] { |
| 443 |
return gridPosition.x + 32 * gridPosition.y; |
| 444 |
}; |
| 445 |
auto copyPatternColorPopup = [&] { |
| 446 |
return im::PopupContextWindow("PatternCopyPopup", [&]{ |
| 447 |
bool hasColorData = mode == one_of(SCR1, SCR2); |
| 448 |
const char* caption = hasColorData |
| 449 |
? "Copy pattern and color data to clipboard" |
| 450 |
: "Copy pattern data to clipboard"; |
| 451 |
if (ImGui::MenuItem(caption)) { |
| 452 |
auto pattern = getPatternFromGrid(); |
| 453 |
auto text = strCat("Pattern data\n", formatBinaryData(patTable.getAddress(8 * pattern))); |
| 454 |
if (hasColorData) { |
| 455 |
auto cp = (mode == SCR1) ? (pattern / 8) : (8 * pattern); |
| 456 |
strAppend(text, "Color data\n", formatBinaryData(colTable.getAddress(cp))); |
| 457 |
} |
| 458 |
ImGui::SetClipboardText(text.c_str()); |
| 459 |
} |
| 460 |
}); |
| 461 |
}; |
| 462 |
auto drawGrid = [&] { |
| 463 |
auto pat = getPatternFromGrid(); |
| 464 |
printPatternNr(pat); |
| 465 |
auto uv1 = gl::vec2(gridPosition) * recipPatTexChars; |
| 466 |
auto uv2 = uv1 + recipPatTexChars; |
| 467 |
auto pos2 = ImGui::GetCursorPos(); |
| 468 |
ImGui::Image(patternTex.getImGui(), zoomCharSize, uv1, uv2); |
| 469 |
if (grid) { |
| 470 |
ImGui::SetCursorPos(pos2); |
| 471 |
ImGui::Image(gridTex.getImGui(), |
| 472 |
zoomCharSize, {}, charSize); |
| 473 |
} |
| 474 |
printPatternColorAddress(pat); |
| 475 |
}; |
| 476 |
|
| 477 |
ImGui::Separator(); |
| 478 |
im::TreeNode("Pattern Table", ImGuiTreeNodeFlags_DefaultOpen, [&]{ |
| 479 |
auto size = gl::vec2(patternDisplaySize) * zm; |
| 480 |
auto textLines = (mode == TEXT40) ? 3.0f : 5.0f; |
| 481 |
auto previewHeight = zoomCharSize.y + textLines * ImGui::GetTextLineHeightWithSpacing(); |
| 482 |
im::Child("##pattern", {0, std::max(size.y, previewHeight)}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{ |
| 483 |
auto pos1 = ImGui::GetCursorPos(); |
| 484 |
gl::vec2 scrnPos = ImGui::GetCursorScreenPos(); |
| 485 |
ImGui::Image(patternTex.getImGui(), size); |
| 486 |
bool hovered = ImGui::IsItemHovered() && (mode != OTHER); |
| 487 |
ImGui::SameLine(); |
| 488 |
im::Group([&]{ |
| 489 |
if (copyPatternColorPopup()) { |
| 490 |
drawGrid(); |
| 491 |
} else if (hovered) { |
| 492 |
|
| 493 |
gridPosition = trunc((gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / charZoom); |
| 494 |
drawGrid(); |
| 495 |
} else { |
| 496 |
ImGui::Dummy(zoomCharSize); |
| 497 |
} |
| 498 |
}); |
| 499 |
if (grid) { |
| 500 |
ImGui::SetCursorPos(pos1); |
| 501 |
ImGui::Image(gridTex.getImGui(), size, |
| 502 |
{}, patternTexChars); |
| 503 |
} |
| 504 |
}); |
| 505 |
}); |
| 506 |
ImGui::Separator(); |
| 507 |
im::TreeNode("Name Table", ImGuiTreeNodeFlags_DefaultOpen, [&]{ |
| 508 |
float rows = float(lines) * (1.0f / 8.0f); |
| 509 |
auto columns = [&] { |
| 510 |
if (mode == TEXT40) return 40; |
| 511 |
if (mode == TEXT80) return 80; |
| 512 |
return 32; |
| 513 |
}(); |
| 514 |
auto charsSize = gl::vec2(float(columns), rows); |
| 515 |
auto msxSize = charSize * charsSize; |
| 516 |
auto hostSize = msxSize * zm; |
| 517 |
|
| 518 |
im::Child("##name", {0.0f, hostSize.y}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{ |
| 519 |
gl::vec2 scrnPos = ImGui::GetCursorScreenPos(); |
| 520 |
auto* drawList = ImGui::GetWindowDrawList(); |
| 521 |
|
| 522 |
auto getPattern = [&](unsigned column, unsigned row) { |
| 523 |
auto block = [&] -> unsigned { |
| 524 |
if (mode == TEXT80 && blink) { |
| 525 |
auto colPat = colTable[10 * row + (column / 8)]; |
| 526 |
return (colPat & (0x80 >> (column % 8))) ? 1 : 0; |
| 527 |
} |
| 528 |
if (mode == SCR2) return row / 8; |
| 529 |
if (mode == SCR3) return row % 4; |
| 530 |
return 0; |
| 531 |
}(); |
| 532 |
return namTable[columns * row + column] + 256 * block; |
| 533 |
}; |
| 534 |
auto getPatternUV = [&](unsigned pattern) -> std::pair<gl::vec2, gl::vec2> { |
| 535 |
auto patRow = pattern / 32; |
| 536 |
auto patCol = pattern % 32; |
| 537 |
gl::vec2 uv1 = gl::vec2{float(patCol), float(patRow)} * recipPatTexChars; |
| 538 |
gl::vec2 uv2 = uv1 + recipPatTexChars; |
| 539 |
return {uv1, uv2}; |
| 540 |
}; |
| 541 |
|
| 542 |
if (mode == OTHER) { |
| 543 |
drawList->AddRectFilled(scrnPos, scrnPos + hostSize, getColor(imColor::GRAY)); |
| 544 |
} else { |
| 545 |
auto rowsCeil = int(ceilf(rows)); |
| 546 |
auto numChars = rowsCeil * columns; |
| 547 |
drawList->PushClipRect(scrnPos, scrnPos + hostSize, true); |
| 548 |
drawList->PushTexture(patternTex.getImGui()); |
| 549 |
drawList->PrimReserve(6 * numChars, 4 * numChars); |
| 550 |
for (auto row : xrange(rowsCeil)) { |
| 551 |
for (auto column : xrange(columns)) { |
| 552 |
gl::vec2 p1 = scrnPos + charZoom * gl::vec2{float(column), float(row)}; |
| 553 |
gl::vec2 p2 = p1 + charZoom; |
| 554 |
auto pattern = getPattern(column, row); |
| 555 |
auto [uv1, uv2] = getPatternUV(pattern); |
| 556 |
drawList->PrimRectUV(p1, p2, uv1, uv2, 0xffffffff); |
| 557 |
} |
| 558 |
} |
| 559 |
drawList->PopTexture(); |
| 560 |
if (nameTableOverlay) { |
| 561 |
drawList->PushTexture(smallHexDigits.getImGui()); |
| 562 |
drawList->PrimReserve(12 * numChars, 8 * numChars); |
| 563 |
static constexpr gl::vec2 digitSize{5.0f, 8.0f}; |
| 564 |
static constexpr float texDigitWidth = 1.0f / 16.0f; |
| 565 |
static constexpr gl::vec2 texDigitSize{texDigitWidth, 1.0f}; |
| 566 |
auto digitOffset = narrow ? gl::vec2{0.0f, digitSize.y} : gl::vec2{digitSize.x, 0.0f}; |
| 567 |
for (auto row : xrange(rowsCeil)) { |
| 568 |
for (auto column : xrange(columns)) { |
| 569 |
gl::vec2 p1 = scrnPos + charZoom * gl::vec2{float(column), float(row)}; |
| 570 |
gl::vec2 p2 = p1 + digitOffset; |
| 571 |
auto pattern = getPattern(column, row); |
| 572 |
gl::vec2 uv1{narrow_cast<float>((pattern >> 4) & 15) * texDigitWidth, 0.0f}; |
| 573 |
gl::vec2 uv2{narrow_cast<float>((pattern >> 0) & 15) * texDigitWidth, 0.0f}; |
| 574 |
drawList->PrimRectUV(p1, p1 + digitSize, uv1, uv1 + texDigitSize, 0xffffffff); |
| 575 |
drawList->PrimRectUV(p2, p2 + digitSize, uv2, uv2 + texDigitSize, 0xffffffff); |
| 576 |
} |
| 577 |
} |
| 578 |
drawList->PopTexture(); |
| 579 |
} |
| 580 |
drawList->PopClipRect(); |
| 581 |
} |
| 582 |
if (rasterBeam) { |
| 583 |
auto center = scrnPos + (gl::vec2(rasterBeamPos) + gl::vec2{0.5f}) * zm; |
| 584 |
auto color = ImGui::ColorConvertFloat4ToU32(rasterBeamColor); |
| 585 |
auto thickness = zm.y * 0.5f; |
| 586 |
auto zm1 = 1.5f * zm; |
| 587 |
auto zm3 = 3.5f * zm; |
| 588 |
drawList->AddRect(center - zm, center + zm, color, 0.0f, thickness); |
| 589 |
drawList->AddLine(center - gl::vec2{zm1.x, 0.0f}, center - gl::vec2{zm3.x, 0.0f}, color, thickness); |
| 590 |
drawList->AddLine(center + gl::vec2{zm1.x, 0.0f}, center + gl::vec2{zm3.x, 0.0f}, color, thickness); |
| 591 |
drawList->AddLine(center - gl::vec2{0.0f, zm1.y}, center - gl::vec2{0.0f, zm3.y}, color, thickness); |
| 592 |
drawList->AddLine(center + gl::vec2{0.0f, zm1.y}, center + gl::vec2{0.0f, zm3.y}, color, thickness); |
| 593 |
} |
| 594 |
|
| 595 |
auto pos1 = ImGui::GetCursorPos(); |
| 596 |
ImGui::Dummy(hostSize); |
| 597 |
bool hovered = ImGui::IsItemHovered() && (mode != OTHER); |
| 598 |
ImGui::SameLine(); |
| 599 |
im::Group([&]{ |
| 600 |
if (hovered) { |
| 601 |
auto gridPos = trunc((gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / charZoom); |
| 602 |
ImGui::StrCat("Column: ", gridPos.x, " Row: ", gridPos.y); |
| 603 |
auto pattern = getPattern(gridPos.x, gridPos.y); |
| 604 |
printPatternNr(pattern); |
| 605 |
auto [uv1, uv2] = getPatternUV(pattern); |
| 606 |
auto pos2 = ImGui::GetCursorPos(); |
| 607 |
ImGui::Image(patternTex.getImGui(), zoomCharSize, uv1, uv2); |
| 608 |
if (grid) { |
| 609 |
ImGui::SetCursorPos(pos2); |
| 610 |
ImGui::Image(gridTex.getImGui(), |
| 611 |
zoomCharSize, {}, charSize); |
| 612 |
} |
| 613 |
auto nameIndex = columns * gridPos.y + gridPos.x; |
| 614 |
printAddress("Name", namTable.getAddress(nameIndex)); |
| 615 |
printPatternColorAddress(pattern); |
| 616 |
if (mode == TEXT80) { |
| 617 |
printAddress("Color", colTable.getAddress(nameIndex / 8)); |
| 618 |
} |
| 619 |
} else { |
| 620 |
gl::vec2 textSize = ImGui::CalcTextSize("Column: 31 Row: 23"sv); |
| 621 |
ImGui::Dummy(max(zoomCharSize, textSize)); |
| 622 |
} |
| 623 |
}); |
| 624 |
if (grid) { |
| 625 |
ImGui::SetCursorPos(pos1); |
| 626 |
ImGui::Image(gridTex.getImGui(), hostSize, {}, charsSize); |
| 627 |
} |
| 628 |
}); |
| 629 |
}); |
| 630 |
}); |
| 631 |
} |
| 632 |
|
| 633 |
static void draw6(uint8_t pattern, uint32_t fgCol, uint32_t bgCol, std::span<uint32_t, 6> out) |
| 634 |
{ |
| 635 |
out[0] = (pattern & 0x80) ? fgCol : bgCol; |
| 636 |
out[1] = (pattern & 0x40) ? fgCol : bgCol; |
| 637 |
out[2] = (pattern & 0x20) ? fgCol : bgCol; |
| 638 |
out[3] = (pattern & 0x10) ? fgCol : bgCol; |
| 639 |
out[4] = (pattern & 0x08) ? fgCol : bgCol; |
| 640 |
out[5] = (pattern & 0x04) ? fgCol : bgCol; |
| 641 |
} |
| 642 |
|
| 643 |
static void draw8(uint8_t pattern, uint32_t fgCol, uint32_t bgCol, std::span<uint32_t, 8> out) |
| 644 |
{ |
| 645 |
out[0] = (pattern & 0x80) ? fgCol : bgCol; |
| 646 |
out[1] = (pattern & 0x40) ? fgCol : bgCol; |
| 647 |
out[2] = (pattern & 0x20) ? fgCol : bgCol; |
| 648 |
out[3] = (pattern & 0x10) ? fgCol : bgCol; |
| 649 |
out[4] = (pattern & 0x08) ? fgCol : bgCol; |
| 650 |
out[5] = (pattern & 0x04) ? fgCol : bgCol; |
| 651 |
out[6] = (pattern & 0x02) ? fgCol : bgCol; |
| 652 |
out[7] = (pattern & 0x01) ? fgCol : bgCol; |
| 653 |
} |
| 654 |
|
| 655 |
void ImGuiCharacter::renderPatterns(int mode, std::span<const uint32_t, 16> palette, |
| 656 |
int fgCol, int bgCol, int fgBlink, int bgBlink, |
| 657 |
VramTable& pat, VramTable& col, int lines, std::span<uint32_t> output) |
| 658 |
{ |
| 659 |
switch (mode) { |
| 660 |
case TEXT40: |
| 661 |
case TEXT80: { |
| 662 |
pat.setIndexSize(11); |
| 663 |
col.setIndexSize(9); |
| 664 |
auto fg = palette[fgCol]; |
| 665 |
auto bg = palette[bgCol]; |
| 666 |
auto fgB = palette[fgBlink]; |
| 667 |
auto bgB = palette[bgBlink]; |
| 668 |
for (auto row : xrange(8)) { |
| 669 |
for (auto column : xrange(32)) { |
| 670 |
auto patNum = 32 * row + column; |
| 671 |
auto offset = 8 * patNum; |
| 672 |
for (auto y : xrange(8)) { |
| 673 |
auto pattern = pat[offset + y]; |
| 674 |
auto out = subspan<6>(output, (8 * row + y) * 192 + 6 * column); |
| 675 |
draw6(pattern, fg, bg, out); |
| 676 |
if (mode == TEXT80) { |
| 677 |
auto out2 = subspan<6>(output, (64 + 8 * row + y) * 192 + 6 * column); |
| 678 |
draw6(pattern, fgB, bgB, out2); |
| 679 |
} |
| 680 |
} |
| 681 |
} |
| 682 |
} |
| 683 |
break; |
| 684 |
} |
| 685 |
case SCR1: |
| 686 |
pat.setIndexSize(11); |
| 687 |
col.setIndexSize(6); |
| 688 |
for (auto row : xrange(8)) { |
| 689 |
for (auto group : xrange(4)) { |
| 690 |
auto color = col[4 * row + group]; |
| 691 |
auto fg = palette[color >> 4]; |
| 692 |
auto bg = palette[color & 15]; |
| 693 |
for (auto subColumn : xrange(8)) { |
| 694 |
auto column = 8 * group + subColumn; |
| 695 |
auto patNum = 32 * row + column; |
| 696 |
auto offset = 8 * patNum; |
| 697 |
for (auto y : xrange(8)) { |
| 698 |
auto pattern = pat[offset + y]; |
| 699 |
auto out = subspan<8>(output, (8 * row + y) * 256 + 8 * column); |
| 700 |
draw8(pattern, fg, bg, out); |
| 701 |
} |
| 702 |
} |
| 703 |
} |
| 704 |
} |
| 705 |
break; |
| 706 |
case SCR2: |
| 707 |
pat.setIndexSize(13); |
| 708 |
col.setIndexSize(13); |
| 709 |
for (auto row : xrange((lines == 192 ? 3 : 4) * 8)) { |
| 710 |
for (auto column : xrange(32)) { |
| 711 |
auto patNum = 32 * row + column; |
| 712 |
auto offset = 8 * patNum; |
| 713 |
for (auto y : xrange(8)) { |
| 714 |
auto pattern = pat[offset + y]; |
| 715 |
auto color = col[offset + y]; |
| 716 |
auto fg = palette[color >> 4]; |
| 717 |
auto bg = palette[color & 15]; |
| 718 |
auto out = subspan<8>(output, (8 * row + y) * 256 + 8 * column); |
| 719 |
draw8(pattern, fg, bg, out); |
| 720 |
} |
| 721 |
} |
| 722 |
} |
| 723 |
break; |
| 724 |
case SCR3: |
| 725 |
pat.setIndexSize(11); |
| 726 |
col.setIndexSize(13); |
| 727 |
for (auto group : xrange(4)) { |
| 728 |
for (auto row : xrange(8)) { |
| 729 |
for (auto column : xrange(32)) { |
| 730 |
auto patNum = 32 * row + column; |
| 731 |
auto offset = 8 * patNum + 2 * group; |
| 732 |
for (auto y : xrange(2)) { |
| 733 |
auto out = subspan<2>(output, (16 * group + 2 * row + y) * 64 + 2 * column); |
| 734 |
auto pattern = pat[offset + y]; |
| 735 |
out[0] = palette[pattern >> 4]; |
| 736 |
out[1] = palette[pattern & 15]; |
| 737 |
} |
| 738 |
} |
| 739 |
} |
| 740 |
} |
| 741 |
break; |
| 742 |
default: |
| 743 |
output[0] = getColor(imColor::GRAY); |
| 744 |
break; |
| 745 |
} |
| 746 |
} |
| 747 |
|
| 748 |
} |