summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/video/bt459.cpp
blob: e26f0af0867ca2e417ba494d0bb250518ef584e2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
// license:BSD-3-Clause
// copyright-holders:Patrick Mackinlay

/*
 * An implementation of the Brooktree Bt459 150MHz Monolithic CMOS 256x24 Color Palette RAMDAC device.
 *
 * The device was initially rated at 135MHz and increased to 150MHz with revision B. The revision
 * register (the only software-visible change) is implemented in this emulation.
 *
 * Reference: http://www.bitsavers.org/components/brooktree/_dataBooks/1991_Brooktree_Product_Databook.pdf
 *
 * TODO
 *   - pixel pan and zoom
 *   - overlay/underlay
 */

#include "emu.h"
#include "bt459.h"

#include "screen.h"

#define VERBOSE 0
#include "logmacro.h"

DEFINE_DEVICE_TYPE(BT459, bt459_device, "bt459", "Brooktree Bt459 256 Color RAMDAC")

void bt459_device::map(address_map &map)
{
	map(0x00, 0x00).rw(FUNC(bt459_device::address_lo_r), FUNC(bt459_device::address_lo_w));
	map(0x01, 0x01).rw(FUNC(bt459_device::address_hi_r), FUNC(bt459_device::address_hi_w));
	map(0x02, 0x02).rw(FUNC(bt459_device::register_r), FUNC(bt459_device::register_w));
	map(0x03, 0x03).rw(FUNC(bt459_device::palette_r), FUNC(bt459_device::palette_w));
}

bt459_device::bt459_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, BT459, tag, owner, clock)
	, device_palette_interface(mconfig, *this)
{
}

void bt459_device::device_start()
{
	save_item(NAME(m_address));
	save_item(NAME(m_address_rgb));

	save_item(NAME(m_overlay_color));
	save_item(NAME(m_cursor_color));

	save_item(NAME(m_command_0));
	save_item(NAME(m_command_1));
	save_item(NAME(m_command_2));
	save_item(NAME(m_pixel_read_mask));
	save_item(NAME(m_pixel_blink_mask));
	save_item(NAME(m_overlay_read_mask));
	save_item(NAME(m_overlay_blink_mask));
	save_item(NAME(m_interleave));
	save_item(NAME(m_test));
	save_item(NAME(m_red_signature));
	save_item(NAME(m_green_signature));
	save_item(NAME(m_blue_signature));
	save_item(NAME(m_cursor_command));

	save_item(NAME(m_cursor_x));
	save_item(NAME(m_cursor_y));
	save_item(NAME(m_window_x));
	save_item(NAME(m_window_y));
	save_item(NAME(m_window_w));
	save_item(NAME(m_window_h));

	save_item(NAME(m_cursor_ram));
	save_item(NAME(m_palette_ram));

	save_item(NAME(m_blink_start));
}

void bt459_device::device_reset()
{
	m_blink_start = -1;
}

/*
 * To write color data, the MPU loads the address register with the address of
 * the primary color palette RAM, overlay RAM or cursor color register location
 * to be modified. The MPU performs three successive write cycles (8 bits each
 * of red, green, and blue), using C0 and C1 to select either the primary color
 * palette RAM, overlay RAM or cursor color registers. After the blue write
 * cycle, the address register then increments to the next location, which the
 * MPU may modify by writing another sequence of red, green and blue data.
 * Reading color data is similar to writing it, except the MPU executes read
 * cycles when it reads color data.
 *
 * When the MPU is accessing the color palette RAM, overlay RAM or cursor color
 * registers, the address register increments after each blue read or write
 * cycle. To keep track of the red, green and blue read/write cycles, the
 * address register has two additional bits (ADDRa, ADDRb) that count modulo
 * three. They are reset to zero when the MPU reads or writes the address
 * register. The MPU does not have access to these bits.
 */
