OXYGENHUB
◆ main openmsx / src / imgui / ImGuiVdpRegs.cc
29.7 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "ImGuiVdpRegs.hh"
2
3
#include "ImGuiCpp.hh"
4
#include "ImGuiUtils.hh"
5
6
#include "VDP.hh"
7
8
#include "strCat.hh"
9
#include "xrange.hh"
10
#include "zstring_view.hh"
11
12
#include "imgui.h"
13
#include "imgui_internal.h"
14
15
#include <array>
16
#include <bit>
17
18
namespace openmsx {
19
20
void ImGuiVdpRegs::save(ImGuiTextBuffer& buf)
21
{
22
	savePersistent(buf, *this, persistentElements);
23
}
24
25
void ImGuiVdpRegs::loadLine(std::string_view name, zstring_view value)
26
{
27
	loadOnePersistent(name, value, *this, persistentElements);
28
}
29
30
static zstring_view modeName(uint32_t v)
31
{
32
	// NOTE: bits M1 and M2 are swapped!!
33
	static constexpr std::array<zstring_view, 32> modeNames = {
34
		"Graphic 1 (screen 1)", // 0
35
		"Multicolor (screen 3)", // 2
36
		"Text 1 (screen 0, width 40)", // 1
37
		"3 - invalid", // 3
38
39
		"Graphic 2 (screen 2)", // 4
40
		"Multicolor-Q", // 6
41
		"Text 1-Q", // 5
42
		"7 - invalid", // 7
43
44
		"Graphic 3 (screen 4)", // 8
45
		"10 - invalid", // 10
46
		"Text 2 (screen 0, width 80)", // 9
47
		"11 - invalid", // 11
48
49
		"Graphic 4 (screen 5)", // 12
50
		"14 - invalid", // 14
51
		"13 - invalid", // 13
52
		"15 - invalid", // 15
53
54
		"Graphic 5 (screen 6)", // 16
55
		"18 - invalid", // 18
56
		"17 - invalid", // 17
57
		"19 - invalid", // 19
58
59
		"Graphic 6 (screen 7)", // 20
60
		"22 - invalid", // 22
61
		"21 - invalid", // 21
62
		"23 - invalid", // 23
63
64
		"24 - invalid", // 24
65
		"26 - invalid", // 26
66
		"25 - invalid", // 25
67
		"27 - invalid", // 27
68
69
		"Graphic 7 (screen 8)", // 28
70
		"30 - invalid", // 30
71
		"29 - invalid", // 29
72
		"31 - invalid", // 31
73
	};
74
	assert(v < 32);
75
	return modeNames[v];
76
}
77
78
using Bits = std::array<const char*, 8>;
79
struct RegisterDescription {
80
	Bits bits;
81
	std::string_view name;
82
};
83
using RD = RegisterDescription;
84
static constexpr auto registerDescriptions = std::array{
85
	RD{{" 0 ", "DG ", "IE2", "IE1", "M5 ", "M4 ", "M3 ", " 0 "}, "Mode register 0"}, // R#0
86
	RD{{" 0 ", "BL ", "IE0", "M1 ", "M2 ", " 0 ", "SI ", "MAG"}, "Mode register 1"}, // R#1
87
	RD{{" 0 ", "A16", "A15", "A14", "A13", "A12", "A11", "A10"}, "Pattern name table base address register"}, // R#2
88
	RD{{"A13", "A12", "A11", "A10", "A9 ", "A8 ", "A7 ", "A6 "}, "Color table base address register low"}, // R#3
89
	RD{{" 0 ", " 0 ", "A16", "A15", "A14", "A13", "A12", "A11"}, "Pattern generator table base address register"}, // R#4
90
	RD{{"A14", "A13", "A12", "A11", "A10", "A9 ", "A8 ", "A7 "}, "Sprite attribute table base address register low"}, // R#5
91
	RD{{" 0 ", " 0 ", "A16", "A15", "A14", "A13", "A12", "A11"}, "Sprite pattern generator table base address register"}, // R#6
92
	RD{{"TC3", "TC2", "TC1", "TC0", "BD3", "BD2", "BD1", "BD0"}, "Text color/Back drop color register"}, // R#7
93
94
	RD{{"MS ", "LP ", "TP ", "CB ", "VR ", " 0 ", "SPD", "BW "}, "Mode register 2"}, // R#8
95
	RD{{"LN ", " 0 ", "S1 ", "S2 ", "IL ", "EO ", "*NT", "DC "}, "Mode register 3"}, // R#9
96
	RD{{" 0 ", " 0 ", " 0 ", " 0 ", " 0 ", "A16", "A15", "A14"}, "Color table base address register high"}, // R#10
97
	RD{{" 0 ", " 0 ", " 0 ", " 0 ", " 0 ", " 0 ", "A16", "A15"}, "Sprite attribute table base address register high"}, // R#11
98
	RD{{"T23", "T22", "T21", "T20", "BC3", "BC2", "BC1", "BC0"}, "Text color/Back color register"}, // R#12
99
	RD{{"ON3", "ON2", "ON1", "ON0", "OF3", "OF2", "OF1", "OF0"}, "Blinking period register"}, // R#13
100
	RD{{" 0 ", " 0 ", " 0 ", " 0 ", " 0 ", "A16", "A15", "A14"}, "VRAM Access base address register"}, // R#14
101
	RD{{" 0 ", " 0 ", " 0 ", " 0 ", "S3 ", "S2 ", "S1 ", "S0 "}, "Status register pointer"}, // R#15
102
103
	RD{{" 0 ", " 0 ", " 0 ", " 0 ", "C3 ", "C2 ", "C1 ", "C0 "}, "Color palette address register"}, // R#16
104
	RD{{"AII", " 0 ", "RS5", "RS4", "RS3", "RS2", "RS1", "RS0"}, "Control register pointer"}, // R#17
105
	RD{{"V3 ", "V2 ", "V1 ", "V0 ", "H3 ", "H2 ", "H1 ", "H0 "}, "Display adjust register"}, // R#18
106
	RD{{"IL7", "IL6", "IL5", "IL4", "IL3", "IL2", "IL1", "IL0"}, "Interrupt line register"}, // R#19
107
	RD{{" 0 ", " 0 ", " 0 ", " 0 ", " 0 ", " 0 ", " 0 ", " 0 "}, "Color burst register 1"}, // R#20
108
	RD{{" 0 ", " 0 ", " 1 ", " 1 ", " 1 ", " 0 ", " 1 ", " 1 "}, "Color burst register 2"}, // R#21
109
	RD{{" 0 ", " 0 ", " 0 ", " 0 ", " 0 ", " 1 ", " 0 ", " 1 "}, "Color burst register 3"}, // R#22
110
	RD{{"DO7", "DO6", "DO5", "DO4", "DO3", "DO2", "DO1", "DO0"}, "Display offset register"}, // R#23
111
112
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#24
113
	RD{{" 0 ", "CMD", "VDS", "YAE", "YJK", "WTE", "MSK", "SP2"}, "V9958 control register"}, // R#25
114
	RD{{" 0 ", " 0 ", "HO8", "HO7", "HO6", "HO5", "HO4", "HO3"}, "Horizontal scroll register high"}, // R#26
115
	RD{{" 0 ", " 0 ", " 0 ", " 0 ", " 0 ", "HO2", "HO1", "HO0"}, "Horizontal scroll register low"}, // R#27
116
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#28
117
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#29
118
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#30
119
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#31
120
121
	RD{{"SX7", "SX6", "SX5", "SX4", "SX3", "SX2", "SX1", "SX0"}, "Source X low register"}, // R#32
122
	RD{{" 0 ", " 0 ", " 0 ", " 0 ", " 0 ", " 0 ", " 0 ", "SX8"}, "Source X high register"}, // R#33
123
	RD{{"SY7", "SY6", "SY5", "SY4", "SY3", "SY2", "SY1", "SY0"}, "Source Y low register"}, // R#34
124
	RD{{" 0 ", " 0 ", " 0 ", " 0 ", " 0 ", " 0 ", "SY9", "SY8"}, "Source Y high register"}, // R#35
125
	RD{{"DX7", "DX6", "DX5", "DX4", "DX3", "DX2", "DX1", "DX0"}, "Destination X low register"}, // R#36
126
	RD{{" 0 ", " 0 ", " 0 ", " 0 ", " 0 ", " 0 ", " 0 ", "DX8"}, "Destination X high register"}, // R#37
127
	RD{{"DY7", "DY6", "DY5", "DY4", "DY3", "DY2", "DY1", "DY0"}, "Destination Y low register"}, // R#38
128
	RD{{" 0 ", " 0 ", " 0 ", " 0 ", " 0 ", " 0 ", "DY9", "DY8"}, "Destination Y high register"}, // R#39
129
130
	RD{{"NX7", "NX6", "NX5", "NX4", "NX3", "NX2", "NX1", "NX0"}, "Number of dots X low register"}, // R#40
131
	RD{{" 0 ", " 0 ", " 0 ", " 0 ", " 0 ", " 0 ", " 0 ", "NX8"}, "Number of dots X high register"}, // R#41
132
	RD{{"NY7", "NY6", "NY5", "NY4", "NY3", "NY2", "NY1", "NY0"}, "Number of dots Y low register"}, // R#42
133
	RD{{" 0 ", " 0 ", " 0 ", " 0 ", " 0 ", " 0 ", "NY9", "NY8"}, "Number of dots Y high register"}, // R#43
134
	RD{{"CH3", "CH2", "CH1", "CH0", "CL3", "CL2", "CL1", "CL0"}, "Color register"}, // R#44
135
	RD{{" 0 ", "MXC", "MXD", "MXS", "DIY", "DIX", "EQ ", "MAJ"}, "Argument register"}, // R#45
136
	RD{{"CM3", "CM2", "CM1", "CM0", "LO3", "LO2", "LO1", "LO0"}, "Command register"}, // R#46
137
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#47
138
139
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#48
140
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#49
141
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#50
142
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#51
143
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#52
144
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#53
145
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#54
146
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#55
147
148
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#56
149
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#57
150
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#58
151
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#59
152
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#60
153
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#61
154
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#62
155
	RD{{" - ", " - ", " - ", " - ", " - ", " - ", " - ", " - "}, ""}, // R#63
156
157
	// We put info on the status registers in the same table as the normal VDP registers,
158
	// (as-if they were registers R#64-R73, but that's nonsense of course)
159
	RD{{" F ", "5S ", " C ", "FS4", "FS3", "FS2", "FS1", "FS0"}, "Status register 0"}, // S#0
160
	RD{{"FL ", "LPS", "ID4", "ID3", "ID2", "ID1", "ID0", "FH "}, "Status register 1"}, // S#1
161
	RD{{"TR ", "VR ", "HR ", "BD ", " 1 ", " 1 ", "EO ", "CE "}, "Status register 2"}, // S#2
162
	RD{{"X7 ", "X6 ", "X5 ", "X4 ", "X3 ", "X2 ", "X1 ", "X0 "}, "Column register low"}, // S#3
163
	RD{{" 1 ", " 1 ", " 1 ", " 1 ", " 1 ", " 1 ", " 1 ", "X8 "}, "Column register high"}, // S#4
164
	RD{{"Y7 ", "Y6 ", "Y5 ", "Y4 ", "Y3 ", "Y2 ", "Y1 ", "Y0 "}, "Row register low"}, // S#5
165
	RD{{" 1 ", " 1 ", " 1 ", " 1 ", " 1 ", " 1 ", "EO ", "Y8 "}, "Row register high"}, // S#6
166
	RD{{"C7 ", "C6 ", "C5 ", "C4 ", "C3 ", "C2 ", "C1 ", "C0 "}, "Color register"}, // S#7
167
168
	RD{{"BX7", "BX6", "BX5", "BX4", "BX3", "BX2", "BX1", "BX0"}, "Border X register low"}, // S#8
169
	RD{{" 1 ", " 1 ", " 1 ", " 1 ", " 1 ", " 1 ", " 1 ", "BX8"}, "Border X register high"}, // S#9
170
};
171
172
struct SubFunction {
173
	uint8_t reg = uint8_t(-1);
174
	uint8_t mask = 0;
175
};
176
struct RegFunction {
177
	// drawSection() draws this item when 'subs[0].reg' or 'subs[1].reg' is part of 'showRegisters'
178
	// when 'subs[0].mask == 0' the item cannot be highlighted (typically such items show a fixed string)
179
	std::array<SubFunction, 2> subs;
180
	std::string_view toolTip;
181
	TemporaryString(*displayFunc)(uint32_t);
182
};
183
using S = SubFunction;
184
using R = RegFunction;
185
using namespace std::literals;
186
static TemporaryString noExplanation(uint32_t) { return TemporaryString{}; }
187
static TemporaryString spacing(uint32_t) { return TemporaryString("\n"); }
188
189
static const VDP* g_vdp; // HACK: global!!
190
static std::array<uint8_t, 64 + 10> registerValues; // HACK: global!!
191
static uint8_t getMode() { return uint8_t(((registerValues[1] & 0x18) >> 3) | ((registerValues[0] & 0x0E) << 1)); }
192
static bool isText2Mode() { return getMode() == 0b01010; } // Note: M1 and M2 swapped!
193
static bool isGraph23Mode() { return getMode() == one_of(0b00100, 0b01000); }
194
static bool isSprite2Mode() { return getMode() == one_of(0b01000, 0b01100, 0b10000, 0b10100, 0b11100); }
195
static bool isBitmapMode() { return getMode() >= 0x0C; }
196
static bool isPlanarMode() { return (getMode() & 0x14) == 0x14; }
197
198
static constexpr auto regFunctions = std::array{
199
	// mode registers
200
	R{{S{0, 0}}, "", [](uint32_t) { return TemporaryString("screen: "); }},
201
	R{{S{1, 0x40}}, "screen display enable/disable",
202
	  [](uint32_t v) { return TemporaryString(v ? "enabled" : "disabled"); }},
203
	R{{S{0, 0}}, "", [](uint32_t) { return TemporaryString(", "); }},
204
	R{{S{1, 0x18}, S{0, 0x0E}}, "display mode",
205
	  [](uint32_t v) { return TemporaryString(modeName(v)); }},
206
	R{{S{9, 0}}, "", [](uint32_t) { return TemporaryString(", "); }},
207
	R{{S{9, 0x02}}, "select PAL or NTSC",
208
	  [](uint32_t v) { return TemporaryString(v ? "PAL (50Hz)" : "NTSC (60Hz)"); }},
209
	R{{S{0, 0}}, "", [](uint32_t) { return TemporaryString("\n"); }},
210
211
	R{{S{9, 0x80}}, "select 192 or 212 lines",
212
	  [](uint32_t v) { return TemporaryString(v ? "212 lines" : "192 lines"); }},
213
	R{{S{9, 0}}, "", [](uint32_t) { return TemporaryString(", "); }},
214
	R{{S{9, 0x08}}, "interlace enable/disable",
215
	  [](uint32_t v) { return TemporaryString(v ? "interlaced" : "non-interlaced"); }},
216
	R{{S{9, 0}}, "", [](uint32_t) { return TemporaryString(", "); }},
217
	R{{S{9, 0x04}}, "alternate odd/even pages enable/disable",
218
	  [](uint32_t v) { return tmpStrCat(v ? ""sv : "do not"sv, " alternate odd/even pages\n"); }},
219
220
	R{{S{1, 0}}, "", [](uint32_t) { return TemporaryString("sprites: "); }},
221
	R{{S{8, 0x02}}, "sprite enable/disable",
222
	  [](uint32_t v) { return TemporaryString(v ? "disabled" : "enabled"); }},
223
	R{{S{8, 0}}, "", [](uint32_t) { return TemporaryString(", "); }},
224
	R{{S{1, 0x02}}, "sprite size, 8x8 or 16x16",
225
	  [](uint32_t v) { return tmpStrCat("size=", v ? "16x16"sv : "8x8"sv); }},
226
	R{{S{1, 0}}, "", [](uint32_t) { return TemporaryString(", "); }},
227
	R{{S{1, 0x01}}, "sprite magnification enable/disable",
228
	  [](uint32_t v) { return TemporaryString(v ? "magnified\n" : "not-magnified\n"); }},
229
230
	R{{S{8, 0x20}}, "color 0 transparent or not",
231
	  [](uint32_t v) { return tmpStrCat("color 0 is ", v ? "a regular color\n"sv : "transparent\n"sv); }},
232
233
	R{{S{1, 0x20}}, "V-Blank interrupt enable/disable",
234
	  [](uint32_t v) { return tmpStrCat("V-Blank-IRQ: ", v ? "enabled"sv : "disabled"sv); }},
235
	R{{S{1, 0}}, "", [](uint32_t) { return TemporaryString(", "); }},
236
	R{{S{0, 0x10}}, "line interrupt enable/disable",
237
	  [](uint32_t v) { return tmpStrCat("line-IRQ: ", v ? "enabled"sv : "disabled"sv, '\n'); }},
238
239
	R{{S{0, 0x40}}, "digitize", &noExplanation},
240
	R{{S{0, 0x20}}, "light pen interrupt enable", &noExplanation},
241
	R{{S{1, 0x04}}, "fast blink mode", &noExplanation},
242
	R{{S{8, 0x80}}, "mouse", &noExplanation},
243
	R{{S{8, 0x40}}, "light pen", &noExplanation},
244
	R{{S{8, 0x10}}, "set color bus input/output", &noExplanation},
245
	R{{S{8, 0x08}}, "select VRAM type", &noExplanation},
246
	R{{S{8, 0x01}}, "select black&white output", &noExplanation},
247
	R{{S{9, 0x30}}, "simultaneous mode", &noExplanation},
248
	R{{S{9, 0x01}}, "set DLCLK input/output", &noExplanation},
249
	R{{S{0, 0}}, "", &spacing},
250
251
	// Table base registers
252
	R{{S{2, 0x7F}}, "name table address", [](uint32_t v) {
253
		if (isText2Mode()) {
254
			v &= ~0x03;
255
		} else if (isBitmapMode()) {
256
			v &= ~0x1F;
257
			if (isPlanarMode()) {
258
				v *= 2;
259
			}
260
		}
261
		return tmpStrCat("name table:    0x", hex_string<5>(v << 10), '\n');
262
	  }},
263
	R{{S{3, 0xFF}, S{10, 0x07}}, "color table address", [](uint32_t v) {
264
		if (isText2Mode()) v &= ~0x07;
265
		if (isGraph23Mode()) v &= ~0x7F;
266
		return tmpStrCat("color table:   0x", hex_string<5>(v << 6), '\n');
267
	  }},
268
	R{{S{4, 0x3F}}, "pattern table address", [](uint32_t v) {
269
		if (isGraph23Mode()) v &= ~0x03;
270
		return tmpStrCat("pattern table: 0x", hex_string<5>(v << 11), '\n');
271
	  }},
272
	R{{S{5, 0xFF}, S{11, 0x03}}, "sprite attribute table address", [](uint32_t v) {
273
		if (isSprite2Mode()) {
274
			v &= ~0x03; // note: '3' instead of '7' because we want to display 'addr + 512'
275
		}
276
		return tmpStrCat("sprite attribute table: 0x", hex_string<5>(v << 7), '\n');
277
	  }},
278
	R{{S{6, 0x3F}}, "sprite pattern table address", [](uint32_t v) {
279
		return tmpStrCat("sprite pattern table:   0x", hex_string<5>(v << 11), '\n');
280
	  }},
281
	R{{S{2, 0}}, "", &spacing},
282
283
	// Color registers
284
	R{{S{13, 0}}, "", [](uint32_t) { return TemporaryString("text color:"); }},
285
	R{{S{7, 0xF0}}, "text color",
286
	  [](uint32_t v) { return tmpStrCat(" foreground=", v); }},
287
	R{{S{7, 0x0F}}, "background color",
288
	  [](uint32_t v) { return tmpStrCat(" background=", v, '\n'); }},
289
	R{{S{13, 0}}, "", [](uint32_t) { return TemporaryString("text blink color:"); }},
290
	R{{S{12, 0xF0}}, "text blink color",
291
	  [](uint32_t v) { return tmpStrCat(" foreground=", v); }},
292
	R{{S{12, 0x0F}}, "background blink color",
293
	  [](uint32_t v) { return tmpStrCat(" background=", v, '\n'); }},
294
	R{{S{13, 0}}, "", [](uint32_t) { return TemporaryString("blink period:"); }},
295
	R{{S{13, 0xF0}}, "blink period on",
296
	  [](uint32_t v) { return tmpStrCat(" on=", 10 * v); }},
297
	R{{S{13, 0x0F}}, "blink period off",
298
	  [](uint32_t v) { return tmpStrCat(" off=", 10 * v); }},
299
	R{{S{13, 0}}, "", [](uint32_t) { return TemporaryString(" frames\n"); }},
300
	R{{S{7, 0}}, "", &spacing},
301
302
	// Display registers
303
	R{{S{18, 0}}, "", [](uint32_t) { return TemporaryString("set-adjust:"); }},
304
	R{{S{18, 0x0F}}, "horizontal set-adjust",
305
	  [](uint32_t v) { return tmpStrCat(" horizontal=", int(v ^ 7) - 7); }},
306
	R{{S{18, 0xF0}}, "vertical set-adjust",
307
	  [](uint32_t v) { return tmpStrCat(" vertical=", int(v ^ 7) - 7, '\n'); }},
308
	R{{S{19, 0xFF}}, "line number for line-interrupt",
309
	  [](uint32_t v) { return tmpStrCat("line interrupt=", v, '\n'); }},
310
	R{{S{23, 0xFF}}, "vertical scroll (line number for 1st line)",
311
	  [](uint32_t v) { return tmpStrCat("vertical scroll=", v, '\n'); }},
312
	R{{S{18, 0}}, "", &spacing},
313
314
	// Access registers
315
	R{{S{14, 0x07}}, "VRAM access base address",
316
	  [](uint32_t v) { return tmpStrCat("VRAM access base address: ", hex_string<5>(v << 14)); }},
317
	R{{S{14, 0}}, "", [](uint32_t) {
318
		auto addr = (registerValues[14] << 14) | g_vdp->getVramPointer();
319
		return tmpStrCat(", full address: ", hex_string<5>(addr), '\n');
320
	}},
321
	R{{S{15, 0x0F}}, "select status register",
322
	  [](uint32_t v) { return tmpStrCat("selected status register: ", v, '\n'); }},
323
	R{{S{16, 0x0F}}, "select palette entry",
324
	  [](uint32_t v) { return tmpStrCat("selected palette entry: ", v, '\n'); }},
325
	R{{S{17, 0x3F}}, "indirect register access",
326
	  [](uint32_t v) { return tmpStrCat("selected indirect register: ", v); }},
327
	R{{S{17, 0}}, "", [](uint32_t) { return TemporaryString(", "); }},
328
	R{{S{17, 0x80}}, "auto-increment",
329
	  [](uint32_t v) { return tmpStrCat(v ? "no "sv : ""sv, "auto-increment\n"); }},
330
	R{{S{14, 0}}, "", &spacing},
331
332
	// V9958 registers
333
	R{{S{27, 0x07}, S{26, 0x3F}}, "horizontal scroll", [](uint32_t v) {
334
		auto s = (v & ~7) - (v & 7);
335
		return tmpStrCat("horizontal scroll: ", s);
336
	}},
337
	R{{S{25, 0}}, "", [](uint32_t) { return TemporaryString(", "); }},
338
	R{{S{25, 0x01}}, "2-page scroll enable/disable",
339
	  [](uint32_t v) { return tmpStrCat(v + 1, "-page"); }},
340
	R{{S{25, 0}}, "", [](uint32_t) { return TemporaryString(", "); }},
341
	R{{S{25, 0x02}}, "mask 8 left dots",
342
	  [](uint32_t v) { return tmpStrCat(v ? ""sv : "not-"sv, "masked\n"); }},
343
	R{{S{25, 0x18}}, "YJK/YAE mode",
344
	  [](uint32_t v) { return tmpStrCat((v & 1) ? ((v & 2) ? "YJK+YAE"sv : "YJK"sv) : "RGB"sv, "-mode\n"); }},
345
	R{{S{25, 0x40}}, "expand command mode",
346
	  [](uint32_t v) { return tmpStrCat("commands ", (v ? "work in all "sv : "only work in bitmap-"sv), "modes\n"); }},
347
	R{{S{25, 0x20}}, "output select between CPUCLK and /VDS", &noExplanation},
348
	R{{S{25, 0x04}}, "enable/disable WAIT (not used on MSX)", &noExplanation},
349
	R{{S{25, 0}}, "", &spacing},
350
351
	// Command registers
352
	R{{S{46, 0xF0}}, "command code", [](uint32_t v) {
353
		static constexpr std::array<zstring_view, 16> COMMANDS = {
354
			" ABRT"," ????"," ????"," ????","POINT"," PSET"," SRCH"," LINE",
355
			" LMMV"," LMMM"," LMCM"," LMMC"," HMMV"," HMMM"," YMMM"," HMMC"
356
		};
357
		return TemporaryString(COMMANDS[v]);
358
	}},
359
	R{{S{46, 0}}, "", [](uint32_t) { return TemporaryString("-"); }},
360
	R{{S{46, 0x0F}}, "logical operation", [](uint32_t v) {
361
		static constexpr std::array<zstring_view, 16> OPS = {
362
			"IMP ","AND ","OR  ","XOR ","NOT ","NOP ","NOP ","NOP ",
363
			"TIMP","TAND","TOR ","TXOR","TNOT","NOP ","NOP ","NOP "
364
		};
365
		return TemporaryString(OPS[v]);
366
	}},
367
	R{{S{32, 0}}, "", [](uint32_t) { return TemporaryString("("); }},
368
	R{{S{32, 0xFF}, S{33, 0x01}}, "source X", [](uint32_t v) { return tmpStrCat(v); }},
369
	R{{S{32, 0}}, "", [](uint32_t) { return TemporaryString(","); }},
370
	R{{S{34, 0xFF}, S{35, 0x03}}, "source Y", [](uint32_t v) { return tmpStrCat(v); }},
371
	R{{S{32, 0}}, "", [](uint32_t) { return TemporaryString(")->("); }},
372
	R{{S{36, 0xFF}, S{37, 0x01}}, "destination X", [](uint32_t v) { return tmpStrCat(v); }},
373
	R{{S{32, 0}}, "", [](uint32_t) { return TemporaryString(","); }},
374
	R{{S{38, 0xFF}, S{39, 0x03}}, "destination Y", [](uint32_t v) { return tmpStrCat(v); }},
375
	R{{S{32, 0}}, "", [](uint32_t) { return TemporaryString("),"); }},
376
	R{{S{44, 0xFF}}, "color", [](uint32_t v) { return tmpStrCat(v); }},
377
	R{{S{32, 0}}, "", [](uint32_t) { return TemporaryString(" ["); }},
378
	R{{S{45, 0x04}}, "direction X", [](uint32_t v) { return TemporaryString(v ? "-" : "+"); }},
379
	R{{S{40, 0xFF}, S{41, 0x01}}, "number of dots X", [](uint32_t v) { return tmpStrCat(v); }},
380
	R{{S{32, 0}}, "", [](uint32_t) { return TemporaryString(","); }},
381
	R{{S{45, 0x08}}, "direction Y", [](uint32_t v) { return TemporaryString(v ? "-" : "+"); }},
382
	R{{S{42, 0xFF}, S{43, 0x03}}, "number of dots Y", [](uint32_t v) { return tmpStrCat(v); }},
383
	R{{S{32, 0}}, "", [](uint32_t) { return TemporaryString("]\n"); }},
384
385
	R{{S{45, 0x40}}, "expanded VRAM for CPU access", &noExplanation},
386
	R{{S{45, 0x20}}, "expanded VRAM for destination", &noExplanation},
387
	R{{S{45, 0x10}}, "expanded VRAM for source", &noExplanation},
388
	R{{S{45, 0x02}}, "select equal/not-equal for SRCH command", &noExplanation},
389
	R{{S{45, 0x01}}, "select major direction (X/Y) for LINE command", &noExplanation},
390
	R{{S{32, 0}}, "", &spacing},
391
392
	// Status registers
393
	R{{S{64, 0x80}}, "VBLANK interrupt pending",
394
	  [](uint32_t v) { return tmpStrCat("VBLANK-IRQ: ", v); }},
395
	R{{S{65, 0}}, "", [](uint32_t) { return TemporaryString(" "); }},
396
	R{{S{65, 0x01}}, "line interrupt pending",
397
	  [](uint32_t v) { return tmpStrCat("line-IRQ: ", v); }},
398
	R{{S{64, 0}}, "", [](uint32_t) { return TemporaryString("\n"); }},
399
400
	R{{S{64, 0x40}}, "5th/9th sprite detected",
401
	  [](uint32_t v) { return tmpStrCat(v ? ""sv : "no "sv, "5th/9th sprite per line"); }},
402
	R{{S{64, 0x1F}}, "5th/9th sprite number",
403
	  [](uint32_t v) { return tmpStrCat(" (sprite number=", v, ")\n"); }},
404
405
	R{{S{64, 0x20}}, "sprite collision",
406
	  [](uint32_t v) { return tmpStrCat(v ? ""sv : "no "sv, "sprite collision"); }},
407
	R{{S{67, 0}}, "", [](uint32_t) { return TemporaryString(" (at coordinate "); }},
408
	R{{S{67, 0xFF}, S{68, 0x01}}, "x-coordinate",
409
	  [](uint32_t v) { return tmpStrCat("x=", v); }},
410
	R{{S{69, 0xFF}, S{70, 0x03}}, "y-coordinate",
411
	  [](uint32_t v) { return tmpStrCat("y=", v); }},
412
	R{{S{67, 0}}, "", [](uint32_t) { return TemporaryString(")"); }},
413
	R{{S{64, 0}}, "", [](uint32_t) { return TemporaryString("\n"); }},
414
415
	R{{S{66, 0}}, "", [](uint32_t) { return TemporaryString("command: "); }},
416
	R{{S{66, 0x01}}, "command executing",
417
	  [](uint32_t v) { return tmpStrCat(v ? ""sv : "not "sv, "running"); }},
418
	R{{S{66, 0}}, "", [](uint32_t) { return TemporaryString(" "); }},
419
	R{{S{66, 0x80}}, "transfer ready flag",
420
	  [](uint32_t v) { return tmpStrCat(v ? ""sv : "not "sv, "ready to transfer\n"); }},
421
422
	R{{S{66, 0}}, "", [](uint32_t) { return TemporaryString("scan position: "); }},
423
	R{{S{66, 0x20}}, "horizontal border",
424
	  [](uint32_t v) { return tmpStrCat(v ? ""sv : "not "sv, "in horizontal border"); }},
425
	R{{S{66, 0}}, "", [](uint32_t) { return TemporaryString(" "); }},
426
	R{{S{66, 0x40}}, "vertical border",
427
	  [](uint32_t v) { return tmpStrCat(v ? ""sv : "not "sv, "in vertical border\n"); }},
428
429
	R{{S{65, 0x80}}, "light pen (not used on MSX)", &noExplanation},
430
	R{{S{65, 0x40}}, "light pen switch (not used on MSX)", &noExplanation},
431
	R{{S{65, 0x3E}}, "VDP identification (V9938 or V9958)", &noExplanation},
432
	R{{S{66, 0x10}}, "SRCH command detected border color", &noExplanation},
433
	R{{S{66, 0x02}}, "odd/even field", &noExplanation},
434
	R{{S{71, 0xFF}}, "result of POINT command", &noExplanation},
435
	R{{S{72, 0xFF}, S{73, 0x01}}, "result of SRCH command", &noExplanation},
436
};
437
438
static int lookupFunction(uint8_t reg, uint8_t mask)
439
{
440
	int i = 0;
441
	for (const auto& f : regFunctions) {
442
		for (const auto& s : f.subs) {
443
			if ((reg == s.reg) && (mask & s.mask)) {
444
				return i;
445
			}
446
		}
447
		++i;
448
	}
449
	return -1;
450
}
451
452
void ImGuiVdpRegs::drawSection(std::span<const uint8_t> showRegisters, std::span<const uint8_t> regValues,
453
                               VDP& vdp, EmuTime time)
454
{
455
	ImGui::SameLine();
456
	HelpMarker("Click to toggle bits, or edit the hex-field.\n"
457
	           "Right-click to show/hide register explanation.");
458
459
	im::Table("table", 10, 0, [&]{
460
		// isCellHovered:
461
		//   This uses an internal API, see here for a discussion
462
		//       https://github.com/ocornut/imgui/issues/6347
463
		//   In other words: this solution is suggested by the Dear ImGui
464
		//   developers until something better gets implemented.
465
		auto mouse_pos = ImGui::GetMousePos();
466
		const auto* table = ImGui::GetCurrentTable();
467
		auto isCellHovered = [&] {
468
			return ImGui::TableGetCellBgRect(table, table->CurrentColumn).Contains(mouse_pos);
469
		};
470
471
		im::ID_for_range(showRegisters.size(), [&](int i) {
472
			auto reg = showRegisters[i];
473
			// note:  0..63  regular register
474
			//       64..73  status register
475
			const auto& rd = registerDescriptions[reg];
476
			if (ImGui::TableNextColumn()) {
477
				auto name = tmpStrCat(reg < 64 ? "R#"sv : "S#"sv, reg < 64 ? reg : reg - 64);
478
				ImGui::AlignTextToFramePadding();
479
				ImGui::TextUnformatted(static_cast<std::string_view>(name));
480
				simpleToolTip(rd.name);
481
			}
482
			uint8_t value = regValues[reg];
483
			bool writeReg = false;
484
			if (ImGui::TableNextColumn()) {
485
				if (reg < 64) {
486
					if (ImGui::InputScalar("##value", ImGuiDataType_U8, &value, nullptr, nullptr, "%02X", ImGuiInputTextFlags_CharsHexadecimal)) {
487
						writeReg = true;
488
					}
489
				} else {
490
					ImGui::Text("%02X", value);
491
				}
492
			}
493
			const auto& bits = rd.bits;
494
			im::ID_for_range(8, [&](int bit_) {
495
				if (ImGui::TableNextColumn()) {
496
					int bit = 7 - bit_;
497
					auto mask = narrow<uint8_t>(1 << bit);
498
					int f = lookupFunction(reg, mask);
499
					if (f != -1 && f == hoveredFunction) {
500
						ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,
501
							getColor(imColor::YELLOW));
502
					}
503
					im::StyleColor(ImGuiCol_Button, getColor(value & mask ? imColor::KEY_ACTIVE : imColor::KEY_NOT_ACTIVE), [&]{
504
						if (ImGui::Button(bits[bit_], {40.0f, 0.0f})) {
505
							value ^=  mask;
506
							writeReg = true;
507
						}
508
					});
509
					if ((f != -1) && isCellHovered()) {
510
						newHoveredFunction = f;
511
						if (auto tip = regFunctions[f].toolTip; !tip.empty()) {
512
							simpleToolTip(tip);
513
						}
514
					}
515
				}
516
			});
517
			if (writeReg && (reg < 64)) {
518
				vdp.changeRegister(reg, value, time);
519
			}
520
		});
521
	});
522
	if (explanation) {
523
		auto* drawList = ImGui::GetWindowDrawList();
524
		gl::vec2 framePadding = ImGui::GetStyle().FramePadding;
525
		for (auto f : xrange(narrow<int>(regFunctions.size()))) {
526
			const auto& func = regFunctions[f];
527
			if (!(contains(showRegisters, func.subs[0].reg) ||
528
			      contains(showRegisters, func.subs[1].reg))) {
529
				continue;
530
			}
531
			uint32_t value = 0;
532
			int shift = 0;
533
			for (const auto& sub : func.subs) {
534
				if (sub.reg == uint8_t(-1)) break;
535
				auto tmp = ((regValues[sub.reg] & sub.mask) >> std::countr_zero(sub.mask));
536
				value |= tmp << shift;
537
				shift += std::popcount(sub.mask);
538
			}
539
			if (auto s = func.displayFunc(value); !s.empty()) {
540
				auto textColor = getColor(imColor::TEXT);
541
				bool canHover = func.subs[0].mask != 0;
542
				if (canHover && (f == hoveredFunction)) {
543
					gl::vec2 textSize = ImGui::CalcTextSize(s);
544
					gl::vec2 pos = ImGui::GetCursorScreenPos();
545
					textColor = getColor(imColor::BLACK);
546
					drawList->AddRectFilled(pos - framePadding, pos + textSize + 2.0f * framePadding, getColor(imColor::YELLOW));
547
				}
548
				im::StyleColor(ImGuiCol_Text, textColor, [&]{
549
					ImGui::TextUnformatted(s);
550
				});
551
				if (canHover) {
552
					if (ImGui::IsItemHovered()) {
553
						newHoveredFunction = f;
554
					}
555
					if (shift == 1) { // single bit
556
						if (auto reg = func.subs[0].reg; reg < 64) { // non-status-register
557
							if (ImGui::IsItemClicked()) {
558
								auto mask = func.subs[0].mask;
559
								vdp.changeRegister(reg, regValues[reg] ^ mask, time);
560
							}
561
						}
562
					}
563
				}
564
				if (!s.ends_with('\n')) ImGui::SameLine();
565
			}
566
		}
567
	}
568
}
569
570
void ImGuiVdpRegs::paint(MSXMotherBoard* motherBoard)
571
{
572
	if (!show || !motherBoard) return;
573
574
	ImGui::SetNextWindowSize(gl::vec2{41, 29} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
575
	im::Window("VDP registers", &show, [&]{
576
		auto* vdp = dynamic_cast<VDP*>(motherBoard->findDevice("VDP")); // TODO name based OK?
577
		if (!vdp) return;
578
		auto time = motherBoard->getCurrentTime();
579
		const bool tms99x8 = vdp->isMSX1VDP();
580
		const bool v9958 = vdp->hasYJK();
581
582
		g_vdp = vdp;
583
		for (auto reg : xrange(64)) {
584
			registerValues[reg] = vdp->peekRegister(reg, time);
585
		}
586
		for (auto reg : xrange(uint8_t(10))) {
587
			registerValues[reg + 64] = vdp->peekStatusReg(reg, time);
588
		}
589
590
		newHoveredFunction = -1;
591
		auto make_span = [](const auto& array) {
592
			return std::span<const uint8_t, std::dynamic_extent>(std::span(array));
593
		};
594
		im::TreeNode("Control registers", &openControl, [&]{
595
			im::TreeNode("Mode registers", &openMode, [&]{
596
				static constexpr auto modeRegs1 = std::to_array<uint8_t>({0, 1});
597
				static constexpr auto modeRegs2 = std::to_array<uint8_t>({0, 1, 8, 9});
598
				auto modeRegs = tms99x8 ? make_span(modeRegs1) : make_span(modeRegs2);
599
				drawSection(modeRegs, registerValues, *vdp, time);
600
			});
601
			im::TreeNode("Table base registers", &openBase, [&]{
602
				static constexpr auto tableRegs1 = std::to_array<uint8_t>({2, 3, 4, 5, 6});
603
				static constexpr auto tableRegs2 = std::to_array<uint8_t>({2, 3, 10, 4, 5, 11, 6});
604
				auto tableRegs = tms99x8 ? make_span(tableRegs1) : make_span(tableRegs2);
605
				drawSection(tableRegs, registerValues, *vdp, time);
606
			});
607
			im::TreeNode("Color registers", &openColor, [&]{
608
				static constexpr auto colorRegs1 = std::to_array<uint8_t>({7});
609
				static constexpr auto colorRegs2 = std::to_array<uint8_t>({7, 12, 13, 20, 21, 22});
610
				auto colorRegs = tms99x8 ? make_span(colorRegs1) : make_span(colorRegs2);
611
				drawSection(colorRegs, registerValues, *vdp, time);
612
			});
613
			if (!tms99x8) {
614
				im::TreeNode("Display registers", &openDisplay, [&]{
615
					static constexpr auto displayRegs = std::to_array<uint8_t>({18, 19, 23});
616
					drawSection(displayRegs, registerValues, *vdp, time);
617
				});
618
			}
619
			im::TreeNode("Access registers", &openAccess, [&]{
620
				if (tms99x8) {
621
					ImGui::StrCat("VRAM access address: 0x",
622
					              hex_string<4>(vdp->getVramPointer()), '\n');
623
				} else {
624
					static constexpr auto accessRegs = std::to_array<uint8_t>({14, 15, 16, 17});
625
					drawSection(accessRegs, registerValues, *vdp, time);
626
				}
627
			});
628
			if (v9958) {
629
				im::TreeNode("V9958 registers", &openV9958, [&]{
630
					static constexpr auto v9958Regs = std::to_array<uint8_t>({25, 26, 27});
631
					drawSection(v9958Regs, registerValues, *vdp, time);
632
				});
633
			}
634
		});
635
		if (!tms99x8) {
636
			im::TreeNode("Command registers", &openCommand, [&]{
637
				static constexpr auto cmdRegs = std::to_array<uint8_t>({
638
					32, 33, 34, 35,  36, 37, 38, 39,  40, 41, 42, 43,  44, 45, 46
639
				});
640
				drawSection(cmdRegs, registerValues, *vdp, time);
641
			});
642
		}
643
		im::TreeNode("Status registers", &openStatus, [&]{
644
			static constexpr auto statusRegs1 = std::to_array<uint8_t>({64});
645
			static constexpr auto statusRegs2 = std::to_array<uint8_t>({
646
				64, 65, 66, 67, 68, 69, 70, 71, 72, 73
647
			});
648
			auto statusRegs = tms99x8 ? make_span(statusRegs1) : make_span(statusRegs2);
649
			drawSection(statusRegs, registerValues, *vdp, time);
650
		});
651
		hoveredFunction = newHoveredFunction;
652
653
		if (ImGui::IsWindowHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Right)) {
654
			ImGui::OpenPopup("context");
655
		}
656
		im::Popup("context", [&]{
657
			if (ImGui::Checkbox("Show explanation ", &explanation)) {
658
				ImGui::CloseCurrentPopup();
659
			}
660
		});
661
	});
662
}
663
664
} // namespace openmsx