135 if (!
show || !motherBoard)
return;
137 ImGui::SetNextWindowSize({748, 1010}, ImGuiCond_FirstUseEver);
139 auto* vdp =
dynamic_cast<VDP*
>(motherBoard->
findDevice(
"VDP"));
143 auto modeToStr = [](
int mode) {
144 if (mode == 0)
return "no sprites";
145 if (mode == 1)
return "1";
146 if (mode == 2)
return "2";
147 assert(
false);
return "ERROR";
149 auto sizeToStr = [](
int size) {
150 if (size == 8)
return "8 x 8";
151 if (size == 16)
return "16 x 16";
152 assert(
false);
return "ERROR";
154 auto yesNo = [](
int x) {
155 if (x == 0)
return "no";
156 if (x == 1)
return "yes";
157 assert(
false);
return "ERROR";
159 auto attMult = [](
int mode) {
return 1 << ((mode == 2) ? 10 : 7); };
161 bool isMSX1 = vdp->isMSX1VDP();
162 auto displayMode = vdp->getDisplayMode();
163 bool planar = displayMode.isPlanar();
164 int vdpMode = displayMode.getSpriteMode(isMSX1);
165 int vdpVerticalScroll = vdp->getVerticalScroll();
166 int vdpLines = vdp->getNumberOfLines();
168 int vdpSize = vdp->getSpriteSize();
169 int vdpMag = vdp->isSpriteMag();
170 int vdpTransparent = vdp->getTransparency();
172 int vdpPatBase = vdp->getSpritePatternTableBase();
173 int vdpAttBase = vdp->getSpriteAttributeTableBase() & ~(attMult(vdpMode) - 1);
175 std::array<uint32_t, 16> palette;
178 [](uint16_t msx) { return ImGuiPalette::toRGBA(msx); });
181 bool manMode = overrideAll || overrideMode;
182 bool manSize = overrideAll || overrideSize;
183 bool manMag = overrideAll || overrideMag;
184 bool manTrans = overrideAll || overrideTrans;
185 bool manPat = overrideAll || overridePat;
186 bool manAtt = overrideAll || overrideAtt;
187 bool manScroll = overrideAll || overrideScroll;
188 bool manLines = overrideAll || overrideLines;
190 im::TreeNode(
"Settings", ImGuiTreeNodeFlags_DefaultOpen, [&]{
194 ImGui::AlignTextToFramePadding();
198 ImGui::AlignTextToFramePadding();
202 ImGui::AlignTextToFramePadding();
206 ImGui::AlignTextToFramePadding();
207 ImGui::StrCat(
"Color 0 transparent: ", yesNo(vdpTransparent));
210 ImGui::AlignTextToFramePadding();
211 ImGui::StrCat(
"Pattern table: 0x", hex_string<5>(vdpPatBase));
214 ImGui::AlignTextToFramePadding();
215 ImGui::StrCat(
"Attribute table: 0x", hex_string<5>(vdpAttBase | (vdpMode == 2 ? 512 : 0)));
218 ImGui::AlignTextToFramePadding();
222 ImGui::AlignTextToFramePadding();
223 ImGui::StrCat(
"Visible lines: ", (vdpLines == 192) ?
"192" :
"212");
228 ImGui::Checkbox(
"Manual override", &overrideAll);
231 ImGui::Checkbox(
"##o-mode", overrideAll ? &overrideAll : &overrideMode);
232 ImGui::Checkbox(
"##o-size", overrideAll ? &overrideAll : &overrideSize);
233 ImGui::Checkbox(
"##o-mag", overrideAll ? &overrideAll : &overrideMag);
234 ImGui::Checkbox(
"##o-trans", overrideAll ? &overrideAll : &overrideTrans);
235 ImGui::Checkbox(
"##o-pat", overrideAll ? &overrideAll : &overridePat);
236 ImGui::Checkbox(
"##o-att", overrideAll ? &overrideAll : &overrideAtt);
237 ImGui::Checkbox(
"##o-scroll", overrideAll ? &overrideAll : &overrideScroll);
238 ImGui::Checkbox(
"##o-lines", overrideAll ? &overrideAll : &overrideLines);
245 im::Combo(
"##mode", modeToStr(manualMode), [&]{
246 if (ImGui::Selectable(
"1")) manualMode = 1;
247 if (ImGui::Selectable(
"2")) manualMode = 2;
251 im::Combo(
"##size", sizeToStr(manualSize), [&]{
252 if (ImGui::Selectable(
" 8 x 8")) manualSize = 8;
253 if (ImGui::Selectable(
"16 x 16")) manualSize = 16;
257 im::Combo(
"##mag", yesNo(manualMag), [&]{
258 if (ImGui::Selectable(
"no")) manualMag = 0;
259 if (ImGui::Selectable(
"yes")) manualMag = 1;
263 im::Combo(
"##trans", yesNo(manualTransparent), [&]{
264 if (ImGui::Selectable(
"no")) manualTransparent = 0;
265 if (ImGui::Selectable(
"yes")) manualTransparent = 1;
269 comboHexSequence<5>(
"##pat", &manualPatBase, 8 * 256);
272 comboHexSequence<5>(
"##att", &manualAttBase, attMult(manualMode), manualMode == 2 ? 512 : 0);
275 ImGui::InputInt(
"##verticalScroll", &manualVerticalScroll);
276 manualVerticalScroll &= 0xff;
279 ImGui::Combo(
"##lines", &manualLines,
"192\000212\000256\000");
285 ImGui::Dummy(ImVec2(25, 1));
288 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10.0f);
289 ImGui::Combo(
"Palette", &
manager.
palette->whichPalette,
"VDP\000Custom\000Fixed\000");
290 if (ImGui::Button(
"Open palette editor")) {
294 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.0f);
295 ImGui::Combo(
"Zoom", &zoom,
"1x\0002x\0003x\0004x\0005x\0006x\0007x\0008x\000");
296 ImGui::Checkbox(
"grid", &grid);
299 ImGui::ColorEdit4(
"Grid color", gridColor.data(),
300 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar);
304 simpleToolTip(
"Used as background in 'Sprite attribute' and 'Rendered sprites' view");
306 ImGui::ColorEdit4(
"checkerboard color1", checkerBoardColor1.data(), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel);
308 ImGui::ColorEdit4(
"checkerboard color2", checkerBoardColor2.data(), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel);
310 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6.0f);
311 ImGui::InputInt(
"size", &checkerBoardSize);
317 int mode = manMode ? manualMode : vdpMode;
318 int size = manSize ? manualSize : vdpSize;
319 int mag = manMag ? manualMag : vdpMag;
320 int verticalScroll = manScroll ? manualVerticalScroll : vdpVerticalScroll;
321 int lines = manLines ? (manualLines == 0 ? 192 :
322 manualLines == 1 ? 212 :
325 int transparent = manTrans ? manualTransparent : vdpTransparent;
328 unsigned patReg = (manPat ? (manualPatBase | ((8 * 256) - 1)) : vdp->getSpritePatternTableBase()) >> 11;
333 unsigned attReg = (manAtt ? (manualAttBase | (attMult(manualMode) - 1)) : vdp->getSpriteAttributeTableBase()) >> 7;
338 if (!patternTex.get()) {
342 std::array<uint32_t, 256 * 64> pixels;
345 renderPatterns8 (patTable, pixels);
347 renderPatterns16(patTable, pixels);
349 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 64, 0,
350 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
353 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0,
354 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
358 int zm = 2 * (1 + zoom);
359 auto gColor = ImGui::ColorConvertFloat4ToU32(gridColor);
361 auto gridSize = size * zm;
362 for (
auto y :
xrange(gridSize)) {
363 auto* line = &pixels[y * gridSize];
364 for (
auto x :
xrange(gridSize)) {
365 line[x] = (x == 0 || y == 0) ? gColor : 0;
368 if (!gridTex.
get()) {
372 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gridSize, gridSize, 0,
373 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
377 if (checkerBoardSize) {
378 pixels[0] = pixels[3] = ImGui::ColorConvertFloat4ToU32(checkerBoardColor1);
379 pixels[1] = pixels[2] = ImGui::ColorConvertFloat4ToU32(checkerBoardColor2);
380 if (!checkerTex.
get()) {
384 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0,
385 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
388 im::TreeNode(
"Sprite patterns", ImGuiTreeNodeFlags_DefaultOpen, [&]{
389 auto fullSize =
gl::vec2(256, 64) * float(zm);
390 im::Child(
"##pattern", {0, fullSize.y}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
391 auto pos1 = ImGui::GetCursorPos();
392 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
393 ImGui::Image(patternTex.getImGui(), fullSize);
394 bool hovered = ImGui::IsItemHovered() && (mode != 0);
397 gl::vec2 zoomPatSize{float(size * zm)};
399 auto gridPos = trunc((
gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / zoomPatSize);
400 auto pattern = (size == 16) ? ((16 * gridPos.y) + gridPos.x) * 4
401 : ((32 * gridPos.y) + gridPos.x) * 1;
404 auto uv1 =
gl::vec2(gridPos) * recipPatTex;
405 auto uv2 = uv1 + recipPatTex;
406 auto pos2 = ImGui::GetCursorPos();
407 int z = (size == 16) ? 3 : 6;
408 ImGui::Image(patternTex.getImGui(),
float(z) * zoomPatSize, uv1, uv2);
410 if (!zoomGridTex.
get()) {
414 for (
auto y :
xrange(s)) {
415 auto* line = &pixels[y * s];
416 for (
auto x :
xrange(s)) {
417 line[x] = (x == 0 || y == 0) ? gColor : 0;
421 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, s, s, 0,
422 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
423 ImGui::SetCursorPos(pos2);
424 ImGui::Image(zoomGridTex.
getImGui(),
425 float(z) * zoomPatSize, {},
gl::vec2{float(size)});
428 ImGui::Dummy(zoomPatSize);
432 ImGui::SetCursorPos(pos1);
433 ImGui::Image(gridTex.
getImGui(), fullSize,
440 im::TreeNode(
"Sprite attributes", ImGuiTreeNodeFlags_DefaultOpen, [&]{
441 auto zoomSize = float(zm * size);
442 auto fullSize = zoomSize *
gl::vec2(8, 4);
443 im::Child(
"##attrib", {0, fullSize.y}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
447 gl::vec2 topLeft = ImGui::GetCursorPos();
448 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
449 if (checkerBoardSize) {
450 ImGui::SetCursorPos(topLeft);
451 ImGui::Image(checkerTex.
getImGui(), fullSize,
452 {}, fullSize / (4.0f *
float(checkerBoardSize)));
454 for (
auto row :
xrange(4)) {
455 for (
auto column :
xrange(8)) {
456 int sprite = 8 * row + column;
457 ImGui::SetCursorPos(topLeft + zoomSize *
gl::vec2(
float(column),
float(row)));
458 renderSpriteAttrib(attTable, sprite, mode, size, transparent,
459 float(zm), palette, patternTex.getImGui());
462 ImGui::SetCursorPos(topLeft);
464 ImGui::Image(gridTex.
getImGui(), fullSize,
467 ImGui::Dummy(fullSize);
469 bool hovered = ImGui::IsItemHovered();
471 gl::vec2 zoomPatSize{float(size * zm)};
472 auto gridPos = trunc((
gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / zoomPatSize);
473 auto sprite = 8 * gridPos.y + gridPos.x;
478 auto pos = ImGui::GetCursorPos();
479 if (checkerBoardSize) {
480 ImGui::Image(checkerTex.
getImGui(), 3.0f * zoomPatSize,
481 {}, zoomPatSize / (4.0f *
float(checkerBoardSize)));
483 ImGui::SetCursorPos(pos);
484 renderSpriteAttrib(attTable, sprite, mode, size, transparent,
485 float(3 * zm), palette, patternTex.getImGui());
489 int addr = getSpriteAttrAddr(sprite, mode);
491 " y: ", attTable[addr + 0]);
494 auto c = attTable[addr + 3];
497 int colorBase = getSpriteColorAddr(sprite, mode);
498 im::StyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1), [&]{
500 for (
auto y :
xrange(4)) {
501 for (
auto x :
xrange(4)) {
502 auto line = 4 * y + x;
503 auto a = attTable[colorBase + line];
505 hex_string<1>(a & 15),
506 (a & 0xe0 ?
'*' :
' '),
508 if (x != 3) ImGui::SameLine();
520 im::TreeNode(
"Rendered sprites", ImGuiTreeNodeFlags_DefaultOpen, [&]{
524 uint8_t vramX, vramY;
528 std::array<uint8_t, 256> spriteCount = {};
529 std::array<std::array<SpriteChecker::SpriteInfo, 32 + 1>, 256> spriteBuffer;
530 std::array<SpriteBox, 32> spriteBoxes;
532 uint8_t spriteLimit = (mode == 1) ? 4 : 8;
533 uint8_t stopY = (mode == 1) ? 208 : 216;
534 uint8_t patMask = (size == 8) ? 0xff : 0xfc;
535 int magFactor = mag ? 2 : 1;
537 uint8_t spriteCnt = 0;
538 for (; spriteCnt < 32; ++spriteCnt) {
539 int addr = getSpriteAttrAddr(spriteCnt, mode);
540 uint8_t originalY = attTable[addr + 0];
541 if (enableStopY && (originalY == stopY))
break;
542 auto y = uint8_t(originalY + 1 - verticalScroll);
545 uint8_t x = attTable[addr + 1];
546 uint8_t pat = attTable[addr + 2] & patMask;
547 uint8_t att1 = attTable[addr + 3];
550 bool anyNonEC =
false;
551 for (
int spriteY :
xrange(size)) {
553 if (mode != 2)
return att1;
554 int colorBase = getSpriteColorAddr(spriteCnt, mode);
555 return attTable[colorBase + spriteY];
558 bool EC = attr & 0x80;
559 (EC ? anyEC : anyNonEC) =
true;
560 int xx = EC ? x - 32 : x;
563 uint8_t p0 = patTable[8 * pat + spriteY + 0];
565 if (size == 8)
return result;
566 uint8_t p1 = patTable[8 * pat + spriteY + 16];
567 return result | (p1 << 16);
571 for ([[maybe_unused]]
int mm :
xrange(magFactor)) {
572 auto count = spriteCount[y];
573 if (!enableLimitPerLine || (count < spriteLimit)) {
574 auto& spr = spriteBuffer[y][count];
575 spr.pattern = pattern;
576 spr.x = narrow<int16_t>(xx);
577 spr.colorAttrib = attr;
579 spriteCount[y] = count + 1;
580 spriteBuffer[y][count + 1].colorAttrib = 0;
585 assert(anyEC || anyNonEC);
586 spriteBoxes[spriteCnt] = SpriteBox{
589 anyEC && anyNonEC ? size + 32 : size,
591 spriteCnt, x, originalY, pat};
594 std::array<uint32_t, 256 * 256> screen;
595 memset(screen.data(), 0,
sizeof(uint32_t) * 256 * lines);
596 for (
auto line :
xrange(lines)) {
597 auto count = spriteCount[line];
598 if (count == 0)
continue;
599 auto lineBuf = subspan<256>(screen, 256 * line);
602 auto visibleSprites =
subspan(spriteBuffer[line], 0, count);
604 uint8_t colIdx = spr.colorAttrib & 0x0f;
605 if (colIdx == 0 && transparent)
continue;
606 auto color = palette[colIdx];
608 auto pattern = spr.pattern;
613 if (pattern & 0x8000'0000) {
620 }
else if (mode == 2) {
621 auto visibleSprites =
subspan(spriteBuffer[line], 0, count + 1);
626 if ((visibleSprites[first].colorAttrib & 0x40) == 0) [[likely]] {
631 for (
int i = narrow<int>(count - 1); i >= first; --i) {
632 const auto& spr = visibleSprites[i];
633 uint8_t c = spr.colorAttrib & 0x0F;
634 if (c == 0 && transparent)
continue;
636 auto pattern = spr.pattern;
641 if (pattern & 0x8000'0000) {
644 for (
int j = i + 1; ; ++j) {
645 const auto& info2 = visibleSprites[j];
646 if (!(info2.colorAttrib & 0x40))
break;
647 unsigned shift2 = x - info2.x;
649 ((info2.pattern << shift2) & 0x8000'0000)) {
650 color |= info2.colorAttrib & 0x0F;
658 lineBuf[x] = palette[color];
666 if (!renderTex.
get()) {
670 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, lines, 0,
671 GL_RGBA, GL_UNSIGNED_BYTE, screen.data());
673 std::array<SpriteBox, 2 * 32> clippedBoxes;
674 int nrClippedBoxes = 0;
675 auto addClippedBox = [&](SpriteBox b) {
676 assert(nrClippedBoxes < 64);
677 clippedBoxes[nrClippedBoxes] = b;
680 for (
int sprite :
xrange(spriteCnt)) {
681 const auto& b = spriteBoxes[sprite];
688 if (w <= 0)
continue;
690 }
else if (x + w > 256) {
696 addClippedBox(SpriteBox{x, y, w, h, b.sprite, b.vramX, b.vramY, b.pattern});
698 addClippedBox(SpriteBox{x, y, w, 256 - y, b.sprite, b.vramX, b.vramY, b.pattern});
699 addClippedBox(SpriteBox{x, 0, w, yEnd - 256, b.sprite, b.vramX, b.vramY, b.pattern});
703 auto fullSize = float(zm) *
gl::vec2(256,
float(lines));
704 im::Child(
"##screen", {0.0f, fullSize.y}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
705 auto* drawList = ImGui::GetWindowDrawList();
706 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
707 auto boxColor = ImGui::ColorConvertFloat4ToU32(boundingBoxColor);
708 auto drawBox = [&](
int x,
int y,
int w,
int h) {
711 drawList->AddRect(tl, br, boxColor);
714 gl::vec2 topLeft = ImGui::GetCursorPos();
715 if (checkerBoardSize) {
716 ImGui::Image(checkerTex.
getImGui(), fullSize,
717 {}, fullSize / (4.0f *
float(checkerBoardSize)));
719 ImGui::SetCursorPos(topLeft);
720 ImGui::Image(renderTex.
getImGui(), fullSize);
721 bool hovered = ImGui::IsItemHovered();
722 auto hoverPos = trunc((
gl::vec2(ImGui::GetIO().MousePos) - scrnPos) /
gl::vec2(
float(zm)));
726 ImGui::Checkbox(
"Bounding box", &drawBoundingBox);
729 ImGui::ColorEdit4(
"color", boundingBoxColor.data(),
730 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaBar);
731 ImGui::RadioButton(
"On hovered sprites", &boundingBoxOnAll, 0);
732 ImGui::RadioButton(
"On all sprites", &boundingBoxOnAll, 1);
737 auto maxStr =
tmpStrCat(
"Max ", spriteLimit,
" sprites per line");
738 ImGui::Checkbox(maxStr.c_str(), &enableLimitPerLine);
741 auto stopStr =
tmpStrCat(
"Stop at y=", stopY);
742 ImGui::Checkbox(stopStr.c_str(), &enableStopY);
747 auto [hx, hy] = hoverPos;
748 ImGui::Text(
"x=%d y=%d", hx, hy);
751 for (
int i :
xrange(nrClippedBoxes)) {
752 const auto& b = clippedBoxes[i];
753 if ((b.x <= hx) && (hx < (b.x + b.w)) &&
754 (b.y <= hy) && (hy < (b.y + b.h))) {
755 if (drawBoundingBox && (boundingBoxOnAll == 0)) {
756 drawBox(b.x, b.y, b.w, b.h);
758 ImGui::Text(
"sprite=%d x=%d y=%d pat=%d", b.sprite, b.vramX, b.vramY, b.pattern);
764 if (drawBoundingBox && (boundingBoxOnAll == 1)) {
765 for (
int i :
xrange(nrClippedBoxes)) {
766 const auto& b = clippedBoxes[i];
767 drawBox(b.x, b.y, b.w, b.h);