u8 bt459_device::get_component(rgb_t *arr, int index)
{
	switch (m_address_rgb)
	{
	case 0: // red component
		if (!machine().side_effects_disabled())
			m_address_rgb = 1;
		return (m_command_2 & CR2524) == CR2524_RED ? arr[index].g() : arr[index].r();

	case 1: // green component
		if (!machine().side_effects_disabled())
			m_address_rgb = 2;
		return arr[index].g();

	case 2: // blue component
		if (!machine().side_effects_disabled())
		{
			m_address_rgb = 0;
			m_address = (m_address + 1) & ADDRESS_MASK;
		}
		return (m_command_2 & CR2524) == CR2524_BLUE ? arr[index].g() : arr[index].b();
	}

	// can't happen
	return 0;
}

void bt459_device::set_component(rgb_t *arr, int index, u8 data)
{
	switch (m_address_rgb)
	{
	case 0: // red component
		m_address_rgb = 1;
		(m_command_2 & CR2524) == CR2524_RED ? arr[index].set_g(data) : arr[index].set_r(data);
		break;

	case 1: // green component
		m_address_rgb = 2;
		arr[index].set_g(data);
		break;

	case 2: // blue component
		m_address_rgb = 0;
		m_address = (m_address + 1) & ADDRESS_MASK;
		(m_command_2 & CR2524) == CR2524_BLUE ? arr[index].set_g(data) : arr[index].set_b(data);
		break;
	}
}

READ8_MEMBER(bt459_device::address_lo_r)
{
	// reset component pointer and return address register lsb
	if (!machine().side_effects_disabled())
		m_address_rgb = 0;
	return m_address & ADDRESS_LSB;
}

WRITE8_MEMBER(bt459_device::address_lo_w)
{
	// reset component pointer and set address register lsb
	m_address_rgb = 0;
	m_address = (m_address & ADDRESS_MSB) | data;
}

READ8_MEMBER(bt459_device::address_hi_r)
{
	// reset component pointer and return address register msb
	if (!machine().side_effects_disabled())
		m_address_rgb = 0;
	return (m_address & ADDRESS_MSB) >> 8;
}

WRITE8_MEMBER(bt459_device::address_hi_w)
{
	// reset component pointer and set address register msb
	m_address_rgb = 0;
	m_address = ((data << 8) | (m_address & ADDRESS_LSB)) & ADDRESS_MASK;
}

