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