144 if (!
show || !motherBoard)
return;
146 ImGui::SetNextWindowSize({748, 1010}, ImGuiCond_FirstUseEver);
148 auto* vdp = dynamic_cast<VDP*>(motherBoard->findDevice(
"VDP"));
150 const auto& vram = vdp->getVRAM().getData();
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";
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";
163 auto yesNo = [](
int x) {
164 if (x == 0)
return "no";
165 if (x == 1)
return "yes";
166 assert(
false);
return "ERROR";
168 auto attMult = [](
int mode) {
return 1 << ((mode == 2) ? 10 : 7); };
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();
177 int vdpSize = vdp->getSpriteSize();
178 int vdpMag = vdp->isSpriteMag();
179 int vdpTransparent = vdp->getTransparency();
181 int vdpPatBase = vdp->getSpritePatternTableBase();
182 int vdpAttBase = vdp->getSpriteAttributeTableBase() & ~(attMult(vdpMode) - 1);
184 auto vramSize = std::min(vdp->getVRAM().getSize(), 0x20000u);
186 std::array<uint32_t, 16> palette;
189 [](uint16_t msx) { return ImGuiPalette::toRGBA(msx); });
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;
201 im::TreeNode(
"Settings", ImGuiTreeNodeFlags_DefaultOpen, [&]{
205 ImGui::AlignTextToFramePadding();
209 ImGui::AlignTextToFramePadding();
213 ImGui::AlignTextToFramePadding();
217 ImGui::AlignTextToFramePadding();
218 ImGui::StrCat(
"Color 0 transparent: ", yesNo(vdpTransparent));
221 ImGui::AlignTextToFramePadding();
222 ImGui::StrCat(
"Pattern table: 0x", hex_string<5>(vdpPatBase));
225 ImGui::AlignTextToFramePadding();
226 ImGui::StrCat(
"Attribute table: 0x", hex_string<5>(vdpAttBase | (vdpMode == 2 ? 512 : 0)));
229 ImGui::AlignTextToFramePadding();
233 ImGui::AlignTextToFramePadding();
234 ImGui::StrCat(
"Visible lines: ", (vdpLines == 192) ?
"192" :
"212");
239 ImGui::Checkbox(
"Manual override", &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);
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;
263 im::Combo(
"##size", sizeToStr(manualSize), [&]{
264 if (ImGui::Selectable(
" 8 x 8")) manualSize = 8;
265 if (ImGui::Selectable(
"16 x 16")) manualSize = 16;
269 im::Combo(
"##mag", yesNo(manualMag), [&]{
270 if (ImGui::Selectable(
"no")) manualMag = 0;
271 if (ImGui::Selectable(
"yes")) manualMag = 1;
275 im::Combo(
"##trans", yesNo(manualTransparent), [&]{
276 if (ImGui::Selectable(
"no")) manualTransparent = 0;
277 if (ImGui::Selectable(
"yes")) manualTransparent = 1;
281 comboHexSequence<5>(
"##pat", &manualPatBase, 8 * 256, vramSize, 0);
284 comboHexSequence<5>(
"##att", &manualAttBase, attMult(manualMode), vramSize, manualMode == 2 ? 512 : 0);
287 ImGui::InputInt(
"##verticalScroll", &manualVerticalScroll);
288 manualVerticalScroll &= 0xff;
291 ImGui::Combo(
"##lines", &manualLines,
"192\000212\000256\000");
297 ImGui::Dummy(ImVec2(25, 1));
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")) {
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);
311 ImGui::ColorEdit4(
"Grid color", gridColor.data(),
312 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar);
316 simpleToolTip(
"Used as background in 'Sprite attribute' and 'Rendered sprites' view");
318 ImGui::ColorEdit4(
"checkerboard color1", checkerBoardColor1.data(), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel);
320 ImGui::ColorEdit4(
"checkerboard color2", checkerBoardColor2.data(), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel);
322 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6.0f);
323 ImGui::InputInt(
"size", &checkerBoardSize);
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 :
337 int transparent = manTrans ? manualTransparent : vdpTransparent;
340 unsigned patReg = (manPat ? (manualPatBase | ((8 * 256) - 1)) : vdp->getSpritePatternTableBase()) >> 11;
345 unsigned attReg = (manAtt ? (manualAttBase | (attMult(manualMode) - 1)) : vdp->getSpriteAttributeTableBase()) >> 7;
350 if (!patternTex.get()) {
354 std::array<uint32_t, 256 * 64> pixels;
357 renderPatterns8 (patTable, pixels);
359 renderPatterns16(patTable, pixels);
361 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 64, 0,
362 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
365 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0,
366 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
370 int zm = 2 * (1 + zoom);
371 auto gColor = ImGui::ColorConvertFloat4ToU32(gridColor);
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;
380 if (!gridTex.
get()) {
384 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
385 narrow<int>(gridSize), narrow<int>(gridSize),
386 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
390 if (checkerBoardSize) {
391 pixels[0] = pixels[3] = ImGui::ColorConvertFloat4ToU32(checkerBoardColor1);
392 pixels[1] = pixels[2] = ImGui::ColorConvertFloat4ToU32(checkerBoardColor2);
393 if (!checkerTex.
get()) {
397 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0,
398 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
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);
410 gl::vec2 zoomPatSize{float(size * zm)};
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;
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);
423 if (!zoomGridTex.
get()) {
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;
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)});
441 ImGui::Dummy(zoomPatSize);
445 ImGui::SetCursorPos(pos1);
446 ImGui::Image(gridTex.
getImGui(), fullSize,
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, [&]{
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)));
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());
475 ImGui::SetCursorPos(topLeft);
477 ImGui::Image(gridTex.
getImGui(), fullSize,
480 ImGui::Dummy(fullSize);
482 bool hovered = ImGui::IsItemHovered();
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;
491 auto pos = ImGui::GetCursorPos();
492 if (checkerBoardSize) {
493 ImGui::Image(checkerTex.
getImGui(), 3.0f * zoomPatSize,
494 {}, zoomPatSize / (4.0f *
float(checkerBoardSize)));
496 ImGui::SetCursorPos(pos);
497 renderSpriteAttrib(attTable, sprite, mode, size, transparent,
498 float(3 * zm), palette, patternTex.getImGui());
502 int addr = getSpriteAttrAddr(sprite, mode);
504 " y: ", attTable[addr + 0]);
507 auto c = attTable[addr + 3];
510 int colorBase = getSpriteColorAddr(sprite, mode);
511 im::StyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1), [&]{
513 for (
auto y :
xrange(4)) {
514 for (
auto x :
xrange(4)) {
515 auto line = 4 * y + x;
516 auto a = attTable[colorBase + line];
518 hex_string<1>(a & 15),
519 (a & 0xe0 ?
'*' :
' '),
521 if (x != 3) ImGui::SameLine();
533 im::TreeNode(
"Rendered sprites", ImGuiTreeNodeFlags_DefaultOpen, [&]{
537 uint8_t vramX, vramY;
541 std::array<uint8_t, 256> spriteCount = {};
542 std::array<std::array<SpriteChecker::SpriteInfo, 32 + 1>, 256> spriteBuffer;
543 std::array<SpriteBox, 32> spriteBoxes;
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;
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);
559 uint8_t x = attTable[addr + 1];
560 uint8_t pat = attTable[addr + 2] & patMask;
561 uint8_t att1 = attTable[addr + 3];
564 bool anyNonEC =
false;
565 for (
int spriteY :
xrange(size)) {
567 if (mode != 2)
return att1;
568 int colorBase = getSpriteColorAddr(spriteCnt, mode);
569 return attTable[colorBase + spriteY];
572 bool EC = attr & 0x80;
573 (EC ? anyEC : anyNonEC) =
true;
574 int xx = EC ? x - 32 : x;
577 uint8_t p0 = patTable[8 * pat + spriteY + 0];
579 if (size == 8)
return result;
580 uint8_t p1 = patTable[8 * pat + spriteY + 16];
581 return result | (p1 << 16);
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;
593 spriteCount[y] = count + 1;
594 spriteBuffer[y][count + 1].colorAttrib = 0;
599 assert(anyEC || anyNonEC);
600 spriteBoxes[spriteCnt] = SpriteBox{
603 magSize + (anyEC && anyNonEC ? 32 : 0),
605 spriteCnt, x, originalY, pat};
608 std::array<uint32_t, 256 * 256> screen;
609 memset(screen.data(), 0,
sizeof(uint32_t) * 256 * lines);
610 for (
auto line :
xrange(lines)) {
611 auto count = spriteCount[line];
612 if (count == 0)
continue;
613 auto lineBuf = subspan<256>(screen, 256 * line);
616 auto visibleSprites =
subspan(spriteBuffer[line], 0, count);
618 uint8_t colIdx = spr.colorAttrib & 0x0f;
619 if (colIdx == 0 && transparent)
continue;
620 auto color = palette[colIdx];
622 auto pattern = spr.pattern;
627 if (pattern & 0x8000'0000) {
634 }
else if (mode == 2) {
635 auto visibleSprites =
subspan(spriteBuffer[line], 0, count + 1);
640 if ((visibleSprites[first].colorAttrib & 0x40) == 0) [[likely]] {
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;
650 auto pattern = spr.pattern;
655 if (pattern & 0x8000'0000) {
658 for (
int j = i + 1; ; ++j) {
659 const auto& info2 = visibleSprites[j];
660 if (!(info2.colorAttrib & 0x40))
break;
661 unsigned shift2 = x - info2.x;
663 ((info2.pattern << shift2) & 0x8000'0000)) {
664 color |= info2.colorAttrib & 0x0F;
672 lineBuf[x] = palette[color];
680 if (!renderTex.
get()) {
684 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, lines, 0,
685 GL_RGBA, GL_UNSIGNED_BYTE, screen.data());
687 std::array<SpriteBox, 2 * 32> clippedBoxes;
688 int nrClippedBoxes = 0;
689 auto addClippedBox = [&](SpriteBox b) {
690 assert(nrClippedBoxes < 64);
691 clippedBoxes[nrClippedBoxes] = b;
694 for (
int sprite :
xrange(spriteCnt)) {
695 const auto& b = spriteBoxes[sprite];
702 if (w <= 0)
continue;
704 }
else if (x + w > 256) {
710 addClippedBox(SpriteBox{x, y, w, h, b.sprite, b.vramX, b.vramY, b.pattern});
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});
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) {
725 drawList->AddRect(tl, br, boxColor);
728 gl::vec2 topLeft = ImGui::GetCursorPos();
729 if (checkerBoardSize) {
730 ImGui::Image(checkerTex.
getImGui(), fullSize,
731 {}, fullSize / (4.0f *
float(checkerBoardSize)));
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)));
740 ImGui::Checkbox(
"Bounding box", &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);
751 auto maxStr =
tmpStrCat(
"Max ", spriteLimit,
" sprites per line");
752 ImGui::Checkbox(maxStr.c_str(), &enableLimitPerLine);
755 auto stopStr =
tmpStrCat(
"Stop at y=", stopY);
756 ImGui::Checkbox(stopStr.c_str(), &enableStopY);
761 auto [hx, hy] = hoverPos;
762 ImGui::Text(
"x=%d y=%d", hx, hy);
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);
772 ImGui::Text(
"sprite=%d x=%d y=%d pat=%d", b.sprite, b.vramX, b.vramY, b.pattern);
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);