READ8_MEMBER(bt459_device::register_r)
{
	u8 result = 0;

	switch (m_address)
	{
	case REG_OVERLAY_COLOR_0:
	case REG_OVERLAY_COLOR_1:
	case REG_OVERLAY_COLOR_2:
	case REG_OVERLAY_COLOR_3:
	case REG_OVERLAY_COLOR_4:
	case REG_OVERLAY_COLOR_5:
	case REG_OVERLAY_COLOR_6:
	case REG_OVERLAY_COLOR_7:
	case REG_OVERLAY_COLOR_8:
	case REG_OVERLAY_COLOR_9:
	case REG_OVERLAY_COLOR_10:
	case REG_OVERLAY_COLOR_11:
	case REG_OVERLAY_COLOR_12:
	case REG_OVERLAY_COLOR_13:
	case REG_OVERLAY_COLOR_14:
	case REG_OVERLAY_COLOR_15:
		return get_component(m_overlay_color, m_address & 0xf);

	case REG_CURSOR_COLOR_1: return get_component(m_cursor_color, 0);
	case REG_CURSOR_COLOR_2: return get_component(m_cursor_color, 1);
	case REG_CURSOR_COLOR_3: return get_component(m_cursor_color, 2);

	case REG_ID:
		result = m_id;
		LOG("id register read (%s)\n", machine().describe_context());
		break;

	case REG_COMMAND_0: result = m_command_0; break;
	case REG_COMMAND_1: result = m_command_1; break;
	case REG_COMMAND_2: result = m_command_2; break;
	case REG_PIXEL_READ_MASK: result = m_pixel_read_mask; break;

	case REG_PIXEL_BLINK_MASK: result = m_pixel_blink_mask; break;

	case REG_OVERLAY_READ_MASK: result = m_overlay_read_mask; break;
	case REG_OVERLAY_BLINK_MASK: result = m_overlay_blink_mask; break;
	case REG_INTERLEAVE: result = m_interleave; break;

	case REG_TEST: result = m_test; break;
	case REG_RED_SIGNATURE: result = m_red_signature; break;
	case REG_GREEN_SIGNATURE: result = m_green_signature; break;
	case REG_BLUE_SIGNATURE: result = m_blue_signature; break;

	case REG_REVISION:
		result = m_revision;
		LOG("revision register read (%s)\n", machine().describe_context());
		break;

	case REG_CURSOR_COMMAND: result = m_cursor_command; break;

	case REG_CURSOR_X_LO: result = m_cursor_x & 0xff; break;
	case REG_CURSOR_X_HI: result = (m_cursor_x >> 8); break;
	case REG_CURSOR_Y_LO: result = m_cursor_y & 0xff; break;
	case REG_CURSOR_Y_HI: result = (m_cursor_y >> 8); break;

	case REG_WINDOW_X_LO: result = m_window_x & 0xff; break;
	case REG_WINDOW_X_HI: result = (m_window_x >> 8); break;
	case REG_WINDOW_Y_LO: result = m_window_y & 0xff; break;
	case REG_WINDOW_Y_HI: result = (m_window_y >> 8); break;

	case REG_WINDOW_W_LO: result = m_window_w & 0xff; break;
	case REG_WINDOW_W_HI: result = (m_window_w >> 8); break;
	case REG_WINDOW_H_LO: result = m_window_h & 0xff; break;
	case REG_WINDOW_H_HI: result = (m_window_h >> 8); break;

	default:
		if (m_address >= CURSOR_RAM_START && m_address <= CURSOR_RAM_END)
			result = m_cursor_ram[m_address & CURSOR_RAM_MASK];
		else
			LOG("read from unknown address 0x%04x (%s)\n", m_address, machine().describe_context());
		break;
	}

	// increment address register and return result
	if (!machine().side_effects_disabled())
		m_address = (m_address + 1) & ADDRESS_MASK;

	return result;
}

