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);
189 bool manMode = overrideAll || overrideMode;
190 bool manSize = overrideAll || overrideSize;
191 bool manMag = overrideAll || overrideMag;
192 bool manTrans = overrideAll || overrideTrans;
193 bool manPat = overrideAll || overridePat;
194 bool manAtt = overrideAll || overrideAtt;
195 bool manScroll = overrideAll || overrideScroll;
196 bool manLines = overrideAll || overrideLines;
198 im::TreeNode(
"Settings", ImGuiTreeNodeFlags_DefaultOpen, [&]{
202 ImGui::AlignTextToFramePadding();
206 ImGui::AlignTextToFramePadding();
210 ImGui::AlignTextToFramePadding();
214 ImGui::AlignTextToFramePadding();
215 ImGui::StrCat(
"Color 0 transparent: ", yesNo(vdpTransparent));
218 ImGui::AlignTextToFramePadding();
219 ImGui::StrCat(
"Pattern table: 0x", hex_string<5>(vdpPatBase));
222 ImGui::AlignTextToFramePadding();
223 ImGui::StrCat(
"Attribute table: 0x", hex_string<5>(vdpAttBase | (vdpMode == 2 ? 512 : 0)));
226 ImGui::AlignTextToFramePadding();
230 ImGui::AlignTextToFramePadding();
231 ImGui::StrCat(
"Visible lines: ", (vdpLines == 192) ?
"192" :
"212");
236 ImGui::Checkbox(
"Manual override", &overrideAll);
239 ImGui::Checkbox(
"##o-mode", overrideAll ? &overrideAll : &overrideMode);
240 ImGui::Checkbox(
"##o-size", overrideAll ? &overrideAll : &overrideSize);
241 ImGui::Checkbox(
"##o-mag", overrideAll ? &overrideAll : &overrideMag);
242 ImGui::Checkbox(
"##o-trans", overrideAll ? &overrideAll : &overrideTrans);
243 ImGui::Checkbox(
"##o-pat", overrideAll ? &overrideAll : &overridePat);
244 ImGui::Checkbox(
"##o-att", overrideAll ? &overrideAll : &overrideAtt);
245 ImGui::Checkbox(
"##o-scroll", overrideAll ? &overrideAll : &overrideScroll);
246 ImGui::Checkbox(
"##o-lines", overrideAll ? &overrideAll : &overrideLines);
253 if (manMode && isMSX1) manualMode = 1;
254 im::Combo(
"##mode", modeToStr(manualMode), [&]{
255 if (ImGui::Selectable(
"1")) manualMode = 1;
256 if (!isMSX1 && ImGui::Selectable(
"2")) manualMode = 2;
260 im::Combo(
"##size", sizeToStr(manualSize), [&]{
261 if (ImGui::Selectable(
" 8 x 8")) manualSize = 8;
262 if (ImGui::Selectable(
"16 x 16")) manualSize = 16;
266 im::Combo(
"##mag", yesNo(manualMag), [&]{
267 if (ImGui::Selectable(
"no")) manualMag = 0;
268 if (ImGui::Selectable(
"yes")) manualMag = 1;
272 im::Combo(
"##trans", yesNo(manualTransparent), [&]{
273 if (ImGui::Selectable(
"no")) manualTransparent = 0;
274 if (ImGui::Selectable(
"yes")) manualTransparent = 1;
278 comboHexSequence<5>(
"##pat", &manualPatBase, 8 * 256, vramSize, 0);
281 comboHexSequence<5>(
"##att", &manualAttBase, attMult(manualMode), vramSize, manualMode == 2 ? 512 : 0);
284 ImGui::InputInt(
"##verticalScroll", &manualVerticalScroll);
285 manualVerticalScroll &= 0xff;
288 ImGui::Combo(
"##lines", &manualLines,
"192\000212\000256\000");
294 ImGui::Dummy(ImVec2(25, 1));
297 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10.0f);
298 ImGui::Combo(
"Palette", &
manager.
palette->whichPalette,
"VDP\000Custom\000Fixed\000");
299 if (ImGui::Button(
"Open palette editor")) {
303 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.0f);
304 ImGui::Combo(
"Zoom", &zoom,
"1x\0002x\0003x\0004x\0005x\0006x\0007x\0008x\000");
305 ImGui::Checkbox(
"grid", &grid);
308 ImGui::ColorEdit4(
"Grid color", gridColor.data(),
309 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar);
313 simpleToolTip(
"Used as background in 'Sprite attribute' and 'Rendered sprites' view");
315 ImGui::ColorEdit4(
"checkerboard color1", checkerBoardColor1.data(), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel);
317 ImGui::ColorEdit4(
"checkerboard color2", checkerBoardColor2.data(), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel);
319 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6.0f);
320 ImGui::InputInt(
"size", &checkerBoardSize);
326 int mode = manMode ? manualMode : vdpMode;
327 int size = manSize ? manualSize : vdpSize;
328 int mag = manMag ? manualMag : vdpMag;
329 int verticalScroll = manScroll ? manualVerticalScroll : vdpVerticalScroll;
330 int lines = manLines ? (manualLines == 0 ? 192 :
331 manualLines == 1 ? 212 :
334 int transparent = manTrans ? manualTransparent : vdpTransparent;
337 unsigned patReg = (manPat ? (manualPatBase | ((8 * 256) - 1)) : vdp->getSpritePatternTableBase()) >> 11;
342 unsigned attReg = (manAtt ? (manualAttBase | (attMult(manualMode) - 1)) : vdp->getSpriteAttributeTableBase()) >> 7;
347 if (!patternTex.get()) {
351 std::array<uint32_t, 256 * 64> pixels;
354 renderPatterns8 (patTable, pixels);
356 renderPatterns16(patTable, pixels);
358 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 64, 0,
359 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
362 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0,
363 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
367 int zm = 2 * (1 + zoom);
368 auto gColor = ImGui::ColorConvertFloat4ToU32(gridColor);
370 size_t gridSize = size * zm;
371 for (
auto y :
xrange(gridSize)) {
372 auto* line = &pixels[y * gridSize];
373 for (
auto x :
xrange(gridSize)) {
374 line[x] = (x == 0 || y == 0) ? gColor : 0;
377 if (!gridTex.
get()) {
381 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
382 narrow<int>(gridSize), narrow<int>(gridSize),
383 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
387 if (checkerBoardSize) {
388 pixels[0] = pixels[3] = ImGui::ColorConvertFloat4ToU32(checkerBoardColor1);
389 pixels[1] = pixels[2] = ImGui::ColorConvertFloat4ToU32(checkerBoardColor2);
390 if (!checkerTex.
get()) {
394 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0,
395 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
398 im::TreeNode(
"Sprite patterns", ImGuiTreeNodeFlags_DefaultOpen, [&]{
399 auto fullSize =
gl::vec2(256, 64) * float(zm);
400 im::Child(
"##pattern", {0, fullSize.y}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
401 auto pos1 = ImGui::GetCursorPos();
402 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
403 ImGui::Image(patternTex.getImGui(), fullSize);
404 bool hovered = ImGui::IsItemHovered() && (mode != 0);
407 gl::vec2 zoomPatSize{float(size * zm)};
409 auto gridPos = trunc((
gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / zoomPatSize);
410 auto pattern = (size == 16) ? ((16 * gridPos.y) + gridPos.x) * 4
411 : ((32 * gridPos.y) + gridPos.x) * 1;
414 auto uv1 =
gl::vec2(gridPos) * recipPatTex;
415 auto uv2 = uv1 + recipPatTex;
416 auto pos2 = ImGui::GetCursorPos();
417 int z = (size == 16) ? 3 : 6;
418 ImGui::Image(patternTex.getImGui(),
float(z) * zoomPatSize, uv1, uv2);
420 if (!zoomGridTex.
get()) {
424 for (
auto y :
xrange(s)) {
425 auto* line = &pixels[y * s];
426 for (
auto x :
xrange(s)) {
427 line[x] = (x == 0 || y == 0) ? gColor : 0;
431 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, s, s, 0,
432 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
433 ImGui::SetCursorPos(pos2);
434 ImGui::Image(zoomGridTex.
getImGui(),
435 float(z) * zoomPatSize, {},
gl::vec2{float(size)});
438 ImGui::Dummy(zoomPatSize);
442 ImGui::SetCursorPos(pos1);
443 ImGui::Image(gridTex.
getImGui(), fullSize,
450 im::TreeNode(
"Sprite attributes", ImGuiTreeNodeFlags_DefaultOpen, [&]{
451 auto zoomSize = float(zm * size);
452 auto fullSize = zoomSize *
gl::vec2(8, 4);
453 im::Child(
"##attrib", {0, fullSize.y}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
457 gl::vec2 topLeft = ImGui::GetCursorPos();
458 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
459 if (checkerBoardSize) {
460 ImGui::SetCursorPos(topLeft);
461 ImGui::Image(checkerTex.
getImGui(), fullSize,
462 {}, fullSize / (4.0f *
float(checkerBoardSize)));
464 for (
auto row :
xrange(4)) {
465 for (
auto column :
xrange(8)) {
466 int sprite = 8 * row + column;
467 ImGui::SetCursorPos(topLeft + zoomSize *
gl::vec2(
float(column),
float(row)));
468 renderSpriteAttrib(attTable, sprite, mode, size, transparent,
469 float(zm), palette, patternTex.getImGui());
472 ImGui::SetCursorPos(topLeft);
474 ImGui::Image(gridTex.
getImGui(), fullSize,
477 ImGui::Dummy(fullSize);
479 bool hovered = ImGui::IsItemHovered();
481 gl::vec2 zoomPatSize{float(size * zm)};
482 auto gridPos = trunc((
gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / zoomPatSize);
483 auto sprite = 8 * gridPos.y + gridPos.x;
488 auto pos = ImGui::GetCursorPos();
489 if (checkerBoardSize) {
490 ImGui::Image(checkerTex.
getImGui(), 3.0f * zoomPatSize,
491 {}, zoomPatSize / (4.0f *
float(checkerBoardSize)));
493 ImGui::SetCursorPos(pos);
494 renderSpriteAttrib(attTable, sprite, mode, size, transparent,
495 float(3 * zm), palette, patternTex.getImGui());
499 int addr = getSpriteAttrAddr(sprite, mode);
501 " y: ", attTable[addr + 0]);
504 auto c = attTable[addr + 3];
507 int colorBase = getSpriteColorAddr(sprite, mode);
508 im::StyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1), [&]{
510 for (
auto y :
xrange(4)) {
511 for (
auto x :
xrange(4)) {
512 auto line = 4 * y + x;
513 auto a = attTable[colorBase + line];
515 hex_string<1>(a & 15),
516 (a & 0xe0 ?
'*' :
' '),
518 if (x != 3) ImGui::SameLine();
530 im::TreeNode(
"Rendered sprites", ImGuiTreeNodeFlags_DefaultOpen, [&]{
534 uint8_t vramX, vramY;
538 std::array<uint8_t, 256> spriteCount = {};
539 std::array<std::array<SpriteChecker::SpriteInfo, 32 + 1>, 256> spriteBuffer;
540 std::array<SpriteBox, 32> spriteBoxes;
542 uint8_t spriteLimit = (mode == 1) ? 4 : 8;
543 uint8_t stopY = (mode == 1) ? 208 : 216;
544 uint8_t patMask = (size == 8) ? 0xff : 0xfc;
545 int magFactor = mag ? 2 : 1;
546 auto magSize = magFactor * size;
548 uint8_t spriteCnt = 0;
549 for (; spriteCnt < 32; ++spriteCnt) {
550 int addr = getSpriteAttrAddr(spriteCnt, mode);
551 uint8_t originalY = attTable[addr + 0];
552 if (enableStopY && (originalY == stopY))
break;
553 auto y = uint8_t(originalY + 1 - verticalScroll);
556 uint8_t x = attTable[addr + 1];
557 uint8_t pat = attTable[addr + 2] & patMask;
558 uint8_t att1 = attTable[addr + 3];
561 bool anyNonEC =
false;
562 for (
int spriteY :
xrange(size)) {
564 if (mode != 2)
return att1;
565 int colorBase = getSpriteColorAddr(spriteCnt, mode);
566 return attTable[colorBase + spriteY];
569 bool EC = attr & 0x80;
570 (EC ? anyEC : anyNonEC) =
true;
571 int xx = EC ? x - 32 : x;
574 uint8_t p0 = patTable[8 * pat + spriteY + 0];
576 if (size == 8)
return result;
577 uint8_t p1 = patTable[8 * pat + spriteY + 16];
578 return result | (p1 << 16);
582 for ([[maybe_unused]]
int mm :
xrange(magFactor)) {
583 auto count = spriteCount[y];
584 if (!enableLimitPerLine || (count < spriteLimit)) {
585 auto& spr = spriteBuffer[y][count];
586 spr.pattern = pattern;
587 spr.x = narrow<int16_t>(xx);
588 spr.colorAttrib = attr;
590 spriteCount[y] = count + 1;
591 spriteBuffer[y][count + 1].colorAttrib = 0;
596 assert(anyEC || anyNonEC);
597 spriteBoxes[spriteCnt] = SpriteBox{
600 magSize + (anyEC && anyNonEC ? 32 : 0),
602 spriteCnt, x, originalY, pat};
605 std::array<uint32_t, 256 * 256> screen;
606 memset(screen.data(), 0,
sizeof(uint32_t) * 256 * lines);
607 for (
auto line :
xrange(lines)) {
608 auto count = spriteCount[line];
609 if (count == 0)
continue;
610 auto lineBuf = subspan<256>(screen, 256 * line);
613 auto visibleSprites =
subspan(spriteBuffer[line], 0, count);
615 uint8_t colIdx = spr.colorAttrib & 0x0f;
616 if (colIdx == 0 && transparent)
continue;
617 auto color = palette[colIdx];
619 auto pattern = spr.pattern;
624 if (pattern & 0x8000'0000) {
631 }
else if (mode == 2) {
632 auto visibleSprites =
subspan(spriteBuffer[line], 0, count + 1);
637 if ((visibleSprites[first].colorAttrib & 0x40) == 0) [[likely]] {
642 for (
int i = narrow<int>(count - 1); i >= first; --i) {
643 const auto& spr = visibleSprites[i];
644 uint8_t c = spr.colorAttrib & 0x0F;
645 if (c == 0 && transparent)
continue;
647 auto pattern = spr.pattern;
652 if (pattern & 0x8000'0000) {
655 for (
int j = i + 1; ; ++j) {
656 const auto& info2 = visibleSprites[j];
657 if (!(info2.colorAttrib & 0x40))
break;
658 unsigned shift2 = x - info2.x;
660 ((info2.pattern << shift2) & 0x8000'0000)) {
661 color |= info2.colorAttrib & 0x0F;
669 lineBuf[x] = palette[color];
677 if (!renderTex.
get()) {
681 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, lines, 0,
682 GL_RGBA, GL_UNSIGNED_BYTE, screen.data());
684 std::array<SpriteBox, 2 * 32> clippedBoxes;
685 int nrClippedBoxes = 0;
686 auto addClippedBox = [&](SpriteBox b) {
687 assert(nrClippedBoxes < 64);
688 clippedBoxes[nrClippedBoxes] = b;
691 for (
int sprite :
xrange(spriteCnt)) {
692 const auto& b = spriteBoxes[sprite];
699 if (w <= 0)
continue;
701 }
else if (x + w > 256) {
707 addClippedBox(SpriteBox{x, y, w, h, b.sprite, b.vramX, b.vramY, b.pattern});
709 addClippedBox(SpriteBox{x, y, w, 256 - y, b.sprite, b.vramX, b.vramY, b.pattern});
710 addClippedBox(SpriteBox{x, 0, w, yEnd - 256, b.sprite, b.vramX, b.vramY, b.pattern});
714 auto fullSize = float(zm) *
gl::vec2(256,
float(lines));
715 im::Child(
"##screen", {0.0f, fullSize.y}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
716 auto* drawList = ImGui::GetWindowDrawList();
717 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
718 auto boxColor = ImGui::ColorConvertFloat4ToU32(boundingBoxColor);
719 auto drawBox = [&](
int x,
int y,
int w,
int h) {
722 drawList->AddRect(tl, br, boxColor);
725 gl::vec2 topLeft = ImGui::GetCursorPos();
726 if (checkerBoardSize) {
727 ImGui::Image(checkerTex.
getImGui(), fullSize,
728 {}, fullSize / (4.0f *
float(checkerBoardSize)));
730 ImGui::SetCursorPos(topLeft);
731 ImGui::Image(renderTex.
getImGui(), fullSize);
732 bool hovered = ImGui::IsItemHovered();
733 auto hoverPos = trunc((
gl::vec2(ImGui::GetIO().MousePos) - scrnPos) /
gl::vec2(
float(zm)));
737 ImGui::Checkbox(
"Bounding box", &drawBoundingBox);
740 ImGui::ColorEdit4(
"color", boundingBoxColor.data(),
741 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaBar);
742 ImGui::RadioButton(
"On hovered sprites", &boundingBoxOnAll, 0);
743 ImGui::RadioButton(
"On all sprites", &boundingBoxOnAll, 1);
748 auto maxStr =
tmpStrCat(
"Max ", spriteLimit,
" sprites per line");
749 ImGui::Checkbox(maxStr.c_str(), &enableLimitPerLine);
752 auto stopStr =
tmpStrCat(
"Stop at y=", stopY);
753 ImGui::Checkbox(stopStr.c_str(), &enableStopY);
758 auto [hx, hy] = hoverPos;
759 ImGui::Text(
"x=%d y=%d", hx, hy);
762 for (
int i :
xrange(nrClippedBoxes)) {
763 const auto& b = clippedBoxes[i];
764 if ((b.x <= hx) && (hx < (b.x + b.w)) &&
765 (b.y <= hy) && (hy < (b.y + b.h))) {
766 if (drawBoundingBox && (boundingBoxOnAll == 0)) {
767 drawBox(b.x, b.y, b.w, b.h);
769 ImGui::Text(
"sprite=%d x=%d y=%d pat=%d", b.sprite, b.vramX, b.vramY, b.pattern);
775 if (drawBoundingBox && (boundingBoxOnAll == 1)) {
776 for (
int i :
xrange(nrClippedBoxes)) {
777 const auto& b = clippedBoxes[i];
778 drawBox(b.x, b.y, b.w, b.h);