OXYGENHUB
◆ main openmsx / src / video / VDPCmdEngine.hh
10.5 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#ifndef VDPCMDENGINE_HH
2
#define VDPCMDENGINE_HH
3
4
#include "VDP.hh"
5
#include "VDPAccessSlots.hh"
6
7
#include "BooleanSetting.hh"
8
#include "Probe.hh"
9
#include "TclCallback.hh"
10
#include "serialize_meta.hh"
11
#include "static_vector.hh"
12
13
#include <cstdint>
14
#include <optional>
15
16
namespace openmsx {
17
18
class VDPVRAM;
19
class DisplayMode;
20
class CommandController;
21
22
/** VDP command engine by Alex Wulms.
23
  * Implements command execution unit of V9938/58.
24
  */
25
class VDPCmdEngine
26
{
27
public:
28
	// bits in ARG register
29
	static constexpr uint8_t MXD = 0x20;
30
	static constexpr uint8_t MXS = 0x10;
31
	static constexpr uint8_t DIY = 0x08;
32
	static constexpr uint8_t DIX = 0x04;
33
	static constexpr uint8_t EQ  = 0x02;
34
	static constexpr uint8_t MAJ = 0x01;
35
36
	// bits in status register S#2
37
	static constexpr uint8_t TR = 1 << 7;
38
	static constexpr uint8_t BD = 1 << 4;
39
	static constexpr uint8_t CE = 1 << 0;
40
41
public:
42
	VDPCmdEngine(VDP& vdp, CommandController& commandController);
43
44
	/** Reinitialize Renderer state.
45
	  * @param time The moment in time the reset occurs.
46
	  */
47
	void reset(EmuTime time);
48
49
	/** Synchronizes the command engine with the VDP.
50
	  * Ideally this would be a private method, but the current
51
	  * design doesn't allow that.
52
	  * @param time The moment in emulated time to sync to.
53
	  */
54
	void sync(EmuTime time) {
55
		if (CMD) sync2(time);
56
	}
57
	void sync2(EmuTime time);
58
59
	/** Steal a VRAM access slot from the CmdEngine.
60
	 * Used when the CPU reads/writes VRAM.
61
	 * @param time The moment in time the CPU read/write is performed.
62
	 */
63
	void stealAccessSlot(EmuTime time) {
64
		if (CMD && engineTime <= time) {
65
			// take the next available slot
66
			engineTime = getNextAccessSlot(time, VDPAccessSlots::Delta::D1);
67
			assert(engineTime > time);
68
		}
69
	}
70
71
	/** Gets the command engine status (part of S#2).
72
	  * Bit 7 (TR) is set when the command engine is ready for
73
	  * a pixel transfer.
74
	  * Bit 4 (BD) is set when the boundary color is detected.
75
	  * Bit 0 (CE) is set when a command is in progress.
76
	  */
77
	[[nodiscard]] uint8_t peekStatus2(EmuTime time) {
78
		if (time >= statusChangeTime) {
79
			sync(time);
80
		}
81
		return status;
82
	}
83
	// should be called after reading S#9
84
	void resetBD() {
85
		status &= ~BD;
86
	}
87
88
	/** Use this method to transfer pixel(s) from VDP to CPU.
89
	  * This method implements V9938 S#7.
90
	  * @param time The moment in emulated time this read occurs.
91
	  * @return Color value of the pixel.
92
	  */
93
	[[nodiscard]] uint8_t readColor(EmuTime time) {
94
		sync(time);
95
		return COL;
96
	}
97
	void resetColor() {
98
		// Note: Real VDP always resets TR, but for such a short time
99
		//       that the MSX won't notice it.
100
		// TODO: What happens on non-transfer commands?
101
		if (!CMD) status &= ~TR;
102
		transfer = true;
103
	}
104
105
	/** Gets the X coordinate of a border detected by SRCH (intended behaviour,
106
          * as documented in the V9938 technical data book). However, real VDP
107
          * simply returns the current value of the ASX 'temporary source X' counter,
108
          * regardless of the command that is being executed or was executed most
109
          * recently
110
	  * @param time The moment in emulated time this get occurs.
111
	  */
112
	[[nodiscard]] unsigned getBorderX(EmuTime time) {
113
		sync(time);
114
		return ASX;
115
	}
116
117
	/** Writes to a command register.
118
	  * @param index The register [0..14] to write to.
119
	  * @param value The new value for the specified register.
120
	  * @param time The moment in emulated time this write occurs.
121
	  */
122
	void setCmdReg(uint8_t index, uint8_t value, EmuTime time);
123
124
	/** Read the content of a command register. This method is meant to
125
	  * be used by the debugger, there is no strict guarantee that the
126
	  * returned value is the correct value at _exactly_ this moment in
127
	  * time (IOW this method does not sync the complete CmdEngine)
128
	  * @param index The register [0..14] to read from.
129
	  */
130
	[[nodiscard]] uint8_t peekCmdReg(uint8_t index, EmuTime time);
131
132
	/** Informs the command engine of a VDP display mode change.
133
	  * @param mode The new display mode.
134
	  * @param cmdBit Are VDP commands allowed in non-bitmap mode.
135
	  * @param time The moment in emulated time this change occurs.
136
	  */
137
	void updateDisplayMode(DisplayMode mode, bool cmdBit, EmuTime time);
138
139
	// For debugging only
140
	bool commandInProgress(EmuTime time) {
141
		sync(time);
142
		return status & CE;
143
	}
144
	/** Get the register-values for the last executed (or still in progress)
145
	 * command. For debugging purposes only.
146
	 */
147
	struct CmdRegs {
148
		int sx, sy, dx, dy, nx, ny;
149
		uint8_t col, arg, cmd;
150
	};
151
	CmdRegs getLastCommand() const {
152
		return {
153
			.sx = lastSX, .sy = lastSY,
154
			.dx = lastDX, .dy = lastDY,
155
			.nx = lastNX, .ny = lastNY,
156
			.col = lastCOL, .arg = lastARG, .cmd = lastCMD
157
		};
158
	}
159
160
	using Point = gl::ivec2;
161
	struct Rect {
162
		Point p1, p2;
163
	};
164
	struct FormatCmdResult {
165
		std::optional<static_vector<Rect, 2>> srcRect;
166
		std::optional<static_vector<Rect, 2>> dstRect;
167
		std::string str;
168
	};
169
	[[nodiscard]] static FormatCmdResult formatCommand(const CmdRegs& r, int mode);
170
171
	/** Get the (source and destination) X/Y coordinates of the currently
172
	  * executing command. For debugging purposes only.
173
	  */
174
	auto getInprogressPosition() const {
175
		return (status & CE) ? std::tuple{int(ASX), int(SY), int(ADX), int(DY)}
176
		                     : std::tuple{-1, -1, -1, -1};
177
	}
178
179
	/** Interface for logical operations.
180
	  */
181
	template<typename Archive>
182
	void serialize(Archive& ar, unsigned version);
183
184
private:
185
	void executeCommand(EmuTime time);
186
187
	void setStatusChangeTime(EmuTime t);
188
	void calcFinishTime(unsigned NX, unsigned NY, unsigned ticksPerPixel);
189
190
	                        void startAbrt(EmuTime time);
191
	                        void startPoint(EmuTime time);
192
	                        void startPset(EmuTime time);
193
	                        void startSrch(EmuTime time);
194
	                        void startLine(EmuTime time);
195
	template<typename Mode> void startLmmv(EmuTime time);
196
	template<typename Mode> void startLmmm(EmuTime time);
197
	template<typename Mode> void startLmcm(EmuTime time);
198
	template<typename Mode> void startLmmc(EmuTime time);
199
	template<typename Mode> void startHmmv(EmuTime time);
200
	template<typename Mode> void startHmmm(EmuTime time);
201
	template<typename Mode> void startYmmm(EmuTime time);
202
	template<typename Mode> void startHmmc(EmuTime time);
203
204
	template<typename Mode>                 void executePoint(EmuTime limit);
205
	template<typename Mode, typename LogOp> void executePset(EmuTime limit);
206
	template<typename Mode>                 void executeSrch(EmuTime limit);
207
	template<typename Mode, typename LogOp> void executeLine(EmuTime limit);
208
	template<typename Mode, typename LogOp> void executeLmmv(EmuTime limit);
209
	template<typename Mode, typename LogOp> void executeLmmm(EmuTime limit);
210
	template<typename Mode>                 void executeLmcm(EmuTime limit);
211
	template<typename Mode, typename LogOp> void executeLmmc(EmuTime limit);
212
	template<typename Mode>                 void executeHmmv(EmuTime limit);
213
	template<typename Mode>                 void executeHmmm(EmuTime limit);
214
	template<typename Mode>                 void executeYmmm(EmuTime limit);
215
	template<typename Mode>                 void executeHmmc(EmuTime limit);
216
217
	// Advance to the next access slot at or past the given time.
218
	EmuTime getNextAccessSlot(EmuTime time) const {
219
		return vdp.getAccessSlot(time, VDPAccessSlots::Delta::D0);
220
	}
221
	void nextAccessSlot(EmuTime time) {
222
		engineTime = getNextAccessSlot(time);
223
	}
224
	// Advance to the next access slot that is at least 'delta' cycles past
225
	// the current one.
226
	EmuTime getNextAccessSlot(EmuTime time, VDPAccessSlots::Delta delta) const {
227
		return vdp.getAccessSlot(time, delta);
228
	}
229
	void nextAccessSlot(VDPAccessSlots::Delta delta) {
230
		engineTime = getNextAccessSlot(engineTime, delta);
231
	}
232
	void nextAccessSlot(EmuTime time, VDPAccessSlots::Delta delta) {
233
		engineTime = getNextAccessSlot(time, delta);
234
	}
235
	VDPAccessSlots::Calculator getSlotCalculator(
236
			EmuTime limit) const {
237
		return vdp.getAccessSlotCalculator(engineTime, limit);
238
	}
239
240
	/** Finished executing graphical operation.
241
	  */
242
	void commandDone(EmuTime time);
243
244
private:
245
	/** The VDP this command engine is part of.
246
	  */
247
	VDP& vdp;
248
	VDPVRAM& vram;
249
250
	TclCallback cmdInProgressCallback;
251
252
	struct CmdProbe final : ProbeBase {
253
		CmdProbe(Debugger& debugger, std::string name);
254
		[[nodiscard]] TclObject getValue() const override;
255
		[[nodiscard]] TraceValue getTraceValue() const override;
256
		void signal() { notify(); }
257
	} cmdProbe;
258
	Probe<bool> executingProbe;
259
260
	/** Time at which the next vram access slot is available.
261
	  * Only valid when a command is executing.
262
	  */
263
	EmuTime engineTime{EmuTime::zero()};
264
265
	/** Lower bound for the time when the status register will change, IOW
266
	  * the status register will not change before this time.
267
	  * Can also be EmuTime::zero -> status can change any moment
268
	  * or EmuTime::infinity -> this command doesn't change the status
269
	  */
270
	EmuTime statusChangeTime{EmuTime::infinity()};
271
272
	/** Some commands execute multiple VRAM accesses per pixel
273
	  * (e.g. LMMM does two reads and a write). This variable keeps
274
	  * track of where in the (sub)command we are. */
275
	int phase{0};
276
277
	/** Current screen mode.
278
	  * 0 -> SCREEN5, 1 -> SCREEN6, 2 -> SCREEN7, 3 -> SCREEN8,
279
	  * 4 -> Non-BitMap mode (like SCREEN8 but non-planar addressing)
280
	  * -1 -> other.
281
	  */
282
	int scrMode{-1};
283
284
	/** VDP command registers.
285
	  */
286
	unsigned SX{0}, SY{0}, DX{0}, DY{0}, NX{0}, NY{0}; // registers that can be set by CPU
287
	unsigned ASX{0}, ADX{0}, ANX{0}; // Temporary registers used in the VDP commands
288
	                                 // Register ASX can be read (via status register 8/9)
289
	uint8_t COL{0}, ARG{0}, CMD{0};
290
291
	/** The last executed command (for debugging only).
292
	  * A copy of the above registers when the command starts. Remains valid
293
	  * until the next command starts (also if the corresponding VDP
294
	  * registers are being changed).
295
	  */
296
	int lastSX{0}, lastSY{0}, lastDX{0}, lastDY{0}, lastNX{0}, lastNY{0};
297
	uint8_t lastCOL{0}, lastARG{0}, lastCMD{0};
298
299
	/** When a command needs multiple VRAM accesses per pixel, the result
300
	 * of intermediate reads is stored in these variables. */
301
	uint8_t tmpSrc{0};
302
	uint8_t tmpDst{0};
303
304
	/** The command engine status (part of S#2).
305
	  * Bit 7 (TR) is set when the command engine is ready for
306
	  * a pixel transfer.
307
	  * Bit 4 (BD) is set when the boundary color is detected.
308
	  * Bit 0 (CE) is set when a command is in progress.
309
	  */
310
	uint8_t status{0};
311
312
	/** Used in LMCM LMMC HMMC cmds, true when CPU has read or written
313
	  * next byte.
314
	  */
315
	bool transfer{false};
316
317
	/** Flag that indicated whether extended VRAM is available
318
	 */
319
	const bool hasExtendedVRAM;
320
};
321
SERIALIZE_CLASS_VERSION(VDPCmdEngine, 4);
322
323
} // namespace openmsx
324
325
#endif