WRITE8_MEMBER(bt459_device::register_w)
{
	switch (m_address)
	{
	case REG_COMMAND_0:
		m_command_0 = data;
		LOG("command register 0: multiplex select %s, use %s, blink rate %s, block mode %d bits per pixel\n",
			(data & CR0706) == CR0706_51MPX ? "5:1" :
			(data & CR0706) == CR0706_11MPX ? "1:1" :
			(data & CR0706) == CR0706_41MPX ? "4:1" : "reserved",
			(data & CR05) ? "overlay color 0" : "color palette RAM",
			(data & CR0302) == CR0302_6464 ? "64 on 64 off" :
			(data & CR0302) == CR0302_3232 ? "32 on 32 off" :
			(data & CR0302) == CR0302_1616 ? "16 on 16 off" : "16 on 48 off",
			8 >> (data & CR0100));

		// reset the blink timer
		m_blink_start = -1;
		break;

	case REG_COMMAND_1:
		m_command_1 = data;
		LOG("command register 1: pan select %d pixels, zoom factor %dx\n",
			(data >> 5), (data & CR1310) + 1);
		break;

	case REG_COMMAND_2:
		m_command_2 = data;
		LOG("command register 2: %s sync, %s IRE pedestal, load palette RAM select %s, PLL select %s, %s overlays, %s cursor, %s test\n",
			(data & CR27) ? "enable" : "disable",
			(data & CR26) ? "7.5" : "0",
			(data & CR2524) == CR2524_BLUE ? "blue RAMDAC" :
			(data & CR2524) == CR2524_GREEN ? "green RAMDAC" :
			(data & CR2524) == CR2524_RED ? "red RAMDAC" : "normal",
			(data & CR23) ? "BLANK*" : "SYNC*",
			(data & CR22) ? "X Windows" : "normal",
			(data & CR21) ? "X Windows" : "normal",
			(data & CR20) ? "data strobe" : "signature analysis");
		break;

	case REG_PIXEL_READ_MASK:
		m_pixel_read_mask = data;
		LOG("pixel read mask register: 0x%02x\n", data);
		break;

	case REG_PIXEL_BLINK_MASK:
		m_pixel_blink_mask = data;
		LOG("pixel blink mask register: 0x%02x\n", data);
		break;

	case REG_OVERLAY_READ_MASK:
		m_overlay_read_mask = data;
		LOG("overlay read mask register: 0x%02x\n", data);
		break;

	case REG_OVERLAY_BLINK_MASK:
		m_overlay_blink_mask = data;
		LOG("overlay blink mask register: 0x%02x\n", data);
		break;

	case REG_INTERLEAVE:
		m_interleave = data;
		LOG("interleave register: interleave select %d pixels, first pixel select pixel %c, overlay interleave %s, underlay %s\n",
			data >> 5,
			((data & CR3432) >> 2) + 'A',
			(data & CR31) ? "enabled" : "disabled",
			(data & CR30) ? "enabled" : "disabled");
		break;

	case REG_TEST:
		m_test = data;
		LOG("test register: 0x%02x\n", data);
		break;

	case REG_RED_SIGNATURE:
		m_red_signature = data;
		LOG("red signature register: 0x%02x\n", data);
		break;
	case REG_GREEN_SIGNATURE:
		m_green_signature = data;
		LOG("green signature register: 0x%02x\n", data);
		break;
	case REG_BLUE_SIGNATURE:
		m_blue_signature = data;
		LOG("blue signature register: 0x%02x\n", data);
		break;

	case REG_CURSOR_COMMAND:
		m_cursor_command = data;
		LOG("cursor command register: 64x64 cursor plane1 %s, 64x64 cursor plane0 %s, cross hair cursor plane1 %s, "
			"cross hair cursor plane0 %s, cursor format %s, cross hair thickness %d pixels, cursor blink %s\n",
			(data & CR47) ? "enable" : "disable",
			(data & CR46) ? "enable" : "disable",
			(data & CR45) ? "enable" : "disable",
			(data & CR44) ? "enable" : "disable",
			(data & CR43) ? "OR" : "XOR",
			(data & CR4241) + 1,
			(data & CR40) ? "enable" : "disable"
		);
		break;

	case REG_CURSOR_X_LO:
		m_cursor_x = (m_cursor_x & 0x0f00) | data;
		LOG("cursor x low register: 0x%02x\n", data);
		break;
	case REG_CURSOR_X_HI:
		m_cursor_x = ((data & 0xf) << 8) | (m_cursor_x & 0xff);
		LOG("cursor x high register: 0x%02x\n", data);
		break;
	case REG_CURSOR_Y_LO:
		m_cursor_y = (m_cursor_y & 0x0f00) | data;
		LOG("cursor y low register: 0x%02x\n", data);
		break;
	case REG_CURSOR_Y_HI:
		m_cursor_y = ((data & 0xf) << 8) | (m_cursor_y & 0xff);
		LOG("cursor y high register: 0x%02x\n", data);
		break;

	case REG_WINDOW_X_LO:
		m_window_x = (m_window_x & 0x0f00) | data;
		LOG("window x low register: 0x%02x\n", data);
		break;
	case REG_WINDOW_X_HI:
		m_window_x = ((data & 0xf) << 8) | (m_window_x & 0xff);
		LOG("window x high register: 0x%02x\n", data);
		break;
	case REG_WINDOW_Y_LO:
		m_window_y = (m_window_y & 0x0f00) | data;
		LOG("window y low register: 0x%02x\n", data);
		break;
	case REG_WINDOW_Y_HI:
		m_window_y = ((data & 0xf) << 8) | (m_window_y & 0xff);
		LOG("window y high register: 0x%02x\n", data);
		break;

	case REG_WINDOW_W_LO:
		m_window_w = (m_window_w & 0x0f00) | data;
		LOG("window width low register: 0x%02x\n", data);
		break;
	case REG_WINDOW_W_HI:
		m_window_w = ((data & 0xf) << 8) | (m_window_w & 0xff);
		LOG("window width high register: 0x%02x\n", data);
		break;
	case REG_WINDOW_H_LO:
		m_window_h = (m_window_h & 0x0f00) | data;
		LOG("window height low register: 0x%02x\n", data);
		break;
	case REG_WINDOW_H_HI:
		m_window_h = ((data & 0xf) << 8) | (m_window_h & 0xff);
		LOG("window height high register: 0x%02x\n", data);
		break;

	case REG_OVERLAY_COLOR_0:
	case REG_OVERLAY_COLOR_1:
	case REG_OVERLAY_COLOR_2:
	case REG_OVERLAY_COLOR_3:
	case REG_OVERLAY_COLOR_4:
	case REG_OVERLAY_COLOR_5:
	case REG_OVERLAY_COLOR_6:
	case REG_OVERLAY_COLOR_7:
	case REG_OVERLAY_COLOR_8:
	case REG_OVERLAY_COLOR_9:
	case REG_OVERLAY_COLOR_10:
	case REG_OVERLAY_COLOR_11:
	case REG_OVERLAY_COLOR_12:
	case REG_OVERLAY_COLOR_13:
	case REG_OVERLAY_COLOR_14:
	case REG_OVERLAY_COLOR_15:
	{
		const int index = m_address & 0xf;
		set_component(m_overlay_color, index, data);

		// update the mame palette to match the device
		if (m_address_rgb == 0)
			set_pen_color(BT459_PIXEL_COLORS + index, m_overlay_color[index]);
		return;
	}

	case REG_CURSOR_COLOR_1:
		set_component(m_cursor_color, 0, data);

		// update the mame palette to match the device
		if (m_address_rgb == 0)
			set_pen_color(BT459_PIXEL_COLORS + BT459_OVERLAY_COLORS + 0, m_cursor_color[0]);
		return;

	case REG_CURSOR_COLOR_2:
		set_component(m_cursor_color, 1, data);

		// update the mame palette to match the device
		if (m_address_rgb == 0)
			set_pen_color(BT459_PIXEL_COLORS + BT459_OVERLAY_COLORS + 1, m_cursor_color[1]);
		return;

	case REG_CURSOR_COLOR_3:
		set_component(m_cursor_color, 2, data);

		// update the mame palette to match the device
		if (m_address_rgb == 0)
			set_pen_color(BT459_PIXEL_COLORS + BT459_OVERLAY_COLORS + 2, m_cursor_color[2]);
		return;

	default:
		if (m_address >= CURSOR_RAM_START && m_address <= CURSOR_RAM_END)
			m_cursor_ram[m_address & CURSOR_RAM_MASK] = data;
		else
			LOG("write to unknown address 0x%04x data 0x%02x (%s)\n", m_address, data, machine().describe_context());
		break;
	}

	// increment address register
	m_address = (m_address + 1) & ADDRESS_MASK;
}

