1 /**
2 Copyright: Copyright (c) 2013-2014 Andrey Penechko.
3 License: a$(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 
7 
8 module emulator.dcpu.devices.lem1802;
9 
10 import std.stdio;
11 
12 import anchovy.graphics.bitmap;
13 
14 import emulator.dcpu.devices.idevice;
15 import emulator.dcpu.emulator;
16 import emulator.dcpu.dcpu;
17 import emulator.utils.undoproxy;
18 
19 @trusted nothrow:
20 
21 /++
22  + NE_LEM1802 v1.0
23  + Low Energy Monitor
24  + See 'docs/LEM1802 monitor.txt' for specification.
25  +/
26 
27 private struct Lem1802Registers
28 {
29 	ushort fontAddress;
30 	ushort videoAddress;
31 	ushort paletteAddress;
32 	ushort borderColor;
33 
34 	bool blinkPhase;
35 	bool enabled = false; 
36 	bool splash = false;
37 }
38 
39 class Lem1802(Cpu) : IDevice!Cpu
40 {
41 protected:
42 	Emulator!Cpu _emulator;
43 	Cpu* _dcpu;
44 	Bitmap _bitmap;
45 
46 	UndoableStruct!(Lem1802Registers, ushort) regs;
47 
48 	bool showSplash = true;
49 	uint blinkInterval = 70000;
50 	uint splashDelay = 50000;
51 
52 	enum numRows = 12;
53 	enum numCols = 32;
54 	enum charWidth = 4;
55 	enum charHeight = 8;
56 	enum borderSize = 4;
57 	enum screenWidth = numCols * charWidth + borderSize * 2;
58 	enum screenHeight = numRows * charHeight + borderSize * 2;
59 
60 public:
61 	this()
62 	{
63 		_bitmap = new Bitmap(screenWidth, screenHeight, 4);
64 	}
65 
66 	Bitmap bitmap() @property
67 	{
68 		return _bitmap;
69 	}
70 
71 	override void attachEmulator(Emulator!Cpu emulator)
72 	{
73 		_emulator = emulator;
74 		_dcpu = &emulator.dcpu;
75 		(cast(uint[])_bitmap.data)[] = 0xFF000000;
76 		regs.enabled = false;
77 		regs.splash = false;
78 		regs.fontAddress = 0;
79 		regs.videoAddress = 0;
80 		regs.paletteAddress = 0;
81 		regs.borderColor = 0;
82 
83 		_bitmap.dataChanged.emit();
84 	}
85 
86 	/// Handles hardware interrupt and returns a number of cycles.
87 	override uint handleInterrupt()
88 	{
89 		ushort aRegister = _emulator.dcpu.regs.a;
90 		ushort bRegister = _emulator.dcpu.regs.b;
91 
92 		switch(aRegister)
93 		{
94 			case 0:
95 				mapScreen(bRegister);
96 				return 0;
97 			case 1:
98 				mapFont(bRegister);
99 				return 0;
100 			case 2:
101 				mapPalette(bRegister);
102 				return 0;
103 			case 3:
104 				setBorderColor(bRegister);
105 				return 0;
106 			case 4:
107 				dumpFont(bRegister);
108 				return 256;
109 			case 5:
110 				dumpPalette(bRegister);
111 				return 16;
112 			default:
113 				break;
114 		}
115 
116 		return 0;
117 	}
118 
119 	/// Called every application frame.
120 	/// Can be used to update screens.
121 	override void updateFrame()
122 	{
123 		if (regs.enabled)
124 		{
125 			if (regs.splash && showSplash)
126 				drawSplash();
127 			else
128 				repaintScreen();
129 		}
130 		else
131 			clearScreen();
132 	}
133 
134 	override void handleUpdateQuery(ref ulong delay)
135 	{
136 		if (regs.splash) // remove splash
137 		{
138 			delay = blinkInterval;
139 			regs.splash = false;
140 		}
141 		else
142 		{
143 			regs.blinkPhase = !regs.blinkPhase;
144 			delay = blinkInterval;
145 		}
146 	}
147 
148 	/// Returns: 32 bit word identifying the hardware id.
149 	override uint hardwareId() @property
150 	{
151 		return 0x7349f615;
152 	}
153 
154 	/// Returns: 16 bit word identifying the hardware version.
155 	override ushort hardwareVersion() @property
156 	{
157 		return 0x1802;
158 	}
159 
160 	/// Returns: 32 bit word identifying the manufacturer
161 	override uint manufacturer() @property
162 	{
163 		return 0x1c6c8b36;
164 	}
165 
166 	override void commitFrame(ulong frameNumber)
167 	{
168 		regs.commitFrame(frameNumber);
169 	}
170 
171 	override void discardFrame()
172 	{
173 		regs.discardFrame();
174 	}
175 
176 	override void undoFrames(ulong numFrames)
177 	{
178 		regs.undoFrames(numFrames);
179 	}
180 
181 	override void discardUndoStack()
182 	{
183 		regs.discardUndoStack();
184 	}
185 
186 	override size_t undoStackSize() @property
187 	{
188 		return regs.undoStackSize;
189 	}
190 
191 protected:
192 
193 	void repaintScreen()
194 	{
195 		if (_dcpu is null || regs.videoAddress == 0) return;
196 
197 		foreach(line; 0..numRows)
198 		{
199 			foreach(column; 0..numCols)
200 			{
201 				ushort memoryAddress = (regs.videoAddress + line * numCols + column) & 0xFFFF;
202 				drawChar(_dcpu.mem[memoryAddress], column, line);
203 			}
204 		}
205 
206 		_bitmap.dataChanged.emit();
207 	}
208 
209 	void clearScreen()
210 	{
211 		(cast(uint[])_bitmap.data)[] = 0xFF000000;
212 		_bitmap.dataChanged.emit();
213 	}
214 
215 	void drawChar(ushort charData, size_t x, size_t y)
216 	{
217 		uint charIndex = charData & 0x7F;
218 		bool blinkBit  = (charData & 0x80) > 0;
219 		ushort foreIndex = (charData & 0xF000) >> 12;
220 		ushort backIndex = (charData & 0xF00) >> 8;
221 
222 		uint foreRGB = paletteToRGB8(foreIndex);
223 		uint backRGB = paletteToRGB8(backIndex);
224 
225 		if (blinkBit && regs.blinkPhase)
226 		{
227 			fillCell(x, y, backRGB);
228 		}
229 		else if (regs.fontAddress == 0)
230 		{
231 			drawCell(x, y, foreRGB, backRGB, (cast(uint[])defaultFont)[charIndex]);
232 		}
233 		else
234 		{
235 			drawCell(x, y, foreRGB, backRGB, _dcpu.mem[(regs.fontAddress + charIndex*2) & 0xFFFF] |
236 					_dcpu.mem[(regs.fontAddress + charIndex*2 + 1) & 0xFFFF] << 16);
237 		}
238 
239 		drawBorder();
240 	}
241 
242 	uint paletteToRGB8(ushort colorIndex)
243 	{
244 		ushort rgb12color;
245 		if (regs.paletteAddress == 0)
246 		{
247 			rgb12color = defaultPalette[colorIndex & 0xF];
248 		}
249 		else
250 		{
251 			rgb12color = _dcpu.mem[(regs.paletteAddress + (colorIndex & 0xF)) & 0xFFFF];
252 		}
253 
254 		return ((rgb12color & 0xF) << 16) * 17 +
255 			((rgb12color & 0xF0) << 4) * 17 +
256 			((rgb12color & 0xF00) >> 8) * 17 +
257 			0xFF000000;
258 	}
259 
260 	void fillCell(size_t x, size_t y, uint color)
261 	{
262 		uint cellX = borderSize + x * charWidth;
263 		uint cellY = borderSize + y * charHeight;
264 
265 		uint[] data = cast(uint[])_bitmap.data;
266 
267 		size_t dataPos; 
268 		uint[] cellLine;
269 
270 		foreach(i; 0..8)
271 		{
272 			dataPos = cellX + screenWidth * (cellY + i);
273 			cellLine = data[dataPos .. dataPos + 4];
274 			cellLine[] = color;
275 		}
276 	}
277 
278 	void drawCell(size_t x, size_t y, uint foreColor, uint backColor, uint charData)
279 	{
280 		uint cellX = borderSize + x * charWidth;
281 		uint cellY = borderSize + y * charHeight;
282 
283 		uint[] colorData = cast(uint[])_bitmap.data;
284 
285 		size_t dataPos;
286 		uint[] cellLine;
287 
288 		foreach(i; 0..8)
289 		{
290 			dataPos = cellX + screenWidth * (cellY + i);
291 			cellLine = colorData[dataPos .. dataPos + 4];
292 
293 			cellLine[0] = charData & (1 << (i +  8)) ? foreColor : backColor;
294 			cellLine[1] = charData & (1 << (i +  0)) ? foreColor : backColor;
295 			cellLine[2] = charData & (1 << (i + 24)) ? foreColor : backColor;
296 			cellLine[3] = charData & (1 << (i + 16)) ? foreColor : backColor;
297 		}
298 	}
299 
300 	void drawBorder()
301 	{
302 		uint borderRgb = paletteToRGB8(regs.borderColor);
303 		uint[] colorData = cast(uint[])_bitmap.data;
304 
305 		colorData[0..borderSize * screenWidth][] = borderRgb;
306 
307 		size_t topOffset;
308 		foreach(line; borderSize..screenHeight - borderSize)
309 		{
310 			topOffset = line * screenWidth;
311 			colorData[topOffset..topOffset + borderSize][] = borderRgb;
312 			colorData[topOffset + screenWidth - borderSize .. topOffset + screenWidth][] = borderRgb;
313 		}
314 
315 		colorData[$-borderSize * screenWidth..$][] = borderRgb;
316 	}
317 
318 	void drawSplash()
319 	{
320 		import std.bitmanip;
321 		(cast(uint[])_bitmap.data)[] = splashBackRgb;
322 		
323 		BitArray array;
324 		array.init(cast(void[])splashImage, splashImage.length * 16);
325 
326 		foreach(line; 0..splashHeight)
327 		{
328 			foreach(col; 0.. splashWidth)
329 			{
330 				(cast(uint[])_bitmap.data)[(splashY+line+borderSize) * bitmap.size.x + splashX + col + borderSize] =
331 					array[line * splashWidth + col] ? splashForeRgb : splashBackRgb; 
332 			}
333 		}
334 
335 		_bitmap.dataChanged.emit();
336 	}
337 
338 	void mapScreen(ushort b)
339 	{
340 		if (b != 0 && regs.videoAddress == 0)
341 		{
342 			regs.splash = true;
343 			regs.enabled = true;
344 
345 			drawSplash();
346 
347 			_dcpu.updateQueue.addQuery(this, splashDelay);
348 		}
349 		else if (b == 0)
350 		{
351 			(cast(uint[])_bitmap.data)[] = 0xFF000000;
352 			_dcpu.updateQueue.removeQueries(this);
353 
354 			regs.enabled = false;
355 		}
356 
357 		regs.videoAddress = b;
358 	}
359 
360 	void mapFont(ushort b)
361 	{
362 		regs.fontAddress = b;
363 	}
364 
365 	void mapPalette(ushort b)
366 	{
367 		regs.paletteAddress = b;
368 	}
369 
370 	void setBorderColor(ushort b)
371 	{
372 		regs.borderColor = b & 0xF;
373 	}
374 
375 	void dumpFont(ushort b)
376 	{
377 		ushort pointer = b;
378 
379 		foreach(word; cast(ushort[])defaultFont)
380 		{
381 			_dcpu.mem[pointer] = word;
382 			++pointer;
383 		}
384 	}
385 
386 	void dumpPalette(ushort b)
387 	{
388 		ushort pointer = b;
389 
390 		foreach(word; defaultPalette)
391 		{
392 			_dcpu.mem[pointer] = word;
393 			++pointer;
394 		}
395 	}
396 }
397 
398 static immutable ushort[] defaultPalette = [
399 	0x000, 0x00a, 0x0a0, 0x0aa,
400 	0xa00, 0xa0a, 0xa50, 0xaaa,
401 	0x555, 0x55f, 0x5f5, 0x5ff,
402 	0xf55, 0xf5f, 0xff5, 0xfff
403 ];
404 
405 static immutable ushort[] defaultFont = [
406 0xb79e, 0x388e, 0x722c, 0x75f4, 0x19bb, 0x7f8f, 0x85f9, 0xb158,
407 0x242e, 0x2400, 0x082a, 0x0800, 0x0008, 0x0000, 0x0808, 0x0808,
408 0x00ff, 0x0000, 0x00f8, 0x0808, 0x08f8, 0x0000, 0x080f, 0x0000,
409 0x000f, 0x0808, 0x00ff, 0x0808, 0x08f8, 0x0808, 0x08ff, 0x0000,
410 0x080f, 0x0808, 0x08ff, 0x0808, 0x6633, 0x99cc, 0x9933, 0x66cc,
411 0xfef8, 0xe080, 0x7f1f, 0x0701, 0x0107, 0x1f7f, 0x80e0, 0xf8fe,
412 0x5500, 0xaa00, 0x55aa, 0x55aa, 0xffaa, 0xff55, 0x0f0f, 0x0f0f,
413 0xf0f0, 0xf0f0, 0x0000, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff,
414 0x0000, 0x0000, 0x005f, 0x0000, 0x0300, 0x0300, 0x3e14, 0x3e00,
415 0x266b, 0x3200, 0x611c, 0x4300, 0x3629, 0x7650, 0x0002, 0x0100,
416 0x1c22, 0x4100, 0x4122, 0x1c00, 0x1408, 0x1400, 0x081c, 0x0800,
417 0x4020, 0x0000, 0x0808, 0x0800, 0x0040, 0x0000, 0x601c, 0x0300,
418 0x3e49, 0x3e00, 0x427f, 0x4000, 0x6259, 0x4600, 0x2249, 0x3600,
419 0x0f08, 0x7f00, 0x2745, 0x3900, 0x3e49, 0x3200, 0x6119, 0x0700,
420 0x3649, 0x3600, 0x2649, 0x3e00, 0x0024, 0x0000, 0x4024, 0x0000,
421 0x0814, 0x2200, 0x1414, 0x1400, 0x2214, 0x0800, 0x0259, 0x0600,
422 0x3e59, 0x5e00, 0x7e09, 0x7e00, 0x7f49, 0x3600, 0x3e41, 0x2200,
423 0x7f41, 0x3e00, 0x7f49, 0x4100, 0x7f09, 0x0100, 0x3e41, 0x7a00,
424 0x7f08, 0x7f00, 0x417f, 0x4100, 0x2040, 0x3f00, 0x7f08, 0x7700,
425 0x7f40, 0x4000, 0x7f06, 0x7f00, 0x7f01, 0x7e00, 0x3e41, 0x3e00,
426 0x7f09, 0x0600, 0x3e61, 0x7e00, 0x7f09, 0x7600, 0x2649, 0x3200,
427 0x017f, 0x0100, 0x3f40, 0x7f00, 0x1f60, 0x1f00, 0x7f30, 0x7f00,
428 0x7708, 0x7700, 0x0778, 0x0700, 0x7149, 0x4700, 0x007f, 0x4100,
429 0x031c, 0x6000, 0x417f, 0x0000, 0x0201, 0x0200, 0x8080, 0x8000,
430 0x0001, 0x0200, 0x2454, 0x7800, 0x7f44, 0x3800, 0x3844, 0x2800,
431 0x3844, 0x7f00, 0x3854, 0x5800, 0x087e, 0x0900, 0x4854, 0x3c00,
432 0x7f04, 0x7800, 0x047d, 0x0000, 0x2040, 0x3d00, 0x7f10, 0x6c00,
433 0x017f, 0x0000, 0x7c18, 0x7c00, 0x7c04, 0x7800, 0x3844, 0x3800,
434 0x7c14, 0x0800, 0x0814, 0x7c00, 0x7c04, 0x0800, 0x4854, 0x2400,
435 0x043e, 0x4400, 0x3c40, 0x7c00, 0x1c60, 0x1c00, 0x7c30, 0x7c00, 
436 0x6c10, 0x6c00, 0x4c50, 0x3c00, 0x6454, 0x4c00, 0x0836, 0x4100,
437 0x0077, 0x0000, 0x4136, 0x0800, 0x0201, 0x0201, 0x0205, 0x0200
438 ];
439 
440 // 1bit image. 1 - splashForeRgb, 0 - splashBackRgb.
441 // only center piece 52 x 36.
442 static immutable ushort[] splashImage = [
443 0x6000, 0x0180, 0x0000, 0x0000, 0x1806, 0x0000, 0x0000, 0x80e0, 0x0001, 0x0000,
444 0x0c00, 0x0018, 0x0000, 0xc000, 0x0181, 0x0000, 0x3000, 0x1818, 0x0000, 0x0000,
445 0x8383, 0x0001, 0x0000, 0x3070, 0x0018, 0x0000, 0x0700, 0x8187, 0x0fff, 0xf000,
446 0x1860, 0xfff8, 0x0000, 0x8e0f, 0x0001, 0x0000, 0xc1f0, 0x0018, 0x0000, 0x1b00,
447 0x019c, 0x0000, 0xb000, 0x1983, 0x0000, 0x0000, 0xb833, 0x0001, 0x0000, 0x0730,
448 0x001b, 0x0000, 0x6300, 0x01f0, 0x0000, 0x3000, 0x1e0e, 0x0000, 0x0000, 0xe0c3,
449 0xff81, 0x000f, 0x1c30, 0xf81c, 0x00ff, 0x8300, 0x01c1, 0x0000, 0x3000, 0x1838,
450 0x0000, 0x0000, 0x8303, 0x0001, 0x0000, 0x7030, 0x0000, 0x0000, 0x0300, 0x0006,
451 0x0000, 0x3000, 0x00e0, 0x0000, 0x0000, 0x0c03, 0x0000, 0x0000, 0xc030, 0x0000,
452 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
453 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x6530, 0xb8b8, 0xcbba,
454 0x75ca, 0x8987, 0x9919, 0x5e64, 0xb852, 0x92bb, 0xaa6a, 0x0000, 
455 ];
456 
457 enum splashBackRgb = 0xFFAA0000;
458 enum splashForeRgb = 0xFF00FFFF;
459 
460 enum splashWidth = 52;
461 enum splashHeight = 36;
462 
463 enum splashX = 38;
464 enum splashY = 25;