1 // license:BSD-3-Clause
2 // copyright-holders:Sergey Svishchev, F. Ulivi
3 /*
4     HP 1LL3-0005 GPU emulation.
5 
6     Used by HP Integral PC, possibly other HP products.
7 
8     On IPC, memory is 4 16Kx4bit DRAM chips = 32KB total (16K words),
9     but firmware probes memory size and can work with 128KB memory.
10     Undocumented "_desktop" mode requires this.
11 
12     Capabilities:
13     - up to 1024x1024 px on screen
14     - lines
15     - rectangles
16     - area fill with user-defined pattern
17     - 16x16 user-defined proportional font, with automatic cursor
18     - 16x16 user-defined sprite for mouse cursor (not a sprite layer)
19     - windows with blitter (copy, fill and scroll) and clipping
20 
21     To do:
22     - interrupt generation
23     - realistic timing?
24     - &c.
25 
26     This table summarizes the GPU commands.
27     "Code" is the hex code of command.
28     "Impl" shows whether the command is implemented in this driver or not.
29     "Parameter" reports the I/O data exchanged by the command. Each line
30     is a 16-bit word which is read from/written to the GPU by a pair of
31     8-bit accesses to data register (MSB first).
32     "I" is an input parameter (sent before command code)
33     "D" is an input data word (sent after command code)
34     "O" is an output data word.
35 
36     | *Cmd*     | *Code* | *Impl.* | *Parameter*         | *Description*                     |
37     |-----------+--------+---------+---------------------+-----------------------------------|
38     | NOP       |     00 | Y       | -                   | No operation. Used to close       |
39     |           |        |         |                     | RDMEM/WRMEM/RDWIN/WRWIN commands. |
40     | CONF      |     02 | Y       | D@0: H. timing 0    | Configure GPU                     |
41     |           |        |         | D@1: H. timing 1    |                                   |
42     |           |        |         | D@2: H. timing 2    |                                   |
43     |           |        |         | D@3: H. timing 3    |                                   |
44     |           |        |         | D@4: V. timing 0    |                                   |
45     |           |        |         | D@5: V. timing 1    |                                   |
46     |           |        |         | D@6: V. timing 2    |                                   |
47     |           |        |         | D@7: V. timing 3    |                                   |
48     |           |        |         | D@8: Words per line |                                   |
49     |           |        |         | D@9: Lines          |                                   |
50     |           |        |         | D@10: Option bits   |                                   |
51     | DISVID    |     03 | Y       | -                   | Disable video                     |
52     | ENVID     |     04 | Y       | -                   | Enable video                      |
53     | ?         |     06 | Y       | -                   | Unknown                           |
54     |           |        |         |                     | My guess is interrupt enable      |
55     |           |        |         |                     | 05 is probably int. disable       |
56     |           |        |         |                     | It's only used by diagb ROM       |
57     | WRMEM     |     07 | Y       | I@0: RAM address    | Write video RAM. Keep writing     |
58     |           |        |         | D@0: RAM word       | words with auto-incremented addr  |
59     |           |        |         | ...                 | until terminated by NOP.          |
60     | RDMEM     |     08 | Y       | I@0: RAM address    | Read video RAM. Read words until  |
61     |           |        |         | O@0: RAM word       | terminated by NOP.                |
62     |           |        |         | ...                 |                                   |
63     | WRSAD     |     09 | Y       | I@0: Start address  | Set screen start address          |
64     | WRORG     |     0a | Y       | I@0: Start address  | Set raster start address          |
65     | WRDAD     |     0b | Y       | I@0: Start address  | Set data start address            |
66     | WRRR      |     0c | Y       | I@0: RR             | Set replacement rule              |
67     | MOVEP     |     0d | Y       | I@0: X              | Set pen position                  |
68     |           |        |         | I@1: Y              |                                   |
69     | IMOVEP    |     0e | Y       | I@0: delta X        | Set pen position (incremental)    |
70     |           |        |         | I@1: delta Y        |                                   |
71     | DRAWP     |     0f | Y       | I@0: X              | Draw a line and move pen          |
72     |           |        |         | I@1: Y              |                                   |
73     | IDRAWP    |     10 | N       | I@0: delta X        | Draw a line (incremental)         |
74     |           |        |         | I@1: delta Y        |                                   |
75     | RDP       |     11 | Y       | O@0: pen X pos      | Read current pen position         |
76     |           |        |         | O@1: pen Y pos      |                                   |
77     | WRUDL     |     12 | Y       | I@0: UDL            | Set user-defined line pattern     |
78     | WRWINSIZ  |     13 | Y       | I@0: Window width   | Set window size                   |
79     |           |        |         | I@1: Window height  |                                   |
80     | WRWINORG  |     14 | Y       | I@0: Origin X       | Set window origin (UL corner)     |
81     |           |        |         | I@1: Origin Y       |                                   |
82     | COPY      |     15 | Y       | I@0: Dest. origin X | Copy a window. Parameter sets     |
83     |           |        |         | I@1: Dest. origin Y | the UL corner of the dest. win.   |
84     | FILL      |     16 | Y       | I@0: Pattern no.    | Fill a window                     |
85     | FRAME     |     17 | N       | -                   | Draw a frame (rectangle)          |
86     | SCROLUP   |     18 | Y       | I@0: Fill pattern   | Scroll a window upwards           |
87     |           |        |         | I@1: Scroll amount  |                                   |
88     | SCROLDN   |     19 | Y       | I@0: Fill pattern   | Scroll a window downwards         |
89     |           |        |         | I@1: Scroll amount  |                                   |
90     | SCROLLF   |     1a | N       | I@0: Fill pattern   | Scroll a window to the left       |
91     |           |        |         | I@1: Scroll amount  |                                   |
92     | SCROLLF   |     1b | N       | I@0: Fill pattern   | Scroll a window to the right      |
93     |           |        |         | I@1: Scroll amount  |                                   |
94     | RDWIN     |     1c | Y       | I@0: bit offset     | Read a window. Terminated by NOP. |
95     |           |        |         | O@0: data word      |                                   |
96     |           |        |         | ...                 |                                   |
97     | WRWIN     |     1d | Y       | I@0: bit offset     | Write a window. Terminated by NOP.|
98     |           |        |         | D@0: data word      |                                   |
99     |           |        |         | ...                 |                                   |
100     | RDWINPARM |     1e | Y       | O@0: Win. origin X  | Read current window parameters    |
101     |           |        |         | O@1: Win. origin Y  |                                   |
102     |           |        |         | O@2: Window width   |                                   |
103     |           |        |         | O@3: Window height  |                                   |
104     | CR        |     1f | N       | -                   | Carriage return: move pen to      |
105     |           |        |         |                     | start of line                     |
106     | CRLF      |     20 | Y       | -                   | CRLF: move pen to start of next   |
107     |           |        |         |                     | text row                          |
108     | LABEL     |     24 | Y       | I@0: character code | Write a character and move pen    |
109     | ENSP      |     26 | Y       | I@0: sprite pattern | Enable sprite                     |
110     | DISSP     |     27 | Y       | -                   | Disable sprite                    |
111     | MOVESP    |     28 | Y       | I@0: Sprite X pos   | Move sprite                       |
112     |           |        |         | I@1: Sprite Y pos   |                                   |
113     | IMOVESP   |     29 | N       | I@0: Sprite delta X | Move sprite (incremental)         |
114     |           |        |         | I@1: Sprite delta Y |                                   |
115     | RDSP      |     2a | Y       | O@0: Sprite X pos   | Read sprite position              |
116     |           |        |         | O@1: Sprite Y pos   |                                   |
117     | DRAWPX    |     2b | Y       | I@0: X              | Write a pixel                     |
118     |           |        |         | I@1: Y              |                                   |
119     | WRFAD     |     2c | Y       | I@0: Font address   | Set address of font data          |
120     | ENCURS    |     2d | Y       | I@0: cursor pattern | Enable cursor                     |
121     |           |        |         | I@1: cursor offset  |                                   |
122     | DISCURS   |     2e | Y       | -                   | Disable cursor                    |
123     | ID        |     3f | Y       | O@0: ID             | Read hw ID                        |
124 */
125 
126 #include "emu.h"
127 #include "hp1ll3.h"
128 
129 #include "screen.h"
130 
131 
132 ///*************************************************************************
133 //  MACROS / CONSTANTS
134 ///*************************************************************************
135 
136 /*
137  * command types (send)
138  *
139  * 0 -- no data
140  * 1 -- write 1 word of data, then command
141  * 2 -- write 2 words of data, then command
142  * 3 -- write command, then 12 words of data (= CONF only)
143  * 4 -- write 1 word of data, then command, then write X words of data, then write NOP
144  *
145  * (read)
146  *
147  * 3 -- write command, then read 2 words of data (X & Y coordinates)
148  * 4 -- write 1 word of data, then command, then read X words of data, then write NOP
149  */
150 #define NOP         0   // type 0
151 #define CONF        2   // type 3, configure GPU (screen size, timings...).  11 words of data.
152 #define DISVID      3   // type 0, disable video
153 #define ENVID       4   // type 0, enable video
154 #define WRMEM       7   // type 4, write GPU memory at offset, terminate by NOP
155 #define RDMEM       8   // type 4, read GPU memory from offset, terminate by NOP
156 #define WRSAD       9   // type 1, set screen area start address
157 #define WRORG       10  // type 1, set raster start address
158 #define WRDAD       11  // type 1, set data area start address (16x16 area fill, sprite and cursor)
159 #define WRRR        12  // type 1, set replacement rule (rasterop)
160 #define MOVEP       13  // type 2, move pointer
161 #define IMOVEP      14  // type 2, incremental move pointer
162 #define DRAWP       15  // type 2, draw line
163 #define IDRAWP      16  // type 2, incremental draw line
164 #define RDP         17  // type 3, read pointer position
165 #define WRUDL       18  // type 1, set user-defined line pattern (16-bit)
166 #define WRWINSIZ    19  // type 2, set window size
167 #define WRWINORG    20  // type 2, set window origin
168 #define COPY        21  // type 2, block copy
169 #define FILL        22  // type 1, fill area
170 #define FRAME       23  // type 0, draw rectangle
171 #define SCROLUP     24  // type 2, scroll up
172 #define SCROLDN     25  // type 2, scroll down
173 #define SCROLLF     26  // type 2, scroll left
174 #define SCROLRT     27  // type 2, scroll right
175 #define RDWIN       28  // type 4, read window raster
176 #define WRWIN       29  // type 4, write window raster
177 #define RDWINPARM   30
178 #define CR          31  // type 0, carriage return
179 #define CRLFx       32  // type 0, CR + LF
180 #define LABEL       36  // type 1, draw text
181 #define ENSP        38  // type 1, enable sprite
182 #define DISSP       39  // type 0, disable sprite
183 #define MOVESP      40  // type 2, move sprite
184 #define IMOVESP     41  // type 2, incremental move sprite
185 #define RDSP        42  // type 3, read sprite position
186 #define DRAWPX      43  // type 2, draw single pixel
187 #define WRFAD       44  // type 1, set font area start address
188 #define ENCURS      45  // type 2, enable cursor
189 #define DISCURS     46  // type 0, disable cursor
190 #define ID          63
191 
192 
193 /*
194  * Replacement Rules (rops).  sources:
195  *
196  * - NetBSD's diofbvar.h (definitions for Topcat chip)
197  * - pdf/hp/9000_300/specs/A-5958-4362-9_Series_300_Display_Color_Card_Theory_of_Operation_Oct85.pdf
198  *   refers to TOPCAT documentation p/n A-1FH2-2001-7 (not online)
199  */
200 #define RR_FORCE_ZERO   0x0
201 #define RR_CLEAR        RR_FORCE_ZERO
202 #define RR_AND          0x1
203 #define RR_AND_NOT_OLD  0x2
204 #define RR_NEW          0x3
205 #define RR_COPY         RR_NEW
206 #define RR_AND_NOT_NEW  0x4
207 #define RR_OLD          0x5
208 #define RR_XOR          0x6
209 #define RR_OR           0x7
210 #define RR_NOR          0x8
211 #define RR_XNOR         0x9
212 #define RR_NOT_OLD      0xa
213 #define RR_INVERT       RR_NOT_OLD
214 #define RR_OR_NOT_OLD   0xb
215 #define RR_NOT_NEW      0xc
216 #define RR_COPYINVERTED RR_NOT_NEW
217 #define RR_OR_NOT_NEW   0xd
218 #define RR_NAND         0xe
219 #define RR_FORCE_ONE    0xf
220 
221 constexpr unsigned WS = 16; // bits in a word
222 constexpr uint16_t MSB_MASK = 0x8000;   // Mask of MSB
223 
224 //**************************************************************************
225 //  MACROS / CONSTANTS
226 //**************************************************************************
227 
228 #define VERBOSE_DBG 2       /* general debug messages */
229 
230 #define DBG_LOG(N,M,A) \
231 	do { \
232 		if(VERBOSE_DBG>=N) \
233 		{ \
234 			if( M ) \
235 				logerror("%11.6f at %s: %-16s",machine().time().as_double(),machine().describe_context(),(char*)M ); \
236 			logerror A; \
237 		} \
238 	} while (0)
239 
240 // Index of words in configuration vector
241 enum : unsigned
242 {
243 	CONF_HOR_0 = 0,     // Horizontal timing, 1st word
244 	CONF_HOR_1 = 1,     // Horizontal timing, 2nd word
245 	CONF_HOR_2 = 2,     // Horizontal timing, 3rd word
246 	CONF_HOR_3 = 3,     // Horizontal timing, 4th word
247 	CONF_VER_0 = 4,     // Vertical timing, 1st word
248 	CONF_VER_1 = 5,     // Vertical timing, 2nd word
249 	CONF_VER_2 = 6,     // Vertical timing, 3rd word
250 	CONF_VER_3 = 7,     // Vertical timing, 4th word
251 	CONF_WPL   = 8,     // Words per line
252 	CONF_LINES = 9,     // Lines
253 	CONF_OPTS  = 10     // RAM size or option bits (more likely)
254 };
255 
256 // Fields of hor/ver timing words
257 enum : uint16_t
258 {
259 	HV_CNT_MASK  = 0x3ff,   // Mask of counter part
260 	HV_ZONE_MASK = 0xc00,   // Mask of zone part
261 	HV_ZONE_0    = 0x000,   // Value for zone 0
262 	HV_ZONE_1    = 0x400,   // Value for zone 1
263 	HV_ZONE_2    = 0x800,   // Value for zone 2
264 	HV_ZONE_3    = 0xc00    // Value for zone 3
265 };
266 
267 //**************************************************************************
268 //  GLOBAL VARIABLES
269 //**************************************************************************
270 
271 // devices
272 DEFINE_DEVICE_TYPE(HP1LL3, hp1ll3_device, "hp1ll3", "Hewlett-Packard 1LL3-0005 GPU")
273 
274 
275 //**************************************************************************
276 //  LIVE DEVICE
277 //**************************************************************************
278 
279 //-------------------------------------------------
280 //  hp1ll3_device - constructor
281 //-------------------------------------------------
282 
hp1ll3_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)283 hp1ll3_device::hp1ll3_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
284 	: device_t(mconfig, HP1LL3, tag, owner, clock)
285 	, device_video_interface(mconfig, *this)
286 	, m_vram_size(16)
287 {
288 }
289 
290 //-------------------------------------------------
291 //  device_start - device-specific startup
292 //-------------------------------------------------
293 
device_start()294 void hp1ll3_device::device_start()
295 {
296 	const rectangle &visarea = screen().visible_area();
297 
298 	// register for state saving
299 	save_item(NAME(m_conf));
300 
301 	screen().register_screen_bitmap(m_bitmap);
302 
303 	m_videoram = std::make_unique<uint16_t[]>(m_vram_size);
304 
305 	save_pointer(NAME(m_videoram) , m_vram_size);
306 	m_ram_addr_mask = uint16_t(m_vram_size - 1);
307 	m_horiz_pix_total = visarea.max_x + 1;
308 	m_vert_pix_total = visarea.max_y + 1;
309 }
310 
device_reset()311 void hp1ll3_device::device_reset()
312 {
313 	m_rd_ptr = m_wr_ptr = m_command = 0;
314 	m_sad = m_fad = m_dad = m_org = m_rr = m_udl = 0;
315 	m_enable_video = m_enable_cursor = m_enable_sprite = m_busy = false;
316 }
317 
get_pix_addr(uint16_t x,uint16_t y) const318 uint16_t hp1ll3_device::get_pix_addr(uint16_t x, uint16_t y) const
319 {
320 	return m_org + y * m_conf[CONF_WPL] + x / WS;
321 }
322 
point(int x,int y,bool pix,const uint16_t masks[])323 inline void hp1ll3_device::point(int x, int y, bool pix, const uint16_t masks[])
324 {
325 	uint16_t offset = get_pix_addr(x, y);
326 	uint16_t pix_mask = MSB_MASK >> (x % WS);
327 
328 	rmw_rop(offset, pix ? ~0 : 0, pix_mask, masks);
329 }
330 
331 // Bresenham algorithm -- from ef9365.cpp
line(int x1,int y1,int x2,int y2)332 void hp1ll3_device::line(int x1, int y1, int x2, int y2)
333 {
334 	int dx;
335 	int dy,t;
336 	int e;
337 	int x,y;
338 	int incy;
339 	int diago,horiz;
340 	unsigned char c1;
341 
342 	uint16_t rop_masks[4];
343 	get_rop_masks(m_rr, rop_masks);
344 
345 	uint16_t udl_scan = MSB_MASK;
346 
347 	c1 = 0;
348 	incy = 1;
349 
350 	if (x2 > x1)
351 		dx = x2 - x1;
352 	else
353 		dx = x1 - x2;
354 
355 	if (y2 > y1)
356 		dy = y2 - y1;
357 	else
358 		dy = y1 - y2;
359 
360 	if (dy > dx)
361 	{
362 		t = y2;
363 		y2 = x2;
364 		x2 = t;
365 
366 		t = y1;
367 		y1 = x1;
368 		x1 = t;
369 
370 		t = dx;
371 		dx = dy;
372 		dy = t;
373 
374 		c1 = 1;
375 	}
376 
377 	if (x1 > x2)
378 	{
379 		t = y2;
380 		y2 = y1;
381 		y1 = t;
382 
383 		t = x1;
384 		x1 = x2;
385 		x2 = t;
386 	}
387 
388 	horiz = dy << 1;
389 	diago = (dy - dx) << 1;
390 	e = (dy << 1) - dx;
391 
392 	if (y1 <= y2)
393 		incy = 1;
394 	else
395 		incy = -1;
396 
397 	x = x1;
398 	y = y1;
399 
400 	if (c1)
401 	{
402 		do
403 		{
404 			point(y, x, m_udl & udl_scan,rop_masks);
405 			udl_scan >>= 1;
406 			if (!udl_scan)
407 				udl_scan = MSB_MASK;
408 
409 			if (e > 0)
410 			{
411 				y = y + incy;
412 				e = e + diago;
413 			}
414 			else
415 			{
416 				e = e + horiz;
417 			}
418 
419 			x++;
420 
421 		} while (x <= x2);
422 	}
423 	else
424 	{
425 		do
426 		{
427 			point(x, y, m_udl & udl_scan,rop_masks);
428 			udl_scan >>= 1;
429 			if (!udl_scan)
430 				udl_scan = MSB_MASK;
431 
432 			if (e > 0)
433 			{
434 				y = y + incy;
435 				e = e + diago;
436 			}
437 			else
438 			{
439 				e = e + horiz;
440 			}
441 
442 			x++;
443 
444 		} while (x <= x2);
445 	}
446 }
447 
get_font(uint16_t & font_data,uint16_t & font_height) const448 void hp1ll3_device::get_font(uint16_t& font_data, uint16_t& font_height) const
449 {
450 	font_data = m_fad + rd_video(m_fad + 1) + 2;
451 	font_height = rd_video(m_fad);
452 }
453 
label(uint8_t chr,int width)454 void hp1ll3_device::label(uint8_t chr, int width)
455 {
456 	draw_cursor_sprite();
457 
458 	uint16_t font_data;
459 	uint16_t font_height;
460 	get_font(font_data, font_height);
461 
462 	Rectangle clip = get_window();
463 	Rectangle dst{{ m_cursor_x, m_cursor_y }, { uint16_t(width), font_height }};
464 	bitblt(font_data + chr * 16, 16, 16, Point { 0, 0 }, clip, dst, m_rr);
465 	m_cursor_x += width;
466 	draw_cursor_sprite();
467 }
468 
wr_video(uint16_t addr,uint16_t v)469 void hp1ll3_device::wr_video(uint16_t addr, uint16_t v)
470 {
471 	m_videoram[addr & m_ram_addr_mask] = v;
472 }
473 
rd_video(uint16_t addr) const474 uint16_t hp1ll3_device::rd_video(uint16_t addr) const
475 {
476 	return m_videoram[addr & m_ram_addr_mask];
477 }
478 
get_rop_masks(uint16_t rop,uint16_t masks[])479 void hp1ll3_device::get_rop_masks(uint16_t rop, uint16_t masks[])
480 {
481 	masks[0] = BIT(rop, 0) ? ~0 : 0;
482 	masks[1] = BIT(rop, 1) ? ~0 : 0;
483 	masks[2] = BIT(rop, 2) ? ~0 : 0;
484 	masks[3] = BIT(rop, 3) ? ~0 : 0;
485 }
486 
apply_rop(uint16_t old_pix,uint16_t new_pix,uint16_t glob_mask,const uint16_t masks[])487 uint16_t hp1ll3_device::apply_rop(uint16_t old_pix, uint16_t new_pix, uint16_t glob_mask, const uint16_t masks[])
488 {
489 	uint16_t diff0 = old_pix & new_pix;
490 	uint16_t diff1 = ~old_pix & new_pix;
491 	uint16_t diff2 = old_pix & ~new_pix;
492 	uint16_t diff3 = ~old_pix & ~new_pix;
493 
494 	// The idea here is that ROP is forced to be "RR_OLD" for all zero bits in glob_mask
495 	// so that the corresponding bits in new_pix are ignored
496 	return
497 			((masks[0] | ~glob_mask) & diff0) |
498 			(masks[1] & glob_mask & diff1) |
499 			((masks[2] | ~glob_mask) & diff2) |
500 			(masks[3] & glob_mask & diff3);
501 }
502 
rmw_rop(uint16_t addr,uint16_t new_pix,uint16_t glob_mask,const uint16_t masks[])503 void hp1ll3_device::rmw_rop(uint16_t addr, uint16_t new_pix, uint16_t glob_mask, const uint16_t masks[])
504 {
505 	wr_video(addr, apply_rop(rd_video(addr), new_pix, glob_mask, masks));
506 }
507 
clip_coord(int size_1,int & p1,int origin_clip,int size_clip,int & origin_2,int & size_2) const508 void hp1ll3_device::clip_coord(int size_1, int &p1, int origin_clip, int size_clip, int &origin_2, int &size_2) const
509 {
510 	// origin_1 is implicitly 0
511 	int corner_2 = origin_2 + size_2;
512 	// Clip origin_2 & p1 w.r.t. clip rectangle
513 	int t = origin_2 - origin_clip;
514 	if (t < 0)
515 	{
516 		p1 -= t;
517 		origin_2 = origin_clip;
518 	}
519 	// Clip size_2 w.r.t. clip rectangle
520 	int corner_clip = origin_clip + size_clip;
521 	if (corner_2 > corner_clip)
522 		size_2 = corner_clip - origin_2;
523 
524 	// Clip size_2 w.r.t. rectangle 1 (whose origin is 0,0)
525 	t = p1 + size_2 - size_1;
526 	if (t > 0)
527 		size_2 -= t;
528 }
529 
bitblt(uint16_t src_base_addr,unsigned src_width,unsigned src_height,Point src_p,const Rectangle & clip_rect,const Rectangle & dst_rect,uint16_t rop,bool use_m_org)530 bool hp1ll3_device::bitblt(uint16_t src_base_addr, unsigned src_width, unsigned src_height, Point src_p,
531 						   const Rectangle &clip_rect, const Rectangle &dst_rect, uint16_t rop, bool use_m_org)
532 {
533 	DBG_LOG(3,0,("bitblt %04x,%u,%u,(%u,%u),(%u,%u,%u,%u),(%u,%u,%u,%u),%u\n", src_base_addr,
534 				 src_width,
535 				 src_height,
536 				 src_p.x,
537 				 src_p.y,
538 				 clip_rect.origin.x, clip_rect.origin.y, clip_rect.size.x, clip_rect.size.y,
539 				 dst_rect.origin.x, dst_rect.origin.y, dst_rect.size.x, dst_rect.size.y,
540 				 rop));
541 	int src_x = src_p.x;
542 	int dst_x = static_cast<int16_t>(dst_rect.origin.x);
543 	int dst_width = dst_rect.size.x;
544 	int src_y = src_p.y;
545 	int dst_y = static_cast<int16_t>(dst_rect.origin.y);
546 	int dst_height = dst_rect.size.y;
547 	// Clip x-coordinates
548 	clip_coord(src_width, src_x, clip_rect.origin.x, clip_rect.size.x, dst_x, dst_width);
549 	// Clip y-coordinates
550 	clip_coord(src_height, src_y, clip_rect.origin.y, clip_rect.size.y, dst_y, dst_height);
551 
552 	DBG_LOG(3,0,("bitblt (%u,%u) (%u,%u,%u,%u)\n", src_x, src_y, dst_x, dst_y, dst_width, dst_height));
553 	if (dst_width <= 0 || dst_height <= 0)
554 		return false;
555 
556 	unsigned dst_rounded_width = m_conf[CONF_WPL] * WS;
557 
558 	// p1_pix & p2_pix point to a uniquely identified pixel in video RAM
559 	unsigned p1_pix = unsigned(src_base_addr) * WS + src_x + src_width * src_y;
560 	unsigned p2_pix = unsigned(use_m_org ? m_org : m_sad) * WS + dst_x + dst_rounded_width * dst_y;
561 
562 	DBG_LOG(3,0,("bitblt p1_pix=%u p2_pix=%u\n", p1_pix, p2_pix));
563 	uint16_t rop_masks[4];
564 	get_rop_masks(rop, rop_masks);
565 
566 	if (p1_pix < p2_pix)
567 	{
568 		// Move block going up (decrease y)
569 		p1_pix += dst_height * src_width + dst_width - 1;
570 		p2_pix += dst_height * dst_rounded_width + dst_width - 1;
571 		DBG_LOG(3,0,("bitblt rev p1_pix=%u p2_pix=%u\n", p1_pix, p2_pix));
572 		while (dst_height--)
573 		{
574 			p1_pix -= src_width;
575 			p2_pix -= dst_rounded_width;
576 			rowbltneg(p1_pix, p2_pix, dst_width, rop_masks);
577 		}
578 	}
579 	else
580 	{
581 		// Move block going down (increase y)
582 		DBG_LOG(3,0,("bitblt fwd\n"));
583 		while (dst_height--)
584 		{
585 			rowbltpos(p1_pix, p2_pix, dst_width, rop_masks);
586 			p1_pix += src_width;
587 			p2_pix += dst_rounded_width;
588 		}
589 	}
590 
591 	return true;
592 }
593 
rowbltpos(unsigned p1_pix,unsigned p2_pix,int width,const uint16_t masks[])594 void hp1ll3_device::rowbltpos(unsigned p1_pix, unsigned p2_pix, int width, const uint16_t masks[])
595 {
596 	// p1_pix and p2_pix point to the leftmost pixel of the row
597 	while (width > 0)
598 	{
599 		unsigned p1_word = p1_pix / WS;
600 		unsigned p1_bit = p1_pix % WS;
601 		// Get src pixels and align to MSB
602 		uint16_t new_pix = rd_video(p1_word) << p1_bit;
603 		if (p1_bit)
604 			new_pix |= rd_video(p1_word + 1) >> (WS - p1_bit);
605 
606 		unsigned p2_word = p2_pix / WS;
607 		unsigned p2_bit = p2_pix % WS;
608 		uint16_t old_pix = rd_video(p2_word);
609 		unsigned w = p2_bit + width;
610 		uint16_t glob_mask = ~0;
611 		if (p2_bit)
612 		{
613 			// Left end
614 			new_pix >>= p2_bit;
615 			glob_mask >>= p2_bit;
616 		}
617 		if (w < WS)
618 		{
619 			// Right end
620 			glob_mask &= ~0 << (WS - w);
621 		}
622 		rmw_rop(p2_word, new_pix, glob_mask, masks);
623 		DBG_LOG(3,0,("rowbltpos %04x %04x %04x>%04x %u %u %u\n", old_pix, new_pix, glob_mask, rd_video(p2_word), p1_pix, p2_pix, width));
624 		w = WS - p2_bit;
625 		p1_pix += w;
626 		p2_pix += w;
627 		width -= w;
628 	}
629 }
630 
rowbltneg(unsigned p1_pix,unsigned p2_pix,int width,const uint16_t masks[])631 void hp1ll3_device::rowbltneg(unsigned p1_pix, unsigned p2_pix, int width, const uint16_t masks[])
632 {
633 	// p1_pix and p2_pix point to the rightmost pixel of the row
634 	while (width > 0)
635 	{
636 		unsigned p1_word = p1_pix / WS;
637 		unsigned p1_bit = p1_pix % WS;
638 		// Get src pixels and align to LSB
639 		uint16_t new_pix = rd_video(p1_word) >> (WS - 1 - p1_bit);
640 		if (p1_bit != (WS - 1))
641 			new_pix |= rd_video(p1_word - 1) << (p1_bit + 1);
642 
643 		unsigned p2_word = p2_pix / WS;
644 		unsigned p2_bit = p2_pix % WS;
645 		uint16_t old_pix = rd_video(p2_word);
646 		int w = p2_bit - width;
647 		uint16_t glob_mask = ~0;
648 		if (p2_bit != (WS - 1))
649 		{
650 			// Right end
651 			new_pix <<= (WS - 1 - p2_bit);
652 			glob_mask <<= (WS - 1 - p2_bit);
653 		}
654 		if (w >= 0)
655 		{
656 			// Left end
657 			uint16_t tmp = ~0;
658 			glob_mask &= tmp >> (w + 1);
659 		}
660 		rmw_rop(p2_word, new_pix, glob_mask, masks);
661 		DBG_LOG(3,0,("rowbltneg %04x %04x %04x>%04x %u %u %u\n", old_pix, new_pix, glob_mask, rd_video(p2_word), p1_pix, p2_pix, width));
662 		w = p2_bit + 1;
663 		p1_pix -= w;
664 		p2_pix -= w;
665 		width -= w;
666 	}
667 }
668 
fill(const Rectangle & fill_rect,uint16_t pattern_no)669 void hp1ll3_device::fill(const Rectangle &fill_rect, uint16_t pattern_no)
670 {
671 	uint16_t pattern_addr = get_pattern_addr(pattern_no);
672 
673 	Point src_p{ 0, 0 };
674 	// Align to 16x16 tiles in absolute coordinates
675 	uint16_t start_x = fill_rect.origin.x & ~0xf;
676 	Rectangle dst_rect{{ start_x, uint16_t(fill_rect.origin.y & ~0xf) }, { 16, 16 }};
677 
678 	// Iterate over vertical span
679 	while (bitblt(pattern_addr, 16, 16, src_p, fill_rect, dst_rect, m_rr))
680 	{
681 		// Iterate over horizontal span
682 		do {
683 			dst_rect.origin.x += 16;
684 		} while (bitblt(pattern_addr, 16, 16, src_p, fill_rect, dst_rect, m_rr));
685 		dst_rect.origin.x = start_x;
686 		dst_rect.origin.y += 16;
687 	}
688 }
689 
get_pattern_addr(uint16_t pattern_no) const690 uint16_t hp1ll3_device::get_pattern_addr(uint16_t pattern_no) const
691 {
692 	return m_dad + pattern_no * 16;
693 }
694 
draw_cursor()695 void hp1ll3_device::draw_cursor()
696 {
697 	if (m_enable_cursor)
698 	{
699 		Rectangle dst{{ uint16_t(m_cursor_x + m_cursor_offset), m_cursor_y }, { 16, 16 }};
700 		bitblt(get_pattern_addr(m_cursor_pattern), 16, 16, Point{ 0, 0 }, get_screen(), dst, RR_XOR, false);
701 	}
702 }
703 
draw_sprite()704 void hp1ll3_device::draw_sprite()
705 {
706 	if (m_enable_sprite)
707 	{
708 		Rectangle dst{{ m_sprite_x, m_sprite_y }, { 16, 16 }};
709 		bitblt(get_pattern_addr(m_sprite_pattern), 16, 16, Point{ 0, 0 }, get_screen(), dst, RR_XOR, false);
710 	}
711 }
712 
draw_cursor_sprite()713 void hp1ll3_device::draw_cursor_sprite()
714 {
715 	draw_cursor();
716 	draw_sprite();
717 }
718 
set_pen_pos(Point p)719 void hp1ll3_device::set_pen_pos(Point p)
720 {
721 	if (p.x != m_cursor_x || p.y != m_cursor_y)
722 	{
723 		draw_cursor();
724 		m_cursor_x = p.x;
725 		m_cursor_y = p.y;
726 		draw_cursor();
727 	}
728 }
729 
set_sprite_pos(Point p)730 void hp1ll3_device::set_sprite_pos(Point p)
731 {
732 	if (p.x != m_sprite_x || p.y != m_sprite_y)
733 	{
734 		draw_sprite();
735 		m_sprite_x = p.x;
736 		m_sprite_y = p.y;
737 		draw_sprite();
738 	}
739 }
740 
disable_cursor()741 void hp1ll3_device::disable_cursor()
742 {
743 	draw_cursor();
744 	m_enable_cursor = false;
745 }
746 
disable_sprite()747 void hp1ll3_device::disable_sprite()
748 {
749 	draw_sprite();
750 	m_enable_sprite = false;
751 }
752 
get_window() const753 hp1ll3_device::Rectangle hp1ll3_device::get_window() const
754 {
755 	return Rectangle{{ m_window.org_x, m_window.org_y }, { m_window.width, m_window.height }};
756 }
757 
get_screen() const758 hp1ll3_device::Rectangle hp1ll3_device::get_screen() const
759 {
760 	return Rectangle{{ 0, 0 }, { uint16_t(m_horiz_pix_total), uint16_t(m_vert_pix_total) }};
761 }
762 
get_hv_timing(bool vertical,unsigned & total,unsigned & active) const763 void hp1ll3_device::get_hv_timing(bool vertical, unsigned& total, unsigned& active) const
764 {
765 	unsigned idx = vertical ? CONF_VER_0 : CONF_HOR_0;
766 	// Look for active part (zone 1) & compute total time
767 	total = 0;
768 	active = 0;
769 	for (unsigned i = 0; i < 4; ++i)
770 	{
771 		unsigned cnt = (m_conf[idx + i] & HV_CNT_MASK) + 1;
772 		total += cnt;
773 		if ((m_conf[idx + i] & HV_ZONE_MASK) == HV_ZONE_1)
774 		{
775 			active = cnt;
776 			// It seems that if a zone 2 comes immediately before a zone 1, it's
777 			// counted in the active part
778 			unsigned idx_1 = (i + 3) % 4 + idx;
779 			if ((m_conf[idx_1] & HV_ZONE_MASK) == HV_ZONE_2)
780 				active += (m_conf[idx_1] & HV_CNT_MASK) + 1;
781 		}
782 	}
783 	DBG_LOG(2, 0, ("H/V=%d total=%u active=%u\n", vertical, total, active));
784 }
785 
apply_conf()786 void hp1ll3_device::apply_conf()
787 {
788 	unsigned h_total;
789 	unsigned h_active;
790 	unsigned v_total;
791 	unsigned v_active;
792 
793 	get_hv_timing(false, h_total, h_active);
794 	get_hv_timing(true, v_total, v_active);
795 
796 	// Dot clock is 4 times the GPU clock, H timings are in units of half the GPU clock
797 	// I suspect that the GPU has a kind of "double clock" mode that is enabled by option bits in the last conf word.
798 	// Without clock doubling, the HP-9808A video has a strange frame rate (~32Hz).
799 	h_total *= 8;
800 	h_active *= 8;
801 
802 	attotime frame_rate{clocks_to_attotime(h_total * v_total)};
803 	screen().configure(h_total, v_total, rectangle{0, int32_t(h_active - 1), 0, int32_t(v_active - 1)}, frame_rate.attoseconds() / 4);
804 
805 	m_horiz_pix_total = h_active;
806 	m_vert_pix_total = v_active;
807 }
808 
screen_update(screen_device & screen,bitmap_ind16 & bitmap,const rectangle & cliprect)809 uint32_t hp1ll3_device::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
810 {
811 	if (!m_enable_video)
812 	{
813 		bitmap.fill(0);
814 		return 0;
815 	}
816 
817 	for (int y = 0; y < m_vert_pix_total; y++)
818 	{
819 		int const offset = m_sad + y*m_conf[CONF_WPL];
820 		uint16_t *p = &m_bitmap.pix(y);
821 
822 		for (int x = offset; x < offset + m_horiz_pix_total / 16; x++)
823 		{
824 			uint16_t const gfx = m_videoram[x];
825 
826 			for (int i = 15; i >= 0; i--)
827 			{
828 				*p++ = BIT(gfx, i);
829 			}
830 		}
831 	}
832 
833 	copybitmap(bitmap, m_bitmap, 0, 0, 0, 0, cliprect);
834 
835 	return 0;
836 }
837 
838 //-------------------------------------------------
839 //  read - register read
840 //-------------------------------------------------
841 
842 /*
843  *  offset 0: CSR
844  *
845  *  bit 0   gpu is busy
846  *  bit 1   data is ready
847  *  bit 3   vert blank time
848  *  bit 7   out of window
849  *
850  *  offset 2: data
851  */
852 
read(offs_t offset)853 uint8_t hp1ll3_device::read(offs_t offset)
854 {
855 	uint8_t data = 0;
856 
857 	if (offset == 0)
858 	{
859 		data = m_busy ? 1 : 0;
860 		data |= 2;
861 		data |= (screen().vblank() ? 8 : 0);
862 	}
863 	else
864 	{
865 		if (!(m_rd_ptr & 1))
866 		{
867 			switch (m_command)
868 			{
869 			case RDMEM:
870 				m_io_word = rd_video(m_memory_ptr++);
871 				break;
872 
873 			case RDWIN:
874 				{
875 					uint16_t addr = get_pix_addr(m_rw_win_x, m_rw_win_y);
876 					unsigned bit = m_rw_win_x % WS;
877 					int width = m_window.width + m_window.org_x - m_rw_win_x;
878 					if (width <= 0)
879 					{
880 						m_command = NOP;
881 					}
882 					else
883 					{
884 						unsigned discarded_bits = 0;
885 						// Insert "m_rw_win_off" dummy bits in the first word of each line
886 						if (m_rw_win_x ==  m_window.org_x)
887 							discarded_bits = m_rw_win_off;
888 						unsigned w = std::min(WS - discarded_bits, unsigned(width));
889 
890 						m_io_word = (rd_video(addr) << bit) >> discarded_bits;
891 						if ((bit + w) > WS)
892 							m_io_word |= rd_video(addr + 1) >> (WS - bit + discarded_bits);
893 
894 						m_io_word &= ~0 << (WS - w - discarded_bits);
895 						m_rw_win_x += w;
896 						if (m_rw_win_x >= (m_window.width + m_window.org_x))
897 						{
898 							m_rw_win_x = m_window.org_x;
899 							m_rw_win_y++;
900 							if (m_rw_win_y >= (m_window.height + m_window.org_y))
901 								m_command = NOP;
902 						}
903 					}
904 				}
905 				break;
906 
907 			case RDP:
908 				if (m_rd_ptr == 0)
909 					m_io_word = m_cursor_x;
910 				else if (m_rd_ptr == 2)
911 					m_io_word = m_cursor_y;
912 				break;
913 
914 			case RDSP:
915 				if (m_rd_ptr == 0)
916 					m_io_word = m_sprite_x;
917 				else if (m_rd_ptr == 2)
918 					m_io_word = m_sprite_y;
919 				break;
920 
921 			case RDWINPARM:
922 				switch (m_rd_ptr)
923 				{
924 				case 0:
925 					m_io_word = m_window.org_x;
926 					break;
927 				case 2:
928 					m_io_word = m_window.org_y;
929 					break;
930 				case 4:
931 					m_io_word = m_window.width;
932 					break;
933 				case 6:
934 					m_io_word = m_window.height;
935 					break;
936 				default:
937 					break;
938 				}
939 				break;
940 
941 			case ID:
942 				/*
943 				 * 'diagb' ROM accepts either of these ID values
944 				 * 0x3003, 0x4004, 0x5005, 0x6006
945 				 */
946 				m_io_word = 0x4004;
947 				break;
948 			}
949 			data = uint8_t(m_io_word >> 8);
950 		} else
951 		{
952 			data = m_io_word & 0xff;
953 		}
954 		m_rd_ptr++;
955 	}
956 
957 	DBG_LOG(1,"HPGPU", ("R @ %d == %02x\n", offset, data));
958 
959 	return data;
960 }
961 
962 
963 //-------------------------------------------------
964 //  write - register write
965 //-------------------------------------------------
966 
write(offs_t offset,uint8_t data)967 void hp1ll3_device::write(offs_t offset, uint8_t data)
968 {
969 	DBG_LOG(1,"HPGPU", ("W @ %d <- %02x\n", offset, data));
970 
971 	if (offset == 0)
972 	{
973 		command(data);
974 	}
975 	else
976 	{
977 		if (!(m_wr_ptr & 1))
978 		{
979 			m_io_word = uint16_t(data) << 8;
980 		}
981 		else
982 		{
983 			m_io_word |= data;
984 			switch (m_command)
985 			{
986 			case CONF:
987 				m_conf[m_wr_ptr >> 1] = m_io_word;
988 				DBG_LOG(1,"HPGPU",("CONF data word %d received: %04X\n", m_wr_ptr >> 1, m_io_word));
989 				if ((m_wr_ptr >> 1) == 10)
990 				{
991 					apply_conf();
992 					m_wr_ptr = -1;
993 					m_command = NOP;
994 				}
995 				break;
996 
997 			case WRMEM:
998 				wr_video(m_memory_ptr++, m_io_word);
999 				break;
1000 
1001 			case WRWIN:
1002 				{
1003 					uint16_t rop_masks[4];
1004 					get_rop_masks(m_rr, rop_masks);
1005 
1006 					uint16_t addr = get_pix_addr(m_rw_win_x, m_rw_win_y);
1007 					uint16_t glob_mask = ~0;
1008 					unsigned bit = m_rw_win_x % WS;
1009 					int width = m_window.width + m_window.org_x - m_rw_win_x;
1010 					if (width <= 0)
1011 						m_command = NOP;
1012 					else
1013 					{
1014 						unsigned discarded_bits = 0;
1015 						// Remove "m_rw_win_off" bits from the first word of each line
1016 						if (m_rw_win_x ==  m_window.org_x)
1017 							discarded_bits = m_rw_win_off;
1018 						unsigned w = std::min(WS - discarded_bits, unsigned(width));
1019 
1020 						glob_mask >>= bit;
1021 						if ((bit + w) < WS)
1022 							glob_mask &= ~0 << (WS - (bit + w));
1023 
1024 						rmw_rop(addr, (m_io_word << discarded_bits) >> bit, glob_mask, rop_masks);
1025 						DBG_LOG(3, 0, ("WRWIN (%u,%u) %d %04x %04x->%04x\n", m_rw_win_x, m_rw_win_y, width, (m_io_word << discarded_bits) >> bit, glob_mask, rd_video(addr)));
1026 						if ((bit + w) > WS)
1027 						{
1028 							unsigned pad = WS - bit + discarded_bits;
1029 							glob_mask = ~0 << (2 * WS - bit - w);
1030 							rmw_rop(addr + 1, uint16_t(m_io_word << pad), glob_mask, rop_masks);
1031 							DBG_LOG(3, 0, ("WRWIN %u %04x %04x->%04x\n", pad, m_io_word << pad, glob_mask, rd_video(addr + 1)));
1032 						}
1033 						m_rw_win_x += w;
1034 						if (m_rw_win_x >= (m_window.width + m_window.org_x))
1035 						{
1036 							m_rw_win_x = m_window.org_x;
1037 							m_rw_win_y++;
1038 							if (m_rw_win_y >= (m_window.height + m_window.org_y))
1039 								m_command = NOP;
1040 						}
1041 					}
1042 				}
1043 				break;
1044 
1045 			default:
1046 				if (m_wr_ptr <= 4)
1047 					m_input[m_wr_ptr / 2] = m_io_word;
1048 				DBG_LOG(2,"HPGPU",("wrote %02x at %d, input buffer is %04X %04X\n", data, m_wr_ptr, m_input[0], m_input[1]));
1049 			}
1050 		}
1051 		m_wr_ptr++;
1052 	}
1053 }
1054 
1055 
command(int command)1056 void hp1ll3_device::command(int command)
1057 {
1058 	switch (command)
1059 	{
1060 
1061 	// type 0 commands -- no data
1062 
1063 	case NOP:
1064 		DBG_LOG(2,"HPGPU",("command: NOP [%d, 0x%x]\n", command, command));
1065 		switch (m_command)
1066 		{
1067 		case RDMEM:
1068 			DBG_LOG(1,"HPGPU",("RDMEM of %d words at %04X complete\n", m_memory_ptr - m_input[0], m_input[0]));
1069 			break;
1070 
1071 		case WRMEM:
1072 			DBG_LOG(1,"HPGPU",("WRMEM of %d words to %04X complete\n", m_memory_ptr - m_input[0], m_input[0]));
1073 			break;
1074 		}
1075 		break;
1076 
1077 	case DISVID:
1078 		DBG_LOG(2,"HPGPU",("command: DISVID [%d, 0x%x]\n", command, command));
1079 		m_enable_video = false;
1080 		break;
1081 
1082 	case ENVID:
1083 		DBG_LOG(2,"HPGPU",("command: ENVID [%d, 0x%x]\n", command, command));
1084 		DBG_LOG(1,"HPGPU",("enable video; SAD %04x FAD %04x DAD %04x ORG %04x UDL %04x RR %04x\n",
1085 			m_sad, m_fad, m_dad, m_org, m_udl, m_rr));
1086 		m_enable_video = true;
1087 		break;
1088 
1089 	case DISSP:
1090 		DBG_LOG(2,"HPGPU",("command: DISSP [%d, 0x%x]\n", command, command));
1091 		disable_sprite();
1092 		break;
1093 
1094 	case ENSP:
1095 		DBG_LOG(2,"HPGPU",("command: ENSP [%d, 0x%x]\n", command, command));
1096 		draw_sprite();
1097 		m_sprite_pattern = m_input[0];
1098 		m_enable_sprite = true;
1099 		draw_sprite();
1100 		DBG_LOG(1,"HPGPU",("enable sprite; cursor %d,%d sprite %d,%d\n", m_cursor_x, m_cursor_y, m_sprite_x, m_sprite_y));
1101 		break;
1102 
1103 	case DISCURS:
1104 		DBG_LOG(2,"HPGPU",("command: DISCURS [%d, 0x%x]\n", command, command));
1105 		disable_cursor();
1106 		break;
1107 
1108 	// type 1 commands -- 1 word of data expected in the buffer
1109 
1110 	// start of screen memory
1111 	case WRSAD:
1112 		DBG_LOG(2,"HPGPU",("command: WRSAD [%d, 0x%x] (0x%04x)\n", command, command, m_input[0]));
1113 		m_sad = m_input[0];
1114 		break;
1115 
1116 	// start of font memory
1117 	case WRFAD:
1118 		DBG_LOG(2,"HPGPU",("command: WRFAD [%d, 0x%x] (0x%04x)\n", command, command, m_input[0]));
1119 		m_fad = m_input[0];
1120 		break;
1121 
1122 	// ?? used by diagnostic ROM
1123 	case 6:
1124 		DBG_LOG(1,"HPGPU",("command: 6\n"));
1125 		break;
1126 
1127 	// start of data area
1128 	case WRDAD:
1129 		DBG_LOG(2,"HPGPU",("command: WRDAD [%d, 0x%x] (0x%04x)\n", command, command, m_input[0]));
1130 		m_dad = m_input[0];
1131 		break;
1132 
1133 	// ??
1134 	case WRORG:
1135 		DBG_LOG(2,"HPGPU",("command: WRORG [%d, 0x%x] (0x%04x)\n", command, command, m_input[0]));
1136 		m_org = m_input[0];
1137 		break;
1138 
1139 	// set replacement rule (raster op)
1140 	case WRRR:
1141 		DBG_LOG(2,"HPGPU",("command: WRRR [%d, 0x%x] (0x%04x)\n", command, command, m_input[0]));
1142 		m_rr = m_input[0];
1143 		break;
1144 
1145 	// set user-defined line pattern
1146 	case WRUDL:
1147 		DBG_LOG(2,"HPGPU",("command: WRUDL [%d, 0x%x] (0x%04x)\n", command, command, m_input[0]));
1148 		m_udl = m_input[0];
1149 		break;
1150 
1151 	// area fill
1152 	case FILL:
1153 		DBG_LOG(2,"HPGPU",("command: FILL [%d, 0x%x] (0x%04x) from (%d,%d) size (%d,%d) rop 0x%x\n",
1154 						   command, command, m_input[0],
1155 						   m_window.org_x, m_window.org_y, m_window.width, m_window.height, m_rr));
1156 		draw_cursor_sprite();
1157 		fill(get_window(), m_input[0]);
1158 		draw_cursor_sprite();
1159 		break;
1160 
1161 	case LABEL:
1162 		{
1163 			DBG_LOG(2,"HPGPU",("command: LABEL [%d, 0x%x] (0x%04x, '%c') at %d,%d\n", command, command, m_input[0],
1164 				(m_input[0]<32||m_input[0]>127) ? ' ' : m_input[0], m_cursor_x, m_cursor_y));
1165 			int const c = m_input[0] & 255;
1166 			int const w = (c & 1) ?
1167 					(rd_video(m_fad + 2 + (c>>1)) & 255) :
1168 					(rd_video(m_fad + 2 + (c>>1)) >> 8);
1169 			label(c, w);
1170 		}
1171 		break;
1172 
1173 		// Write window
1174 	case WRWIN:
1175 		// Read window
1176 	case RDWIN:
1177 		DBG_LOG(2,"HPGPU",("command: %s [%d, 0x%x] offset=%u size(%d,%d)\n",
1178 						   command == RDWIN ? "RDWIN":"WRWIN", command, command, m_input[0], m_window.width, m_window.height));
1179 		m_rw_win_x = m_window.org_x;
1180 		m_rw_win_y = m_window.org_y;
1181 		m_rw_win_off = m_input[0] % WS;
1182 		disable_cursor();
1183 		disable_sprite();
1184 		break;
1185 
1186 	// type 2 commands -- 2 words of data expected in the buffer
1187 
1188 	case DRAWPX:
1189 		{
1190 			DBG_LOG(2,"HPGPU",("command: DRAWPX [%d, 0x%x] (%d, %d)\n", command, command, m_input[0], m_input[1]));
1191 			uint16_t rop_masks[4];
1192 			get_rop_masks(m_rr, rop_masks);
1193 			point(m_input[0], m_input[1], true, rop_masks);
1194 		}
1195 		break;
1196 
1197 	// set window size
1198 	case WRWINSIZ:
1199 		DBG_LOG(2,"HPGPU",("command: WRWINSIZ [%d, 0x%x] (%d, %d)\n", command, command, m_input[0], m_input[1]));
1200 		m_window.width = m_input[0];
1201 		m_window.height = m_input[1];
1202 		break;
1203 
1204 	// set window origin
1205 	case WRWINORG:
1206 		DBG_LOG(2,"HPGPU",("command: WRWINORG [%d, 0x%x] (%d, %d)\n", command, command, m_input[0], m_input[1]));
1207 		m_window.org_x = m_input[0];
1208 		m_window.org_y = m_input[1];
1209 		break;
1210 
1211 		// Copy
1212 	case COPY:
1213 		{
1214 			DBG_LOG(2,"HPGPU",("command: COPY [%d, 0x%x] (%d, %d)\n", command, command, m_input[0], m_input[1]));
1215 			draw_cursor_sprite();
1216 			unsigned rounded_width = m_conf[CONF_WPL] * WS;
1217 			Rectangle dst_rect{{ m_input[0], m_input[1] }, { m_window.width, m_window.height }};
1218 			// COPY apparently doesn't clip anything
1219 			bitblt(m_org, rounded_width, 0xffff, Point{ m_window.org_x, m_window.org_y },
1220 				   Rectangle{{ 0, 0 }, { uint16_t(rounded_width), 0xffff }}, dst_rect, m_rr);
1221 			draw_cursor_sprite();
1222 		}
1223 		break;
1224 
1225 	// move pointer absolute
1226 	case MOVEP:
1227 		DBG_LOG(2,"HPGPU",("command: MOVEP [%d, 0x%x] (%d, %d)\n", command, command, m_input[0], m_input[1]));
1228 		set_pen_pos(Point{ m_input[0], m_input[1] });
1229 		m_saved_x = m_cursor_x;
1230 		break;
1231 
1232 	// move pointer incremental
1233 	case IMOVEP:
1234 		DBG_LOG(2,"HPGPU",("command: IMOVEP [%d, 0x%x] (%d, %d)\n", command, command, m_input[0], m_input[1]));
1235 		set_pen_pos(Point{ uint16_t(m_cursor_x + m_input[0]), uint16_t(m_cursor_y + m_input[1]) });
1236 		m_saved_x = m_cursor_x;
1237 		break;
1238 
1239 		// Scroll UP
1240 	case SCROLUP:
1241 		{
1242 			DBG_LOG(2,"HPGPU",("command: SCROLUP [%d, 0x%x] (%d, %d)\n", command, command, m_input[0], m_input[1]));
1243 			draw_cursor_sprite();
1244 			Point src_p{ m_window.org_x, uint16_t(m_window.org_y + m_input[1]) };
1245 			Rectangle dst_rect = get_window();
1246 			dst_rect.size.y -= m_input[1];
1247 			unsigned rounded_width = m_conf[CONF_WPL] * WS;
1248 			bitblt(m_org, rounded_width, m_vert_pix_total, src_p, get_window(), dst_rect, m_rr);
1249 			dst_rect.origin.y += dst_rect.size.y;
1250 			dst_rect.size.y = m_input[1];
1251 			fill(dst_rect, m_input[0]);
1252 			draw_cursor_sprite();
1253 		}
1254 		break;
1255 
1256 		// Scroll DOWN
1257 	case SCROLDN:
1258 		{
1259 			DBG_LOG(2,"HPGPU",("command: SCROLDN [%d, 0x%x] (%d, %d)\n", command, command, m_input[0], m_input[1]));
1260 			draw_cursor_sprite();
1261 			Point src_p{ m_window.org_x, m_window.org_y };
1262 			Rectangle dst_rect = get_window();
1263 			dst_rect.origin.y += m_input[1];
1264 			dst_rect.size.y -= m_input[1];
1265 			unsigned rounded_width = m_conf[CONF_WPL] * WS;
1266 			bitblt(m_org, rounded_width, m_vert_pix_total, src_p, get_window(), dst_rect, m_rr);
1267 			dst_rect.origin.y = m_window.org_y;
1268 			dst_rect.size.y = m_input[1];
1269 			fill(dst_rect, m_input[0]);
1270 			draw_cursor_sprite();
1271 		}
1272 		break;
1273 
1274 	case ENCURS:
1275 		DBG_LOG(2,"HPGPU",("command: ENCURS [%d, 0x%x]\n", command, command));
1276 		draw_cursor();
1277 		m_cursor_pattern = m_input[0];
1278 		m_cursor_offset = m_input[1];
1279 		m_enable_cursor = true;
1280 		draw_cursor();
1281 		DBG_LOG(1,"HPGPU",("enable cursor; cursor %d,%d sprite %d,%d\n", m_cursor_x, m_cursor_y, m_sprite_x, m_sprite_y));
1282 		break;
1283 
1284 	// carriage return, line feed
1285 	case CRLFx:
1286 		{
1287 			DBG_LOG(2,"HPGPU",("command: CRLF [%d, 0x%x]\n", command, command));
1288 
1289 			uint16_t font_data;
1290 			uint16_t font_height;
1291 			get_font(font_data, font_height);
1292 
1293 			set_pen_pos(Point{ m_saved_x, uint16_t(m_cursor_y + font_height) });
1294 		}
1295 		break;
1296 
1297 	// move sprite absolute
1298 	case MOVESP:
1299 		DBG_LOG(2,"HPGPU",("command: MOVESP [%d, 0x%x] (%d, %d)\n", command, command, m_input[0], m_input[1]));
1300 		set_sprite_pos(Point{ m_input[0], m_input[1] });
1301 		break;
1302 
1303 	// draw to ...
1304 	case DRAWP:
1305 		DBG_LOG(2,"HPGPU",("command: DRAWP [%d, 0x%x] (%d, %d) to (%d, %d)\n",
1306 			command, command, m_cursor_x, m_cursor_y, m_input[0], m_input[1]));
1307 		line(m_cursor_x, m_cursor_y, m_input[0], m_input[1]);
1308 		set_pen_pos(Point{ m_input[0], m_input[1] });
1309 		break;
1310 
1311 	// type 3 command -- CONF -- accept configuration parameters (11 words)
1312 
1313 	case CONF:
1314 		break;
1315 
1316 	// type 4 commands -- like type 1 plus data is read or written after command, terminated by NOP
1317 
1318 	case RDMEM:
1319 	case WRMEM:
1320 		DBG_LOG(2,"HPGPU",("command: %s [%d, 0x%x] (0x%04x)\n",
1321 			command == RDMEM?"RDMEM":"WRMEM", command, command, m_input[0]));
1322 		m_memory_ptr = m_input[0]; // memory is word-addressable
1323 		disable_cursor();
1324 		disable_sprite();
1325 		break;
1326 
1327 	// type 3 read commands
1328 	case RDP:
1329 		DBG_LOG(2,"HPGPU",("command: RDP [%d, 0x%x]\n", command, command));
1330 		break;
1331 
1332 	case RDSP:
1333 		DBG_LOG(2,"HPGPU",("command: RDSP [%d, 0x%x]\n", command, command));
1334 		break;
1335 
1336 	case RDWINPARM:
1337 		DBG_LOG(2,"HPGPU",("command: RDWINPARM [%d, 0x%x]\n", command, command));
1338 		break;
1339 
1340 	case ID:
1341 		DBG_LOG(2,"HPGPU",("command: ID [%d, 0x%x]\n", command, command));
1342 		break;
1343 
1344 	default:
1345 		DBG_LOG(1,"HPGPU",("command: UNKNOWN [%d, 0x%x]\n", command, command));
1346 		break;
1347 	}
1348 
1349 	m_rd_ptr = 0;
1350 	m_wr_ptr = 0;
1351 	m_command = command;
1352 }
1353