OXYGENHUB
◆ main openmsx / src / imgui / ImGuiCharacter.cc
28.8 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "ImGuiCharacter.hh"
2
3
#include "ImGuiCpp.hh"
4
#include "ImGuiManager.hh"
5
#include "ImGuiPalette.hh"
6
#include "ImGuiUtils.hh"
7
8
#include "DisplayMode.hh"
9
#include "MSXMotherBoard.hh"
10
#include "VDP.hh"
11
#include "VDPVRAM.hh"
12
13
#include "one_of.hh"
14
#include "ranges.hh"
15
16
#include <imgui.h>
17
18
#include <ranges>
19
20
namespace openmsx {
21
22
using namespace std::literals;
23
24
ImGuiCharacter::ImGuiCharacter(ImGuiManager& manager_, size_t index)
25
	: ImGuiPart(manager_)
26
	, title(strCat("Tile viewer", strCat_if(index, " (", index + 1, ')')))
27
{
28
}
29
30
void ImGuiCharacter::save(ImGuiTextBuffer& buf)
31
{
32
	savePersistent(buf, *this, persistentElements);
33
}
34
35
void ImGuiCharacter::loadLine(std::string_view name, zstring_view value)
36
{
37
	loadOnePersistent(name, value, *this, persistentElements);
38
}
39
40
void ImGuiCharacter::initHexDigits()
41
{
42
	smallHexDigits = gl::Texture(false, false); // no interpolation, no wrapping
43
44
	// font definition: 16 glyphs 0-9 A-F, each 5 x 8 pixels
45
	static constexpr int charWidth = 5;
46
	static constexpr int charHeight = 8;
47
	static constexpr int totalWidth = 16 * charWidth;
48
	static constexpr int totalSize = totalWidth * charHeight;
49
	static constexpr std::string_view glyphs =
50
		" ...  ...  ................................................  ........ .........."
51
		".MMM...M. .MMM..MMM..M.M..MMM..MMM..MMM..MMM..MMM..MMM..MM....MM..MM...MMM..MMM."
52
		".M.M..MM. ...M....M..M.M..M....M......M..M.M..M.M..M.M..M.M..M....M.M..M....M..."
53
		".M.M...M.  .MM..MMM..M.M..MMM..MMM.  .M..MMM..MMM..MMM..MM...M.  .M.M..MMM..MMM."
54
		".M.M. .M. .M.. ...M..MMM....M..M.M.  .M..M.M....M..M.M..M.M..M.  .M.M..M....M..."
55
		".M.M. .M. .M......M....M....M..M.M.  .M..M.M....M..M.M..M.M..M....M.M..M....M.  "
56
		".MMM. .M. .MMM..MMM.  .M..MMM..MMM.  .M..MMM..MMM..M.M..MM....MM..MM...MMM..M.  "
57
		" ...  ... ..........  .............  ......................  ........ ........  ";
58
	static_assert(glyphs.size() == totalSize);
59
60
	// transform to 32-bit RGBA
61
	std::array<uint32_t, totalSize> pixels;
62
	for (auto [c, p] : std::views::zip(glyphs, pixels)) {
63
		p = (c == ' ') ? ImColor(0.0f, 0.0f, 0.0f, 0.0f)  // transparent
64
		  : (c == '.') ? ImColor(0.0f, 0.0f, 0.0f, 0.7f)  // black semi-transparent outline
65
		               : ImColor(1.0f, 1.0f, 1.0f, 0.7f); // white semi-transparent
66
	}
67
68
	// and upload as a texture
69
	smallHexDigits.bind();
70
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, totalWidth, charHeight, 0,
71
	             GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
72
}
73
74
void ImGuiCharacter::paint(MSXMotherBoard* motherBoard)
75
{
76
	if (!show || !motherBoard) return;
77
	ImGui::SetNextWindowSize({692, 886}, ImGuiCond_FirstUseEver);
78
	im::Window(title.c_str(), &show, [&]{
79
		auto* vdp = dynamic_cast<VDP*>(motherBoard->findDevice("VDP")); // TODO name based OK?
80
		if (!vdp) return;
81
		const auto& vram = vdp->getVRAM().getData();
82
83
		int vdpMode = [&] {
84
			auto base = vdp->getDisplayMode().getBase();
85
			if (base == DisplayMode::TEXT1) return TEXT40;
86
			if (base == DisplayMode::TEXT2) return TEXT80;
87
			if (base == DisplayMode::GRAPHIC1) return SCR1;
88
			if (base == DisplayMode::GRAPHIC2) return SCR2;
89
			if (base == DisplayMode::GRAPHIC3) return SCR4;
90
			if (base == DisplayMode::MULTICOLOR) return SCR3;
91
			return OTHER;
92
		}();
93
		auto modeToStr = [](int mode) {
94
			if (mode == TEXT40) return "screen 0, width 40";
95
			if (mode == TEXT80) return "screen 0, width 80";
96
			if (mode == SCR1)   return "screen 1";
97
			if (mode == SCR2)   return "screen 2";
98
			if (mode == SCR3)   return "screen 3";
99
			if (mode == SCR4)   return "screen 4";
100
			if (mode == OTHER)  return "non-character";
101
			assert(false); return "ERROR";
102
		};
103
		auto patMult = [](int mode) { return 1 << (mode == one_of(SCR2, SCR4) ? 13 : 11); };
104
		auto colMult = [](int mode) { return 1 << (mode == one_of(SCR2, SCR4) ? 13 :
105
							mode == TEXT80             ?  9 :  6); };
106
		auto namMult = [](int mode) { return 1 << (mode == TEXT80 ? 12 : 10); };
107
		int vdpFgCol = vdp->getForegroundColor() & 15;
108
		int vdpBgCol = vdp->getBackgroundColor() & 15;
109
		int vdpFgBlink = vdp->getBlinkForegroundColor() & 15;
110
		int vdpBgBlink = vdp->getBlinkBackgroundColor() & 15;
111
		bool vdpBlink = vdp->getBlinkState(0); // fast-blink is not implemented here
112
		int vdpPatBase = vdp->getPatternTableBase() & ~(patMult(vdpMode) - 1);
113
		int vdpColBase = vdp->getColorTableBase() & ~(colMult(vdpMode) - 1);
114
		int vdpNamBase = vdp->getNameTableBase() & ~(namMult(vdpMode) - 1);
115
		int vdpLines = vdp->getNumberOfLines();
116
		int vdpColor0 = vdp->getTransparency() ? vdpBgCol
117
		                                       : 16; // no replacement
118
		auto vramSize = std::min(vdp->getVRAM().getSize(), 0x20000u); // max 128kB
119
		bool isMSX1 = vdp->isMSX1VDP();
120
121
		bool manMode    = overrideAll || overrideMode;
122
		bool manFgCol   = overrideAll || overrideFgCol;
123
		bool manBgCol   = overrideAll || overrideBgCol;
124
		bool manFgBlink = overrideAll || overrideFgBlink;
125
		bool manBgBlink = overrideAll || overrideBgBlink;
126
		bool manBlink   = overrideAll || overrideBlink;
127
		bool manPat     = overrideAll || overridePat;
128
		bool manCol     = overrideAll || overrideCol;
129
		bool manNam     = overrideAll || overrideNam;
130
		bool manRows    = overrideAll || overrideRows;
131
		bool manColor0  = overrideAll || overrideColor0;
132
133
		int mode = manMode ? manualMode : vdpMode;
134
		if (mode == SCR4) mode = SCR2;
135
136
		auto rasterBeamPos = vdp->getMSXPos(vdp->getCurrentTime());
137
		if (mode == one_of(TEXT40, TEXT80)) rasterBeamPos.x -= (512 - (6 * 80)) / 2;
138
		if (mode != TEXT80) rasterBeamPos.x /= 2;
139
140
		im::TreeNode("Settings", ImGuiTreeNodeFlags_DefaultOpen, [&]{
141
			static const char* const color0Str = "0\0001\0002\0003\0004\0005\0006\0007\0008\0009\00010\00011\00012\00013\00014\00015\000none\000";
142
			im::Group([&]{
143
				ImGui::TextUnformatted("VDP settings");
144
				auto pos = ImGui::GetCursorPos();
145
				ImGui::Dummy(ImGui::CalcTextSize("Screen mode: screen 0, width 80"));
146
				ImGui::SetCursorPos(pos);
147
				im::Disabled(manMode, [&]{
148
					ImGui::AlignTextToFramePadding();
149
					ImGui::StrCat("Screen mode: ", modeToStr(vdpMode));
150
				});
151
				im::Disabled(manFgCol, [&]{
152
					ImGui::AlignTextToFramePadding();
153
					ImGui::StrCat("Foreground color: ", vdpFgCol);
154
				});
155
				im::Disabled(manBgCol, [&]{
156
					ImGui::AlignTextToFramePadding();
157
					ImGui::StrCat("Background color: ", vdpBgCol);
158
				});
159
				im::Disabled(manFgBlink, [&]{
160
					ImGui::AlignTextToFramePadding();
161
					ImGui::StrCat("Foreground blink color: ", vdpFgBlink);
162
				});
163
				im::Disabled(manBgBlink, [&]{
164
					ImGui::AlignTextToFramePadding();
165
					ImGui::StrCat("Background blink color: ", vdpBgBlink);
166
				});
167
				im::Disabled(manBlink, [&]{
168
					ImGui::AlignTextToFramePadding();
169
					ImGui::StrCat("Blink: ", vdpBlink ? "enabled"sv : "disabled"sv);
170
				});
171
				im::Disabled(manPat, [&]{
172
					ImGui::AlignTextToFramePadding();
173
					ImGui::StrCat("Pattern table: 0x", hex_string<5>(vdpPatBase));
174
				});
175
				im::Disabled(manCol, [&]{
176
					ImGui::AlignTextToFramePadding();
177
					ImGui::StrCat("Color table: 0x", hex_string<5>(vdpColBase));
178
				});
179
				im::Disabled(manNam, [&]{
180
					ImGui::AlignTextToFramePadding();
181
					ImGui::StrCat("Name table: 0x", hex_string<5>(vdpNamBase));
182
				});
183
				im::Disabled(manRows, [&]{
184
					ImGui::AlignTextToFramePadding();
185
					ImGui::StrCat("Visible rows: ", (vdpLines == 192) ? "24"sv : "26.5"sv);
186
				});
187
				im::Disabled(manColor0, [&]{
188
					ImGui::AlignTextToFramePadding();
189
					ImGui::StrCat("Replace color 0: ", getComboString(vdpColor0, color0Str));
190
				});
191
			});
192
			ImGui::SameLine();
193
			im::Group([&]{
194
				ImGui::Checkbox("Manual override", &overrideAll);
195
				im::Group([&]{
196
					im::Disabled(overrideAll, [&]{
197
						ImGui::Checkbox("##o-mode",    overrideAll ? &overrideAll : &overrideMode);
198
						ImGui::Checkbox("##o-fgCol",   overrideAll ? &overrideAll : &overrideFgCol);
199
						ImGui::Checkbox("##o-bgCol",   overrideAll ? &overrideAll : &overrideBgCol);
200
						ImGui::Checkbox("##o-fgBlink", overrideAll ? &overrideAll : &overrideFgBlink);
201
						ImGui::Checkbox("##o-bgBlink", overrideAll ? &overrideAll : &overrideBgBlink);
202
						ImGui::Checkbox("##o-blink",   overrideAll ? &overrideAll : &overrideBlink);
203
						ImGui::Checkbox("##o-pat",     overrideAll ? &overrideAll : &overridePat);
204
						ImGui::Checkbox("##o-col",     overrideAll ? &overrideAll : &overrideCol);
205
						ImGui::Checkbox("##o-nam",     overrideAll ? &overrideAll : &overrideNam);
206
						ImGui::Checkbox("##o-rows",    overrideAll ? &overrideAll : &overrideRows);
207
						ImGui::Checkbox("##o-color0",  overrideAll ? &overrideAll : &overrideColor0);
208
					});
209
				});
210
				ImGui::SameLine();
211
				im::Group([&]{
212
					im::ItemWidth(ImGui::GetFontSize() * 9.0f, [&]{
213
						im::Disabled(!manMode, [&]{
214
							if (isMSX1 && (manualMode == one_of(TEXT80, SCR4))) manualMode = TEXT40;
215
							im::Combo("##mode", modeToStr(manualMode), [&]{
216
								if            (ImGui::Selectable("screen 0,40")) manualMode = TEXT40;
217
								if (!isMSX1 && ImGui::Selectable("screen 0,80")) manualMode = TEXT80;
218
								if            (ImGui::Selectable("screen 1"))    manualMode = SCR1;
219
								if            (ImGui::Selectable("screen 2"))    manualMode = SCR2;
220
								if            (ImGui::Selectable("screen 3"))    manualMode = SCR3;
221
								if (!isMSX1 && ImGui::Selectable("screen 4"))    manualMode = SCR4;
222
							});
223
						});
224
						static const char* const range0_15 = "0\0001\0002\0003\0004\0005\0006\0007\0008\0009\00010\00011\00012\00013\00014\00015\000";
225
						im::Disabled(manualMode != one_of(TEXT40, TEXT80), [&]{
226
							im::Disabled(!manFgCol, [&]{
227
								ImGui::Combo("##fgCol", &manualFgCol, range0_15);
228
							});
229
							im::Disabled(!manBgCol, [&]{
230
								ImGui::Combo("##bgCol", &manualBgCol, range0_15);
231
							});
232
						});
233
						im::Disabled(manualMode != TEXT80, [&]{
234
							im::Disabled(!manFgBlink, [&]{
235
								ImGui::Combo("##fgBlink", &manualFgBlink, range0_15);
236
							});
237
							im::Disabled(!manBgBlink, [&]{
238
								ImGui::Combo("##bgBlink", &manualBgBlink, range0_15);
239
							});
240
							im::Disabled(!manBlink, [&]{
241
								ImGui::Combo("##blink", &manualBlink, "disabled\000enabled\000");
242
							});
243
						});
244
						im::Disabled(!manPat, [&]{
245
							comboHexSequence<5>("##pattern", &manualPatBase, patMult(manualMode), vramSize, 0);
246
						});
247
						im::Disabled(manualMode == one_of(TEXT40, SCR3), [&]{
248
							im::Disabled(!manCol, [&]{
249
								comboHexSequence<5>("##color", &manualColBase, colMult(manualMode), vramSize, 0);
250
							});
251
						});
252
						im::Disabled(!manNam, [&]{
253
							comboHexSequence<5>("##name", &manualNamBase, namMult(manualMode), vramSize, 0);
254
						});
255
						im::Disabled(!manRows, [&]{
256
							ImGui::Combo("##rows", &manualRows, "24\00026.5\00032\000");
257
						});
258
						im::Disabled(!manColor0, [&]{
259
							ImGui::Combo("##Color 0 replacement", &manualColor0, color0Str);
260
						});
261
					});
262
				});
263
			});
264
265
			ImGui::SameLine();
266
			ImGui::Dummy(ImVec2(25, 1));
267
			ImGui::SameLine();
268
			im::Group([&]{
269
				ImGui::SetNextItemWidth(ImGui::GetFontSize() * 10.0f);
270
				ImGui::Combo("Palette", &manager.palette->whichPalette, "VDP\000Custom\000Fixed\000");
271
				if (ImGui::Button("Open palette editor")) {
272
					manager.palette->window.raise();
273
				}
274
				ImGui::Separator();
275
				ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.0f);
276
				ImGui::Combo("Zoom", &zoom, "1x\0002x\0003x\0004x\0005x\0006x\0007x\0008x\000");
277
				ImGui::Checkbox("grid", &grid);
278
				ImGui::SameLine();
279
				im::Disabled(!grid, [&]{
280
					ImGui::ColorEdit4("Grid color", gridColor.data(),
281
						ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar);
282
				});
283
				ImGui::Checkbox("Name table overlay", &nameTableOverlay);
284
285
				ImGui::Checkbox("Override color table", &overrideColorTable);
286
				HelpMarker("Ignore the content of the color table and instead use this value for everything.\n"
287
				           "Could be useful to visualize the patterns when the color table has the same value for fg and bg.");
288
				const char* col0_15 = "0\0001\0002\0003\0004\0005\0006\0007\000"
289
				                      "8\0009\00010\00011\00012\00013\00014\00015\000";
290
				im::DisabledIndent(!overrideColorTable, [&]{
291
					int fg = (overrideColorValue >> 4) & 0x0f;
292
					int bg = (overrideColorValue >> 0) & 0x0f;
293
					ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.0f);
294
					if (ImGui::Combo("fg", &fg, col0_15)) {
295
						overrideColorValue = (fg << 4) | bg;
296
					}
297
					ImGui::SetNextItemWidth(ImGui::GetFontSize() * 3.0f);
298
					ImGui::SameLine();
299
					if (ImGui::Combo("bg", &bg, col0_15)) {
300
						overrideColorValue = (fg << 4) | bg;
301
					}
302
				});
303
304
				ImGui::Separator();
305
				ImGui::Checkbox("beam", &rasterBeam);
306
				ImGui::SameLine();
307
				im::Disabled(!rasterBeam, [&]{
308
					ImGui::ColorEdit4("raster beam color", rasterBeamColor.data(),
309
						ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaBar);
310
					ImGui::SameLine();
311
					im::Font(manager.fontMono, [&]{
312
						ImGui::StrCat('(', dec_string<4>(rasterBeamPos.x),
313
						              ',', dec_string<4>(rasterBeamPos.y), ')');
314
					});
315
				});
316
				HelpMarker("Position of the raster beam, expressed in MSX coordinates.\n"
317
				           "Left/top border have negative x/y-coordinates.\n"
318
				           "Only practically useful when emulation is paused.");
319
			});
320
		});
321
		int manualLines = (manualRows == 0) ? 192
322
		                : (manualRows == 1) ? 212
323
		                : 256;
324
325
		int lines = manRows ? manualLines : vdpLines;
326
		int color0 = manColor0 ? manualColor0 : vdpColor0;
327
328
		VramTable patTable(vram);
329
		unsigned patReg = (manPat ? (manualPatBase | (patMult(manualMode) - 1)) : vdp->getPatternTableBase()) >> 11;
330
		patTable.setRegister(patReg, 11);
331
332
		VramTable colTable(vram);
333
		unsigned colReg = (manCol ? (manualColBase | (colMult(manualMode) - 1)) : vdp->getColorTableBase()) >> 6;
334
		colTable.setRegister(colReg, 6);
335
336
		auto colorTabVal = uint8_t(overrideColorValue);
337
		if (overrideColorTable) {
338
			colTable = VramTable(std::span(&colorTabVal, 1));
339
			colTable.setRegister(0, 0);
340
		}
341
342
		VramTable namTable(vram);
343
		unsigned namReg = (manNam ? (manualNamBase | (namMult(manualMode) - 1)) : vdp->getNameTableBase()) >> 10;
344
		namTable.setRegister(namReg, 10);
345
		namTable.setIndexSize((mode == TEXT80) ? 12 : 10);
346
347
		auto palette = manager.palette->getPalette(vdp);
348
		if (color0 < 16) palette[0] = palette[color0];
349
350
		auto fgCol = manFgCol ? manualFgCol : vdpFgCol;
351
		auto bgCol = manBgCol ? manualBgCol : vdpBgCol;
352
		auto fgBlink = manFgBlink ? manualFgBlink : vdpFgBlink;
353
		auto bgBlink = manBgBlink ? manualBgBlink : vdpBgBlink;
354
		auto blink = manBlink ? bool(manualBlink) : vdpBlink;
355
356
		bool narrow = mode == TEXT80;
357
		int zx = (1 + zoom) * (narrow ? 1 : 2);
358
		int zy = (1 + zoom) * 2;
359
		gl::vec2 zm{float(zx), float(zy)};
360
361
		// Render the patterns to a texture, we need this both to display the name-table and the pattern-table
362
		auto patternTexSize = [&] -> gl::ivec2 {
363
			if (mode == TEXT40) return {192,  64}; // 8 rows of 32 characters, each 6x8 pixels
364
			if (mode == TEXT80) return {192, 128}; // 8 rows of 32 characters, each 6x8 pixels, x2 for blink
365
			if (mode == SCR1) return {256,  64}; // 8 rows of 32 characters, each 8x8 pixels
366
			if (mode == SCR2) return {256, lines == 192 ? 192 : 256}; // 8 rows of 32 characters, each 8x8 pixels, all this 3 or 4 times
367
			if (mode == SCR3) return { 64,  64}; // 8 rows of 32 characters, each 2x2 pixels, all this 4 times
368
			return {1, 1}; // OTHER -> dummy
369
		}();
370
		auto patternTexChars = [&] -> gl::vec2 {
371
			if (mode == SCR3) return {32.0f, 32.0f};
372
			if (mode == SCR2) return {32.0f, lines == 192 ? 24.0f : 32.0f};
373
			if (mode == TEXT80) return {32.0f, 16.0f};
374
			return {32.0f, 8.0f};
375
		}();
376
		auto recipPatTexChars = recip(patternTexChars);
377
		auto patternDisplaySize = [&] -> gl::ivec2 {
378
			if (mode == TEXT40) return {192,  64};
379
			if (mode == TEXT80) return {192, 128};
380
			if (mode == SCR2) return {256, lines == 192 ? 192 : 256};
381
			if (mode == SCR3) return {256, 256};
382
			return {256,  64}; // SCR1, OTHER
383
		}();
384
		std::array<uint32_t, 256 * 256> pixels; // max size for SCR2
385
		renderPatterns(mode, palette, fgCol, bgCol, fgBlink, bgBlink, patTable, colTable, lines, pixels);
386
		if (!patternTex.get()) {
387
			patternTex = gl::Texture(false, false); // no interpolation, no wrapping
388
		}
389
		patternTex.bind();
390
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, patternTexSize.x, patternTexSize.y, 0,
391
			GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
392
393
		// create grid texture
394
		auto charWidth = mode == one_of(TEXT40, TEXT80) ? 6 : 8;
395
		auto charSize = gl::vec2{float(charWidth), 8.0f};
396
		auto charZoom = zm * charSize;
397
		auto zoomCharSize = gl::vec2(narrow ? 6.0f : 12.0f, 12.0f) * charSize; // hovered size
398
		if (grid) {
399
			auto gColor = ImGui::ColorConvertFloat4ToU32(gridColor);
400
			auto gridWidth = charWidth * zx;
401
			auto gridHeight = 8 * zy;
402
			for (auto y : xrange(gridHeight)) {
403
				auto* line = &pixels[y * gridWidth];
404
				for (auto x : xrange(gridWidth)) {
405
					line[x] = (x == 0 || y == 0) ? gColor : 0;
406
				}
407
			}
408
			if (!gridTex.get()) {
409
				gridTex = gl::Texture(false, true); // no interpolation, with wrapping
410
			}
411
			gridTex.bind();
412
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, gridWidth, gridHeight, 0,
413
				GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
414
		}
415
		if (nameTableOverlay && !smallHexDigits.get()) {
416
			initHexDigits();
417
		}
418
419
		auto printPatternNr = [](unsigned pat) {
420
			ImGui::StrCat("Pattern: ", pat, " (0x", hex_string<3>(pat), ')');
421
		};
422
		auto printAddressName = [](std::string_view name) {
423
			ImGui::StrCat(name, " address:");
424
		};
425
		auto printAddress = [&](std::string_view name, unsigned address) {
426
			printAddressName(name);
427
			ImGui::StrCat("  0x", hex_string<5>(address));
428
		};
429
		auto printAddressRange8 = [&](std::string_view name, unsigned address) {
430
			printAddressName(name);
431
			ImGui::StrCat("  0x", hex_string<5>(address), "-0x", hex_string<5>(address + 7));
432
		};
433
		auto printPatternColorAddress = [&](unsigned pattern) {
434
			printAddressRange8("Pattern", patTable.getAddress(8 * pattern));
435
			if (mode == one_of(SCR1, SCR2, SCR4)) {
436
				printAddressRange8("Color", colTable.getAddress((mode == SCR1) ? (pattern / 8) : (8 * pattern)));
437
			}
438
		};
439
		auto formatBinaryData = [&](unsigned address) {
440
			return formatToString([&](unsigned addr){ return vram[addr]; }, address, address + 7, {}, 1, {}, ", ", "%02X", manager.getInterpreter());
441
		};
442
		auto getPatternFromGrid = [&] {
443
			return gridPosition.x + 32 * gridPosition.y;
444
		};
445
		auto copyPatternColorPopup = [&] {
446
			return im::PopupContextWindow("PatternCopyPopup", [&]{
447
				bool hasColorData = mode == one_of(SCR1, SCR2);
448
				const char* caption = hasColorData
449
					? "Copy pattern and color data to clipboard"
450
					: "Copy pattern data to clipboard";
451
				if (ImGui::MenuItem(caption)) {
452
					auto pattern = getPatternFromGrid();
453
					auto text = strCat("Pattern data\n", formatBinaryData(patTable.getAddress(8 * pattern)));
454
					if (hasColorData) {
455
						auto cp = (mode == SCR1) ? (pattern / 8) : (8 * pattern);
456
						strAppend(text, "Color data\n", formatBinaryData(colTable.getAddress(cp)));
457
					}
458
					ImGui::SetClipboardText(text.c_str());
459
				}
460
			});
461
		};
462
		auto drawGrid = [&] {
463
			auto pat = getPatternFromGrid();
464
			printPatternNr(pat);
465
			auto uv1 = gl::vec2(gridPosition) * recipPatTexChars;
466
			auto uv2 = uv1 + recipPatTexChars;
467
			auto pos2 = ImGui::GetCursorPos();
468
			ImGui::Image(patternTex.getImGui(), zoomCharSize, uv1, uv2);
469
			if (grid) {
470
				ImGui::SetCursorPos(pos2);
471
				ImGui::Image(gridTex.getImGui(),
472
					zoomCharSize, {}, charSize);
473
			}
474
			printPatternColorAddress(pat);
475
		};
476
477
		ImGui::Separator();
478
		im::TreeNode("Pattern Table", ImGuiTreeNodeFlags_DefaultOpen, [&]{
479
			auto size = gl::vec2(patternDisplaySize) * zm;
480
			auto textLines = (mode == TEXT40) ? 3.0f : 5.0f;
481
			auto previewHeight = zoomCharSize.y + textLines * ImGui::GetTextLineHeightWithSpacing();
482
			im::Child("##pattern", {0, std::max(size.y, previewHeight)}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
483
				auto pos1 = ImGui::GetCursorPos();
484
				gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
485
				ImGui::Image(patternTex.getImGui(), size);
486
				bool hovered = ImGui::IsItemHovered() && (mode != OTHER);
487
				ImGui::SameLine();
488
				im::Group([&]{
489
					if (copyPatternColorPopup()) {
490
						drawGrid();
491
					} else if (hovered) {
492
						// store last selected position before popup
493
						gridPosition = trunc((gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / charZoom);
494
						drawGrid();
495
					} else {
496
						ImGui::Dummy(zoomCharSize);
497
					}
498
				});
499
				if (grid) {
500
					ImGui::SetCursorPos(pos1);
501
					ImGui::Image(gridTex.getImGui(), size,
502
						{}, patternTexChars);
503
				}
504
			});
505
		});
506
		ImGui::Separator();
507
		im::TreeNode("Name Table", ImGuiTreeNodeFlags_DefaultOpen, [&]{
508
			float rows = float(lines) * (1.0f / 8.0f); // 24, 26.5 or 32
509
			auto columns = [&] {
510
				if (mode == TEXT40) return 40;
511
				if (mode == TEXT80) return 80;
512
				return 32;
513
			}();
514
			auto charsSize = gl::vec2(float(columns), rows); // (x * y) number of characters
515
			auto msxSize = charSize * charsSize; // (x * y) number of MSX pixels
516
			auto hostSize = msxSize * zm; // (x * y) number of host pixels
517
518
			im::Child("##name", {0.0f, hostSize.y}, 0, ImGuiWindowFlags_HorizontalScrollbar, [&]{
519
				gl::vec2 scrnPos = ImGui::GetCursorScreenPos();
520
				auto* drawList = ImGui::GetWindowDrawList();
521
522
				auto getPattern = [&](unsigned column, unsigned row) {
523
					auto block = [&] -> unsigned {
524
						if (mode == TEXT80 && blink) {
525
							auto colPat = colTable[10 * row + (column / 8)];
526
							return (colPat & (0x80 >> (column % 8))) ? 1 : 0;
527
						}
528
						if (mode == SCR2) return row / 8;
529
						if (mode == SCR3) return row % 4;
530
						return 0;
531
					}();
532
					return namTable[columns * row + column] + 256 * block;
533
				};
534
				auto getPatternUV = [&](unsigned pattern) -> std::pair<gl::vec2, gl::vec2> {
535
					auto patRow = pattern / 32;
536
					auto patCol = pattern % 32;
537
					gl::vec2 uv1 = gl::vec2{float(patCol), float(patRow)} * recipPatTexChars;
538
					gl::vec2 uv2 = uv1 + recipPatTexChars;
539
					return {uv1, uv2};
540
				};
541
542
				if (mode == OTHER) {
543
					drawList->AddRectFilled(scrnPos, scrnPos + hostSize, getColor(imColor::GRAY));
544
				} else {
545
					auto rowsCeil = int(ceilf(rows));
546
					auto numChars = rowsCeil * columns;
547
					drawList->PushClipRect(scrnPos, scrnPos + hostSize, true);
548
					drawList->PushTexture(patternTex.getImGui());
549
					drawList->PrimReserve(6 * numChars, 4 * numChars);
550
					for (auto row : xrange(rowsCeil)) {
551
						for (auto column : xrange(columns)) {
552
							gl::vec2 p1 = scrnPos + charZoom * gl::vec2{float(column), float(row)};
553
							gl::vec2 p2 = p1 + charZoom;
554
							auto pattern = getPattern(column, row);
555
							auto [uv1, uv2] = getPatternUV(pattern);
556
							drawList->PrimRectUV(p1, p2, uv1, uv2, 0xffffffff);
557
						}
558
					}
559
					drawList->PopTexture();
560
					if (nameTableOverlay) {
561
						drawList->PushTexture(smallHexDigits.getImGui());
562
						drawList->PrimReserve(12 * numChars, 8 * numChars);
563
						static constexpr gl::vec2 digitSize{5.0f, 8.0f};
564
						static constexpr float texDigitWidth = 1.0f / 16.0f;
565
						static constexpr gl::vec2 texDigitSize{texDigitWidth, 1.0f};
566
						auto digitOffset = narrow ? gl::vec2{0.0f, digitSize.y} : gl::vec2{digitSize.x, 0.0f};
567
						for (auto row : xrange(rowsCeil)) {
568
							for (auto column : xrange(columns)) {
569
								gl::vec2 p1 = scrnPos + charZoom * gl::vec2{float(column), float(row)};
570
								gl::vec2 p2 = p1 + digitOffset;
571
								auto pattern = getPattern(column, row);
572
								gl::vec2 uv1{narrow_cast<float>((pattern >> 4) & 15) * texDigitWidth, 0.0f};
573
								gl::vec2 uv2{narrow_cast<float>((pattern >> 0) & 15) * texDigitWidth, 0.0f};
574
								drawList->PrimRectUV(p1, p1 + digitSize, uv1, uv1 + texDigitSize, 0xffffffff);
575
								drawList->PrimRectUV(p2, p2 + digitSize, uv2, uv2 + texDigitSize, 0xffffffff);
576
							}
577
						}
578
						drawList->PopTexture();
579
					}
580
					drawList->PopClipRect();
581
				}
582
				if (rasterBeam) {
583
					auto center = scrnPos + (gl::vec2(rasterBeamPos) + gl::vec2{0.5f}) * zm;
584
					auto color = ImGui::ColorConvertFloat4ToU32(rasterBeamColor);
585
					auto thickness = zm.y * 0.5f;
586
					auto zm1 = 1.5f * zm;
587
					auto zm3 = 3.5f * zm;
588
					drawList->AddRect(center - zm, center + zm, color, 0.0f, thickness);
589
					drawList->AddLine(center - gl::vec2{zm1.x, 0.0f}, center - gl::vec2{zm3.x, 0.0f}, color, thickness);
590
					drawList->AddLine(center + gl::vec2{zm1.x, 0.0f}, center + gl::vec2{zm3.x, 0.0f}, color, thickness);
591
					drawList->AddLine(center - gl::vec2{0.0f, zm1.y}, center - gl::vec2{0.0f, zm3.y}, color, thickness);
592
					drawList->AddLine(center + gl::vec2{0.0f, zm1.y}, center + gl::vec2{0.0f, zm3.y}, color, thickness);
593
				}
594
595
				auto pos1 = ImGui::GetCursorPos();
596
				ImGui::Dummy(hostSize);
597
				bool hovered = ImGui::IsItemHovered() && (mode != OTHER);
598
				ImGui::SameLine();
599
				im::Group([&]{
600
					if (hovered) {
601
						auto gridPos = trunc((gl::vec2(ImGui::GetIO().MousePos) - scrnPos) / charZoom);
602
						ImGui::StrCat("Column: ", gridPos.x, " Row: ", gridPos.y);
603
						auto pattern = getPattern(gridPos.x, gridPos.y);
604
						printPatternNr(pattern);
605
						auto [uv1, uv2] = getPatternUV(pattern);
606
						auto pos2 = ImGui::GetCursorPos();
607
						ImGui::Image(patternTex.getImGui(), zoomCharSize, uv1, uv2);
608
						if (grid) {
609
							ImGui::SetCursorPos(pos2);
610
							ImGui::Image(gridTex.getImGui(),
611
								zoomCharSize, {}, charSize);
612
						}
613
						auto nameIndex = columns * gridPos.y + gridPos.x;
614
						printAddress("Name", namTable.getAddress(nameIndex));
615
						printPatternColorAddress(pattern);
616
						if (mode == TEXT80) {
617
							printAddress("Color", colTable.getAddress(nameIndex / 8));
618
						}
619
					} else {
620
						gl::vec2 textSize = ImGui::CalcTextSize("Column: 31 Row: 23"sv);
621
						ImGui::Dummy(max(zoomCharSize, textSize));
622
					}
623
				});
624
				if (grid) {
625
					ImGui::SetCursorPos(pos1);
626
					ImGui::Image(gridTex.getImGui(), hostSize, {}, charsSize);
627
				}
628
			});
629
		});
630
	});
