| e8555c89 Andy McCall |
1 |
#include "ImGuiBitmapViewer.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 "VDP.hh" |
| 10 |
#include "VDPVRAM.hh" |
| 11 |
|
| 12 |
#include "MemBuffer.hh" |
| 13 |
|
| 14 |
#include <imgui.h> |
| 15 |
|
| 16 |
namespace openmsx { |
| 17 |
|
| 18 |
using namespace std::literals; |
| 19 |
using Point = VDPCmdEngine::Point; |
| 20 |
using Rect = VDPCmdEngine::Rect; |
| 21 |
|
| 22 |
|
| 23 |
|
| 24 |
|
| 25 |
|
| 26 |
|
| 27 |
|
| 28 |
|
| 29 |
|
| 30 |
|
| 31 |
|
| 32 |
|
| 33 |
|
| 34 |
|
| 35 |
|
| 36 |
|
| 37 |
|
| 38 |
|
| 39 |
|
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
|
| 44 |
|
| 45 |
|
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
struct DoneTodo { |
| 50 |
static_vector<Rect, 2> done, todo; |
| 51 |
}; |
| 52 |
[[nodiscard]] static DoneTodo splitRect(const Rect& r, int x, int y) |
| 53 |
{ |
| 54 |
DoneTodo result; |
| 55 |
auto& done = result.done; |
| 56 |
auto& todo = result.todo; |
| 57 |
|
| 58 |
auto minX = std::min(r.p1.x, r.p2.x); |
| 59 |
auto maxX = std::max(r.p1.x, r.p2.x); |
| 60 |
auto minY = std::min(r.p1.y, r.p2.y); |
| 61 |
auto maxY = std::max(r.p1.y, r.p2.y); |
| 62 |
|
| 63 |
if ((x < minX) || (x > maxX) || (y < minY) || (y > maxY)) { |
| 64 |
|
| 65 |
done.emplace_back(Point{minX, minY}, Point{maxX, maxY}); |
| 66 |
return result; |
| 67 |
} |
| 68 |
|
| 69 |
bool diy = r.p1.y > r.p2.y; |
| 70 |
if (minY < y) { |
| 71 |
|
| 72 |
auto& res = diy ? todo : done; |
| 73 |
res.emplace_back(Point{minX, minY}, Point{maxX, y - 1}); |
| 74 |
} |
| 75 |
if (r.p1.x <= r.p2.x) { |
| 76 |
|
| 77 |
assert(r.p1.x <= x); |
| 78 |
done.emplace_back(Point{r.p1.x, y}, Point{x, y}); |
| 79 |
if (x < r.p2.x) { |
| 80 |
todo.emplace_back(Point{x + 1, y}, Point{r.p2.x, y}); |
| 81 |
} |
| 82 |
} else { |
| 83 |
|
| 84 |
assert(x <= r.p1.x); |
| 85 |
done.emplace_back(Point{x, y}, Point{r.p1.x, y}); |
| 86 |
if (r.p2.x < x) { |
| 87 |
todo.emplace_back(Point{r.p2.x, y}, Point{x - 1, y}); |
| 88 |
} |
| 89 |
} |
| 90 |
|
| 91 |
if (y < maxY) { |
| 92 |
|
| 93 |
auto& res = diy ? done : todo; |
| 94 |
res.emplace_back(Point{minX, y + 1}, Point{maxX, maxY}); |
| 95 |
} |
| 96 |
|
| 97 |
return result; |
| 98 |
} |
| 99 |
|
| 100 |
ImGuiBitmapViewer::ImGuiBitmapViewer(ImGuiManager& manager_, size_t index) |
| 101 |
: ImGuiPart(manager_) |
| 102 |
, title(strCat("Bitmap viewer", strCat_if(index, " (", index + 1, ')'))) |
| 103 |
{ |
| 104 |
} |
| 105 |
|
| 106 |
void ImGuiBitmapViewer::save(ImGuiTextBuffer& buf) |
| 107 |
{ |
| 108 |
savePersistent(buf, *this, persistentElements); |
| 109 |
} |
| 110 |
|
| 111 |
void ImGuiBitmapViewer::loadLine(std::string_view name, zstring_view value) |
| 112 |
{ |
| 113 |
loadOnePersistent(name, value, *this, persistentElements); |
| 114 |
} |
| 115 |
|
| 116 |
void ImGuiBitmapViewer::paint(MSXMotherBoard* motherBoard) |
| 117 |
{ |
| 118 |
if (!show) return; |
| 119 |
if (!motherBoard) return; |
| 120 |
|
| 121 |
ImGui::SetNextWindowSize({528, 620}, ImGuiCond_FirstUseEver); |
| 122 |
im::Window(title.c_str(), &show, [&]{ |
| 123 |
auto* vdp = dynamic_cast<VDP*>(motherBoard->findDevice("VDP")); |
| 124 |
if (!vdp || vdp->isMSX1VDP()) return; |
| 125 |
|
| 126 |
auto parseMode = [](DisplayMode mode) { |
| 127 |
auto base = mode.getBase(); |
| 128 |
if (base == DisplayMode::GRAPHIC4) return SCR5; |
| 129 |
if (base == DisplayMode::GRAPHIC5) return SCR6; |
| 130 |
if (base == DisplayMode::GRAPHIC6) return SCR7; |
| 131 |
if (base != DisplayMode::GRAPHIC7) return OTHER; |
| 132 |
if (mode.getByte() & DisplayMode::YJK) { |
| 133 |
if (mode.getByte() & DisplayMode::YAE) { |
| 134 |
return SCR11; |
| 135 |
} else { |
| 136 |
return SCR12; |
| 137 |
} |
| 138 |
} else { |
| 139 |
return SCR8; |
| 140 |
} |
| 141 |
}; |
| 142 |
int vdpMode = parseMode(vdp->getDisplayMode()); |
| 143 |
|
| 144 |
int vdpPages = vdpMode <= SCR6 ? 4 : 2; |
| 145 |
int vdpPage = vdp->getDisplayPage(); |
| 146 |
if (vdpPage >= vdpPages) vdpPage &= 1; |
| 147 |
|
| 148 |
int vdpLines = (vdp->getNumberOfLines() == 192) ? 0 : 1; |
| 149 |
|
| 150 |
int vdpColor0 = [&]{ |
| 151 |
if (vdpMode == one_of(SCR8, SCR11, SCR12) || !vdp->getTransparency()) { |
| 152 |
return 16; |
| 153 |
} |
| 154 |
return vdp->getBackgroundColor() & 15; |
| 155 |
}(); |
| 156 |
|
| 157 |
auto modeToStr = [](int mode) { |
| 158 |
if (mode == SCR5 ) return "screen 5"; |
| 159 |
if (mode == SCR6 ) return "screen 6"; |
| 160 |
if (mode == SCR7 ) return "screen 7"; |
| 161 |
if (mode == SCR8 ) return "screen 8"; |
| 162 |
if (mode == SCR11) return "screen 11"; |
| 163 |
if (mode == SCR12) return "screen 12"; |
| 164 |
if (mode == OTHER) return "non-bitmap"; |
| 165 |
assert(false); return "ERROR"; |
| 166 |
}; |
| 167 |
|
| 168 |
static const char* const color0Str = "0\0001\0002\0003\0004\0005\0006\0007\0008\0009\00010\00011\00012\00013\00014\00015\000none\000"; |
| 169 |
bool manualMode = overrideAll || overrideMode; |
| 170 |
bool manualPage = overrideAll || overridePage; |
| 171 |
bool manualLines = overrideAll || overrideLines; |
| 172 |
bool manualColor0 = overrideAll || overrideColor0; |
| 173 |
im::Group([&]{ |
| 174 |
ImGui::AlignTextToFramePadding(); |
| 175 |
ImGui::TextUnformatted("VDP settings"); |
| 176 |
auto pos = ImGui::GetCursorPos(); |
| 177 |
ImGui::Dummy(ImGui::CalcTextSize("Screen mode: non-bitmap")); |
| 178 |
ImGui::SetCursorPos(pos); |
| 179 |
im::Disabled(manualMode, [&]{ |
| 180 |
ImGui::AlignTextToFramePadding(); |
| 181 |
ImGui::StrCat("Screen mode: ", modeToStr(vdpMode)); |
| 182 |
}); |
| 183 |
im::Disabled(manualPage, [&]{ |
| 184 |
ImGui::AlignTextToFramePadding(); |
| 185 |
ImGui::StrCat("Display page: ", vdpPage); |
| 186 |
}); |
| 187 |
im::Disabled(manualLines, [&]{ |
| 188 |
ImGui::AlignTextToFramePadding(); |
| 189 |
ImGui::StrCat("Visible lines: ", vdpLines ? 212 : 192); |
| 190 |
}); |
| 191 |
im::Disabled(manualColor0, [&]{ |
| 192 |
ImGui::AlignTextToFramePadding(); |
| 193 |
ImGui::StrCat("Replace color 0: ", getComboString(vdpColor0, color0Str)); |
| 194 |
}); |
| 195 |
|
| 196 |
}); |
| 197 |
ImGui::SameLine(); |
| 198 |
im::Group([&]{ |
| 199 |
ImGui::Checkbox("Manual override", &overrideAll); |
| 200 |
im::Group([&]{ |
| 201 |
im::Disabled(overrideAll, [&]{ |
| 202 |
ImGui::Checkbox("##mode", overrideAll ? &overrideAll : &overrideMode); |
| 203 |
ImGui::Checkbox("##page", overrideAll ? &overrideAll : &overridePage); |
| 204 |
ImGui::Checkbox("##lines", overrideAll ? &overrideAll : &overrideLines); |
| 205 |
ImGui::Checkbox("##color0", overrideAll ? &overrideAll : &overrideColor0); |
| 206 |
}); |
| 207 |
}); |
| 208 |
ImGui::SameLine(); |
| 209 |
im::Group([&]{ |
| 210 |
im::ItemWidth(ImGui::GetFontSize() * 9.0f, [&]{ |
| 211 |
im::Disabled(!manualMode, [&]{ |
| 212 |
ImGui::Combo("##Screen mode", &bitmapScrnMode, "screen 5\000screen 6\000screen 7\000screen 8\000screen 11\000screen 12\000"); |
| 213 |
}); |
| 214 |
im::Disabled(!manualPage, [&]{ |
| 215 |
|
| 216 |
int mode = manualMode ? bitmapScrnMode : vdpMode; |
| 217 |
int numPages = mode <= SCR6 ? 4 : 2; |
| 218 |
if (bitmapPage >= numPages) bitmapPage = numPages - 1; |
| 219 |
if (bitmapPage < 0) bitmapPage = numPages; |
| 220 |
ImGui::Combo("##Display page", &bitmapPage, numPages == 2 ? "0\0001\000All\000" : "0\0001\0002\0003\000All\000"); |
| 221 |
if (bitmapPage == numPages) bitmapPage = -1; |
| 222 |
}); |
| 223 |
im::Disabled(!manualLines || bitmapPage < 0, [&]{ |
| 224 |
ImGui::Combo("##Visible lines", &bitmapLines, "192\000212\000256\000"); |
| 225 |
}); |
| 226 |
im::Disabled(!manualColor0, [&]{ |
| 227 |
ImGui::Combo("##Color 0 replacement", &bitmapColor0, color0Str); |
| 228 |
}); |
| 229 |
}); |
| 230 |
}); |
| 231 |
}); |
| 232 |
|
| 233 |
ImGui::SameLine(); |
| 234 |
ImGui::Dummy(ImVec2(15, 1)); |
| 235 |
ImGui::SameLine(); |
| 236 |
|
| 237 |
const auto& vram = vdp->getVRAM(); |
| 238 |
int mode = manualMode ? bitmapScrnMode : vdpMode; |
| 239 |
int page = manualPage ? bitmapPage : vdpPage; |
| 240 |
int lines = manualLines ? bitmapLines : vdpLines; |
| 241 |
int color0 = manualColor0 ? bitmapColor0 : vdpColor0; |
| 242 |
int divX = mode == one_of(SCR6, SCR7) ? 1 : 2; |
| 243 |
int width = 512 / divX; |
| 244 |
int height = (lines == 0) ? 192 |
| 245 |
: (lines == 1) ? 212 |
| 246 |
: 256; |
| 247 |
if (page < 0) { |
| 248 |
int numPages = mode <= SCR6 ? 4 : 2; |
| 249 |
height = 256 * numPages; |
| 250 |
page = 0; |
| 251 |
} |
| 252 |
auto rasterBeamPos = vdp->getMSXPos(vdp->getCurrentTime()); |
| 253 |
rasterBeamPos.x /= divX; |
| 254 |
|
| 255 |
im::Group([&]{ |
| 256 |
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10.0f); |
| 257 |
ImGui::Combo("Palette", &manager.palette->whichPalette, "VDP\000Custom\000Fixed\000"); |
| 258 |
if (ImGui::Button("Open palette editor")) { |
| 259 |
manager.palette->window.raise(); |
| 260 |
} |
| 261 |
ImGui::Separator(); |
| 262 |
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.0f); |
| 263 |
ImGui::Combo("Zoom", &bitmapZoom, "1x\0002x\0003x\0004x\0005x\0006x\0007x\0008x\000"); |
| 264 |
ImGui::Checkbox("grid", &bitmapGrid); |
| 265 |
ImGui::SameLine(); |
| 266 |
im::Disabled(!bitmapGrid, [&]{ |
| 267 |
ImGui::ColorEdit4("Grid color", bitmapGridColor.data(), |
| 268 |
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar); |
| 269 |
}); |
| 270 |
ImGui::Separator(); |
| 271 |
ImGui::Checkbox("beam", &rasterBeam); |
| 272 |
ImGui::SameLine(); |
| 273 |
im::Disabled(!rasterBeam, [&]{ |
| 274 |
ImGui::ColorEdit4("raster beam color", rasterBeamColor.data(), |
| 275 |
ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar); |
| 276 |
ImGui::SameLine(); |
| 277 |
im::Font(manager.fontMono, [&]{ |
| 278 |
ImGui::StrCat('(', dec_string<4>(rasterBeamPos.x), |
| 279 |
',', dec_string<4>(rasterBeamPos.y), ')'); |
| 280 |
}); |
| 281 |
}); |
| 282 |
HelpMarker("Position of the raster beam, expressed in MSX coordinates.\n" |
| 283 |
"Left/top border have negative x/y-coordinates.\n" |
| 284 |
"Only practically useful when emulation is paused."); |
| 285 |
}); |
| 286 |
|
| 287 |
|
| 288 |
auto& cmdEngine = vdp->getCmdEngine(); |
| 289 |
bool inProgress = cmdEngine.commandInProgress(motherBoard->getCurrentTime()); |
| 290 |
|
| 291 |
auto regs = cmdEngine.getLastCommand(); |
| 292 |
auto formatted = VDPCmdEngine::formatCommand(regs, mode); |
| 293 |
|
| 294 |
im::TreeNode("Show VDP block command overlay", [&]{ |
| 295 |
ImGui::RadioButton("never", &showCmdOverlay, 0); |
| 296 |
HelpMarker("Don't draw any overlay in the bitmap view"); |
| 297 |
ImGui::SameLine(); |
| 298 |
ImGui::RadioButton("in progress", &showCmdOverlay, 1); |
| 299 |
HelpMarker("Only show overlay for VDP block command that is currently executing"); |
| 300 |
ImGui::SameLine(); |
| 301 |
ImGui::RadioButton("also finished", &showCmdOverlay, 2); |
| 302 |
HelpMarker("Show overlay for the currently executing VDP block command, but also the last finished block command"); |
| 303 |
|
| 304 |
im::TreeNode("Overlay colors", [&]{ |
| 305 |
int colorFlags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | |
| 306 |
ImGuiColorEditFlags_AlphaBar; |
| 307 |
im::Group([&]{ |
| 308 |
ImGui::AlignTextToFramePadding(); |
| 309 |
ImGui::TextUnformatted("Source (done)"sv); |
| 310 |
ImGui::AlignTextToFramePadding(); |
| 311 |
ImGui::TextUnformatted("Source (todo)"sv); |
| 312 |
}); |
| 313 |
ImGui::SameLine(); |
| 314 |
im::Group([&]{ |
| 315 |
ImGui::ColorEdit4("src-done", colorSrcDone.data(), colorFlags); |
| 316 |
ImGui::ColorEdit4("src-todo", colorSrcTodo.data(), colorFlags); |
| 317 |
}); |
| 318 |
ImGui::SameLine(0.0f, ImGui::GetFontSize() * 3.0f); |
| 319 |
im::Group([&]{ |
| 320 |
ImGui::AlignTextToFramePadding(); |
| 321 |
ImGui::TextUnformatted("Destination (done)"sv); |
| 322 |
ImGui::AlignTextToFramePadding(); |
| 323 |
ImGui::TextUnformatted("Destination (todo)"sv); |
| 324 |
}); |
| 325 |
ImGui::SameLine(); |
| 326 |
im::Group([&]{ |
| 327 |
ImGui::ColorEdit4("dst-done", colorDstDone.data(), colorFlags); |
| 328 |
ImGui::ColorEdit4("dst-todo", colorDstTodo.data(), colorFlags); |
| 329 |
}); |
| 330 |
}); |
| 331 |
|
| 332 |
ImGui::TextUnformatted("Last command "sv); |
| 333 |
ImGui::SameLine(); |
| 334 |
ImGui::TextUnformatted(inProgress ? "[in progress]"sv : "[finished]"sv); |
| 335 |
|
| 336 |
|
| 337 |
|
| 338 |
|
| 339 |
|
| 340 |
|
| 341 |
|
| 342 |
|
| 343 |
|
| 344 |
|
| 345 |
|
| 346 |
|
| 347 |
|
| 348 |
|
| 349 |
|
| 350 |
im::DisabledIndent(!inProgress, [&]{ |
| 351 |
ImGui::TextUnformatted(formatted.str); |
| 352 |
}); |
| 353 |
}); |
| 354 |
|
| 355 |
ImGui::Separator(); |
| 356 |
|
| 357 |
auto palette = manager.palette->getPalette(vdp); |
| 358 |
if (color0 < 16) palette[0] = palette[color0]; |
| 359 |
|
| 360 |
MemBuffer<uint32_t> pixels(512 * 256 * 4); |
| 361 |
renderBitmap(vram.getData(), palette, mode, height, page, |
| 362 |
pixels.data()); |
| 363 |
if (!bitmapTex) { |
| 364 |
bitmapTex.emplace(false, false); |
| 365 |
} |
| 366 |
bitmapTex->bind(); |
| 367 |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, |
| 368 |
GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); |
| 369 |
int zx = (1 + bitmapZoom) * divX; |
| 370 |
int zy = (1 + bitmapZoom) * 2; |
| 371 |
auto zm = gl::vec2(float(zx), float(zy)); |
| 372 |
|
| 373 |
gl::vec2 scrnPos; |
| 374 |
auto msxSize = gl::vec2(float(width), float(height)); |
| 375 |
auto size = msxSize * zm; |
| 376 |
auto availSize = gl::vec2(ImGui::GetContentRegionAvail()) - gl::vec2(0.0f, ImGui::GetTextLineHeightWithSpacing()); |
| 377 |
auto reqSize = size + gl::vec2(ImGui::GetStyle().ScrollbarSize); |
| 378 |
im::Child("##bitmap", min(availSize, reqSize), 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{ |
| 379 |
scrnPos = ImGui::GetCursorScreenPos(); |
| 380 |
auto pos = ImGui::GetCursorPos(); |
| 381 |
ImGui::Image(bitmapTex->getImGui(), size); |
| 382 |
|
| 383 |
if (bitmapGrid && (zx > 1) && (zy > 1)) { |
| 384 |
auto color = ImGui::ColorConvertFloat4ToU32(bitmapGridColor); |
| 385 |
for (auto y : xrange(zy)) { |
| 386 |
auto* line = &pixels[y * zx]; |
| 387 |
for (auto x : xrange(zx)) { |
| 388 |
line[x] = (x == 0 || y == 0) ? color : 0; |
| 389 |
} |
| 390 |
} |
| 391 |
if (!bitmapGridTex) { |
| 392 |
bitmapGridTex.emplace(false, true); |
| 393 |
} |
| 394 |
bitmapGridTex->bind(); |
| 395 |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, zx, zy, 0, |
| 396 |
GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); |
| 397 |
ImGui::SetCursorPos(pos); |
| 398 |
ImGui::Image(bitmapGridTex->getImGui(), size, gl::vec2{}, msxSize); |
| 399 |
} |
| 400 |
auto* drawList = ImGui::GetWindowDrawList(); |
| 401 |
if (rasterBeam) { |
| 402 |
auto center = scrnPos + (gl::vec2(rasterBeamPos) + gl::vec2{0.5f}) * zm; |
| 403 |
auto color = ImGui::ColorConvertFloat4ToU32(rasterBeamColor); |
| 404 |
auto thickness = zm.y * 0.5f; |
| 405 |
auto zm1 = 1.5f * zm; |
| 406 |
auto zm3 = 3.5f * zm; |
| 407 |
drawList->AddRect(center - zm, center + zm, color, 0.0f, thickness); |
| 408 |
drawList->AddLine(center - gl::vec2{zm1.x, 0.0f}, center - gl::vec2{zm3.x, 0.0f}, color, thickness); |
| 409 |
drawList->AddLine(center + gl::vec2{zm1.x, 0.0f}, center + gl::vec2{zm3.x, 0.0f}, color, thickness); |
| 410 |
drawList->AddLine(center - gl::vec2{0.0f, zm1.y}, center - gl::vec2{0.0f, zm3.y}, color, thickness); |
| 411 |
drawList->AddLine(center + gl::vec2{0.0f, zm1.y}, center + gl::vec2{0.0f, zm3.y}, color, thickness); |
| 412 |
} |
| 413 |
|
| 414 |
|
| 415 |
auto offset = gl::vec2(0.0f, height > 256 ? 0.0f : float(256 * page)); |
| 416 |
auto drawRect = [&](const Rect& r, ImU32 color) { |
| 417 |
auto tl = zm * (gl::vec2(r.p1) - offset); |
| 418 |
auto br = zm * (gl::vec2(r.p2) - offset + gl::vec2(1.0f)); |
| 419 |
drawList->AddRectFilled(scrnPos + tl, scrnPos + br, color); |
| 420 |
}; |
| 421 |
auto drawSplit = [&](const DoneTodo& dt, ImU32 colDone, ImU32 colTodo) { |
| 422 |
for (const auto& r : dt.done) drawRect(r, colDone); |
| 423 |
for (const auto& r : dt.todo) drawRect(r, colTodo); |
| 424 |
}; |
| 425 |
auto drawOverlay = [&](const static_vector<Rect, 2>& rects, int x, int y, ImU32 colDone, ImU32 colTodo) { |
| 426 |
auto split1 = splitRect(rects[0], x, y); |
| 427 |
drawSplit(split1, colDone, colTodo); |
| 428 |
if (rects.size() == 2) { |
| 429 |
auto split2 = splitRect(rects[1], x, y); |
| 430 |
auto col1 = (split1.done.size() == 2) ? colTodo : colDone; |
| 431 |
drawSplit(split2, col1, colTodo); |
| 432 |
} |
| 433 |
}; |
| 434 |
if ((showCmdOverlay == 2) || ((showCmdOverlay == 1) && inProgress)) { |
| 435 |
auto [sx2, sy2, dx2, dy2] = cmdEngine.getInprogressPosition(); |
| 436 |
if (formatted.srcRect) { |
| 437 |
drawOverlay(*formatted.srcRect, sx2, sy2, |
| 438 |
ImGui::ColorConvertFloat4ToU32(colorSrcDone), |
| 439 |
ImGui::ColorConvertFloat4ToU32(colorSrcTodo)); |
| 440 |
} |
| 441 |
if (formatted.dstRect) { |
| 442 |
drawOverlay(*formatted.dstRect, dx2, dy2, |
| 443 |
ImGui::ColorConvertFloat4ToU32(colorDstDone), |
| 444 |
ImGui::ColorConvertFloat4ToU32(colorDstTodo)); |
| 445 |
} |
| 446 |
} |
| 447 |
}); |
| 448 |
if (ImGui::IsItemHovered() && (mode != OTHER)) { |
| 449 |
auto [x, y] = trunc((gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / zm); |
| 450 |
if ((0 <= x) && (x < width) && (0 <= y) && (y < height)) { |
| 451 |
auto dec3 = [&](int d) { |
| 452 |
im::ScopedFont sf(manager.fontMono); |
| 453 |
ImGui::SameLine(0.0f, 0.0f); |
| 454 |
ImGui::Text("%3d", d); |
| 455 |
}; |
| 456 |
auto hex2 = [&](unsigned h) { |
| 457 |
im::ScopedFont sf(manager.fontMono); |
| 458 |
ImGui::SameLine(0.0f, 0.0f); |
| 459 |
ImGui::StrCat(hex_string<2>(h)); |
| 460 |
}; |
| 461 |
auto hex5 = [&](unsigned h) { |
| 462 |
im::ScopedFont sf(manager.fontMono); |
| 463 |
ImGui::SameLine(0.0f, 0.0f); |
| 464 |
ImGui::StrCat(hex_string<5>(h)); |
| 465 |
}; |
| 466 |
|
| 467 |
ImGui::TextUnformatted("x="sv); dec3(x); |
| 468 |
ImGui::SameLine(); |
| 469 |
ImGui::TextUnformatted("y="sv); dec3(y); |
| 470 |
|
| 471 |
unsigned physAddr = 0x8000 * page + 128 * y; |
| 472 |
switch (mode) { |
| 473 |
case SCR5: physAddr += x / 2; break; |
| 474 |
case SCR6: physAddr += x / 4; break; |
| 475 |
case SCR7: physAddr += x / 4 + 0x08000 * (x & 2); break; |
| 476 |
case SCR8: case SCR11: case SCR12: |
| 477 |
physAddr += x / 2 + 0x10000 * (x & 1); break; |
| 478 |
default: assert(false); |
| 479 |
} |
| 480 |
|
| 481 |
auto value = vram.getData()[physAddr]; |
| 482 |
auto color = [&] -> uint8_t { |
| 483 |
switch (mode) { |
| 484 |
case SCR5: case SCR7: |
| 485 |
return (value >> (4 * (1 - (x & 1)))) & 0x0f; |
| 486 |
case SCR6: |
| 487 |
return (value >> (2 * (3 - (x & 3)))) & 0x03; |
| 488 |
default: |
| 489 |
return value; |
| 490 |
} |
| 491 |
}(); |
| 492 |
if (mode != one_of(SCR11, SCR12)) { |
| 493 |
ImGui::SameLine(); |
| 494 |
ImGui::TextUnformatted(" color="sv); dec3(color); |
| 495 |
} |
| 496 |
|
| 497 |
ImGui::SameLine(); |
| 498 |
ImGui::TextUnformatted(" vram: addr=0x"); |
| 499 |
if (mode == one_of(SCR5, SCR6)) { |
| 500 |
hex5(physAddr); |
| 501 |
} else { |
| 502 |
unsigned logAddr = (physAddr & 0x0ffff) << 1 | (physAddr >> 16); |
| 503 |
hex5(logAddr), |
| 504 |
ImGui::SameLine(0.0f, 0.0f); |
| 505 |
ImGui::TextUnformatted("(log)/0x"sv); hex5(physAddr); |
| 506 |
ImGui::SameLine(0.0f, 0.0f); |
| 507 |
ImGui::TextUnformatted("(phys)"sv); |
| 508 |
} |
| 509 |
ImGui::SameLine(); |
| 510 |
ImGui::TextUnformatted(" value=0x"sv); hex2(value); |
| 511 |
} |
| 512 |
} |
| 513 |
}); |
| 514 |
} |
| 515 |
|
| 516 |
|
| 517 |
void ImGuiBitmapViewer::renderBitmap(std::span<const uint8_t> vram, std::span<const uint32_t, 16> palette16, |
| 518 |
int mode, int lines, int page, uint32_t* output) const |
| 519 |
{ |
| 520 |
auto yjk2rgb = [](int y, int j, int k) -> std::tuple<int, int, int> { |
| 521 |
|
| 522 |
|
| 523 |
|
| 524 |
|
| 525 |
|
| 526 |
int r = std::clamp(y + j, 0, 31); |
| 527 |
int g = std::clamp(y + k, 0, 31); |
| 528 |
int b = std::clamp((5 * y - 2 * j - k + 2) / 4, 0, 31); |
| 529 |
return {r, g, b}; |
| 530 |
}; |
| 531 |
|
| 532 |
|
| 533 |
size_t addr = 0x8000 * page; |
| 534 |
switch (mode) { |
| 535 |
case SCR5: |
| 536 |
for (auto y : xrange(lines)) { |
| 537 |
auto* line = &output[256 * y]; |
| 538 |
for (auto x : xrange(128)) { |
| 539 |
auto value = vram[addr]; |
| 540 |
line[2 * x + 0] = palette16[(value >> 4) & 0x0f]; |
| 541 |
line[2 * x + 1] = palette16[(value >> 0) & 0x0f]; |
| 542 |
++addr; |
| 543 |
} |
| 544 |
} |
| 545 |
break; |
| 546 |
|
| 547 |
case SCR6: |
| 548 |
for (auto y : xrange(lines)) { |
| 549 |
auto* line = &output[512 * y]; |
| 550 |
for (auto x : xrange(128)) { |
| 551 |
auto value = vram[addr]; |
| 552 |
line[4 * x + 0] = palette16[(value >> 6) & 3]; |
| 553 |
line[4 * x + 1] = palette16[(value >> 4) & 3]; |
| 554 |
line[4 * x + 2] = palette16[(value >> 2) & 3]; |
| 555 |
line[4 * x + 3] = palette16[(value >> 0) & 3]; |
| 556 |
++addr; |
| 557 |
} |
| 558 |
} |
| 559 |
break; |
| 560 |
|
| 561 |
case SCR7: |
| 562 |
for (auto y : xrange(lines)) { |
| 563 |
auto* line = &output[512 * y]; |
| 564 |
for (auto x : xrange(128)) { |
| 565 |
auto value0 = vram[addr + 0x00000]; |
| 566 |
auto value1 = vram[addr + 0x10000]; |
| 567 |
line[4 * x + 0] = palette16[(value0 >> 4) & 0x0f]; |
| 568 |
line[4 * x + 1] = palette16[(value0 >> 0) & 0x0f]; |
| 569 |
line[4 * x + 2] = palette16[(value1 >> 4) & 0x0f]; |
| 570 |
line[4 * x + 3] = palette16[(value1 >> 0) & 0x0f]; |
| 571 |
++addr; |
| 572 |
} |
| 573 |
} |
| 574 |
break; |
| 575 |
|
| 576 |
case SCR8: { |
| 577 |
auto toColor = [](uint8_t value) { |
| 578 |
int r = (value & 0x1c) >> 2; |
| 579 |
int g = (value & 0xe0) >> 5; |
| 580 |
int b = (value & 0x03) >> 0; |
| 581 |
int rr = (r << 5) | (r << 2) | (r >> 1); |
| 582 |
int gg = (g << 5) | (g << 2) | (g >> 1); |
| 583 |
int bb = (b << 6) | (b << 4) | (b << 2) | (b << 0); |
| 584 |
int aa = 255; |
| 585 |
return (rr << 0) | (gg << 8) | (bb << 16) | (aa << 24); |
| 586 |
}; |
| 587 |
for (auto y : xrange(lines)) { |
| 588 |
auto* line = &output[256 * y]; |
| 589 |
for (auto x : xrange(128)) { |
| 590 |
line[2 * x + 0] = toColor(vram[addr + 0x00000]); |
| 591 |
line[2 * x + 1] = toColor(vram[addr + 0x10000]); |
| 592 |
++addr; |
| 593 |
} |
| 594 |
} |
| 595 |
break; |
| 596 |
} |
| 597 |
|
| 598 |
case SCR11: |
| 599 |
for (auto y : xrange(lines)) { |
| 600 |
auto* line = &output[256 * y]; |
| 601 |
for (auto x : xrange(64)) { |
| 602 |
std::array<unsigned, 4> p = { |
| 603 |
vram[addr + 0 + 0x00000], |
| 604 |
vram[addr + 0 + 0x10000], |
| 605 |
vram[addr + 1 + 0x00000], |
| 606 |
vram[addr + 1 + 0x10000], |
| 607 |
}; |
| 608 |
addr += 2; |
| 609 |
int j = narrow<int>((p[2] & 7) + ((p[3] & 3) << 3)) - narrow<int>((p[3] & 4) << 3); |
| 610 |
int k = narrow<int>((p[0] & 7) + ((p[1] & 3) << 3)) - narrow<int>((p[1] & 4) << 3); |
| 611 |
for (auto n : xrange(4)) { |
| 612 |
uint32_t pix; |
| 613 |
if (p[n] & 0x08) { |
| 614 |
pix = palette16[p[n] >> 4]; |
| 615 |
} else { |
| 616 |
int Y = narrow<int>(p[n] >> 3); |
| 617 |
auto [r, g, b] = yjk2rgb(Y, j, k); |
| 618 |
int rr = (r << 3) | (r >> 2); |
| 619 |
int gg = (g << 3) | (g >> 2); |
| 620 |
int bb = (b << 3) | (b >> 2); |
| 621 |
int aa = 255; |
| 622 |
pix = (rr << 0) | (gg << 8) | (bb << 16) | (aa << 24); |
| 623 |
} |
| 624 |
line[4 * x + n] = pix; |
| 625 |
} |
| 626 |
} |
| 627 |
} |
| 628 |
break; |
| 629 |
|
| 630 |
case SCR12: |
| 631 |
for (auto y : xrange(lines)) { |
| 632 |
auto* line = &output[256 * y]; |
| 633 |
for (auto x : xrange(64)) { |
| 634 |
std::array<unsigned, 4> p = { |
| 635 |
vram[addr + 0 + 0x00000], |
| 636 |
vram[addr + 0 + 0x10000], |
| 637 |
vram[addr + 1 + 0x00000], |
| 638 |
vram[addr + 1 + 0x10000], |
| 639 |
}; |
| 640 |
addr += 2; |
| 641 |
int j = narrow<int>((p[2] & 7) + ((p[3] & 3) << 3)) - narrow<int>((p[3] & 4) << 3); |
| 642 |
int k = narrow<int>((p[0] & 7) + ((p[1] & 3) << 3)) - narrow<int>((p[1] & 4) << 3); |
| 643 |
for (auto n : xrange(4)) { |
| 644 |
int Y = narrow<int>(p[n] >> 3); |
| 645 |
auto [r, g, b] = yjk2rgb(Y, j, k); |
| 646 |
int rr = (r << 3) | (r >> 2); |
| 647 |
int gg = (g << 3) | (g >> 2); |
| 648 |
int bb = (b << 3) | (b >> 2); |
| 649 |
int aa = 255; |
| 650 |
line[4 * x + n] = (rr << 0) | (gg << 8) | (bb << 16) | (aa << 24); |
| 651 |
} |
| 652 |
} |
| 653 |
} |
| 654 |
break; |
| 655 |
|
| 656 |
case OTHER: |
| 657 |
for (auto y : xrange(lines)) { |
| 658 |
auto* line = &output[256 * y]; |
| 659 |
for (auto x : xrange(256)) { |
| 660 |
line[x] = getColor(imColor::GRAY); |
| 661 |
} |
| 662 |
} |
| 663 |
break; |
| 664 |
} |
| 665 |
} |
| 666 |
|
| 667 |
} |