READ8_MEMBER(bt459_device::palette_r)
{
	// return component from palette ram
	return get_component(m_palette_ram, m_address & 0xff);
}

WRITE8_MEMBER(bt459_device::palette_w)
{
	// set component in color palette ram
	const int index = m_address & 0xff;
	set_component(m_palette_ram, index, data);

	// update the mame palette to match the device
	if (m_address_rgb == 0)
		set_pen_color(index, m_palette_ram[index]);
}

void bt459_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect, u8 *pixel_data)
{
	// initialise the blink timer
	if (m_blink_start > screen.frame_number())
		m_blink_start = screen.frame_number();

	// compute the blink state according to the programmed duty cycle
	const bool blink_state = ((screen.frame_number() - m_blink_start) & (
		(m_command_0 & CR0302) == CR0302_1616 ? 0x10 :
		(m_command_0 & CR0302) == CR0302_3232 ? 0x20 :
		(m_command_0 & CR0302) == CR0302_6464 ? 0x40 : 0x30)) == 0;

	// compute the pixel mask from the pixel read mask and blink mask/state
	const u8 pixel_mask = m_pixel_read_mask & (blink_state ? 0xffU : ~m_pixel_blink_mask);

	// draw visible pixel data
	switch (m_command_0 & CR0100)
	{
	case CR0100_1BPP:
		for (int y = screen.visible_area().min_y; y <= screen.visible_area().max_y; y++)
			for (int x = screen.visible_area().min_x; x <= screen.visible_area().max_x; x += 8)
			{
				u8 data = *pixel_data++;

				bitmap.pix(y, x + 7) = get_rgb(data & 0x1, pixel_mask); data >>= 1;
				bitmap.pix(y, x + 6) = get_rgb(data & 0x1, pixel_mask); data >>= 1;
				bitmap.pix(y, x + 5) = get_rgb(data & 0x1, pixel_mask); data >>= 1;
				bitmap.pix(y, x + 4) = get_rgb(data & 0x1, pixel_mask); data >>= 1;
				bitmap.pix(y, x + 3) = get_rgb(data & 0x1, pixel_mask); data >>= 1;
				bitmap.pix(y, x + 2) = get_rgb(data & 0x1, pixel_mask); data >>= 1;
				bitmap.pix(y, x + 1) = get_rgb(data & 0x1, pixel_mask); data >>= 1;
				bitmap.pix(y, x + 0) = get_rgb(data & 0x1, pixel_mask);
			}
		break;

	case CR0100_2BPP:
		for (int y = screen.visible_area().min_y; y <= screen.visible_area().max_y; y++)
			for (int x = screen.visible_area().min_x; x <= screen.visible_area().max_x; x += 4)
			{
				u8 data = *pixel_data++;

				bitmap.pix(y, x + 3) = get_rgb(data & 0x3, pixel_mask); data >>= 2;
				bitmap.pix(y, x + 2) = get_rgb(data & 0x3, pixel_mask); data >>= 2;
				bitmap.pix(y, x + 1) = get_rgb(data & 0x3, pixel_mask); data >>= 2;
				bitmap.pix(y, x + 0) = get_rgb(data & 0x3, pixel_mask);
			}
		break;

	case CR0100_4BPP:
		for (int y = screen.visible_area().min_y; y <= screen.visible_area().max_y; y++)
			for (int x = screen.visible_area().min_x; x <= screen.visible_area().max_x; x += 2)
			{
				u8 data = *pixel_data++;

				bitmap.pix(y, x + 1) = get_rgb(data & 0x7, pixel_mask); data >>= 4;
				bitmap.pix(y, x + 0) = get_rgb(data & 0x7, pixel_mask);
			}
		break;

	case CR0100_8BPP:
		for (int y = screen.visible_area().min_y; y <= screen.visible_area().max_y; y++)
			for (int x = screen.visible_area().min_x; x <= screen.visible_area().max_x; x++)
				bitmap.pix(y, x) = get_rgb(*pixel_data++, pixel_mask);
		break;
	}

	// draw cursors when visible and not blinked off
	if ((m_cursor_command & (CR47 | CR46 | CR45 | CR44)) && ((m_cursor_command & CR40) == 0 || blink_state))
	{
		// get 64x64 bitmap and cross hair cursor plane enable
		const u8 bm_cursor_enable = (m_cursor_command & (CR47 | CR46)) >> 6;
		const u8 ch_cursor_enable = (m_cursor_command & (CR45 | CR44)) >> 4;

		// get cross hair cursor half thickness
		const int ch_thickness = (m_cursor_command & CR4241) >> 1;

		/*
		 * The cursor (x) value to be written is calculated as follows:
		 *
		 *   Cx = desired display screen (x) position + H - P
		 *
		 * where
		 *
		 *   P = 37 if 1:1 input multiplexing, 52 if 4:1 input multiplexing,
		 *       57 if 5:1 input multiplexing
		 *   H = number of pixels between the first rising edge of LD*
		 *       following the falling edge of HSYNC* to active video
		 *
		 * The cursor (y) value to be written is calculated as follows:
		 *
		 *   Cy = desired display screen (y) position + V - 32
		 *
		 * where
		 *
		 *   V = number of scan lines from the second sync pulse during
		 *       vertical blanking to active video
		 *
		 * Values from $0FC0 (-64) to $0FBF (+4031) may be loaded into the
		 * cursor (y) register. The negative values ($0FC0 to $0FFF) are used
		 * in situations where V < 32, and the cursor must be moved off the
		 * top of the screen.
		 */
		const int cursor_x = m_cursor_x + (
			(m_command_0 & CR0706) == CR0706_11MPX ? 37 :
			(m_command_0 & CR0706) == CR0706_41MPX ? 52 :
			(m_command_0 & CR0706) == CR0706_51MPX ? 57 : 0);
		const int cursor_y = (m_cursor_y < 0xfc0 ? m_cursor_y : m_cursor_y - 0x1000) + 32;

		// 64x64 bitmap cursor
		if (bm_cursor_enable)
		{
			// compute target 64x64 rectangle
			rectangle cursor(cursor_x - 31, cursor_x + 32, cursor_y - 31, cursor_y + 32);

			// intersect with screen bitmap
			cursor &= bitmap.cliprect();

			// draw if any portion is visible
			if (!cursor.empty())
			{
				for (int y = 0; y < 64; y++)
				{
					// get screen y pixel coordinate
					const int ypos = cursor_y - 31 + y;

					for (int x = 0; x < 64; x++)
					{
						// get screen x pixel coordinate
						const int xpos = cursor_x - 31 + x;

						// check if pixel is visible
						if (cursor.contains(xpos, ypos))
						{
							// retrieve 2 bits of 64x64 bitmap cursor data
							u8 data = (m_cursor_ram[y * 16 + (x >> 2)] >> ((3 - (x & 3)) << 1)) & bm_cursor_enable;

							// check for dual-cursor mode and combine with cross-hair data
							if (ch_cursor_enable)
								if (((x >= 31 - ch_thickness) && (x <= 31 + ch_thickness)) || ((y >= 31 - ch_thickness) && (y <= 31 + ch_thickness)))
									data = (m_cursor_command & CR43) ? data | ch_cursor_enable : data ^ ch_cursor_enable;

							// write cursor data to screen (normal or X Window mode)
							if (data && !((m_command_2 & CR21) && data == 1))
								bitmap.pix(ypos, xpos) = m_cursor_color[data - 1];
						}
					}
				}
			}
		}

		// cross hair cursor
		if (ch_cursor_enable)
		{
			// get the cross hair cursor color
			const rgb_t ch_color = m_cursor_color[ch_cursor_enable - 1];

			/*
			 * The window (x) value to be written is calculated as follows:
			 *
			 *   Wx = desired display screen (x) position + H - P
			 *
			 * where
			 *
			 *   P = 5 if 1:1 input multiplexing, 20 if 4:1 input multiplexing,
			 *       25 if 5:1 input multiplexing
			 *   H = number of pixels between the first rising edge of LD*
			 *       following the falling edge of HSYNC* to active video
			 *
			 * The window (y) value to be written is calculated as follows:
			 *
			 *   Wy = desired display screen (y) position + V
			 *
			 * where
			 *
			 *   V = number of scan lines from the second sync pulse during
			 *       vertical blanking to active video
			 *
			 * Values from $0000 to $0FFF may be written to the window (x) and
			 * (y) registers. A full-screen cross hair is implemented by
			 * loading the window (x,y) registers with $0000, and the window
			 * width and height registers with $0FFF.
			 */
			const bool full_screen = (m_window_x == 0 && m_window_y == 0 && m_window_w == 0x0fff && m_window_h == 0x0fff);
			const int window_x = full_screen ? screen.visible_area().min_x : m_window_x + (
				(m_command_0 & CR0706) == CR0706_11MPX ? 5 :
				(m_command_0 & CR0706) == CR0706_41MPX ? 20 :
				(m_command_0 & CR0706) == CR0706_51MPX ? 25 : 0);
			const int window_y = full_screen ? screen.visible_area().min_y : m_window_y;

			/*
			 * The actual window width is 2, 8 or 10 pixels more than the
			 * value specified by the window width register, depending on
			 * whether 1:1, 4:1 or 5:1 input multiplexing is specified. The
			 * actual window height is 2 pixels more than the value specified
			 * by the window height register. Therefore, the minimum window
			 * width is 2, 8 or 10 pixels for 1:1, 4:1 and 5:1 multiplexing,
			 * respectively. The minimum window height is 2 pixels.
			 *
			 * Values from $0000 to $0FFF may be written to the window width
			 * and height registers.
			 *
			 * Note: testing indicates the cross-hair cursor should be drawn
			 * strictly inside the window, although this is not 100% clear from
			 * the documentation.
			 */
			const int window_w = full_screen ? screen.visible_area().width() : m_window_w + (
				(m_command_0 & CR0706) == CR0706_11MPX ? 2 :
				(m_command_0 & CR0706) == CR0706_41MPX ? 8 :
				(m_command_0 & CR0706) == CR0706_51MPX ? 10 : 0);
			const int window_h = full_screen ? screen.visible_area().height() : m_window_h + 2;

			// check for dual-cursor mode
			if (bm_cursor_enable)
			{
				// draw the cross hair cursor as vertical and horizontal filled rectangles broken by the 64x64 cursor area
				rectangle v1(cursor_x - ch_thickness, cursor_x + ch_thickness, window_y + 1, cursor_y - 32);
				rectangle v2(cursor_x - ch_thickness, cursor_x + ch_thickness, cursor_y + 33, window_y + window_h);
				rectangle h1(window_x + 1, cursor_x - 32, cursor_y - ch_thickness, cursor_y + ch_thickness);
				rectangle h2(cursor_x + 33, window_x + window_w, cursor_y - ch_thickness, cursor_y + ch_thickness);

				v1 &= bitmap.cliprect();
				v2 &= bitmap.cliprect();
				h1 &= bitmap.cliprect();
				h2 &= bitmap.cliprect();

				if (!v1.empty())
					bitmap.fill(ch_color, v1);
				if (!v2.empty())
					bitmap.fill(ch_color, v2);
				if (!h1.empty())
					bitmap.fill(ch_color, h1);
				if (!h2.empty())
					bitmap.fill(ch_color, h2);
			}
			else
			{
				// draw the cross hair cursor as unbroken vertical and horizontal filled rectangles
				rectangle v(cursor_x - ch_thickness, cursor_x + ch_thickness, window_y + 1, window_y + window_h);
				rectangle h(window_x + 1, window_x + window_w, cursor_y - ch_thickness, cursor_y + ch_thickness);

				v &= bitmap.cliprect();
				h &= bitmap.cliprect();

				if (!v.empty())
					bitmap.fill(ch_color, v);
				if (!h.empty())
					bitmap.fill(ch_color, h);
			}
		}
	}
}