openMSX
ImGuiSpriteViewer.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 "MSXMotherBoard.hh"
9#include "SpriteChecker.hh"
10#include "SpriteConverter.hh"
11#include "VDP.hh"
12#include "VDPVRAM.hh"
13
14#include "xrange.hh"
15
16#include <imgui.h>
17
18#include <cassert>
19#include <cstdint>
20#include <span>
21
22namespace openmsx {
23
24using namespace std::literals;
25
27 : ImGuiPart(manager_)
28 , title("Sprite viewer")
29{
30 if (index) {
31 strAppend(title, " (", index + 1, ')');
32 }
33}
34
35void ImGuiSpriteViewer::save(ImGuiTextBuffer& buf)
36{
37 savePersistent(buf, *this, persistentElements);
38}
39
40void ImGuiSpriteViewer::loadLine(std::string_view name, zstring_view value)
41{
42 loadOnePersistent(name, value, *this, persistentElements);
43}
44
45static void draw8(uint8_t pattern, uint32_t fgCol, uint32_t bgCol, std::span<uint32_t, 8> out)
46{
47 out[0] = (pattern & 0x80) ? fgCol : bgCol;
48 out[1] = (pattern & 0x40) ? fgCol : bgCol;
49 out[2] = (pattern & 0x20) ? fgCol : bgCol;
50 out[3] = (pattern & 0x10) ? fgCol : bgCol;
51 out[4] = (pattern & 0x08) ? fgCol : bgCol;
52 out[5] = (pattern & 0x04) ? fgCol : bgCol;
53 out[6] = (pattern & 0x02) ? fgCol : bgCol;
54 out[7] = (pattern & 0x01) ? fgCol : bgCol;
55}
56
57static void renderPatterns8(const VramTable& pat, std::span<uint32_t> output)
58{
59 auto zero = getColor(imColor::TRANSPARENT);
60 auto one = getColor(imColor::TEXT);
61 for (auto row : xrange(8)) {
62 for (auto column : xrange(32)) {
63 auto patNum = 32 * row + column;
64 auto offset = 8 * patNum;
65 for (auto y : xrange(8)) {
66 auto pattern = pat[offset + y];
67 auto out = subspan<8>(output, (8 * row + y) * 256 + 8 * column);
68 draw8(pattern, one, zero, out);
69 }
70 }
71 }
72}
73
74static void renderPatterns16(const VramTable& pat, std::span<uint32_t> output)
75{
76 auto zero = getColor(imColor::TRANSPARENT);
77 auto one = getColor(imColor::TEXT);
78 for (auto row : xrange(4)) {
79 for (auto column : xrange(16)) {
80 auto patNum = 4 * (16 * row + column);
81 auto offset = 8 * patNum;
82 for (auto y : xrange(16)) {
83 auto patternA = pat[offset + y + 0];
84 auto patternB = pat[offset + y + 16];
85 auto outA = subspan<8>(output, (16 * row + y) * 256 + 16 * column + 0);
86 auto outB = subspan<8>(output, (16 * row + y) * 256 + 16 * column + 8);
87 draw8(patternA, one, zero, outA);
88 draw8(patternB, one, zero, outB);
89 }
90 }
91 }
92}
93
94[[nodiscard]] static int getSpriteAttrAddr(int sprite, int mode)
95{
96 return (mode == 2 ? 512 : 0) + 4 * sprite;
97}
98[[nodiscard]] static int getSpriteColorAddr(int sprite, int mode)
99{
100 assert(mode == 2); (void)mode;
101 return 16 * sprite;
102}
103
104static void renderSpriteAttrib(const VramTable& att, int sprite, int mode, int size, int transparent,
105 float zoom, std::span<uint32_t, 16> palette, ImTextureID patternTex)
106{
107 int addr = getSpriteAttrAddr(sprite, mode);
108 int pattern = att[addr + 2];
109 if (size == 16) pattern /= 4;
110
111 int patternsPerRow = 256 / size;
112 int cc = pattern % patternsPerRow;
113 int rr = pattern / patternsPerRow;
114 float u1 = float(cc + 0) / float(patternsPerRow);
115 float u2 = float(cc + 1) / float(patternsPerRow);
116 float v1 = float(size * rr) * (1.0f / 64.0f);
117
118 auto getColor = [&](int color) -> gl::vec4 {
119 if (color == 0 && transparent) return {};
120 return ImGui::ColorConvertU32ToFloat4(palette[color]);
121 };
122
123 if (mode == 1) {
124 auto attrib = att[addr + 3];
125 auto color = attrib & 0x0f;
126 float v2 = float(size * (rr + 1)) * (1.0f / 64.0f);
127 ImGui::Image(patternTex, zoom * gl::vec2{float(size)}, {u1, v1}, {u2, v2}, getColor(color));
128 } else {
129 int colorBase = getSpriteColorAddr(sprite, mode);
130 gl::vec2 pos = ImGui::GetCursorPos();
131 for (auto y : xrange(size)) {
132 auto attrib = att[colorBase + y];
133 auto color = attrib & 0x0f;
134 ImGui::SetCursorPos({pos.x, pos.y + zoom * float(y)});
135 float v2 = v1 + (1.0f / 64.0f);
136 ImGui::Image(patternTex, zoom * gl::vec2{float(size), 1.0f}, {u1, v1}, {u2, v2}, getColor(color));
137 v1 = v2;
138 }
139 }
140}
141
143{
144 if (!show || !motherBoard) return;
145
146 ImGui::SetNextWindowSize({748, 1010}, ImGuiCond_FirstUseEver);
147 im::Window(title.c_str(), &show, [&]{
148 auto* vdp = dynamic_cast<VDP*>(motherBoard->findDevice("VDP")); // TODO name based OK?
149 if (!vdp) return;
150 const auto& vram = vdp->getVRAM().getData();
151
152 auto modeToStr = [](int mode) {
153 if (mode == 0) return "no sprites";
154 if (mode == 1) return "1";
155 if (mode == 2) return "2";
156 assert(false); return "ERROR";
157 };
158 auto sizeToStr = [](int size) {
159 if (size == 8) return "8 x 8";
160 if (size == 16) return "16 x 16";
161 assert(false); return "ERROR";
162 };
163 auto yesNo = [](int x) {
164 if (x == 0) return "no";
165 if (x == 1) return "yes";
166 assert(false); return "ERROR";
167 };
168 auto attMult = [](int mode) { return 1 << ((mode == 2) ? 10 : 7); };
169
170 bool isMSX1 = vdp->isMSX1VDP();
171 auto displayMode = vdp->getDisplayMode();
172 bool planar = displayMode.isPlanar();
173 int vdpMode = displayMode.getSpriteMode(isMSX1);
174 int vdpVerticalScroll = vdp->getVerticalScroll();
175 int vdpLines = vdp->getNumberOfLines();
176
177 int vdpSize = vdp->getSpriteSize();
178 int vdpMag = vdp->isSpriteMag();
179 int vdpTransparent = vdp->getTransparency();
180
181 int vdpPatBase = vdp->getSpritePatternTableBase();
182 int vdpAttBase = vdp->getSpriteAttributeTableBase() & ~(attMult(vdpMode) - 1);
183
184 auto vramSize = std::min(vdp->getVRAM().getSize(), 0x20000u); // max 128kB
185
186 std::array<uint32_t, 16> palette;
187 auto msxPalette = manager.palette->getPalette(vdp);
188 ranges::transform(msxPalette, palette.data(),
189 [](uint16_t msx) { return ImGuiPalette::toRGBA(msx); });
190 // TODO? if (color0 < 16) palette[0] = palette[color0];
191
192 bool manMode = overrideAll || overrideMode;
193 bool manSize = overrideAll || overrideSize;
194 bool manMag = overrideAll || overrideMag;
195 bool manTrans = overrideAll || overrideTrans;
196 bool manPat = overrideAll || overridePat;
197 bool manAtt = overrideAll || overrideAtt;
198 bool manScroll = overrideAll || overrideScroll;
199 bool manLines = overrideAll || overrideLines;
200
201 im::TreeNode("Settings", ImGuiTreeNodeFlags_DefaultOpen, [&]{
202 im::Group([&]{
203 ImGui::TextUnformatted("VDP settings");
204 im::Disabled(manMode, [&]{
205 ImGui::AlignTextToFramePadding();
206 ImGui::StrCat("Sprite mode: ", modeToStr(vdpMode));
207 });
208 im::Disabled(manSize, [&]{
209 ImGui::AlignTextToFramePadding();
210 ImGui::StrCat("Sprite size: ", sizeToStr(vdpSize));
211 });
212 im::Disabled(manMag, [&]{
213 ImGui::AlignTextToFramePadding();
214 ImGui::StrCat("Sprites magnified: ", yesNo(vdpMag));
215 });
216 im::Disabled(manTrans, [&]{
217 ImGui::AlignTextToFramePadding();
218 ImGui::StrCat("Color 0 transparent: ", yesNo(vdpTransparent));
219 });
220 im::Disabled(manPat, [&]{
221 ImGui::AlignTextToFramePadding();
222 ImGui::StrCat("Pattern table: 0x", hex_string<5>(vdpPatBase));
223 });
224 im::Disabled(manAtt, [&]{
225 ImGui::AlignTextToFramePadding();
226 ImGui::StrCat("Attribute table: 0x", hex_string<5>(vdpAttBase | (vdpMode == 2 ? 512 : 0)));
227 });
228 im::Disabled(manScroll, [&]{
229 ImGui::AlignTextToFramePadding();
230 ImGui::StrCat("Vertical scroll: ", vdpVerticalScroll);
231 });
232 im::Disabled(manLines, [&]{
233 ImGui::AlignTextToFramePadding();
234 ImGui::StrCat("Visible lines: ", (vdpLines == 192) ? "192" : "212");
235 });
236 });
237 ImGui::SameLine();
238 im::Group([&]{
239 ImGui::Checkbox("Manual override", &overrideAll);
240 im::Group([&]{
241 im::Disabled(overrideAll, [&]{
242 ImGui::Checkbox("##o-mode", overrideAll ? &overrideAll : &overrideMode);
243 ImGui::Checkbox("##o-size", overrideAll ? &overrideAll : &overrideSize);
244 ImGui::Checkbox("##o-mag", overrideAll ? &overrideAll : &overrideMag);
245 ImGui::Checkbox("##o-trans", overrideAll ? &overrideAll : &overrideTrans);
246 ImGui::Checkbox("##o-pat", overrideAll ? &overrideAll : &overridePat);
247 ImGui::Checkbox("##o-att", overrideAll ? &overrideAll : &overrideAtt);
248 ImGui::Checkbox("##o-scroll", overrideAll ? &overrideAll : &overrideScroll);
249 ImGui::Checkbox("##o-lines", overrideAll ? &overrideAll : &overrideLines);
250 });
251 });
252 ImGui::SameLine();
253 im::Group([&]{
254 im::ItemWidth(ImGui::GetFontSize() * 9.0f, [&]{
255 im::Disabled(!manMode, [&]{
256 if (manMode && isMSX1) manualMode = 1;
257 im::Combo("##mode", modeToStr(manualMode), [&]{
258 if (ImGui::Selectable("1")) manualMode = 1;
259 if (!isMSX1 && ImGui::Selectable("2")) manualMode = 2;
260 });
261 });
262 im::Disabled(!manSize, [&]{
263 im::Combo("##size", sizeToStr(manualSize), [&]{
264 if (ImGui::Selectable(" 8 x 8")) manualSize = 8;
265 if (ImGui::Selectable("16 x 16")) manualSize = 16;
266 });
267 });
268 im::Disabled(!manMag, [&]{
269 im::Combo("##mag", yesNo(manualMag), [&]{
270 if (ImGui::Selectable("no")) manualMag = 0;
271 if (ImGui::Selectable("yes")) manualMag = 1;
272 });
273 });
274 im::Disabled(!manTrans, [&]{
275 im::Combo("##trans", yesNo(manualTransparent), [&]{
276 if (ImGui::Selectable("no")) manualTransparent = 0;
277 if (ImGui::Selectable("yes")) manualTransparent = 1;
278 });
279 });
280 im::Disabled(!manPat, [&]{
281 comboHexSequence<5>("##pat", &manualPatBase, 8 * 256, vramSize, 0);
282 });
283 im::Disabled(!manAtt, [&]{
284 comboHexSequence<5>("##att", &manualAttBase, attMult(manualMode), vramSize, manualMode == 2 ? 512 : 0);
285 });
286 im::Disabled(!manScroll, [&]{
287 ImGui::InputInt("##verticalScroll", &manualVerticalScroll);
288 manualVerticalScroll &= 0xff;
289 });
290 im::Disabled(!manLines, [&]{
291 ImGui::Combo("##lines", &manualLines, "192\000212\000256\000");
292 });
293 });
294 });
295 });
296 ImGui::SameLine();
297 ImGui::Dummy(ImVec2(25, 1));
298 ImGui::SameLine();
299 im::Group([&]{
300 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10.0f);
301 ImGui::Combo("Palette", &manager.palette->whichPalette, "VDP\000Custom\000Fixed\000");
302 if (ImGui::Button("Open palette editor")) {
303 manager.palette->window.raise();
304 }
305 ImGui::Separator();
306 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.0f);
307 ImGui::Combo("Zoom", &zoom, "1x\0002x\0003x\0004x\0005x\0006x\0007x\0008x\000");
308 ImGui::Checkbox("grid", &grid);
309 ImGui::SameLine();
310 im::Disabled(!grid, [&]{
311 ImGui::ColorEdit4("Grid color", gridColor.data(),
312 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar);
313 });
314
315 ImGui::TextUnformatted("Checkerboard:"sv);
316 simpleToolTip("Used as background in 'Sprite attribute' and 'Rendered sprites' view");
317 ImGui::SameLine();
318 ImGui::ColorEdit4("checkerboard color1", checkerBoardColor1.data(), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel);
319 ImGui::SameLine();
320 ImGui::ColorEdit4("checkerboard color2", checkerBoardColor2.data(), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel);
321 im::Indent([&]{
322 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6.0f);
323 ImGui::InputInt("size", &checkerBoardSize);
324 });
325 });
326 });
327 ImGui::Separator();
328
329 int mode = manMode ? manualMode : vdpMode;
330 int size = manSize ? manualSize : vdpSize;
331 int mag = manMag ? manualMag : vdpMag;
332 int verticalScroll = manScroll ? manualVerticalScroll : vdpVerticalScroll;
333 int lines = manLines ? (manualLines == 0 ? 192 :
334 manualLines == 1 ? 212 :
335 256)
336 : vdpLines;
337 int transparent = manTrans ? manualTransparent : vdpTransparent;
338
339 VramTable patTable(vram, planar);
340 unsigned patReg = (manPat ? (manualPatBase | ((8 * 256) - 1)) : vdp->getSpritePatternTableBase()) >> 11;
341 patTable.setRegister(patReg, 11);
342 patTable.setIndexSize(11);
343
344 VramTable attTable(vram, planar);
345 unsigned attReg = (manAtt ? (manualAttBase | (attMult(manualMode) - 1)) : vdp->getSpriteAttributeTableBase()) >> 7;
346 attTable.setRegister(attReg, 7);
347 attTable.setIndexSize((mode == 2) ? 10 : 7);
348
349 // create pattern texture
350 if (!patternTex.get()) {
351 patternTex = gl::Texture(false, false); // no interpolation, no wrapping
352 }
353 patternTex.bind();
354 std::array<uint32_t, 256 * 64> pixels;
355 if (mode != 0) {
356 if (size == 8) {
357 renderPatterns8 (patTable, pixels);
358 } else {
359 renderPatterns16(patTable, pixels);
360 }
361 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 64, 0,
362 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
363 } else {
364 pixels[0] = getColor(imColor::GRAY);
365 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0,
366 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
367 }
368
369 // create grid texture
370 int zm = 2 * (1 + zoom);
371 auto gColor = ImGui::ColorConvertFloat4ToU32(gridColor);
372 if (grid) {
373 size_t gridSize = size * zm;
374 for (auto y : xrange(gridSize)) {
375 auto* line = &pixels[y * gridSize];
376 for (auto x : xrange(gridSize)) {
377 line[x] = (x == 0 || y == 0) ? gColor : 0;
378 }
379 }
380 if (!gridTex.get()) {
381 gridTex = gl::Texture(false, true); // no interpolation, with wrapping
382 }
383 gridTex.bind();
384 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
385 narrow<int>(gridSize), narrow<int>(gridSize),
386 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
387 }
388
389 // create checker board texture
390 if (checkerBoardSize) {
391 pixels[0] = pixels[3] = ImGui::ColorConvertFloat4ToU32(checkerBoardColor1);
392 pixels[1] = pixels[2] = ImGui::ColorConvertFloat4ToU32(checkerBoardColor2);
393 if (!checkerTex.get()) {
394 checkerTex = gl::Texture(false, true); // no interpolation, with wrapping
395 }
396 checkerTex.bind();
397 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0,
398 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
399 }
400
401 im::TreeNode("Sprite patterns", ImGuiTreeNodeFlags_DefaultOpen, [&]{
402 auto fullSize = gl::vec2(256, 64) * float(zm);
403 im::Child("##pattern", {0, fullSize.y}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
404 auto pos1 = ImGui::GetCursorPos();
405 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
406 ImGui::Image(patternTex.getImGui(), fullSize);
407 bool hovered = ImGui::IsItemHovered() && (mode != 0);
408 ImGui::SameLine();
409 im::Group([&]{
410 gl::vec2 zoomPatSize{float(size * zm)};
411 if (hovered) {
412 auto gridPos = trunc((gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / zoomPatSize);
413 auto pattern = (size == 16) ? ((16 * gridPos.y) + gridPos.x) * 4
414 : ((32 * gridPos.y) + gridPos.x) * 1;
415 ImGui::StrCat("pattern: ", pattern);
416 auto recipPatTex = recip((size == 16) ? gl::vec2{16, 4} : gl::vec2{32, 8});
417 auto uv1 = gl::vec2(gridPos) * recipPatTex;
418 auto uv2 = uv1 + recipPatTex;
419 auto pos2 = ImGui::GetCursorPos();
420 int z = (size == 16) ? 3 : 6;
421 ImGui::Image(patternTex.getImGui(), float(z) * zoomPatSize, uv1, uv2);
422 if (grid) {
423 if (!zoomGridTex.get()) {
424 zoomGridTex = gl::Texture(false, true); // no interpolation, with wrapping
425 }
426 int s = z * zm;
427 for (auto y : xrange(s)) {
428 auto* line = &pixels[y * s];
429 for (auto x : xrange(s)) {
430 line[x] = (x == 0 || y == 0) ? gColor : 0;
431 }
432 }
433 zoomGridTex.bind();
434 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, s, s, 0,
435 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
436 ImGui::SetCursorPos(pos2);
437 ImGui::Image(zoomGridTex.getImGui(),
438 float(z) * zoomPatSize, {}, gl::vec2{float(size)});
439 }
440 } else {
441 ImGui::Dummy(zoomPatSize);
442 }
443 });
444 if (grid) {
445 ImGui::SetCursorPos(pos1);
446 ImGui::Image(gridTex.getImGui(), fullSize,
447 {}, (size == 8) ? gl::vec2{32.0f, 8.0f} : gl::vec2{16.0f, 4.0f});
448 }
449 });
450 });
451 ImGui::Separator();
452
453 im::TreeNode("Sprite attributes", ImGuiTreeNodeFlags_DefaultOpen, [&]{
454 auto zoomSize = float(zm * size);
455 auto fullSize = zoomSize * gl::vec2(8, 4);
456 im::Child("##attrib", {0, fullSize.y}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
457 if (mode == 0) {
458 ImGui::TextUnformatted("No sprites in this screen mode"sv);
459 } else {
460 gl::vec2 topLeft = ImGui::GetCursorPos();
461 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
462 if (checkerBoardSize) {
463 ImGui::SetCursorPos(topLeft);
464 ImGui::Image(checkerTex.getImGui(), fullSize,
465 {}, fullSize / (4.0f * float(checkerBoardSize)));
466 }
467 for (auto row : xrange(4)) {
468 for (auto column : xrange(8)) {
469 int sprite = 8 * row + column;
470 ImGui::SetCursorPos(topLeft + zoomSize * gl::vec2(float(column), float(row)));
471 renderSpriteAttrib(attTable, sprite, mode, size, transparent,
472 float(zm), palette, patternTex.getImGui());
473 }
474 }
475 ImGui::SetCursorPos(topLeft);
476 if (grid) {
477 ImGui::Image(gridTex.getImGui(), fullSize,
478 {}, gl::vec2{8, 4});
479 } else {
480 ImGui::Dummy(fullSize);
481 }
482 bool hovered = ImGui::IsItemHovered();
483 if (hovered) {
484 gl::vec2 zoomPatSize{float(size * zm)};
485 auto gridPos = trunc((gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / zoomPatSize);
486 auto sprite = 8 * gridPos.y + gridPos.x;
487
488 ImGui::SameLine();
489 im::Group([&]{
490 ImGui::StrCat("sprite: ", sprite);
491 auto pos = ImGui::GetCursorPos();
492 if (checkerBoardSize) {
493 ImGui::Image(checkerTex.getImGui(), 3.0f * zoomPatSize,
494 {}, zoomPatSize / (4.0f * float(checkerBoardSize)));
495 }
496 ImGui::SetCursorPos(pos);
497 renderSpriteAttrib(attTable, sprite, mode, size, transparent,
498 float(3 * zm), palette, patternTex.getImGui());
499 });
500 ImGui::SameLine();
501 im::Group([&]{
502 int addr = getSpriteAttrAddr(sprite, mode);
503 ImGui::StrCat("x: ", attTable[addr + 1],
504 " y: ", attTable[addr + 0]);
505 ImGui::StrCat("pattern: ", attTable[addr + 2]);
506 if (mode == 1) {
507 auto c = attTable[addr + 3];
508 ImGui::StrCat("color: ", c & 15, (c & 80 ? " (EC)" : ""));
509 } else {
510 int colorBase = getSpriteColorAddr(sprite, mode);
511 im::StyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1), [&]{ // Tighten spacing
512 ImGui::TextUnformatted("Colors per line (hex):"sv);
513 for (auto y : xrange(4)) {
514 for (auto x : xrange(4)) {
515 auto line = 4 * y + x;
516 auto a = attTable[colorBase + line];
517 ImGui::StrCat(hex_string<1>(line), ": ",
518 hex_string<1>(a & 15),
519 (a & 0xe0 ? '*' : ' '),
520 ' ');
521 if (x != 3) ImGui::SameLine();
522 }
523 }
524 });
525 }
526 });
527 }
528 }
529 });
530 });
531 ImGui::Separator();
532
533 im::TreeNode("Rendered sprites", ImGuiTreeNodeFlags_DefaultOpen, [&]{
534 struct SpriteBox {
535 int x, y, w, h; // box
536 uint8_t sprite;
537 uint8_t vramX, vramY;
538 uint8_t pattern;
539 };
540
541 std::array<uint8_t, 256> spriteCount = {}; // zero initialize
542 std::array<std::array<SpriteChecker::SpriteInfo, 32 + 1>, 256> spriteBuffer; // uninitialized
543 std::array<SpriteBox, 32> spriteBoxes; // uninitialized
544
545 uint8_t spriteLimit = (mode == 1) ? 4 : 8;
546 uint8_t stopY = (mode == 1) ? 208 : 216;
547 uint8_t patMask = (size == 8) ? 0xff : 0xfc;
548 int magFactor = mag ? 2 : 1;
549 auto magSize = magFactor * size;
550
551 uint8_t spriteCnt = 0;
552 for (; spriteCnt < 32; ++spriteCnt) {
553 int addr = getSpriteAttrAddr(spriteCnt, mode);
554 uint8_t originalY = attTable[addr + 0];
555 if (enableStopY && (originalY == stopY)) break;
556 auto y = uint8_t(originalY + 1 - verticalScroll);
557 int initialY = y;
558
559 uint8_t x = attTable[addr + 1];
560 uint8_t pat = attTable[addr + 2] & patMask;
561 uint8_t att1 = attTable[addr + 3]; // only mode 1
562
563 bool anyEC = false;
564 bool anyNonEC = false;
565 for (int spriteY : xrange(size)) { // each line in the sprite
566 auto attr = [&]{
567 if (mode != 2) return att1;
568 int colorBase = getSpriteColorAddr(spriteCnt, mode);
569 return attTable[colorBase + spriteY];
570 }();
571
572 bool EC = attr & 0x80;
573 (EC ? anyEC : anyNonEC) = true;
574 int xx = EC ? x - 32 : x;
575
576 auto pattern = [&]{
577 uint8_t p0 = patTable[8 * pat + spriteY + 0];
578 SpriteChecker::SpritePattern result = p0 << 24;
579 if (size == 8) return result;
580 uint8_t p1 = patTable[8 * pat + spriteY + 16];
581 return result | (p1 << 16);
582 }();
583 if (mag) pattern = SpriteChecker::doublePattern(pattern);
584
585 for ([[maybe_unused]] int mm : xrange(magFactor)) {
586 auto count = spriteCount[y];
587 if (!enableLimitPerLine || (count < spriteLimit)) {
588 auto& spr = spriteBuffer[y][count];
589 spr.pattern = pattern;
590 spr.x = narrow<int16_t>(xx);
591 spr.colorAttrib = attr;
592
593 spriteCount[y] = count + 1;
594 spriteBuffer[y][count + 1].colorAttrib = 0; // sentinel (mode 2)
595 }
596 ++y; // wraps 256->0
597 }
598 }
599 assert(anyEC || anyNonEC);
600 spriteBoxes[spriteCnt] = SpriteBox{
601 anyEC ? x - 32 : x,
602 initialY,
603 magSize + (anyEC && anyNonEC ? 32 : 0),
604 magSize,
605 spriteCnt, x, originalY, pat};
606 }
607
608 std::array<uint32_t, 256 * 256> screen; // TODO screen6 striped colors
609 memset(screen.data(), 0, sizeof(uint32_t) * 256 * lines); // transparent
610 for (auto line : xrange(lines)) {
611 auto count = spriteCount[line];
612 if (count == 0) continue;
613 auto lineBuf = subspan<256>(screen, 256 * line);
614
615 if (mode == 1) {
616 auto visibleSprites = subspan(spriteBuffer[line], 0, count);
617 for (const auto& spr : view::reverse(visibleSprites)) {
618 uint8_t colIdx = spr.colorAttrib & 0x0f;
619 if (colIdx == 0 && transparent) continue;
620 auto color = palette[colIdx];
621
622 auto pattern = spr.pattern;
623 int x = spr.x;
624 if (!SpriteConverter::clipPattern(x, pattern, 0, 256)) continue;
625
626 while (pattern) {
627 if (pattern & 0x8000'0000) {
628 lineBuf[x] = color;
629 }
630 pattern <<= 1;
631 ++x;
632 }
633 }
634 } else if (mode == 2) {
635 auto visibleSprites = subspan(spriteBuffer[line], 0, count + 1); // +1 for sentinel
636
637 // see SpriteConverter
638 int first = 0;
639 while (true /*sentinel*/) {
640 if ((visibleSprites[first].colorAttrib & 0x40) == 0) [[likely]] {
641 break;
642 }
643 ++first;
644 }
645 for (int i = narrow<int>(count - 1); i >= first; --i) {
646 const auto& spr = visibleSprites[i];
647 uint8_t c = spr.colorAttrib & 0x0F;
648 if (c == 0 && transparent) continue;
649
650 auto pattern = spr.pattern;
651 int x = spr.x;
652 if (!SpriteConverter::clipPattern(x, pattern, 0, 256)) continue;
653
654 while (pattern) {
655 if (pattern & 0x8000'0000) {
656 uint8_t color = c;
657 // Merge in any following CC=1 sprites.
658 for (int j = i + 1; /*sentinel*/; ++j) {
659 const auto& info2 = visibleSprites[j];
660 if (!(info2.colorAttrib & 0x40)) break;
661 unsigned shift2 = x - info2.x;
662 if ((shift2 < 32) &&
663 ((info2.pattern << shift2) & 0x8000'0000)) {
664 color |= info2.colorAttrib & 0x0F;
665 }
666 }
667 // TODO screen 6
668 // auto pixL = palette[color >> 2];
669 // auto pixR = palette[color & 3];
670 // lineBuf[x * 2 + 0] = pixL;
671 // lineBuf[x * 2 + 1] = pixR;
672 lineBuf[x] = palette[color];
673 }
674 ++x;
675 pattern <<= 1;
676 }
677 }
678 }
679 }
680 if (!renderTex.get()) {
681 renderTex = gl::Texture(false, true); // no interpolation, with wrapping
682 }
683 renderTex.bind();
684 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, lines, 0,
685 GL_RGBA, GL_UNSIGNED_BYTE, screen.data());
686
687 std::array<SpriteBox, 2 * 32> clippedBoxes;
688 int nrClippedBoxes = 0;
689 auto addClippedBox = [&](SpriteBox b) {
690 assert(nrClippedBoxes < 64);
691 clippedBoxes[nrClippedBoxes] = b;
692 ++nrClippedBoxes;
693 };
694 for (int sprite : xrange(spriteCnt)) {
695 const auto& b = spriteBoxes[sprite];
696 int x = b.x;
697 int y = b.y;
698 int w = b.w;
699 int h = b.h;
700 if (x < 0) {
701 w += x;
702 if (w <= 0) continue;
703 x = 0;
704 } else if (x + w > 256) {
705 w = 256 - x;
706 }
707
708 int yEnd = y + h;
709 if (yEnd < 256) {
710 addClippedBox(SpriteBox{x, y, w, h, b.sprite, b.vramX, b.vramY, b.pattern});
711 } else {
712 addClippedBox(SpriteBox{x, y, w, 256 - y, b.sprite, b.vramX, b.vramY, b.pattern});
713 addClippedBox(SpriteBox{x, 0, w, yEnd - 256, b.sprite, b.vramX, b.vramY, b.pattern});
714 }
715 }
716
717 auto fullSize = float(zm) * gl::vec2(256, float(lines));
718 im::Child("##screen", {0.0f, fullSize.y}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
719 auto* drawList = ImGui::GetWindowDrawList();
720 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
721 auto boxColor = ImGui::ColorConvertFloat4ToU32(boundingBoxColor);
722 auto drawBox = [&](int x, int y, int w, int h) {
723 gl::vec2 tl = scrnPos + gl::vec2{float(x), float(y)} * float(zm);
724 gl::vec2 br = tl + gl::vec2{float(w), float(h)} * float(zm);
725 drawList->AddRect(tl, br, boxColor);
726 };
727
728 gl::vec2 topLeft = ImGui::GetCursorPos();
729 if (checkerBoardSize) {
730 ImGui::Image(checkerTex.getImGui(), fullSize,
731 {}, fullSize / (4.0f * float(checkerBoardSize)));
732 }
733 ImGui::SetCursorPos(topLeft);
734 ImGui::Image(renderTex.getImGui(), fullSize);
735 bool hovered = ImGui::IsItemHovered();
736 auto hoverPos = trunc((gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / gl::vec2(float(zm)));
737 ImGui::SameLine();
738
739 im::Group([&]{
740 ImGui::Checkbox("Bounding box", &drawBoundingBox);
741 im::Indent([&]{
742 im::Disabled(!drawBoundingBox, [&]{
743 ImGui::ColorEdit4("color", boundingBoxColor.data(),
744 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaBar);
745 ImGui::RadioButton("On hovered sprites", &boundingBoxOnAll, 0);
746 ImGui::RadioButton("On all sprites", &boundingBoxOnAll, 1);
747
748 });
749 });
750 {
751 auto maxStr = tmpStrCat("Max ", spriteLimit, " sprites per line");
752 ImGui::Checkbox(maxStr.c_str(), &enableLimitPerLine);
753 }
754 {
755 auto stopStr = tmpStrCat("Stop at y=", stopY);
756 ImGui::Checkbox(stopStr.c_str(), &enableStopY);
757 }
758
759 if (hovered) {
760 ImGui::Separator();
761 auto [hx, hy] = hoverPos;
762 ImGui::Text("x=%d y=%d", hx, hy);
763 ImGui::Spacing();
764
765 for (int i : xrange(nrClippedBoxes)) {
766 const auto& b = clippedBoxes[i];
767 if ((b.x <= hx) && (hx < (b.x + b.w)) &&
768 (b.y <= hy) && (hy < (b.y + b.h))) {
769 if (drawBoundingBox && (boundingBoxOnAll == 0)) {
770 drawBox(b.x, b.y, b.w, b.h);
771 }
772 ImGui::Text("sprite=%d x=%d y=%d pat=%d", b.sprite, b.vramX, b.vramY, b.pattern);
773 }
774 }
775 }
776 });
777
778 if (drawBoundingBox && (boundingBoxOnAll == 1)) {
779 for (int i : xrange(nrClippedBoxes)) {
780 const auto& b = clippedBoxes[i];
781 drawBox(b.x, b.y, b.w, b.h);
782 }
783 }
784 });
785 });
786
787 });
788}
789
790} // namespace openmsx
Most basic/generic texture: only contains a texture ID.
Definition GLUtil.hh:40
void bind() const
Makes this texture the active GL texture.
Definition GLUtil.hh:88
GLuint get() const
Returns the underlying openGL handler id.
Definition GLUtil.hh:74
unsigned long long getImGui() const
Return as a 'void*' (needed for 'Dear ImGui').
Definition GLUtil.hh:78
std::unique_ptr< ImGuiPalette > palette
ImGuiManager & manager
Definition ImGuiPart.hh:30
void save(ImGuiTextBuffer &buf) override
void paint(MSXMotherBoard *motherBoard) override
ImGuiSpriteViewer(ImGuiManager &manager_, size_t index)
void loadLine(std::string_view name, zstring_view value) override
uint32_t SpritePattern
Bitmap of length 32 describing a sprite pattern.
static constexpr SpritePattern doublePattern(SpritePattern a)
static bool clipPattern(int &x, SpriteChecker::SpritePattern &pattern, int minX, int maxX)
void setRegister(unsigned value, unsigned extraLsbBits)
void setIndexSize(unsigned bits)
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 Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:63
void StyleVar(ImGuiStyleVar idx, float val, std::invocable<> auto next)
Definition ImGuiCpp.hh:190
bool TreeNode(const char *label, ImGuiTreeNodeFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:302
void Combo(const char *label, const char *preview_value, ImGuiComboFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:289
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 Indent(float indent_w, std::invocable<> auto next)
Definition ImGuiCpp.hh:224
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 simpleToolTip(std::string_view desc)
Definition ImGuiUtils.hh:79
void savePersistent(ImGuiTextBuffer &buf, C &c, const std::tuple< Elements... > &tup)
ImU32 getColor(imColor col)
auto transform(InputRange &&range, OutputIter out, UnaryOperation op)
Definition ranges.hh:279
size_t size(std::string_view utf8)
constexpr auto reverse(Range &&range)
Definition view.hh:514
constexpr auto subspan(Range &&range, size_t offset, size_t count=std::dynamic_extent)
Definition ranges.hh:481
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
void strAppend(std::string &result, Ts &&...ts)
Definition strCat.hh:752
constexpr auto xrange(T e)
Definition xrange.hh:132