openMSX
ImGuiBitmapViewer.cc
Go to the documentation of this file.
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#include "ranges.hh"
14
15#include <imgui.h>
16
17namespace openmsx {
18
19using namespace std::literals;
20
21// Take VDPCmdEngine info like {sx,sy} (or {dx,dy}), {nx,ny}, ...
22// And turn that into a rectangle that's properly clipped horizontally for the
23// given screen mode. Taking into account pixel vs byte mode.
24
25// Usually this returns just 1 retangle, but in case of vertical wrapping this
26// can be split over two (disjunct) rectangles. In that case the order in which
27// the VDP handles these rectangles (via 'diy') is preserved.
28//
29// Also within a single rectangle the VDP order (via 'dix' and 'diy') is
30// preserved. So start at corner 'p1', end at corner 'p2'.
31//
32// Both start and end points of the rectangle(s) are inclusive.
34 int x, int y, // either sx,sy or dx,dy
35 int nx, int ny,
36 bool dix, bool diy,
38 bool byteMode) // Lxxx or Hxxx command
39{
40 const auto [width, height, pixelsPerByte] = [&] {
41 switch (screenMode) {
43 case SCR5: return std::tuple{256, 1024, 2};
44 case SCR6: return std::tuple{512, 1024, 4};
45 case SCR7: return std::tuple{512, 512, 2};
46 default: return std::tuple{256, 512, 1}; // screen 8, 11, 12 (and fallback for non-bitmap)
47 }
48 }();
49
50 // clamp/wrap start-point
51 x = std::clamp(x, 0, width - 1); // Clamp to border. This is different from the real VDP behavior.
52 // But for debugging it's visually less confusing??
53 y &= (height - 1); // wrap around
54
55 if (nx <= 0) nx = width;
56 if (ny <= 0) ny = height;
57 if (byteMode) { // round to byte positions
58 auto mask = ~(pixelsPerByte - 1);
59 x &= mask;
60 nx &= mask;
61 }
62 nx -= 1; // because coordinates are inclusive
63 ny -= 1;
64
65 // horizontally clamp to left/right border
66 auto endX = std::clamp(x + (dix ? -nx : nx), 0, width - 1);
67
68 // but vertically wrap around, possibly that splits the rectangle in two parts
69 if (diy) {
70 auto endY = y - ny;
71 if (endY >= 0) {
72 return {Rect{Point{x, y}, Point{endX, endY}}};
73 } else {
74 return {Rect{Point{x, y}, Point{endX, 0}},
75 Rect{Point{x, height - 1},
76 Point{endX, endY & (height - 1)}}};
77 }
78 } else {
79 auto endY = (y + ny);
80 if (endY < height) {
81 return {Rect{Point{x, y}, Point{endX, endY}}};
82 } else {
83 return {Rect{Point{x, y}, Point{endX, height - 1}},
84 Rect{Point{x, 0},
85 Point{endX, endY & (height - 1)}}};
86 }
87 }
88}
89
90// Given a VDPCmdEngine-rectangle and a point, split that rectangle in a 'done'
91// and a 'todo' part.
92//
93// The VDPCmdEngine walks over this rectangle line by line. But the direction
94// can still vary ('dix' and 'diy'). These directions are implicit: the first
95// 'Point' in 'Rect' is the start point, the second is the end point. (Start and
96// end points are inclusive).
97//
98// The given point is assumed to be 'done' (in reality we can make a small error
99// here, but that's just a few dozen VDP cycles, so just a few Z80
100// instructions). If the point lies outside the rectangle, the result will be
101// shown as fully 'done'.
102//
103// The full result contains 1 or 2 'done' parts, and 0, 1 or 2 'todo' parts. So
104// e.g. a top rectangle of fully processed lines, a middle line that's in
105// progress (split over two lines (represented as a rectangle with height=1)),
106// and a bottom rectangle of still todo lines.
107//
108// All the sub-rectangles in the result have their corners in 'natural' order:
109// left-to-right and top-to-bottom (this simplifies drawing later). This is
110// explicitly not the case for the input rectangle(s) (those are ordered
111// according to 'dix' and 'diy').
112//
113// The order of the sub-rectangles in the output is no longer guaranteed to be
114// in VDP command processing order, and for drawing the overlay that doesn't
115// matter.
116DoneTodo splitRect(const Rect& r, int x, int y)
117{
118 DoneTodo result;
119 auto& done = result.done;
120 auto& todo = result.todo;
121
122 auto minX = std::min(r.p1.x, r.p2.x);
123 auto maxX = std::max(r.p1.x, r.p2.x);
124 auto minY = std::min(r.p1.y, r.p2.y);
125 auto maxY = std::max(r.p1.y, r.p2.y);
126
127 if ((x < minX) || (x > maxX) || (y < minY) || (y > maxY)) {
128 // outside rect
129 done.emplace_back(Point{minX, minY}, Point{maxX, maxY});
130 return result;
131 }
132
133 bool diy = r.p1.y > r.p2.y;
134 if (minY < y) {
135 // top part (either done or todo), full lines
136 auto& res = diy ? todo : done;
137 res.emplace_back(Point{minX, minY}, Point{maxX, y - 1});
138 }
139 if (r.p1.x <= r.p2.x) {
140 // left-to-right
141 assert(r.p1.x <= x);
142 done.emplace_back(Point{r.p1.x, y}, Point{x, y});
143 if (x < r.p2.x) {
144 todo.emplace_back(Point{x + 1, y}, Point{r.p2.x, y});
145 }
146 } else {
147 // right to-left
148 assert(x <= r.p1.x);
149 done.emplace_back(Point{x, y}, Point{r.p1.x, y});
150 if (r.p2.x < x) {
151 todo.emplace_back(Point{r.p2.x, y}, Point{x - 1, y});
152 }
153 }
154
155 if (y < maxY) {
156 // bottom part (either done or todo), full lines
157 auto& res = diy ? done : todo;
158 res.emplace_back(Point{minX, y + 1}, Point{maxX, maxY});
159 }
160
161 return result;
162}
163
165 : ImGuiPart(manager_)
166 , title("Bitmap viewer")
167{
168 if (index) {
169 strAppend(title, " (", index + 1, ')');
170 }
171}
172
173void ImGuiBitmapViewer::save(ImGuiTextBuffer& buf)
174{
175 savePersistent(buf, *this, persistentElements);
176}
177
178void ImGuiBitmapViewer::loadLine(std::string_view name, zstring_view value)
179{
180 loadOnePersistent(name, value, *this, persistentElements);
181}
182
184{
185 if (!show) return;
186 if (!motherBoard) return;
187
188 ImGui::SetNextWindowSize({528, 620}, ImGuiCond_FirstUseEver);
189 im::Window(title.c_str(), &show, [&]{
190 auto* vdp = dynamic_cast<VDP*>(motherBoard->findDevice("VDP")); // TODO name based OK?
191 if (!vdp || vdp->isMSX1VDP()) return;
192
193 auto parseMode = [](DisplayMode mode) {
194 auto base = mode.getBase();
195 if (base == DisplayMode::GRAPHIC4) return SCR5;
196 if (base == DisplayMode::GRAPHIC5) return SCR6;
197 if (base == DisplayMode::GRAPHIC6) return SCR7;
198 if (base != DisplayMode::GRAPHIC7) return OTHER;
199 if (mode.getByte() & DisplayMode::YJK) {
200 if (mode.getByte() & DisplayMode::YAE) {
201 return SCR11;
202 } else {
203 return SCR12;
204 }
205 } else {
206 return SCR8;
207 }
208 };
209 int vdpMode = parseMode(vdp->getDisplayMode());
210
211 int vdpPages = vdpMode <= SCR6 ? 4 : 2;
212 int vdpPage = vdp->getDisplayPage();
213 if (vdpPage >= vdpPages) vdpPage &= 1;
214
215 int vdpLines = (vdp->getNumberOfLines() == 192) ? 0 : 1;
216
217 int vdpColor0 = [&]{
218 if (vdpMode == one_of(SCR8, SCR11, SCR12) || !vdp->getTransparency()) {
219 return 16; // no replacement
220 }
221 return vdp->getBackgroundColor() & 15;
222 }();
223
224 auto modeToStr = [](int mode) {
225 if (mode == SCR5 ) return "screen 5";
226 if (mode == SCR6 ) return "screen 6";
227 if (mode == SCR7 ) return "screen 7";
228 if (mode == SCR8 ) return "screen 8";
229 if (mode == SCR11) return "screen 11";
230 if (mode == SCR12) return "screen 12";
231 if (mode == OTHER) return "non-bitmap";
232 assert(false); return "ERROR";
233 };
234
235 static const char* const color0Str = "0\0001\0002\0003\0004\0005\0006\0007\0008\0009\00010\00011\00012\00013\00014\00015\000none\000";
236 bool manualMode = overrideAll || overrideMode;
237 bool manualPage = overrideAll || overridePage;
238 bool manualLines = overrideAll || overrideLines;
239 bool manualColor0 = overrideAll || overrideColor0;
240 im::Group([&]{
241 ImGui::TextUnformatted("VDP settings");
242 im::Disabled(manualMode, [&]{
243 ImGui::AlignTextToFramePadding();
244 ImGui::StrCat("Screen mode: ", modeToStr(vdpMode));
245 });
246 im::Disabled(manualPage, [&]{
247 ImGui::AlignTextToFramePadding();
248 ImGui::StrCat("Display page: ", vdpPage);
249 });
250 im::Disabled(manualLines, [&]{
251 ImGui::AlignTextToFramePadding();
252 ImGui::StrCat("Visible lines: ", vdpLines ? 212 : 192);
253 });
254 im::Disabled(manualColor0, [&]{
255 ImGui::AlignTextToFramePadding();
256 ImGui::StrCat("Replace color 0: ", getComboString(vdpColor0, color0Str));
257 });
258 // TODO interlace
259 });
260 ImGui::SameLine();
261 im::Group([&]{
262 ImGui::Checkbox("Manual override", &overrideAll);
263 im::Group([&]{
264 im::Disabled(overrideAll, [&]{
265 ImGui::Checkbox("##mode", overrideAll ? &overrideAll : &overrideMode);
266 ImGui::Checkbox("##page", overrideAll ? &overrideAll : &overridePage);
267 ImGui::Checkbox("##lines", overrideAll ? &overrideAll : &overrideLines);
268 ImGui::Checkbox("##color0", overrideAll ? &overrideAll : &overrideColor0);
269 });
270 });
271 ImGui::SameLine();
272 im::Group([&]{
273 im::ItemWidth(ImGui::GetFontSize() * 9.0f, [&]{
274 im::Disabled(!manualMode, [&]{
275 ImGui::Combo("##Screen mode", &bitmapScrnMode, "screen 5\000screen 6\000screen 7\000screen 8\000screen 11\000screen 12\000");
276 });
277 im::Disabled(!manualPage, [&]{
278 int numPages = bitmapScrnMode <= SCR6 ? 4 : 2; // TODO extended VRAM
279 if (bitmapPage >= numPages) bitmapPage = numPages - 1;
280 if (bitmapPage < 0) bitmapPage = numPages;
281 ImGui::Combo("##Display page", &bitmapPage, numPages == 2 ? "0\0001\000All\000" : "0\0001\0002\0003\000All\000");
282 if (bitmapPage == numPages) bitmapPage = -1;
283 });
284 im::Disabled(!manualLines || bitmapPage < 0, [&]{
285 ImGui::Combo("##Visible lines", &bitmapLines, "192\000212\000256\000");
286 });
287 im::Disabled(!manualColor0, [&]{
288 ImGui::Combo("##Color 0 replacement", &bitmapColor0, color0Str);
289 });
290 });
291 });
292 });
293
294 ImGui::SameLine();
295 ImGui::Dummy(ImVec2(15, 1));
296 ImGui::SameLine();
297
298 const auto& vram = vdp->getVRAM();
299 int mode = manualMode ? bitmapScrnMode : vdpMode;
300 int page = manualPage ? bitmapPage : vdpPage;
301 int lines = manualLines ? bitmapLines : vdpLines;
302 int color0 = manualColor0 ? bitmapColor0 : vdpColor0;
303 int divX = mode == one_of(SCR6, SCR7) ? 1 : 2;
304 int width = 512 / divX;
305 int height = (lines == 0) ? 192
306 : (lines == 1) ? 212
307 : 256;
308 if (page < 0) {
309 int numPages = mode <= SCR6 ? 4 : 2;
310 height = 256 * numPages;
311 page = 0;
312 }
313 auto rasterBeamPos = vdp->getMSXPos(vdp->getCurrentTime());
314 rasterBeamPos.x /= divX;
315
316 im::Group([&]{
317 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10.0f);
318 ImGui::Combo("Palette", &manager.palette->whichPalette, "VDP\000Custom\000Fixed\000");
319 if (ImGui::Button("Open palette editor")) {
320 manager.palette->window.raise();
321 }
322 ImGui::Separator();
323 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.0f);
324 ImGui::Combo("Zoom", &bitmapZoom, "1x\0002x\0003x\0004x\0005x\0006x\0007x\0008x\000");
325 ImGui::Checkbox("grid", &bitmapGrid);
326 ImGui::SameLine();
327 im::Disabled(!bitmapGrid, [&]{
328 ImGui::ColorEdit4("Grid color", bitmapGridColor.data(),
329 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar);
330 });
331 ImGui::Separator();
332 ImGui::Checkbox("beam", &rasterBeam);
333 ImGui::SameLine();
334 im::Disabled(!rasterBeam, [&]{
335 ImGui::ColorEdit4("raster beam color", rasterBeamColor.data(),
336 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar);
337 ImGui::SameLine();
339 ImGui::StrCat('(', dec_string<4>(rasterBeamPos.x),
340 ',', dec_string<4>(rasterBeamPos.y), ')');
341 });
342 });
343 HelpMarker("Position of the raster beam, expressed in MSX coordinates.\n"
344 "Left/top border have negative x/y-coordinates.\n"
345 "Only practically useful when emulation is paused.");
346 });
347
348 // VDP block command overlay settings
349 auto& cmdEngine = vdp->getCmdEngine();
350 bool inProgress = cmdEngine.commandInProgress(motherBoard->getCurrentTime());
351
352 auto [sx, sy, dx, dy, nx, ny, col, arg, cmdReg] = cmdEngine.getLastCommand();
353 auto cmd = cmdReg >> 4;
354 bool dix = arg & VDPCmdEngine::DIX;
355 bool diy = arg & VDPCmdEngine::DIY;
356 bool byteMode = cmd >= 12; // hmmv, hmmm, ymmm, hmmc
357
358 // only calculate src/dst rect when needed for the command
359 std::optional<static_vector<Rect, 2>> srcRect;
360 std::optional<static_vector<Rect, 2>> dstRect;
361 if (cmd == one_of(9, 10, 13)) { // lmmm, lmcm, hmmm
362 srcRect = rectFromVdpCmd(sx, sy, nx, ny, dix, diy, ScrnMode(mode), byteMode);
363 }
364 if (cmd == one_of(8, 9, 11, 12, 13, 15)) { // lmmv, lmmm, lmmc, hmmv, hmmm, hmmc
365 dstRect = rectFromVdpCmd(dx, dy, nx, ny, dix, diy, ScrnMode(mode), byteMode);
366 }
367 if (cmd == 14) { // ymmm
368 // different from normal: NO 'sx', and NO 'nx'
369 srcRect = rectFromVdpCmd(dx, sy, 512, ny, dix, diy, ScrnMode(mode), byteMode);
370 dstRect = rectFromVdpCmd(dx, dy, 512, ny, dix, diy, ScrnMode(mode), byteMode);
371 }
372
373 im::TreeNode("Show VDP block command overlay", [&]{
374 ImGui::RadioButton("never", &showCmdOverlay, 0);
375 HelpMarker("Don't draw any overlay in the bitmap view");
376 ImGui::SameLine();
377 ImGui::RadioButton("in progress", &showCmdOverlay, 1);
378 HelpMarker("Only show overlay for VDP block command that is currently executing");
379 ImGui::SameLine();
380 ImGui::RadioButton("also finished", &showCmdOverlay, 2);
381 HelpMarker("Show overlay for the currently executing VDP block command, but also the last finished block command");
382
383 im::TreeNode("Overlay colors", [&]{
384 int colorFlags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel |
385 ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreview;
386 im::Group([&]{
387 ImGui::AlignTextToFramePadding();
388 ImGui::TextUnformatted("Source (done)"sv);
389 ImGui::AlignTextToFramePadding();
390 ImGui::TextUnformatted("Source (todo)"sv);
391 });
392 ImGui::SameLine();
393 im::Group([&]{
394 ImGui::ColorEdit4("src-done", colorSrcDone.data(), colorFlags);
395 ImGui::ColorEdit4("src-todo", colorSrcTodo.data(), colorFlags);
396 });
397 ImGui::SameLine(0.0f, ImGui::GetFontSize() * 3.0f);
398 im::Group([&]{
399 ImGui::AlignTextToFramePadding();
400 ImGui::TextUnformatted("Destination (done)"sv);
401 ImGui::AlignTextToFramePadding();
402 ImGui::TextUnformatted("Destination (todo)"sv);
403 });
404 ImGui::SameLine();
405 im::Group([&]{
406 ImGui::ColorEdit4("dst-done", colorDstDone.data(), colorFlags);
407 ImGui::ColorEdit4("dst-todo", colorDstTodo.data(), colorFlags);
408 });
409 });
410
411 ImGui::TextUnformatted("Last command "sv);
412 ImGui::SameLine();
413 ImGui::TextUnformatted(inProgress ? "[in progress]"sv : "[finished]"sv);
414
415 // Textual-representation of last command (grayed-out if finished).
416 // Note:
417 // * The representation _resembles_ to notation in MSX-BASIC, but is (intentionally) not
418 // the same. Here we need to convey more information (e.g. for copy commands we show
419 // both corners of the destination).
420 // * For the non-block commands we don't clamp/wrap the coordinates.
421 // * For the logical commands we don't mask the color register (e.g. to 4 bits for SCRN5).
422 // * For the LINE command, we don't clamp/wrap the end-point.
423 // * Src and dst rectangles are _separately_ clamped to left/right borders.
424 // That's different from the real VDP behavior, but maybe less confusing to show in the debugger.
425 // * The first coordinate is always the start-corner. The second may be larger or
426 // smaller depending on 'dix' and 'diy'.
427 // * The textual representation for commands that wrap around the top/bottom border is
428 // ambiguous (but the drawn overlay should be fine).
429 im::DisabledIndent(!inProgress, [&]{
430 static constexpr std::array<const char*, 16> logOps = {
431 "", ",AND", ",OR", ",XOR", ",NOT", "","","",
432 ",TIMP",",TAND",",TOR",",TXOR",",TNOT","","",""
433 };
434 const char* logOp = logOps[cmdReg & 15];
435 auto printRect = [](const char* s, std::optional<static_vector<Rect, 2>>& r) {
436 assert(r);
438 ImGui::SameLine(0.0f, 0.0f);
439 ImGui::Text("(%d,%d)-(%d,%d)",
440 r->front().p1.x, // front/back for the (unlikely) case of vertical wrapping
441 r->front().p1.y,
442 r->back().p2.x,
443 r->back().p2.y);
444 };
445 auto printSrcDstRect = [&](const char* c) {
446 printRect(c, srcRect);
447 ImGui::SameLine(0.0f, 0.0f);
448 printRect(" TO ", dstRect);
449 };
450 auto printCol = [&]{
451 ImGui::SameLine(0.0f, 0.0f);
452 ImGui::Text(",%d", col);
453 };
454 auto printLogOp = [&]{
455 ImGui::SameLine(0.0f, 0.0f);
457 };
458 switch (cmd) {
459 case 0: case 1: case 2: case 3:
460 ImGui::TextUnformatted("ABORT"sv);
461 break;
462 case 4:
463 ImGui::Text("POINT (%d,%d)", sx, sy);
464 break;
465 case 5:
466 ImGui::Text("PSET (%d,%d),%d%s", dx, dy, col, logOp);
467 break;
468 case 6:
469 ImGui::Text("SRCH (%d,%d) %s %d", sx, sy,
470 ((arg & VDPCmdEngine::EQ) ? "==" : "!="), col);
471 break;
472 case 7: {
473 auto nx2 = nx; auto ny2 = ny;
474 if (arg & VDPCmdEngine::MAJ) std::swap(nx2, ny2);
475 auto x = int(sx) + (dix ? -int(nx2) : int(nx2));
476 auto y = int(sy) + (diy ? -int(ny2) : int(ny2));
477 ImGui::Text("LINE (%d,%d)-(%d,%d),%d%s", sx, sy, x, y, col, logOp);
478 break;
479 }
480 case 8:
481 printRect("LMMV ", dstRect);
482 printCol();
483 printLogOp();
484 break;
485 case 9:
486 printSrcDstRect("LMMM ");
487 printLogOp();
488 break;
489 case 10:
490 printRect("LMCM ", srcRect);
491 break;
492 case 11:
493 printRect("LMMC ", dstRect);
494 printLogOp();
495 break;
496 case 12:
497 printRect("HMMV ", dstRect);
498 printCol();
499 break;
500 case 13:
501 printSrcDstRect("HMMM ");
502 break;
503 case 14:
504 printSrcDstRect("YMMM ");
505 break;
506 case 15:
507 printRect("HMMC ", dstRect);
508 break;
509 default:
511 }
512 });
513 });
514
515 ImGui::Separator();
516
517 std::array<uint32_t, 16> palette;
518 auto msxPalette = manager.palette->getPalette(vdp);
519 ranges::transform(msxPalette, palette.data(),
520 [](uint16_t msx) { return ImGuiPalette::toRGBA(msx); });
521 if (color0 < 16) palette[0] = palette[color0];
522
523 MemBuffer<uint32_t> pixels(512 * 256 * 4); // max size: screen 6/7, show all pages
524 renderBitmap(vram.getData(), palette, mode, height, page,
525 pixels.data());
526 if (!bitmapTex) {
527 bitmapTex.emplace(false, false); // no interpolation, no wrapping
528 }
529 bitmapTex->bind();
530 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
531 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
532 int zx = (1 + bitmapZoom) * divX;
533 int zy = (1 + bitmapZoom) * 2;
534 auto zm = gl::vec2(float(zx), float(zy));
535
536 gl::vec2 scrnPos;
537 auto msxSize = gl::vec2(float(width), float(height));
538 auto size = msxSize * zm;
539 auto availSize = gl::vec2(ImGui::GetContentRegionAvail()) - gl::vec2(0.0f, ImGui::GetTextLineHeightWithSpacing());
540 auto reqSize = size + gl::vec2(ImGui::GetStyle().ScrollbarSize);
541 im::Child("##bitmap", min(availSize, reqSize), 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
542 scrnPos = ImGui::GetCursorScreenPos();
543 auto pos = ImGui::GetCursorPos();
544 ImGui::Image(bitmapTex->getImGui(), size);
545
546 if (bitmapGrid && (zx > 1) && (zy > 1)) {
547 auto color = ImGui::ColorConvertFloat4ToU32(bitmapGridColor);
548 for (auto y : xrange(zy)) {
549 auto* line = &pixels[y * zx];
550 for (auto x : xrange(zx)) {
551 line[x] = (x == 0 || y == 0) ? color : 0;
552 }
553 }
554 if (!bitmapGridTex) {
555 bitmapGridTex.emplace(false, true); // no interpolation, with wrapping
556 }
557 bitmapGridTex->bind();
558 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, zx, zy, 0,
559 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
560 ImGui::SetCursorPos(pos);
561 ImGui::Image(bitmapGridTex->getImGui(), size, gl::vec2{}, msxSize);
562 }
563 auto* drawList = ImGui::GetWindowDrawList();
564 if (rasterBeam) {
565 auto center = scrnPos + (gl::vec2(rasterBeamPos) + gl::vec2{0.5f}) * zm;
566 auto color = ImGui::ColorConvertFloat4ToU32(rasterBeamColor);
567 auto thickness = zm.y * 0.5f;
568 auto zm1 = 1.5f * zm;
569 auto zm3 = 3.5f * zm;
570 drawList->AddRect(center - zm, center + zm, color, 0.0f, 0, thickness);
571 drawList->AddLine(center - gl::vec2{zm1.x, 0.0f}, center - gl::vec2{zm3.x, 0.0f}, color, thickness);
572 drawList->AddLine(center + gl::vec2{zm1.x, 0.0f}, center + gl::vec2{zm3.x, 0.0f}, color, thickness);
573 drawList->AddLine(center - gl::vec2{0.0f, zm1.y}, center - gl::vec2{0.0f, zm3.y}, color, thickness);
574 drawList->AddLine(center + gl::vec2{0.0f, zm1.y}, center + gl::vec2{0.0f, zm3.y}, color, thickness);
575 }
576
577 // draw the VDP command overlay
578 auto offset = gl::vec2(0.0f, height > 256 ? 0.0f : float(256 * page));
579 auto drawRect = [&](const Rect& r, ImU32 color) {
580 auto tl = zm * (gl::vec2(r.p1) - offset);
581 auto br = zm * (gl::vec2(r.p2) - offset + gl::vec2(1.0f)); // rect is inclusive, openGL is exclusive
582 drawList->AddRectFilled(scrnPos + tl, scrnPos + br, color);
583 };
584 auto drawSplit = [&](const DoneTodo& dt, ImU32 colDone, ImU32 colTodo) {
585 for (const auto& r : dt.done) drawRect(r, colDone);
586 for (const auto& r : dt.todo) drawRect(r, colTodo);
587 };
588 auto drawOverlay = [&](const static_vector<Rect, 2>& rects, int x, int y, ImU32 colDone, ImU32 colTodo) {
589 auto split1 = splitRect(rects[0], x, y);
590 drawSplit(split1, colDone, colTodo);
591 if (rects.size() == 2) {
592 auto split2 = splitRect(rects[1], x, y);
593 auto col1 = (split1.done.size() == 2) ? colTodo : colDone; // if (x,y) is in rects[0], then use todo-color
594 drawSplit(split2, col1, colTodo);
595 }
596 };
597 if ((showCmdOverlay == 2) || ((showCmdOverlay == 1) && inProgress)) {
598 auto [sx2, sy2, dx2, dy2] = cmdEngine.getInprogressPosition();
599 if (srcRect) {
600 drawOverlay(*srcRect, sx2, sy2,
601 ImGui::ColorConvertFloat4ToU32(colorSrcDone),
602 ImGui::ColorConvertFloat4ToU32(colorSrcTodo));
603 }
604 if (dstRect) {
605 drawOverlay(*dstRect, dx2, dy2,
606 ImGui::ColorConvertFloat4ToU32(colorDstDone),
607 ImGui::ColorConvertFloat4ToU32(colorDstTodo));
608 }
609 }
610 });
611 if (ImGui::IsItemHovered() && (mode != OTHER)) {
612 auto [x_, y_] = trunc((gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / zm);
613 auto x = x_; auto y = y_; // clang workaround
614 if ((0 <= x) && (x < width) && (0 <= y) && (y < height)) {
615 auto dec3 = [&](int d) {
617 ImGui::SameLine(0.0f, 0.0f);
618 ImGui::Text("%3d", d);
619 };
620 auto hex2 = [&](unsigned h) {
622 ImGui::SameLine(0.0f, 0.0f);
623 ImGui::StrCat(hex_string<2>(h));
624 };
625 auto hex5 = [&](unsigned h) {
627 ImGui::SameLine(0.0f, 0.0f);
628 ImGui::StrCat(hex_string<5>(h));
629 };
630
631 ImGui::TextUnformatted("x="sv); dec3(x);
632 ImGui::SameLine();
633 ImGui::TextUnformatted("y="sv); dec3(y % 256);
634
635 if (bitmapPage == -1) {
636 ImGui::SameLine();
637 ImGui::TextUnformatted("page="sv);
639 ImGui::SameLine(0.0f, 0.0f);
640 ImGui::StrCat(y / 256);
641 }
642
643 unsigned physAddr = 0x8000 * page + 128 * y;
644 switch (mode) {
645 case SCR5: physAddr += x / 2; break;
646 case SCR6: physAddr += x / 4; break;
647 case SCR7: physAddr += x / 4 + 0x08000 * (x & 2); break;
648 case SCR8: case SCR11: case SCR12:
649 physAddr += x / 2 + 0x10000 * (x & 1); break;
650 default: assert(false);
651 }
652
653 auto value = vram.getData()[physAddr];
654 auto color = [&]() -> uint8_t {
655 switch (mode) {
656 case SCR5: case SCR7:
657 return (value >> (4 * (1 - (x & 1)))) & 0x0f;
658 case SCR6:
659 return (value >> (2 * (3 - (x & 3)))) & 0x03;
660 default:
661 return value;
662 }
663 }();
664 if (mode != one_of(SCR11, SCR12)) {
665 ImGui::SameLine();
666 ImGui::TextUnformatted(" color="sv); dec3(color);
667 }
668
669 ImGui::SameLine();
670 ImGui::TextUnformatted(" vram: addr=0x");
671 if (mode == one_of(SCR5, SCR6)) {
672 hex5(physAddr);
673 } else {
674 unsigned logAddr = (physAddr & 0x0ffff) << 1 | (physAddr >> 16);
675 hex5(logAddr),
676 ImGui::SameLine(0.0f, 0.0f);
677 ImGui::TextUnformatted("(log)/0x"sv); hex5(physAddr);
678 ImGui::SameLine(0.0f, 0.0f);
679 ImGui::TextUnformatted("(phys)"sv);
680 }
681 ImGui::SameLine();
682 ImGui::TextUnformatted(" value=0x"sv); hex2(value);
683 }
684 }
685 });
686}
687
688// TODO avoid code duplication with src/video/BitmapConverter
689void ImGuiBitmapViewer::renderBitmap(std::span<const uint8_t> vram, std::span<const uint32_t, 16> palette16,
690 int mode, int lines, int page, uint32_t* output) const
691{
692 auto yjk2rgb = [](int y, int j, int k) -> std::tuple<int, int, int> {
693 // Note the formula for 'blue' differs from the 'traditional' formula
694 // (e.g. as specified in the V9958 datasheet) in the rounding behavior.
695 // Confirmed on real turbor machine. For details see:
696 // https://github.com/openMSX/openMSX/issues/1394
697 // https://twitter.com/mdpc___/status/1480432007180341251?s=20
698 int r = std::clamp(y + j, 0, 31);
699 int g = std::clamp(y + k, 0, 31);
700 int b = std::clamp((5 * y - 2 * j - k + 2) / 4, 0, 31);
701 return {r, g, b};
702 };
703
704 // TODO handle less than 128kB VRAM (will crash now)
705 size_t addr = 0x8000 * page;
706 switch (mode) {
707 case SCR5:
708 for (auto y : xrange(lines)) {
709 auto* line = &output[256 * y];
710 for (auto x : xrange(128)) {
711 auto value = vram[addr];
712 line[2 * x + 0] = palette16[(value >> 4) & 0x0f];
713 line[2 * x + 1] = palette16[(value >> 0) & 0x0f];
714 ++addr;
715 }
716 }
717 break;
718
719 case SCR6:
720 for (auto y : xrange(lines)) {
721 auto* line = &output[512 * y];
722 for (auto x : xrange(128)) {
723 auto value = vram[addr];
724 line[4 * x + 0] = palette16[(value >> 6) & 3];
725 line[4 * x + 1] = palette16[(value >> 4) & 3];
726 line[4 * x + 2] = palette16[(value >> 2) & 3];
727 line[4 * x + 3] = palette16[(value >> 0) & 3];
728 ++addr;
729 }
730 }
731 break;
732
733 case SCR7:
734 for (auto y : xrange(lines)) {
735 auto* line = &output[512 * y];
736 for (auto x : xrange(128)) {
737 auto value0 = vram[addr + 0x00000];
738 auto value1 = vram[addr + 0x10000];
739 line[4 * x + 0] = palette16[(value0 >> 4) & 0x0f];
740 line[4 * x + 1] = palette16[(value0 >> 0) & 0x0f];
741 line[4 * x + 2] = palette16[(value1 >> 4) & 0x0f];
742 line[4 * x + 3] = palette16[(value1 >> 0) & 0x0f];
743 ++addr;
744 }
745 }
746 break;
747
748 case SCR8: {
749 auto toColor = [](uint8_t value) {
750 int r = (value & 0x1c) >> 2;
751 int g = (value & 0xe0) >> 5;
752 int b = (value & 0x03) >> 0;
753 int rr = (r << 5) | (r << 2) | (r >> 1);
754 int gg = (g << 5) | (g << 2) | (g >> 1);
755 int bb = (b << 6) | (b << 4) | (b << 2) | (b << 0);
756 int aa = 255;
757 return (rr << 0) | (gg << 8) | (bb << 16) | (aa << 24);
758 };
759 for (auto y : xrange(lines)) {
760 auto* line = &output[256 * y];
761 for (auto x : xrange(128)) {
762 line[2 * x + 0] = toColor(vram[addr + 0x00000]);
763 line[2 * x + 1] = toColor(vram[addr + 0x10000]);
764 ++addr;
765 }
766 }
767 break;
768 }
769
770 case SCR11:
771 for (auto y : xrange(lines)) {
772 auto* line = &output[256 * y];
773 for (auto x : xrange(64)) {
774 std::array<unsigned, 4> p = {
775 vram[addr + 0 + 0x00000],
776 vram[addr + 0 + 0x10000],
777 vram[addr + 1 + 0x00000],
778 vram[addr + 1 + 0x10000],
779 };
780 addr += 2;
781 int j = narrow<int>((p[2] & 7) + ((p[3] & 3) << 3)) - narrow<int>((p[3] & 4) << 3);
782 int k = narrow<int>((p[0] & 7) + ((p[1] & 3) << 3)) - narrow<int>((p[1] & 4) << 3);
783 for (auto n : xrange(4)) {
784 uint32_t pix;
785 if (p[n] & 0x08) {
786 pix = palette16[p[n] >> 4];
787 } else {
788 int Y = narrow<int>(p[n] >> 3);
789 auto [r, g, b] = yjk2rgb(Y, j, k);
790 int rr = (r << 3) | (r >> 2);
791 int gg = (g << 3) | (g >> 2);
792 int bb = (b << 3) | (b >> 2);
793 int aa = 255;
794 pix = (rr << 0) | (gg << 8) | (bb << 16) | (aa << 24);
795 }
796 line[4 * x + n] = pix;
797 }
798 }
799 }
800 break;
801
802 case SCR12:
803 for (auto y : xrange(lines)) {
804 auto* line = &output[256 * y];
805 for (auto x : xrange(64)) {
806 std::array<unsigned, 4> p = {
807 vram[addr + 0 + 0x00000],
808 vram[addr + 0 + 0x10000],
809 vram[addr + 1 + 0x00000],
810 vram[addr + 1 + 0x10000],
811 };
812 addr += 2;
813 int j = narrow<int>((p[2] & 7) + ((p[3] & 3) << 3)) - narrow<int>((p[3] & 4) << 3);
814 int k = narrow<int>((p[0] & 7) + ((p[1] & 3) << 3)) - narrow<int>((p[1] & 4) << 3);
815 for (auto n : xrange(4)) {
816 int Y = narrow<int>(p[n] >> 3);
817 auto [r, g, b] = yjk2rgb(Y, j, k);
818 int rr = (r << 3) | (r >> 2);
819 int gg = (g << 3) | (g >> 2);
820 int bb = (b << 3) | (b >> 2);
821 int aa = 255;
822 line[4 * x + n] = (rr << 0) | (gg << 8) | (bb << 16) | (aa << 24);
823 }
824 }
825 }
826 break;
827
828 case OTHER:
829 for (auto y : xrange(lines)) {
830 auto* line = &output[256 * y];
831 for (auto x : xrange(256)) {
832 line[x] = getColor(imColor::GRAY);
833 }
834 }
835 break;
836 }
837}
838
839} // namespace openmsx
int g
void loadLine(std::string_view name, zstring_view value) override
void paint(MSXMotherBoard *motherBoard) override
void save(ImGuiTextBuffer &buf) override
ImGuiBitmapViewer(ImGuiManager &manager_, size_t index)
std::unique_ptr< ImGuiPalette > palette
ImGuiManager & manager
Definition ImGuiPart.hh:30
EmuTime::param getCurrentTime() const
Convenience method: This is the same as getScheduler().getCurrentTime().
This class manages the lifetime of a block of memory.
Definition MemBuffer.hh:32
const T * data() const
Returns pointer to the start of the memory buffer.
Definition MemBuffer.hh:79
static constexpr byte MAJ
static constexpr byte DIX
static constexpr byte DIY
static constexpr byte EQ
constexpr size_t size() const noexcept
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
void StrCat(Ts &&...ts)
Definition ImGuiUtils.hh:45
void TextUnformatted(const std::string &str)
Definition ImGuiUtils.hh:26
vecN< 2, float > vec2
Definition gl_vec.hh:382
void DisabledIndent(bool b, std::invocable<> auto next)
Definition ImGuiCpp.hh:514
void Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:63
bool TreeNode(const char *label, ImGuiTreeNodeFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:302
void ItemWidth(float item_width, std::invocable<> auto next)
Definition ImGuiCpp.hh:204
void Child(const char *str_id, const ImVec2 &size, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:110
void Disabled(bool b, std::invocable<> auto next)
Definition ImGuiCpp.hh:506
void Group(std::invocable<> auto next)
Definition ImGuiCpp.hh:236
void Font(ImFont *font, std::invocable<> auto next)
Definition ImGuiCpp.hh:131
This file implemented 3 utility functions:
Definition Autofire.cc:11
bool loadOnePersistent(std::string_view name, zstring_view value, C &c, const std::tuple< Elements... > &tup)
void savePersistent(ImGuiTextBuffer &buf, C &c, const std::tuple< Elements... > &tup)
DoneTodo splitRect(const Rect &r, int x, int y)
static_vector< Rect, 2 > rectFromVdpCmd(int x, int y, int nx, int ny, bool dix, bool diy, ImGuiBitmapViewer::ScrnMode screenMode, bool byteMode)
void HelpMarker(std::string_view desc)
Definition ImGuiUtils.cc:23
ImU32 getColor(imColor col)
const char * getComboString(int item, const char *itemsSeparatedByZeros)
auto transform(InputRange &&range, OutputIter out, UnaryOperation op)
Definition ranges.hh:279
void strAppend(std::string &result, Ts &&...ts)
Definition strCat.hh:752
static_vector< Rect, 2 > done
static_vector< Rect, 2 > todo
#define UNREACHABLE
constexpr auto xrange(T e)
Definition xrange.hh:132