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