631
}
632
633
static void draw6(uint8_t pattern, uint32_t fgCol, uint32_t bgCol, std::span<uint32_t, 6> out)
634
{
635
	out[0] = (pattern & 0x80) ? fgCol : bgCol;
636
	out[1] = (pattern & 0x40) ? fgCol : bgCol;
637
	out[2] = (pattern & 0x20) ? fgCol : bgCol;
638
	out[3] = (pattern & 0x10) ? fgCol : bgCol;
639
	out[4] = (pattern & 0x08) ? fgCol : bgCol;
640
	out[5] = (pattern & 0x04) ? fgCol : bgCol;
641
}
642
643
static void draw8(uint8_t pattern, uint32_t fgCol, uint32_t bgCol, std::span<uint32_t, 8> out)
644
{
645
	out[0] = (pattern & 0x80) ? fgCol : bgCol;
646
	out[1] = (pattern & 0x40) ? fgCol : bgCol;
647
	out[2] = (pattern & 0x20) ? fgCol : bgCol;
648
	out[3] = (pattern & 0x10) ? fgCol : bgCol;
649
	out[4] = (pattern & 0x08) ? fgCol : bgCol;
650
	out[5] = (pattern & 0x04) ? fgCol : bgCol;
651
	out[6] = (pattern & 0x02) ? fgCol : bgCol;
652
	out[7] = (pattern & 0x01) ? fgCol : bgCol;
653
}
654
655
void ImGuiCharacter::renderPatterns(int mode, std::span<const uint32_t, 16> palette,
656
                                    int fgCol, int bgCol, int fgBlink, int bgBlink,
657
                                    VramTable& pat, VramTable& col, int lines, std::span<uint32_t> output)
