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
21void ImGuiBitmapViewer::save(ImGuiTextBuffer& buf)
22{
23 savePersistent(buf, *this, persistentElements);
24}
25
26void ImGuiBitmapViewer::loadLine(std::string_view name, zstring_view value)
27{
28 loadOnePersistent(name, value, *this, persistentElements);
29}
30
32{
33 if (!showBitmapViewer) return;
34 if (!motherBoard) return;
35
36 ImGui::SetNextWindowSize({528, 618}, ImGuiCond_FirstUseEver);
37 im::Window("Bitmap viewer", &showBitmapViewer, [&]{
38 auto* vdp = dynamic_cast<VDP*>(motherBoard->findDevice("VDP")); // TODO name based OK?
39 if (!vdp) return;
40
41 auto parseMode = [](DisplayMode mode) {
42 auto base = mode.getBase();
43 if (base == DisplayMode::GRAPHIC4) return SCR5;
44 if (base == DisplayMode::GRAPHIC5) return SCR6;
45 if (base == DisplayMode::GRAPHIC6) return SCR7;
46 if (base != DisplayMode::GRAPHIC7) return OTHER;
47 if (mode.getByte() & DisplayMode::YJK) {
48 if (mode.getByte() & DisplayMode::YAE) {
49 return SCR11;
50 } else {
51 return SCR12;
52 }
53 } else {
54 return SCR8;
55 }
56 };
57 int vdpMode = parseMode(vdp->getDisplayMode());
58
59 int vdpPages = vdpMode <= SCR6 ? 4 : 2;
60 int vdpPage = vdp->getDisplayPage();
61 if (vdpPage >= vdpPages) vdpPage &= 1;
62
63 int vdpLines = (vdp->getNumberOfLines() == 192) ? 0 : 1;
64
65 int vdpColor0 = [&]{
66 if (vdpMode == one_of(SCR8, SCR11, SCR12) || !vdp->getTransparency()) {
67 return 16; // no replacement
68 }
69 return vdp->getBackgroundColor() & 15;
70 }();
71
72 auto modeToStr = [](int mode) {
73 if (mode == SCR5 ) return "screen 5";
74 if (mode == SCR6 ) return "screen 6";
75 if (mode == SCR7 ) return "screen 7";
76 if (mode == SCR8 ) return "screen 8";
77 if (mode == SCR11) return "screen 11";
78 if (mode == SCR12) return "screen 12";
79 if (mode == OTHER) return "non-bitmap";
80 assert(false); return "ERROR";
81 };
82
83 static const char* const color0Str = "0\0001\0002\0003\0004\0005\0006\0007\0008\0009\00010\00011\00012\00013\00014\00015\000none\000";
84 bool manualMode = overrideAll || overrideMode;
85 bool manualPage = overrideAll || overridePage;
86 bool manualLines = overrideAll || overrideLines;
87 bool manualColor0 = overrideAll || overrideColor0;
88 im::Group([&]{
89 ImGui::TextUnformatted("VDP settings");
90 im::Disabled(manualMode, [&]{
91 ImGui::AlignTextToFramePadding();
92 ImGui::StrCat("Screen mode: ", modeToStr(vdpMode));
93 });
94 im::Disabled(manualPage, [&]{
95 ImGui::AlignTextToFramePadding();
96 ImGui::StrCat("Display page: ", vdpPage);
97 });
98 im::Disabled(manualLines, [&]{
99 ImGui::AlignTextToFramePadding();
100 ImGui::StrCat("Visible lines: ", vdpLines ? 212 : 192);
101 });
102 im::Disabled(manualColor0, [&]{
103 ImGui::AlignTextToFramePadding();
104 ImGui::StrCat("Replace color 0: ", getComboString(vdpColor0, color0Str));
105 });
106 // TODO interlace
107 });
108 ImGui::SameLine();
109 im::Group([&]{
110 ImGui::Checkbox("Manual override", &overrideAll);
111 im::Group([&]{
112 im::Disabled(overrideAll, [&]{
113 ImGui::Checkbox("##mode", overrideAll ? &overrideAll : &overrideMode);
114 ImGui::Checkbox("##page", overrideAll ? &overrideAll : &overridePage);
115 ImGui::Checkbox("##lines", overrideAll ? &overrideAll : &overrideLines);
116 ImGui::Checkbox("##color0", overrideAll ? &overrideAll : &overrideColor0);
117 });
118 });
119 ImGui::SameLine();
120 im::Group([&]{
121 im::ItemWidth(ImGui::GetFontSize() * 9.0f, [&]{
122 im::Disabled(!manualMode, [&]{
123 ImGui::Combo("##Screen mode", &bitmapScrnMode, "screen 5\000screen 6\000screen 7\000screen 8\000screen 11\000screen 12\000");
124 });
125 im::Disabled(!manualPage, [&]{
126 int numPages = bitmapScrnMode <= SCR6 ? 4 : 2; // TODO extended VRAM
127 if (bitmapPage >= numPages) bitmapPage = numPages - 1;
128 if (bitmapPage < 0) bitmapPage = numPages;
129 ImGui::Combo("##Display page", &bitmapPage, numPages == 2 ? "0\0001\000All\000" : "0\0001\0002\0003\000All\000");
130 if (bitmapPage == numPages) bitmapPage = -1;
131 });
132 im::Disabled(!manualLines || bitmapPage < 0, [&]{
133 ImGui::Combo("##Visible lines", &bitmapLines, "192\000212\000256\000");
134 });
135 im::Disabled(!manualColor0, [&]{
136 ImGui::Combo("##Color 0 replacement", &bitmapColor0, color0Str);
137 });
138 });
139 });
140 });
141
142 ImGui::SameLine();
143 ImGui::Dummy(ImVec2(15, 1));
144 ImGui::SameLine();
145
146 const auto& vram = vdp->getVRAM();
147 int mode = manualMode ? bitmapScrnMode : vdpMode;
148 int page = manualPage ? bitmapPage : vdpPage;
149 int lines = manualLines ? bitmapLines : vdpLines;
150 int color0 = manualColor0 ? bitmapColor0 : vdpColor0;
151 int divX = mode == one_of(SCR6, SCR7) ? 1 : 2;
152 int width = 512 / divX;
153 int height = (lines == 0) ? 192
154 : (lines == 1) ? 212
155 : 256;
156 if (page < 0) {
157 int numPages = mode <= SCR6 ? 4 : 2;
158 height = 256 * numPages;
159 page = 0;
160 }
161 auto rasterBeamPos = vdp->getMSXPos(vdp->getCurrentTime());
162 rasterBeamPos.x /= divX;
163
164 im::Group([&]{
165 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10.0f);
166 ImGui::Combo("Palette", &manager.palette->whichPalette, "VDP\000Custom\000Fixed\000");
167 if (ImGui::Button("Open palette editor")) {
168 manager.palette->window.raise();
169 }
170 ImGui::Separator();
171 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.0f);
172 ImGui::Combo("Zoom", &bitmapZoom, "1x\0002x\0003x\0004x\0005x\0006x\0007x\0008x\000");
173 ImGui::Checkbox("grid", &bitmapGrid);
174 ImGui::SameLine();
175 im::Disabled(!bitmapGrid, [&]{
176 ImGui::ColorEdit4("Grid color", bitmapGridColor.data(),
177 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar);
178 });
179 ImGui::Separator();
180 ImGui::Checkbox("beam", &rasterBeam);
181 ImGui::SameLine();
182 im::Disabled(!rasterBeam, [&]{
183 ImGui::ColorEdit4("raster beam color", rasterBeamColor.data(),
184 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar);
185 ImGui::SameLine();
187 ImGui::StrCat('(', dec_string<4>(rasterBeamPos.x),
188 ',', dec_string<4>(rasterBeamPos.y), ')');
189 });
190 });
191 HelpMarker("Position of the raster beam, expressed in MSX coordinates.\n"
192 "Left/top border have negative x/y-coordinates.\n"
193 "Only practically useful when emulation is paused.");
194 });
195
196 ImGui::Separator();
197
198 std::array<uint32_t, 16> palette;
199 auto msxPalette = manager.palette->getPalette(vdp);
200 ranges::transform(msxPalette, palette.data(),
201 [](uint16_t msx) { return ImGuiPalette::toRGBA(msx); });
202 if (color0 < 16) palette[0] = palette[color0];
203
204 MemBuffer<uint32_t> pixels(512 * 256 * 2); // max size: 512*256*2 or 256*256*4
205 renderBitmap(vram.getData(), palette, mode, height, page,
206 pixels.data());
207 if (!bitmapTex) {
208 bitmapTex.emplace(false, false); // no interpolation, no wrapping
209 }
210 bitmapTex->bind();
211 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
212 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
213 int zx = (1 + bitmapZoom) * divX;
214 int zy = (1 + bitmapZoom) * 2;
215 gl::vec2 zm = gl::vec2(float(zx), float(zy));
216
217 gl::vec2 scrnPos;
218 auto msxSize = gl::vec2(float(width), float(height));
219 auto size = msxSize * zm;
220 auto availSize = gl::vec2(ImGui::GetContentRegionAvail()) - gl::vec2(0.0f, ImGui::GetTextLineHeightWithSpacing());
221 auto reqSize = size + gl::vec2(ImGui::GetStyle().ScrollbarSize);
222 im::Child("##bitmap", min(availSize, reqSize), 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
223 scrnPos = ImGui::GetCursorScreenPos();
224 auto pos = ImGui::GetCursorPos();
225 ImGui::Image(bitmapTex->getImGui(), size);
226
227 if (bitmapGrid && (zx > 1) && (zy > 1)) {
228 auto color = ImGui::ColorConvertFloat4ToU32(bitmapGridColor);
229 for (auto y : xrange(zy)) {
230 auto* line = &pixels[y * zx];
231 for (auto x : xrange(zx)) {
232 line[x] = (x == 0 || y == 0) ? color : 0;
233 }
234 }
235 if (!bitmapGridTex) {
236 bitmapGridTex.emplace(false, true); // no interpolation, with wrapping
237 }
238 bitmapGridTex->bind();
239 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, zx, zy, 0,
240 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
241 ImGui::SetCursorPos(pos);
242 ImGui::Image(bitmapGridTex->getImGui(), size, gl::vec2{}, msxSize);
243 }
244 if (rasterBeam) {
245 auto* drawList = ImGui::GetWindowDrawList();
246 auto center = scrnPos + (gl::vec2(rasterBeamPos) + gl::vec2{0.5f}) * zm;
247 auto color = ImGui::ColorConvertFloat4ToU32(rasterBeamColor);
248 auto thickness = zm.y * 0.5f;
249 auto zm1 = 1.5f * zm;
250 auto zm3 = 3.5f * zm;
251 drawList->AddRect(center - zm, center + zm, color, 0.0f, 0, thickness);
252 drawList->AddLine(center - gl::vec2{zm1.x, 0.0f}, center - gl::vec2{zm3.x, 0.0f}, color, thickness);
253 drawList->AddLine(center + gl::vec2{zm1.x, 0.0f}, center + gl::vec2{zm3.x, 0.0f}, color, thickness);
254 drawList->AddLine(center - gl::vec2{0.0f, zm1.y}, center - gl::vec2{0.0f, zm3.y}, color, thickness);
255 drawList->AddLine(center + gl::vec2{0.0f, zm1.y}, center + gl::vec2{0.0f, zm3.y}, color, thickness);
256 }
257 });
258 if (ImGui::IsItemHovered() && (mode != OTHER)) {
259 auto [x_, y_] = trunc((gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / zm);
260 auto x = x_; auto y = y_; // clang workaround
261 if ((0 <= x) && (x < width) && (0 <= y) && (y < height)) {
262 auto dec3 = [&](int d) {
264 ImGui::SameLine(0.0f, 0.0f);
265 ImGui::Text("%3d", d);
266 };
267 auto hex2 = [&](unsigned h) {
269 ImGui::SameLine(0.0f, 0.0f);
270 ImGui::StrCat(hex_string<2>(h));
271 };
272 auto hex5 = [&](unsigned h) {
274 ImGui::SameLine(0.0f, 0.0f);
275 ImGui::StrCat(hex_string<5>(h));
276 };
277
278 ImGui::TextUnformatted("x="sv); dec3(x);
279 ImGui::SameLine();
280 ImGui::TextUnformatted("y="sv); dec3(y);
281
282 unsigned physAddr = 0x8000 * page + 128 * y;
283 switch (mode) {
284 case SCR5: physAddr += x / 2; break;
285 case SCR6: physAddr += x / 4; break;
286 case SCR7: physAddr += x / 4 + 0x08000 * (x & 2); break;
287 case SCR8: case SCR11: case SCR12:
288 physAddr += x / 2 + 0x10000 * (x & 1); break;
289 default: assert(false);
290 }
291
292 auto value = vram.getData()[physAddr];
293 auto color = [&]() -> uint8_t {
294 switch (mode) {
295 case SCR5: case SCR7:
296 return (value >> (4 * (1 - (x & 1)))) & 0x0f;
297 case SCR6:
298 return (value >> (2 * (3 - (x & 3)))) & 0x03;
299 default:
300 return value;
301 }
302 }();
303 if (mode != one_of(SCR11, SCR12)) {
304 ImGui::SameLine();
305 ImGui::TextUnformatted(" color="sv); dec3(color);
306 }
307
308 ImGui::SameLine();
309 ImGui::TextUnformatted(" vram: addr=0x");
310 if (mode == one_of(SCR5, SCR6)) {
311 hex5(physAddr);
312 } else {
313 unsigned logAddr = (physAddr & 0x0ffff) << 1 | (physAddr >> 16);
314 hex5(logAddr),
315 ImGui::SameLine(0.0f, 0.0f);
316 ImGui::TextUnformatted("(log)/0x"sv); hex5(physAddr);
317 ImGui::SameLine(0.0f, 0.0f);
318 ImGui::TextUnformatted("(phys)"sv);
319 }
320 ImGui::SameLine();
321 ImGui::TextUnformatted(" value=0x"sv); hex2(value);
322 }
323 }
324 });
325}
326
327// TODO avoid code duplication with src/video/BitmapConverter
328void ImGuiBitmapViewer::renderBitmap(std::span<const uint8_t> vram, std::span<const uint32_t, 16> palette16,
329 int mode, int lines, int page, uint32_t* output) const
330{
331 auto yjk2rgb = [](int y, int j, int k) -> std::tuple<int, int, int> {
332 // Note the formula for 'blue' differs from the 'traditional' formula
333 // (e.g. as specified in the V9958 datasheet) in the rounding behavior.
334 // Confirmed on real turbor machine. For details see:
335 // https://github.com/openMSX/openMSX/issues/1394
336 // https://twitter.com/mdpc___/status/1480432007180341251?s=20
337 int r = std::clamp(y + j, 0, 31);
338 int g = std::clamp(y + k, 0, 31);
339 int b = std::clamp((5 * y - 2 * j - k + 2) / 4, 0, 31);
340 return {r, g, b};
341 };
342
343 // TODO handle less than 128kB VRAM (will crash now)
344 size_t addr = 0x8000 * page;
345 switch (mode) {
346 case SCR5:
347 for (auto y : xrange(lines)) {
348 auto* line = &output[256 * y];
349 for (auto x : xrange(128)) {
350 auto value = vram[addr];
351 line[2 * x + 0] = palette16[(value >> 4) & 0x0f];
352 line[2 * x + 1] = palette16[(value >> 0) & 0x0f];
353 ++addr;
354 }
355 }
356 break;
357
358 case SCR6:
359 for (auto y : xrange(lines)) {
360 auto* line = &output[512 * y];
361 for (auto x : xrange(128)) {
362 auto value = vram[addr];
363 line[4 * x + 0] = palette16[(value >> 6) & 3];
364 line[4 * x + 1] = palette16[(value >> 4) & 3];
365 line[4 * x + 2] = palette16[(value >> 2) & 3];
366 line[4 * x + 3] = palette16[(value >> 0) & 3];
367 ++addr;
368 }
369 }
370 break;
371
372 case SCR7:
373 for (auto y : xrange(lines)) {
374 auto* line = &output[512 * y];
375 for (auto x : xrange(128)) {
376 auto value0 = vram[addr + 0x00000];
377 auto value1 = vram[addr + 0x10000];
378 line[4 * x + 0] = palette16[(value0 >> 4) & 0x0f];
379 line[4 * x + 1] = palette16[(value0 >> 0) & 0x0f];
380 line[4 * x + 2] = palette16[(value1 >> 4) & 0x0f];
381 line[4 * x + 3] = palette16[(value1 >> 0) & 0x0f];
382 ++addr;
383 }
384 }
385 break;
386
387 case SCR8: {
388 auto toColor = [](uint8_t value) {
389 int r = (value & 0x1c) >> 2;
390 int g = (value & 0xe0) >> 5;
391 int b = (value & 0x03) >> 0;
392 int rr = (r << 5) | (r << 2) | (r >> 1);
393 int gg = (g << 5) | (g << 2) | (g >> 1);
394 int bb = (b << 6) | (b << 4) | (b << 2) | (b << 0);
395 int aa = 255;
396 return (rr << 0) | (gg << 8) | (bb << 16) | (aa << 24);
397 };
398 for (auto y : xrange(lines)) {
399 auto* line = &output[256 * y];
400 for (auto x : xrange(128)) {
401 line[2 * x + 0] = toColor(vram[addr + 0x00000]);
402 line[2 * x + 1] = toColor(vram[addr + 0x10000]);
403 ++addr;
404 }
405 }
406 break;
407 }
408
409 case SCR11:
410 for (auto y : xrange(lines)) {
411 auto* line = &output[256 * y];
412 for (auto x : xrange(64)) {
413 std::array<unsigned, 4> p = {
414 vram[addr + 0 + 0x00000],
415 vram[addr + 0 + 0x10000],
416 vram[addr + 1 + 0x00000],
417 vram[addr + 1 + 0x10000],
418 };
419 addr += 2;
420 int j = narrow<int>((p[2] & 7) + ((p[3] & 3) << 3)) - narrow<int>((p[3] & 4) << 3);
421 int k = narrow<int>((p[0] & 7) + ((p[1] & 3) << 3)) - narrow<int>((p[1] & 4) << 3);
422 for (auto n : xrange(4)) {
423 uint32_t pix;
424 if (p[n] & 0x08) {
425 pix = palette16[p[n] >> 4];
426 } else {
427 int Y = narrow<int>(p[n] >> 3);
428 auto [r, g, b] = yjk2rgb(Y, j, k);
429 int rr = (r << 3) | (r >> 2);
430 int gg = (g << 3) | (g >> 2);
431 int bb = (b << 3) | (b >> 2);
432 int aa = 255;
433 pix = (rr << 0) | (gg << 8) | (bb << 16) | (aa << 24);
434 }
435 line[4 * x + n] = pix;
436 }
437 }
438 }
439 break;
440
441 case SCR12:
442 for (auto y : xrange(lines)) {
443 auto* line = &output[256 * y];
444 for (auto x : xrange(64)) {
445 std::array<unsigned, 4> p = {
446 vram[addr + 0 + 0x00000],
447 vram[addr + 0 + 0x10000],
448 vram[addr + 1 + 0x00000],
449 vram[addr + 1 + 0x10000],
450 };
451 addr += 2;
452 int j = narrow<int>((p[2] & 7) + ((p[3] & 3) << 3)) - narrow<int>((p[3] & 4) << 3);
453 int k = narrow<int>((p[0] & 7) + ((p[1] & 3) << 3)) - narrow<int>((p[1] & 4) << 3);
454 for (auto n : xrange(4)) {
455 int Y = narrow<int>(p[n] >> 3);
456 auto [r, g, b] = yjk2rgb(Y, j, k);
457 int rr = (r << 3) | (r >> 2);
458 int gg = (g << 3) | (g >> 2);
459 int bb = (b << 3) | (b >> 2);
460 int aa = 255;
461 line[4 * x + n] = (rr << 0) | (gg << 8) | (bb << 16) | (aa << 24);
462 }
463 }
464 }
465 break;
466
467 case OTHER:
468 for (auto y : xrange(lines)) {
469 auto* line = &output[256 * y];
470 for (auto x : xrange(256)) {
471 line[x] = getColor(imColor::GRAY);
472 }
473 }
474 break;
475 }
476}
477
478} // namespace openmsx
int g
Represents a VDP display mode.
static constexpr uint8_t GRAPHIC4
static constexpr uint8_t GRAPHIC5
static constexpr uint8_t GRAPHIC7
static constexpr byte YAE
Encoding of YAE flag.
static constexpr uint8_t GRAPHIC6
static constexpr byte YJK
Encoding of YJK flag.
void loadLine(std::string_view name, zstring_view value) override
void paint(MSXMotherBoard *motherBoard) override
void save(ImGuiTextBuffer &buf) override
std::unique_ptr< ImGuiPalette > palette
ImGuiManager & manager
Definition ImGuiPart.hh:30
MSXDevice * findDevice(std::string_view name)
Find a MSXDevice by name.
This class manages the lifetime of a block of memory.
Definition MemBuffer.hh:29
const T * data() const
Returns pointer to the start of the memory buffer.
Definition MemBuffer.hh:81
Unified implementation of MSX Video Display Processors (VDPs).
Definition VDP.hh:67
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
void StrCat(Ts &&...ts)
Definition ImGuiUtils.hh:43
void TextUnformatted(const std::string &str)
Definition ImGuiUtils.hh:24
vecN< 2, float > vec2
Definition gl_vec.hh:178
void Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:63
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)
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
constexpr auto xrange(T e)
Definition xrange.hh:132