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 auto vramSize = std::min(vdp->getVRAM().getSize(), 0x20000u);
177 std::array<uint32_t, 16> palette;
180 [](uint16_t msx) { return ImGuiPalette::toRGBA(msx); });
183 bool manMode = overrideAll || overrideMode;
184 bool manSize = overrideAll || overrideSize;
185 bool manMag = overrideAll || overrideMag;
186 bool manTrans = overrideAll || overrideTrans;
187 bool manPat = overrideAll || overridePat;
188 bool manAtt = overrideAll || overrideAtt;
189 bool manScroll = overrideAll || overrideScroll;
190 bool manLines = overrideAll || overrideLines;
192 im::TreeNode(
"Settings", ImGuiTreeNodeFlags_DefaultOpen, [&]{
196 ImGui::AlignTextToFramePadding();
200 ImGui::AlignTextToFramePadding();
204 ImGui::AlignTextToFramePadding();
208 ImGui::AlignTextToFramePadding();
209 ImGui::StrCat(
"Color 0 transparent: ", yesNo(vdpTransparent));
212 ImGui::AlignTextToFramePadding();
213 ImGui::StrCat(
"Pattern table: 0x", hex_string<5>(vdpPatBase));
216 ImGui::AlignTextToFramePadding();
217 ImGui::StrCat(
"Attribute table: 0x", hex_string<5>(vdpAttBase | (vdpMode == 2 ? 512 : 0)));
220 ImGui::AlignTextToFramePadding();
224 ImGui::AlignTextToFramePadding();
225 ImGui::StrCat(
"Visible lines: ", (vdpLines == 192) ?
"192" :
"212");
230 ImGui::Checkbox(
"Manual override", &overrideAll);
233 ImGui::Checkbox(
"##o-mode", overrideAll ? &overrideAll : &overrideMode);
234 ImGui::Checkbox(
"##o-size", overrideAll ? &overrideAll : &overrideSize);
235 ImGui::Checkbox(
"##o-mag", overrideAll ? &overrideAll : &overrideMag);
236 ImGui::Checkbox(
"##o-trans", overrideAll ? &overrideAll : &overrideTrans);
237 ImGui::Checkbox(
"##o-pat", overrideAll ? &overrideAll : &overridePat);
238 ImGui::Checkbox(
"##o-att", overrideAll ? &overrideAll : &overrideAtt);
239 ImGui::Checkbox(
"##o-scroll", overrideAll ? &overrideAll : &overrideScroll);
240 ImGui::Checkbox(
"##o-lines", overrideAll ? &overrideAll : &overrideLines);
247 if (manMode && isMSX1) manualMode = 1;
248 im::Combo(
"##mode", modeToStr(manualMode), [&]{
249 if (ImGui::Selectable(
"1")) manualMode = 1;
250 if (!isMSX1 && ImGui::Selectable(
"2")) manualMode = 2;
254 im::Combo(
"##size", sizeToStr(manualSize), [&]{
255 if (ImGui::Selectable(
" 8 x 8")) manualSize = 8;
256 if (ImGui::Selectable(
"16 x 16")) manualSize = 16;
260 im::Combo(
"##mag", yesNo(manualMag), [&]{
261 if (ImGui::Selectable(
"no")) manualMag = 0;
262 if (ImGui::Selectable(
"yes")) manualMag = 1;
266 im::Combo(
"##trans", yesNo(manualTransparent), [&]{
267 if (ImGui::Selectable(
"no")) manualTransparent = 0;
268 if (ImGui::Selectable(
"yes")) manualTransparent = 1;
272 comboHexSequence<5>(
"##pat", &manualPatBase, 8 * 256, vramSize, 0);
275 comboHexSequence<5>(
"##att", &manualAttBase, attMult(manualMode), vramSize, manualMode == 2 ? 512 : 0);
278 ImGui::InputInt(
"##verticalScroll", &manualVerticalScroll);
279 manualVerticalScroll &= 0xff;
282 ImGui::Combo(
"##lines", &manualLines,
"192\000212\000256\000");
288 ImGui::Dummy(ImVec2(25, 1));
291 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10.0f);
292 ImGui::Combo(
"Palette", &
manager.
palette->whichPalette,
"VDP\000Custom\000Fixed\000");
293 if (ImGui::Button(
"Open palette editor")) {
297 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.0f);
298 ImGui::Combo(
"Zoom", &zoom,
"1x\0002x\0003x\0004x\0005x\0006x\0007x\0008x\000");
299 ImGui::Checkbox(
"grid", &grid);
302 ImGui::ColorEdit4(
"Grid color", gridColor.data(),
303 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar);
307 simpleToolTip(
"Used as background in 'Sprite attribute' and 'Rendered sprites' view");
309 ImGui::ColorEdit4(
"checkerboard color1", checkerBoardColor1.data(), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel);
311 ImGui::ColorEdit4(
"checkerboard color2", checkerBoardColor2.data(), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel);
313 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6.0f);
314 ImGui::InputInt(
"size", &checkerBoardSize);
320 int mode = manMode ? manualMode : vdpMode;
321 int size = manSize ? manualSize : vdpSize;
322 int mag = manMag ? manualMag : vdpMag;
323 int verticalScroll = manScroll ? manualVerticalScroll : vdpVerticalScroll;
324 int lines = manLines ? (manualLines == 0 ? 192 :
325 manualLines == 1 ? 212 :
328 int transparent = manTrans ? manualTransparent : vdpTransparent;
331 unsigned patReg = (manPat ? (manualPatBase | ((8 * 256) - 1)) : vdp->getSpritePatternTableBase()) >> 11;
336 unsigned attReg = (manAtt ? (manualAttBase | (attMult(manualMode) - 1)) : vdp->getSpriteAttributeTableBase()) >> 7;
341 if (!patternTex.get()) {
345 std::array<uint32_t, 256 * 64> pixels;
348 renderPatterns8 (patTable, pixels);
350 renderPatterns16(patTable, pixels);
352 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 64, 0,
353 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
356 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0,
357 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
361 int zm = 2 * (1 + zoom);
362 auto gColor = ImGui::ColorConvertFloat4ToU32(gridColor);
364 auto gridSize = size * zm;
365 for (
auto y :
xrange(gridSize)) {
366 auto* line = &pixels[y * gridSize];
367 for (
auto x :
xrange(gridSize)) {
368 line[x] = (x == 0 || y == 0) ? gColor : 0;
371 if (!gridTex.
get()) {
375 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gridSize, gridSize, 0,
376 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
380 if (checkerBoardSize) {
381 pixels[0] = pixels[3] = ImGui::ColorConvertFloat4ToU32(checkerBoardColor1);
382 pixels[1] = pixels[2] = ImGui::ColorConvertFloat4ToU32(checkerBoardColor2);
383 if (!checkerTex.
get()) {
387 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0,
388 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
391 im::TreeNode(
"Sprite patterns", ImGuiTreeNodeFlags_DefaultOpen, [&]{
392 auto fullSize =
gl::vec2(256, 64) * float(zm);
393 im::Child(
"##pattern", {0, fullSize.y}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
394 auto pos1 = ImGui::GetCursorPos();
395 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
396 ImGui::Image(patternTex.getImGui(), fullSize);
397 bool hovered = ImGui::IsItemHovered() && (mode != 0);
400 gl::vec2 zoomPatSize{float(size * zm)};
402 auto gridPos = trunc((
gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / zoomPatSize);
403 auto pattern = (size == 16) ? ((16 * gridPos.y) + gridPos.x) * 4
404 : ((32 * gridPos.y) + gridPos.x) * 1;
407 auto uv1 =
gl::vec2(gridPos) * recipPatTex;
408 auto uv2 = uv1 + recipPatTex;
409 auto pos2 = ImGui::GetCursorPos();
410 int z = (size == 16) ? 3 : 6;
411 ImGui::Image(patternTex.getImGui(),
float(z) * zoomPatSize, uv1, uv2);
413 if (!zoomGridTex.
get()) {
417 for (
auto y :
xrange(s)) {
418 auto* line = &pixels[y * s];
419 for (
auto x :
xrange(s)) {
420 line[x] = (x == 0 || y == 0) ? gColor : 0;
424 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, s, s, 0,
425 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
426 ImGui::SetCursorPos(pos2);
427 ImGui::Image(zoomGridTex.
getImGui(),
428 float(z) * zoomPatSize, {},
gl::vec2{float(size)});
431 ImGui::Dummy(zoomPatSize);
435 ImGui::SetCursorPos(pos1);
436 ImGui::Image(gridTex.
getImGui(), fullSize,
443 im::TreeNode(
"Sprite attributes", ImGuiTreeNodeFlags_DefaultOpen, [&]{
444 auto zoomSize = float(zm * size);
445 auto fullSize = zoomSize *
gl::vec2(8, 4);
446 im::Child(
"##attrib", {0, fullSize.y}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
450 gl::vec2 topLeft = ImGui::GetCursorPos();
451 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
452 if (checkerBoardSize) {
453 ImGui::SetCursorPos(topLeft);
454 ImGui::Image(checkerTex.
getImGui(), fullSize,
455 {}, fullSize / (4.0f *
float(checkerBoardSize)));
457 for (
auto row :
xrange(4)) {
458 for (
auto column :
xrange(8)) {
459 int sprite = 8 * row + column;
460 ImGui::SetCursorPos(topLeft + zoomSize *
gl::vec2(
float(column),
float(row)));
461 renderSpriteAttrib(attTable, sprite, mode, size, transparent,
462 float(zm), palette, patternTex.getImGui());
465 ImGui::SetCursorPos(topLeft);
467 ImGui::Image(gridTex.
getImGui(), fullSize,
470 ImGui::Dummy(fullSize);
472 bool hovered = ImGui::IsItemHovered();
474 gl::vec2 zoomPatSize{float(size * zm)};
475 auto gridPos = trunc((
gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / zoomPatSize);
476 auto sprite = 8 * gridPos.y + gridPos.x;
481 auto pos = ImGui::GetCursorPos();
482 if (checkerBoardSize) {
483 ImGui::Image(checkerTex.
getImGui(), 3.0f * zoomPatSize,
484 {}, zoomPatSize / (4.0f *
float(checkerBoardSize)));
486 ImGui::SetCursorPos(pos);
487 renderSpriteAttrib(attTable, sprite, mode, size, transparent,
488 float(3 * zm), palette, patternTex.getImGui());
492 int addr = getSpriteAttrAddr(sprite, mode);
494 " y: ", attTable[addr + 0]);
497 auto c = attTable[addr + 3];
500 int colorBase = getSpriteColorAddr(sprite, mode);
501 im::StyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1), [&]{
503 for (
auto y :
xrange(4)) {
504 for (
auto x :
xrange(4)) {
505 auto line = 4 * y + x;
506 auto a = attTable[colorBase + line];
508 hex_string<1>(a & 15),
509 (a & 0xe0 ?
'*' :
' '),
511 if (x != 3) ImGui::SameLine();
523 im::TreeNode(
"Rendered sprites", ImGuiTreeNodeFlags_DefaultOpen, [&]{
527 uint8_t vramX, vramY;
531 std::array<uint8_t, 256> spriteCount = {};
532 std::array<std::array<SpriteChecker::SpriteInfo, 32 + 1>, 256> spriteBuffer;
533 std::array<SpriteBox, 32> spriteBoxes;
535 uint8_t spriteLimit = (mode == 1) ? 4 : 8;
536 uint8_t stopY = (mode == 1) ? 208 : 216;
537 uint8_t patMask = (size == 8) ? 0xff : 0xfc;
538 int magFactor = mag ? 2 : 1;
539 auto magSize = magFactor * size;
541 uint8_t spriteCnt = 0;
542 for (; spriteCnt < 32; ++spriteCnt) {
543 int addr = getSpriteAttrAddr(spriteCnt, mode);
544 uint8_t originalY = attTable[addr + 0];
545 if (enableStopY && (originalY == stopY))
break;
546 auto y = uint8_t(originalY + 1 - verticalScroll);
549 uint8_t x = attTable[addr + 1];
550 uint8_t pat = attTable[addr + 2] & patMask;
551 uint8_t att1 = attTable[addr + 3];
554 bool anyNonEC =
false;
555 for (
int spriteY :
xrange(size)) {
557 if (mode != 2)
return att1;
558 int colorBase = getSpriteColorAddr(spriteCnt, mode);
559 return attTable[colorBase + spriteY];
562 bool EC = attr & 0x80;
563 (EC ? anyEC : anyNonEC) =
true;
564 int xx = EC ? x - 32 : x;
567 uint8_t p0 = patTable[8 * pat + spriteY + 0];
569 if (size == 8)
return result;
570 uint8_t p1 = patTable[8 * pat + spriteY + 16];
571 return result | (p1 << 16);
575 for ([[maybe_unused]]
int mm :
xrange(magFactor)) {
576 auto count = spriteCount[y];
577 if (!enableLimitPerLine || (count < spriteLimit)) {
578 auto& spr = spriteBuffer[y][count];
579 spr.pattern = pattern;
580 spr.x = narrow<int16_t>(xx);
581 spr.colorAttrib = attr;
583 spriteCount[y] = count + 1;
584 spriteBuffer[y][count + 1].colorAttrib = 0;
589 assert(anyEC || anyNonEC);
590 spriteBoxes[spriteCnt] = SpriteBox{
593 magSize + (anyEC && anyNonEC ? 32 : 0),
595 spriteCnt, x, originalY, pat};
598 std::array<uint32_t, 256 * 256> screen;
599 memset(screen.data(), 0,
sizeof(uint32_t) * 256 * lines);
600 for (
auto line :
xrange(lines)) {
601 auto count = spriteCount[line];
602 if (count == 0)
continue;
603 auto lineBuf = subspan<256>(screen, 256 * line);
606 auto visibleSprites =
subspan(spriteBuffer[line], 0, count);
608 uint8_t colIdx = spr.colorAttrib & 0x0f;
609 if (colIdx == 0 && transparent)
continue;
610 auto color = palette[colIdx];
612 auto pattern = spr.pattern;
617 if (pattern & 0x8000'0000) {
624 }
else if (mode == 2) {
625 auto visibleSprites =
subspan(spriteBuffer[line], 0, count + 1);
630 if ((visibleSprites[first].colorAttrib & 0x40) == 0) [[likely]] {
635 for (
int i = narrow<int>(count - 1); i >= first; --i) {
636 const auto& spr = visibleSprites[i];
637 uint8_t c = spr.colorAttrib & 0x0F;
638 if (c == 0 && transparent)
continue;
640 auto pattern = spr.pattern;
645 if (pattern & 0x8000'0000) {
648 for (
int j = i + 1; ; ++j) {
649 const auto& info2 = visibleSprites[j];
650 if (!(info2.colorAttrib & 0x40))
break;
651 unsigned shift2 = x - info2.x;
653 ((info2.pattern << shift2) & 0x8000'0000)) {
654 color |= info2.colorAttrib & 0x0F;
662 lineBuf[x] = palette[color];
670 if (!renderTex.
get()) {
674 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, lines, 0,
675 GL_RGBA, GL_UNSIGNED_BYTE, screen.data());
677 std::array<SpriteBox, 2 * 32> clippedBoxes;
678 int nrClippedBoxes = 0;
679 auto addClippedBox = [&](SpriteBox b) {
680 assert(nrClippedBoxes < 64);
681 clippedBoxes[nrClippedBoxes] = b;
684 for (
int sprite :
xrange(spriteCnt)) {
685 const auto& b = spriteBoxes[sprite];
692 if (w <= 0)
continue;
694 }
else if (x + w > 256) {
700 addClippedBox(SpriteBox{x, y, w, h, b.sprite, b.vramX, b.vramY, b.pattern});
702 addClippedBox(SpriteBox{x, y, w, 256 - y, b.sprite, b.vramX, b.vramY, b.pattern});
703 addClippedBox(SpriteBox{x, 0, w, yEnd - 256, b.sprite, b.vramX, b.vramY, b.pattern});
707 auto fullSize = float(zm) *
gl::vec2(256,
float(lines));
708 im::Child(
"##screen", {0.0f, fullSize.y}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
709 auto* drawList = ImGui::GetWindowDrawList();
710 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
711 auto boxColor = ImGui::ColorConvertFloat4ToU32(boundingBoxColor);
712 auto drawBox = [&](
int x,
int y,
int w,
int h) {
715 drawList->AddRect(tl, br, boxColor);
718 gl::vec2 topLeft = ImGui::GetCursorPos();
719 if (checkerBoardSize) {
720 ImGui::Image(checkerTex.
getImGui(), fullSize,
721 {}, fullSize / (4.0f *
float(checkerBoardSize)));
723 ImGui::SetCursorPos(topLeft);
724 ImGui::Image(renderTex.
getImGui(), fullSize);
725 bool hovered = ImGui::IsItemHovered();
726 auto hoverPos = trunc((
gl::vec2(ImGui::GetIO().MousePos) - scrnPos) /
gl::vec2(
float(zm)));
730 ImGui::Checkbox(
"Bounding box", &drawBoundingBox);
733 ImGui::ColorEdit4(
"color", boundingBoxColor.data(),
734 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaBar);
735 ImGui::RadioButton(
"On hovered sprites", &boundingBoxOnAll, 0);
736 ImGui::RadioButton(
"On all sprites", &boundingBoxOnAll, 1);
741 auto maxStr =
tmpStrCat(
"Max ", spriteLimit,
" sprites per line");
742 ImGui::Checkbox(maxStr.c_str(), &enableLimitPerLine);
745 auto stopStr =
tmpStrCat(
"Stop at y=", stopY);
746 ImGui::Checkbox(stopStr.c_str(), &enableStopY);
751 auto [hx, hy] = hoverPos;
752 ImGui::Text(
"x=%d y=%d", hx, hy);
755 for (
int i :
xrange(nrClippedBoxes)) {
756 const auto& b = clippedBoxes[i];
757 if ((b.x <= hx) && (hx < (b.x + b.w)) &&
758 (b.y <= hy) && (hy < (b.y + b.h))) {
759 if (drawBoundingBox && (boundingBoxOnAll == 0)) {
760 drawBox(b.x, b.y, b.w, b.h);
762 ImGui::Text(
"sprite=%d x=%d y=%d pat=%d", b.sprite, b.vramX, b.vramY, b.pattern);
768 if (drawBoundingBox && (boundingBoxOnAll == 1)) {
769 for (
int i :
xrange(nrClippedBoxes)) {
770 const auto& b = clippedBoxes[i];
771 drawBox(b.x, b.y, b.w, b.h);