658
{
659
	switch (mode) {
660
	case TEXT40:
661
	case TEXT80: {
662
		pat.setIndexSize(11);
663
		col.setIndexSize(9); // only matters for TEXT80
664
		auto fg = palette[fgCol];
665
		auto bg = palette[bgCol];
666
		auto fgB = palette[fgBlink];
667
		auto bgB = palette[bgBlink];
668
		for (auto row : xrange(8)) {
669
			for (auto column : xrange(32)) {
670
				auto patNum = 32 * row + column;
671
				auto offset = 8 * patNum;
672
				for (auto y : xrange(8)) {
673
					auto pattern = pat[offset + y];
674
					auto out = subspan<6>(output, (8 * row + y) * 192 + 6 * column);
675
					draw6(pattern, fg, bg, out);
676
					if (mode == TEXT80) {
677
						auto out2 = subspan<6>(output, (64 + 8 * row + y) * 192 + 6 * column);
678
						draw6(pattern, fgB, bgB, out2);
679
					}
680
				}
681
			}
682
		}
683
		break;
684
	}
685
	case SCR1:
686
		pat.setIndexSize(11);
687
		col.setIndexSize(6);
688
		for (auto row : xrange(8)) {
689
			for (auto group : xrange(4)) { // 32 columns, split in 4 groups of 8
690
				auto color = col[4 * row + group];
691
				auto fg = palette[color >> 4];
692
				auto bg = palette[color & 15];
693
				for (auto subColumn : xrange(8)) {
694
					auto column = 8 * group + subColumn;
695
					auto patNum = 32 * row + column;
696
					auto offset = 8 * patNum;
697
					for (auto y : xrange(8)) {
698
						auto pattern = pat[offset + y];
699
						auto out = subspan<8>(output, (8 * row + y) * 256 + 8 * column);
700
						draw8(pattern, fg, bg, out);
701
					}
702
				}
703
			}
704
		}
705
		break;
706
	case SCR2:
707
		pat.setIndexSize(13);
708
		col.setIndexSize(13);
709
		for (auto row : xrange((lines == 192 ? 3 : 4) * 8)) {
710
			for (auto column : xrange(32)) {
711
				auto patNum = 32 * row + column;
712
				auto offset = 8 * patNum;
713
				for (auto y : xrange(8)) {
714
					auto pattern = pat[offset + y];
715
					auto color   = col[offset + y];
716
					auto fg = palette[color >> 4];
717
					auto bg = palette[color & 15];
718
					auto out = subspan<8>(output, (8 * row + y) * 256 + 8 * column);
719
					draw8(pattern, fg, bg, out);
720
				}
721
			}
722
		}
723
		break;
724
	case SCR3:
725
		pat.setIndexSize(11);
726
		col.setIndexSize(13); // not used?
727
		for (auto group : xrange(4)) {
728
			for (auto row : xrange(8)) {
729
				for (auto column : xrange(32)) {
730
					auto patNum = 32 * row + column;
731
					auto offset = 8 * patNum + 2 * group;
732
					for (auto y : xrange(2)) {
733
						auto out = subspan<2>(output, (16 * group + 2 * row + y) * 64 + 2 * column);
734
						auto pattern = pat[offset + y];
735
						out[0] = palette[pattern >> 4];
736
						out[1] = palette[pattern & 15];
737
					}
738
				}
739
			}
740
		}
741
		break;
742
	default:
743
		output[0] = getColor(imColor::GRAY);
744
		break;
745
	}
746
}
747
748
} // namespace openmsx