137 if (!
show || !motherBoard)
return;
139 ImGui::SetNextWindowSize({748, 1010}, ImGuiCond_FirstUseEver);
141 auto* vdp =
dynamic_cast<VDP*
>(motherBoard->
findDevice(
"VDP"));
145 auto modeToStr = [](
int mode) {
146 if (mode == 0)
return "no sprites";
147 if (mode == 1)
return "1";
148 if (mode == 2)
return "2";
149 assert(
false);
return "ERROR";
151 auto sizeToStr = [](
int size) {
152 if (size == 8)
return "8 x 8";
153 if (size == 16)
return "16 x 16";
154 assert(
false);
return "ERROR";
156 auto yesNo = [](
int x) {
157 if (x == 0)
return "no";
158 if (x == 1)
return "yes";
159 assert(
false);
return "ERROR";
161 auto attMult = [](
int mode) {
return 1 << ((mode == 2) ? 10 : 7); };
163 bool isMSX1 = vdp->isMSX1VDP();
164 auto displayMode = vdp->getDisplayMode();
165 bool planar = displayMode.isPlanar();
166 int vdpMode = displayMode.getSpriteMode(isMSX1);
167 int vdpVerticalScroll = vdp->getVerticalScroll();
168 int vdpLines = vdp->getNumberOfLines();
170 int vdpSize = vdp->getSpriteSize();
171 int vdpMag = vdp->isSpriteMag();
172 int vdpTransparent = vdp->getTransparency();
174 int vdpPatBase = vdp->getSpritePatternTableBase();
175 int vdpAttBase = vdp->getSpriteAttributeTableBase() & ~(attMult(vdpMode) - 1);
177 std::array<uint32_t, 16> palette;
180 [](uint16_t msx) { return ImGuiPalette::toRGBA(msx); });
183 im::TreeNode(
"Settings", ImGuiTreeNodeFlags_DefaultOpen, [&]{
185 ImGui::RadioButton(
"Use VDP settings", &manual, 0);
187 ImGui::AlignTextToFramePadding();
189 ImGui::AlignTextToFramePadding();
191 ImGui::AlignTextToFramePadding();
193 ImGui::AlignTextToFramePadding();
194 ImGui::StrCat(
"Color 0 transparent: ", yesNo(vdpTransparent));
195 ImGui::AlignTextToFramePadding();
196 ImGui::StrCat(
"Pattern table: 0x", hex_string<5>(vdpPatBase));
197 ImGui::AlignTextToFramePadding();
198 ImGui::StrCat(
"Attribute table: 0x", hex_string<5>(vdpAttBase | (vdpMode == 2 ? 512 : 0)));
199 ImGui::AlignTextToFramePadding();
201 ImGui::AlignTextToFramePadding();
202 ImGui::StrCat(
"Visible lines: 0x", (vdpLines == 192) ?
"192" :
"212");
207 ImGui::RadioButton(
"Manual override", &manual, 1);
210 im::Combo(
"##mode", modeToStr(manualMode), [&]{
211 if (ImGui::Selectable(
"1")) manualMode = 1;
212 if (ImGui::Selectable(
"2")) manualMode = 2;
214 im::Combo(
"##size", sizeToStr(manualSize), [&]{
215 if (ImGui::Selectable(
" 8 x 8")) manualSize = 8;
216 if (ImGui::Selectable(
"16 x 16")) manualSize = 16;
218 im::Combo(
"##mag", yesNo(manualMag), [&]{
219 if (ImGui::Selectable(
"no")) manualMag = 0;
220 if (ImGui::Selectable(
"yes")) manualMag = 1;
222 im::Combo(
"##trans", yesNo(manualTransparent), [&]{
223 if (ImGui::Selectable(
"no")) manualTransparent = 0;
224 if (ImGui::Selectable(
"yes")) manualTransparent = 1;
226 comboHexSequence<5>(
"##pat", &manualPatBase, 8 * 256);
227 comboHexSequence<5>(
"##att", &manualAttBase, attMult(manualMode), manualMode == 2 ? 512 : 0);
228 ImGui::InputInt(
"##verticalScroll", &manualVerticalScroll);
229 manualVerticalScroll &= 0xff;
230 ImGui::Combo(
"##lines", &manualLines,
"192\000212\000256\000");
235 ImGui::Dummy(ImVec2(25, 1));
238 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10.0f);
239 ImGui::Combo(
"Palette", &
manager.
palette->whichPalette,
"VDP\000Custom\000Fixed\000");
240 if (ImGui::Button(
"Open palette editor")) {
244 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.0f);
245 ImGui::Combo(
"Zoom", &zoom,
"1x\0002x\0003x\0004x\0005x\0006x\0007x\0008x\000");
246 ImGui::Checkbox(
"grid", &grid);
249 ImGui::ColorEdit4(
"Grid color", gridColor.data(),
250 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar);
254 simpleToolTip(
"Used as background in 'Sprite attribute' and 'Rendered sprites' view");
256 ImGui::ColorEdit4(
"checkerboard color1", checkerBoardColor1.data(), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel);
258 ImGui::ColorEdit4(
"checkerboard color2", checkerBoardColor2.data(), ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel);
260 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6.0f);
261 ImGui::InputInt(
"size", &checkerBoardSize);
267 int mode = manual ? manualMode : vdpMode;
268 int size = manual ? manualSize : vdpSize;
269 int mag = manual ? manualMag : vdpMag;
270 int verticalScroll = manual ? manualVerticalScroll : vdpVerticalScroll;
271 int lines = manual ? (manualLines == 0 ? 192 :
272 manualLines == 1 ? 212 :
275 int transparent = manual ? manualTransparent : vdpTransparent;
278 unsigned patReg = (manual ? manualPatBase : vdp->getSpritePatternTableBase()) >> 11;
283 unsigned attReg = (manual ? manualAttBase : vdp->getSpriteAttributeTableBase()) >> 7;
288 if (!patternTex.get()) {
292 std::array<uint32_t, 256 * 64> pixels;
295 renderPatterns8 (patTable, pixels);
297 renderPatterns16(patTable, pixels);
299 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 64, 0,
300 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
303 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0,
304 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
308 int zm = 2 * (1 + zoom);
309 auto gColor = ImGui::ColorConvertFloat4ToU32(gridColor);
311 auto gridSize = size * zm;
312 for (
auto y :
xrange(gridSize)) {
313 auto* line = &pixels[y * gridSize];
314 for (
auto x :
xrange(gridSize)) {
315 line[x] = (x == 0 || y == 0) ? gColor : 0;
318 if (!gridTex.
get()) {
322 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gridSize, gridSize, 0,
323 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
327 if (checkerBoardSize) {
328 pixels[0] = pixels[3] = ImGui::ColorConvertFloat4ToU32(checkerBoardColor1);
329 pixels[1] = pixels[2] = ImGui::ColorConvertFloat4ToU32(checkerBoardColor2);
330 if (!checkerTex.
get()) {
334 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0,
335 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
338 im::TreeNode(
"Sprite patterns", ImGuiTreeNodeFlags_DefaultOpen, [&]{
339 auto fullSize =
gl::vec2(256, 64) * float(zm);
340 im::Child(
"##pattern", {0, fullSize.y}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
341 auto pos1 = ImGui::GetCursorPos();
342 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
343 ImGui::Image(patternTex.getImGui(), fullSize);
344 bool hovered = ImGui::IsItemHovered() && (mode != 0);
347 gl::vec2 zoomPatSize{float(size * zm)};
349 auto gridPos = trunc((
gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / zoomPatSize);
350 auto pattern = (size == 16) ? ((16 * gridPos.y) + gridPos.x) * 4
351 : ((32 * gridPos.y) + gridPos.x) * 1;
354 auto uv1 =
gl::vec2(gridPos) * recipPatTex;
355 auto uv2 = uv1 + recipPatTex;
356 auto pos2 = ImGui::GetCursorPos();
357 int z = (size == 16) ? 3 : 6;
358 ImGui::Image(patternTex.getImGui(),
float(z) * zoomPatSize, uv1, uv2);
360 if (!zoomGridTex.
get()) {
364 for (
auto y :
xrange(s)) {
365 auto* line = &pixels[y * s];
366 for (
auto x :
xrange(s)) {
367 line[x] = (x == 0 || y == 0) ? gColor : 0;
371 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, s, s, 0,
372 GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
373 ImGui::SetCursorPos(pos2);
374 ImGui::Image(zoomGridTex.
getImGui(),
375 float(z) * zoomPatSize, {},
gl::vec2{float(size)});
378 ImGui::Dummy(zoomPatSize);
382 ImGui::SetCursorPos(pos1);
383 ImGui::Image(gridTex.
getImGui(), fullSize,
390 im::TreeNode(
"Sprite attributes", ImGuiTreeNodeFlags_DefaultOpen, [&]{
391 auto zoomSize = float(zm * size);
392 auto fullSize = zoomSize *
gl::vec2(8, 4);
393 im::Child(
"##attrib", {0, fullSize.y}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
397 gl::vec2 topLeft = ImGui::GetCursorPos();
398 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
399 if (checkerBoardSize) {
400 ImGui::SetCursorPos(topLeft);
401 ImGui::Image(checkerTex.
getImGui(), fullSize,
402 {}, fullSize / (4.0f *
float(checkerBoardSize)));
404 for (
auto row :
xrange(4)) {
405 for (
auto column :
xrange(8)) {
406 int sprite = 8 * row + column;
407 ImGui::SetCursorPos(topLeft + zoomSize *
gl::vec2(
float(column),
float(row)));
408 renderSpriteAttrib(attTable, sprite, mode, size, transparent,
409 float(zm), palette, patternTex.getImGui());
412 ImGui::SetCursorPos(topLeft);
414 ImGui::Image(gridTex.
getImGui(), fullSize,
417 ImGui::Dummy(fullSize);
419 bool hovered = ImGui::IsItemHovered();
421 gl::vec2 zoomPatSize{float(size * zm)};
422 auto gridPos = trunc((
gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / zoomPatSize);
423 auto sprite = 8 * gridPos.y + gridPos.x;
428 auto pos = ImGui::GetCursorPos();
429 if (checkerBoardSize) {
430 ImGui::Image(checkerTex.
getImGui(), 3.0f * zoomPatSize,
431 {}, zoomPatSize / (4.0f *
float(checkerBoardSize)));
433 ImGui::SetCursorPos(pos);
434 renderSpriteAttrib(attTable, sprite, mode, size, transparent,
435 float(3 * zm), palette, patternTex.getImGui());
439 int addr = getSpriteAttrAddr(sprite, mode);
441 " y: ", attTable[addr + 0]);
444 auto c = attTable[addr + 3];
447 int colorBase = getSpriteColorAddr(sprite, mode);
448 im::StyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1), [&]{
450 for (
auto y :
xrange(4)) {
451 for (
auto x :
xrange(4)) {
452 auto line = 4 * y + x;
453 auto a = attTable[colorBase + line];
455 hex_string<1>(a & 15),
456 (a & 0xe0 ?
'*' :
' '),
458 if (x != 3) ImGui::SameLine();
470 im::TreeNode(
"Rendered sprites", ImGuiTreeNodeFlags_DefaultOpen, [&]{
474 uint8_t vramX, vramY;
478 std::array<uint8_t, 256> spriteCount = {};
479 std::array<std::array<SpriteChecker::SpriteInfo, 32 + 1>, 256> spriteBuffer;
480 std::array<SpriteBox, 32> spriteBoxes;
482 uint8_t spriteLimit = (mode == 1) ? 4 : 8;
483 uint8_t stopY = (mode == 1) ? 208 : 216;
484 uint8_t patMask = (size == 8) ? 0xff : 0xfc;
485 int magFactor = mag ? 2 : 1;
487 uint8_t spriteCnt = 0;
488 for (; spriteCnt < 32; ++spriteCnt) {
489 int addr = getSpriteAttrAddr(spriteCnt, mode);
490 uint8_t originalY = attTable[addr + 0];
491 if (enableStopY && (originalY == stopY))
break;
492 auto y = uint8_t(originalY + 1 - verticalScroll);
495 uint8_t x = attTable[addr + 1];
496 uint8_t pat = attTable[addr + 2] & patMask;
497 uint8_t att1 = attTable[addr + 3];
500 bool anyNonEC =
false;
501 for (
int spriteY :
xrange(size)) {
503 if (mode != 2)
return att1;
504 int colorBase = getSpriteColorAddr(spriteCnt, mode);
505 return attTable[colorBase + spriteY];
508 bool EC = attr & 0x80;
509 (EC ? anyEC : anyNonEC) =
true;
510 int xx = EC ? x - 32 : x;
513 uint8_t p0 = patTable[8 * pat + spriteY + 0];
515 if (size == 8)
return result;
516 uint8_t p1 = patTable[8 * pat + spriteY + 16];
517 return result | (p1 << 16);
521 for ([[maybe_unused]]
int mm :
xrange(magFactor)) {
522 auto count = spriteCount[y];
523 if (!enableLimitPerLine || (count < spriteLimit)) {
524 auto& spr = spriteBuffer[y][count];
525 spr.pattern = pattern;
526 spr.x = narrow<int16_t>(xx);
527 spr.colorAttrib = attr;
529 spriteCount[y] = count + 1;
530 spriteBuffer[y][count + 1].colorAttrib = 0;
535 assert(anyEC || anyNonEC);
536 spriteBoxes[spriteCnt] = SpriteBox{
539 anyEC && anyNonEC ? size + 32 : size,
541 spriteCnt, x, originalY, pat};
544 std::array<uint32_t, 256 * 256> screen;
545 memset(screen.data(), 0,
sizeof(uint32_t) * 256 * lines);
546 for (
auto line :
xrange(lines)) {
547 auto count = spriteCount[line];
548 if (count == 0)
continue;
549 auto lineBuf = subspan<256>(screen, 256 * line);
552 auto visibleSprites =
subspan(spriteBuffer[line], 0, count);
554 uint8_t colIdx = spr.colorAttrib & 0x0f;
555 if (colIdx == 0 && transparent)
continue;
556 auto color = palette[colIdx];
558 auto pattern = spr.pattern;
563 if (pattern & 0x8000'0000) {
570 }
else if (mode == 2) {
571 auto visibleSprites =
subspan(spriteBuffer[line], 0, count + 1);
576 if ((visibleSprites[first].colorAttrib & 0x40) == 0) [[likely]] {
581 for (
int i = narrow<int>(count - 1); i >= first; --i) {
582 const auto& spr = visibleSprites[i];
583 uint8_t c = spr.colorAttrib & 0x0F;
584 if (c == 0 && transparent)
continue;
586 auto pattern = spr.pattern;
591 if (pattern & 0x8000'0000) {
594 for (
int j = i + 1; ; ++j) {
595 const auto& info2 = visibleSprites[j];
596 if (!(info2.colorAttrib & 0x40))
break;
597 unsigned shift2 = x - info2.x;
599 ((info2.pattern << shift2) & 0x8000'0000)) {
600 color |= info2.colorAttrib & 0x0F;
608 lineBuf[x] = palette[color];
616 if (!renderTex.
get()) {
620 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, lines, 0,
621 GL_RGBA, GL_UNSIGNED_BYTE, screen.data());
623 std::array<SpriteBox, 2 * 32> clippedBoxes;
624 int nrClippedBoxes = 0;
625 auto addClippedBox = [&](SpriteBox b) {
626 assert(nrClippedBoxes < 64);
627 clippedBoxes[nrClippedBoxes] = b;
630 for (
int sprite :
xrange(spriteCnt)) {
631 const auto& b = spriteBoxes[sprite];
638 if (w <= 0)
continue;
640 }
else if (x + w > 256) {
646 addClippedBox(SpriteBox{x, y, w, h, b.sprite, b.vramX, b.vramY, b.pattern});
648 addClippedBox(SpriteBox{x, y, w, 256 - y, b.sprite, b.vramX, b.vramY, b.pattern});
649 addClippedBox(SpriteBox{x, 0, w, yEnd - 256, b.sprite, b.vramX, b.vramY, b.pattern});
653 auto fullSize = float(zm) *
gl::vec2(256,
float(lines));
654 im::Child(
"##screen", {0.0f, fullSize.y}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
655 auto* drawList = ImGui::GetWindowDrawList();
656 gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
657 auto boxColor = ImGui::ColorConvertFloat4ToU32(boundingBoxColor);
658 auto drawBox = [&](
int x,
int y,
int w,
int h) {
661 drawList->AddRect(tl, br, boxColor);
664 gl::vec2 topLeft = ImGui::GetCursorPos();
665 if (checkerBoardSize) {
666 ImGui::Image(checkerTex.
getImGui(), fullSize,
667 {}, fullSize / (4.0f *
float(checkerBoardSize)));
669 ImGui::SetCursorPos(topLeft);
670 ImGui::Image(renderTex.
getImGui(), fullSize);
671 bool hovered = ImGui::IsItemHovered();
672 auto hoverPos = trunc((
gl::vec2(ImGui::GetIO().MousePos) - scrnPos) /
gl::vec2(
float(zm)));
676 ImGui::Checkbox(
"Bounding box", &drawBoundingBox);
679 ImGui::ColorEdit4(
"color", boundingBoxColor.data(),
680 ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_AlphaBar);
681 ImGui::RadioButton(
"On hovered sprites", &boundingBoxOnAll, 0);
682 ImGui::RadioButton(
"On all sprites", &boundingBoxOnAll, 1);
686 auto maxStr =
strCat(
"Max ", spriteLimit,
" sprites per line");
687 ImGui::Checkbox(maxStr.c_str(), &enableLimitPerLine);
688 auto stopStr =
strCat(
"Stop at y=", stopY);
689 ImGui::Checkbox(stopStr.c_str(), &enableStopY);
693 auto [hx, hy] = hoverPos;
694 ImGui::Text(
"x=%d y=%d", hx, hy);
697 for (
int i :
xrange(nrClippedBoxes)) {
698 const auto& b = clippedBoxes[i];
699 if ((b.x <= hx) && (hx < (b.x + b.w)) &&
700 (b.y <= hy) && (hy < (b.y + b.h))) {
701 if (!boundingBoxOnAll) {
702 drawBox(b.x, b.y, b.w, b.h);
704 ImGui::Text(
"sprite=%d x=%d y=%d pat=%d", b.sprite, b.vramX, b.vramY, b.pattern);
710 if (boundingBoxOnAll) {
711 for (
int i :
xrange(nrClippedBoxes)) {
712 const auto& b = clippedBoxes[i];
713 drawBox(b.x, b.y, b.w, b.h);