1 // license:BSD-3-Clause
2 // copyright-holders:Philip Bennett
3 /***************************************************************************
4 
5     Microprose Games 3D hardware
6 
7     TODO:
8     * Fix buffering issue
9     * Find correct noise-fill LFSR arrangement
10 
11 ****************************************************************************/
12 
13 #include "emu.h"
14 #include "includes/micro3d.h"
15 #include "audio/micro3d.h"
16 
17 #include "cpu/am29000/am29000.h"
18 
19 
20 /*************************************
21  *
22  *  Defines
23  *
24  *************************************/
25 
26 #define VTX_SEX(x)              ((x) | ((x) & (1 << 29) ? 0xc0000000 : 0))
27 
28 enum
29 {
30 	STATE_DRAW_CMD,
31 	STATE_DRAW_CMD_DATA,
32 	STATE_DRAW_VTX_DATA
33 };
34 
35 
36 /*************************************
37  *
38  *  Video initialisation
39  *
40  *************************************/
41 
video_start()42 void micro3d_state::video_start()
43 {
44 	/* Allocate 512x12 x 2 3D frame buffers */
45 	m_frame_buffers[0] = std::make_unique<uint16_t[]>(1024 * 512);
46 	m_frame_buffers[1] = std::make_unique<uint16_t[]>(1024 * 512);
47 	m_tmp_buffer = std::make_unique<uint16_t[]>(1024 * 512);
48 }
49 
50 
video_reset()51 void micro3d_state::video_reset()
52 {
53 	m_pipeline_state  = 0;
54 	m_creg = 0;
55 
56 	m_drawing_buffer = 0;
57 	m_display_buffer = 1;
58 }
59 
60 
61 /*************************************
62  *
63  *  2D graphics
64  *
65  *************************************/
66 
TMS340X0_SCANLINE_IND16_CB_MEMBER(micro3d_state::scanline_update)67 TMS340X0_SCANLINE_IND16_CB_MEMBER(micro3d_state::scanline_update)
68 {
69 	uint16_t const *const src = &m_sprite_vram[(params->rowaddr << 8) & 0x7fe00];
70 	uint16_t *dest = &bitmap.pix(scanline);
71 	int coladdr = params->coladdr;
72 	int sd_11_7 = (m_creg & 0x1f) << 7;
73 
74 	scanline = std::max((scanline - params->veblnk), 0);
75 	uint16_t const *frame_src = m_frame_buffers[m_display_buffer].get() + (scanline << 10);
76 
77 	/* TODO: XFER3DK - X/Y offsets for 3D */
78 
79 	/* Copy the non-blanked portions of this scanline */
80 	for (int x = params->heblnk; x < params->hsblnk; x += 2)
81 	{
82 		uint16_t pix = src[coladdr++ & 0x1ff];
83 
84 		/*
85 		    TODO: The upper four bits of the 3D buffer affect priority
86 		    0xfxx forces 3D priority?
87 		*/
88 		if (pix & 0x80)
89 			dest[x + 0] = sd_11_7 | (pix & 0x7f);
90 		else
91 			dest[x + 0] = *frame_src & 0xfff;
92 
93 		pix >>= 8;
94 		frame_src++;
95 
96 		if (pix & 0x80)
97 			dest[x + 1] = sd_11_7 | (pix & 0x7f);
98 		else
99 			dest[x + 1] = *frame_src & 0xfff;
100 
101 		frame_src++;
102 	}
103 }
104 
micro3d_creg_w(uint16_t data)105 void micro3d_state::micro3d_creg_w(uint16_t data)
106 {
107 	if (~data & 0x80)
108 		m_vgb->set_input_line(0, CLEAR_LINE);
109 
110 	m_creg = data;
111 }
112 
micro3d_xfer3dk_w(uint16_t data)113 void micro3d_state::micro3d_xfer3dk_w(uint16_t data)
114 {
115 	m_xfer3dk = data;
116 }
117 
WRITE_LINE_MEMBER(micro3d_state::tms_interrupt)118 WRITE_LINE_MEMBER(micro3d_state::tms_interrupt)
119 {
120 //  mc68901_int_gen(device->machine(), GPIP4);
121 }
122 
123 
124 /*************************************
125  *
126  *  3D graphics
127  *
128  *************************************/
129 
inside(micro3d_vtx * v,enum planes plane)130 int micro3d_state::inside(micro3d_vtx *v, enum planes plane)
131 {
132 	switch (plane)
133 	{
134 		case CLIP_Z_MIN: return v->z >= m_z_min;
135 		case CLIP_Z_MAX: return v->z <= m_z_max;
136 		case CLIP_X_MIN: return v->x >= m_x_min;
137 		case CLIP_X_MAX: return v->x <= m_x_max;
138 		case CLIP_Y_MIN: return v->y >= m_y_min;
139 		case CLIP_Y_MAX: return v->y <= m_y_max;
140 	}
141 
142 	return 0;
143 }
144 
145 // Calculate where two points intersect
intersect(micro3d_vtx * v1,micro3d_vtx * v2,enum planes plane)146 micro3d_state::micro3d_vtx micro3d_state::intersect(micro3d_vtx *v1, micro3d_vtx *v2, enum planes plane)
147 {
148 	float m = 0.0;
149 	if (v1->x != v2->x)
150 		m = float(v1->y - v2->y) / float(v1->x - v2->x);
151 
152 	micro3d_vtx vo = { 0, 0, 0 };
153 	switch (plane)
154 	{
155 		case CLIP_Z_MIN:
156 		{
157 			float mxz, myz;
158 
159 			if (v1->z != v2->z)
160 			{
161 				mxz = float(v1->x - v2->x) / float(v1->z - v2->z);
162 				myz = float(v1->y - v2->y) / float(v1->z - v2->z);
163 			}
164 			else
165 			{
166 				mxz = 0.0;
167 				myz = 0.0;
168 			}
169 
170 			vo.x = v2->x + (m_z_min - v2->z) * mxz;
171 			vo.y = v2->y + (m_z_min - v2->z) * myz;
172 			vo.z = m_z_min;
173 			break;
174 		}
175 		case CLIP_Z_MAX:
176 		{
177 			float mxz, myz;
178 
179 			if (v1->z != v2->z)
180 			{
181 				mxz = float(v1->x - v2->x) / float(v1->z - v2->z);
182 				myz = float(v1->y - v2->y) / float(v1->z - v2->z);
183 			}
184 			else
185 			{
186 				mxz = 0.0;
187 				myz = 0.0;
188 			}
189 
190 			vo.x = v2->x + (m_z_max - v2->z) * mxz;
191 			vo.y = v2->y + (m_z_max - v2->z) * myz;
192 			vo.z = m_z_max;
193 			break;
194 		}
195 		case CLIP_X_MIN:
196 		{
197 			vo.x = m_x_min;
198 			vo.y = v2->y + (m_x_min - v2->x) * m;
199 			vo.z = 0;
200 			break;
201 		}
202 		case CLIP_X_MAX:
203 		{
204 			vo.x = m_x_max;
205 			vo.y = v2->y + (m_x_max - v2->x) * m;
206 			vo.z = 0;
207 			break;
208 		}
209 		case CLIP_Y_MIN:
210 		{
211 			if (v1->x != v2->x)
212 				vo.x = v2->x + (m_y_min - v2->y) / m;
213 			else
214 				vo.x = v2->x;
215 
216 			vo.y = m_y_min;
217 			vo.z = 0;
218 			break;
219 		}
220 		case CLIP_Y_MAX:
221 		{
222 			if (v1->x != v2->x)
223 				vo.x = v2->x + (m_y_max - v2->y) / m;
224 			else
225 				vo.x = v2->x;
226 
227 			vo.y = m_y_max;
228 			vo.z = 0;
229 			break;
230 		}
231 	}
232 	return vo;
233 }
234 
write_span(uint32_t y,uint32_t x)235 inline void micro3d_state::write_span(uint32_t y, uint32_t x)
236 {
237 	uint32_t *draw_dpram = m_draw_dpram;
238 	int addr = y << 1;
239 
240 	if (draw_dpram[addr] == 0x3ff000)
241 	{
242 		draw_dpram[addr] = (x << 12) | x;
243 	}
244 	else
245 	{
246 		/* Check start */
247 		if (x < (m_draw_dpram[addr] & 0x3ff))
248 		{
249 			draw_dpram[addr] &= ~0x3ff;
250 			draw_dpram[addr] |= x;
251 		}
252 		/* Check end */
253 		if (x > (draw_dpram[addr] >> 12))
254 		{
255 			draw_dpram[addr] &= ~0x3ff000;
256 			draw_dpram[addr] |= (x << 12);
257 		}
258 	}
259 }
260 
261 /* This is the same algorithm used in the 3D tests */
draw_line(uint32_t x1,uint32_t y1,uint32_t x2,uint32_t y2)262 void micro3d_state::draw_line(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2)
263 {
264 	uint32_t tmp2;
265 	uint32_t acc;
266 	uint32_t y_inc;
267 
268 	uint32_t dx;
269 	uint32_t dy;
270 
271 	if (x2 < x1)
272 	{
273 		uint32_t tmp;
274 
275 		tmp = x1;
276 		x1 = x2;
277 		x2 = tmp;
278 
279 		tmp = y1;
280 		y1 = y2;
281 		y2 = tmp;
282 	}
283 
284 	dx = x2 - x1;
285 
286 	if (y2 >= y1)
287 		dy = y2 - y1;
288 	else
289 		dy = y1 - y2;
290 
291 	write_span(y1, x1);
292 
293 	if (dx == 0 && dy == 0)
294 		return;
295 
296 	if (y1 <= y2)
297 		y_inc = 1;
298 	else
299 		y_inc = -1;
300 
301 	if (dx > dy)
302 	{
303 		if (y2 != y1)
304 		{
305 			tmp2 = dy << 1;
306 			acc = tmp2 - dx;
307 			dy = (dy - dx) << 1;
308 
309 			do
310 			{
311 				if (~acc & 0x80000000)
312 				{
313 					write_span(y1, x1);
314 					y1 += y_inc;
315 					x1++;
316 					acc += dy;
317 					write_span(y1, x1);
318 				}
319 				else
320 				{
321 					acc += tmp2;
322 					x1++;
323 				}
324 			} while (y2 != y1);
325 		}
326 
327 		if (x2 != x1)
328 			write_span(y1, x2);
329 
330 	}
331 	else
332 	{
333 		tmp2 = dx << 1;
334 		acc = tmp2 - dy;
335 		dy = (dx - dy) << 1;
336 
337 		if (y1 != y2)
338 		{
339 			do
340 			{
341 				if (acc & 0x80000000)
342 				{
343 					acc += tmp2;
344 					write_span(y1, x1);
345 					y1 += y_inc;
346 					write_span(y1, x1);
347 				}
348 				else
349 				{
350 					write_span(y1, x1);
351 					x1++;
352 					y1 += y_inc;
353 					write_span(y1, x1);
354 
355 					acc += dy;
356 				}
357 			} while (y1 != y2);
358 		}
359 
360 		if (x2 != x1)
361 			write_span(y1, x2);
362 	}
363 }
364 
rasterise_spans(uint32_t min_y,uint32_t max_y,uint32_t attr)365 void micro3d_state::rasterise_spans(uint32_t min_y, uint32_t max_y, uint32_t attr)
366 {
367 	int y;
368 	int color = attr & 0xfff;
369 
370 	if ((attr >> 24) == 0x85)
371 	{
372 		for (y = min_y; y <= max_y; ++y)
373 		{
374 			int x;
375 			int addr = y << 1;
376 			uint16_t *dest = &m_tmp_buffer[y * 1024];
377 
378 			if (m_draw_dpram[addr] == 0x3ff000)
379 			{
380 				continue;
381 			}
382 			else
383 			{
384 				int start = m_draw_dpram[addr] & 0x3ff;
385 				int end = (m_draw_dpram[addr] >> 12) & 0x3ff;
386 
387 				for (x = start; x <= end; ++x)
388 					dest[x] = color;
389 			}
390 		}
391 	}
392 	else
393 	{
394 		/*
395 		    I don't know the LFSR arrangement inside the DRAW2 ASIC
396 		    but here are some possible tap arrangements
397 		*/
398 		static const uint8_t taps[8][4] =
399 		{
400 			{9, 8, 7, 2},
401 			{9, 8, 6, 5},
402 			{9, 8, 5, 4},
403 			{9, 8, 5, 1},
404 			{9, 8, 4, 2},
405 			{9, 7, 6, 4},
406 			{9, 7, 5, 2},
407 			{9, 6, 5, 3},
408 		};
409 
410 		int noise_val = (attr >> 12) & 0x3ff;
411 		int noise_taps = 0;
412 
413 		for (y = min_y; y <= max_y; ++y)
414 		{
415 			int x;
416 			int addr = y << 1;
417 			uint16_t *dest = &m_tmp_buffer[y * 1024];
418 
419 			if (m_draw_dpram[addr] == 0x3ff000)
420 			{
421 				continue;
422 			}
423 			else
424 			{
425 				int start = m_draw_dpram[addr] & 0x3ff;
426 				int end = (m_draw_dpram[addr] >> 12) & 0x3ff;
427 
428 				for (x = start; x <= end; ++x)
429 				{
430 					int fb;
431 
432 					if (noise_val & 0x1)
433 						dest[x] = color;
434 
435 					fb = (BIT(noise_val, taps[noise_taps][0] - 1) ^ BIT(noise_val, taps[noise_taps][1] - 1) ^ BIT(noise_val, taps[noise_taps][2] - 1) ^ BIT(noise_val, taps[noise_taps][3] - 1));
436 					noise_val = ((noise_val << 1) | fb) & 0x1ff;
437 				}
438 			}
439 		}
440 	}
441 }
442 
clip_triangle(micro3d_vtx * v,micro3d_vtx * vout,int num_vertices,enum planes plane)443 int micro3d_state::clip_triangle(micro3d_vtx *v, micro3d_vtx *vout, int num_vertices, enum planes plane)
444 {
445 	micro3d_vtx clip_out[10];
446 
447 	int i;
448 	int prev_i = num_vertices - 1;
449 	int clip_verts = 0;
450 
451 	for (i = 0; i < num_vertices; ++i)
452 	{
453 		int v1_in = inside(&v[i], plane);
454 		int v2_in = inside(&v[prev_i], plane);
455 
456 		/* Edge is inside */
457 		if (v1_in && v2_in)
458 		{
459 			clip_out[clip_verts++] = v[i];
460 		}
461 		/* Edge is leaving */
462 		else if (v1_in && !v2_in)
463 		{
464 			clip_out[clip_verts++] = intersect(&v[i], &v[prev_i], plane);
465 			clip_out[clip_verts++] = v[i];
466 		}
467 		/* Edge is entering */
468 		else if (!v1_in && v2_in)
469 		{
470 			clip_out[clip_verts++] = intersect(&v[i], &v[prev_i], plane);
471 		}
472 
473 		prev_i = i;
474 	}
475 
476 	memcpy(&vout[0], &clip_out[0], sizeof(vout[0]) * clip_verts);
477 	return clip_verts;
478 }
479 
draw_triangles(uint32_t attr)480 void micro3d_state::draw_triangles(uint32_t attr)
481 {
482 	int i;
483 	bool triangles = false;
484 	int vertices = m_fifo_idx / 3;
485 	int min_y = 0x3ff;
486 	int max_y = 0;
487 
488 	/* This satisifes the burst write test */
489 	if (vertices == 0)
490 	{
491 		int y;
492 		int val = ((m_x_mid + 16) << 12) | m_x_mid;
493 
494 		for (y = m_y_mid; y <= m_y_mid + 16; ++y)
495 			m_draw_dpram[y << 1] = val;
496 
497 		return;
498 	}
499 
500 	/* Draw triangles as fans */
501 	for (i = 2; i < vertices; ++i)
502 	{
503 		int k;
504 		int clip_vertices = 3;
505 
506 		micro3d_vtx vo, vm, vn;
507 		micro3d_vtx vclip_list[10];
508 
509 		vo.x = m_vtx_fifo[0];
510 		vo.y = m_vtx_fifo[1];
511 		vo.z = m_vtx_fifo[2];
512 
513 		vm.x = m_vtx_fifo[(i - 1) * 3 + 0];
514 		vm.y = m_vtx_fifo[(i - 1) * 3 + 1];
515 		vm.z = m_vtx_fifo[(i - 1) * 3 + 2];
516 
517 		vn.x = m_vtx_fifo[i * 3 + 0];
518 		vn.y = m_vtx_fifo[i * 3 + 1];
519 		vn.z = m_vtx_fifo[i * 3 + 2];
520 
521 		vclip_list[0] = vo;
522 		vclip_list[1] = vm;
523 		vclip_list[2] = vn;
524 
525 		/* Clip against near Z and far Z planes */
526 		clip_vertices = clip_triangle(vclip_list, vclip_list, clip_vertices, CLIP_Z_MIN);
527 		clip_vertices = clip_triangle(vclip_list, vclip_list, clip_vertices, CLIP_Z_MAX);
528 
529 		/* Perform perspective divide */
530 		for (k = 0; k < clip_vertices; ++k)
531 		{
532 			vclip_list[k].x = vclip_list[k].x * m_z_min / vclip_list[k].z;
533 			vclip_list[k].y = vclip_list[k].y * m_z_min / vclip_list[k].z;
534 			vclip_list[k].z = 0;
535 		}
536 
537 		/* Perform screen-space clipping */
538 		clip_vertices = clip_triangle(vclip_list, vclip_list, clip_vertices, CLIP_Y_MAX);
539 		clip_vertices = clip_triangle(vclip_list, vclip_list, clip_vertices, CLIP_X_MIN);
540 		clip_vertices = clip_triangle(vclip_list, vclip_list, clip_vertices, CLIP_X_MAX);
541 		clip_vertices = clip_triangle(vclip_list, vclip_list, clip_vertices, CLIP_Y_MIN);
542 
543 		/* Rasterise */
544 		if (clip_vertices >= 3)
545 		{
546 			micro3d_vtx a = vclip_list[0];
547 			micro3d_vtx b = vclip_list[1];
548 
549 			triangles = true;
550 
551 			a.x += m_x_mid;
552 			a.y += m_y_mid;
553 
554 			b.x += m_x_mid;
555 			b.y += m_y_mid;
556 
557 			/* Keep track of the y-extents so we don't have to scan every line later */
558 			if (a.y < min_y)
559 				min_y = a.y;
560 			if (a.y > max_y)
561 				max_y = a.y;
562 
563 			if (b.y < min_y)
564 				min_y = b.y;
565 			if (b.y > max_y)
566 				max_y = b.y;
567 
568 			/* Draw the first line of the triangle/fan */
569 			draw_line(a.x, a.y, b.x, b.y);
570 
571 			for (k = 2; k < clip_vertices; ++k)
572 			{
573 				micro3d_vtx c = vclip_list[k];
574 
575 				c.x += m_x_mid;
576 				c.y += m_y_mid;
577 
578 				if (c.y < min_y)
579 					min_y = c.y;
580 				if (c.y > max_y)
581 					max_y = c.y;
582 
583 				draw_line(b.x, b.y, c.x, c.y);
584 				draw_line(a.x, a.y, c.x, c.y);
585 				b = c;
586 			}
587 		}
588 	}
589 
590 	if (triangles == true)
591 		rasterise_spans(min_y, max_y, attr);
592 }
593 
594 
595 /******************************************************************************
596 
597 MPGDRAW commands
598 
599 80000000     Start triangle list [...]
600 85000xxx     End of vertex list - draw (xxx = colour) [0]
601 8Axxxxxx     End of vertex list - random fill shading (xxx = colour) [0]
602 
603 90000000     Set clipping Z min [1]
604 94000000     Set clipping Z max [1]
605 98000000     Set clipping Y max [1]
606 9c000000     Set clipping X min [1]
607 a0000000     Set clipping X max [1]
608 a4000001     Set clipping Y min [1] (what does 1 mean?)
609 
610 d8000000     End of frame (will swap render buffer)    [0]
611 f8000000     Toggle health LED [0]
612 
613 b8000000-1fe Draw ASIC DPRAM address
614 b80003ff     Data
615 
616 bc000000-1fc DPRAM address for read access
617 
618 ******************************************************************************/
619 
micro3d_fifo_w(uint32_t data)620 void micro3d_state::micro3d_fifo_w(uint32_t data)
621 {
622 	uint32_t opcode = data >> 24;
623 
624 	switch (m_draw_state)
625 	{
626 		case STATE_DRAW_CMD:
627 		{
628 			m_draw_cmd = data;
629 
630 			switch (opcode)
631 			{
632 				case 0xb4:
633 				{
634 					m_x_mid = data & 0x3ff;
635 					m_y_mid = (data >> 10) & 0x3ff;
636 					break;
637 				}
638 				case 0xc8:
639 				{
640 					m_dpram_bank ^= 1;
641 					break;
642 				}
643 				case 0xbc:
644 				{
645 					uint32_t dpram_r_addr = (((data & 0x01ff) << 1) | m_dpram_bank);
646 					m_pipe_data = m_draw_dpram[dpram_r_addr];
647 					m_drmath->set_input_line(AM29000_INTR1, ASSERT_LINE);
648 					break;
649 				}
650 				case 0x80:
651 				{
652 					int addr;
653 					m_fifo_idx = 0;
654 					m_draw_state = STATE_DRAW_VTX_DATA;
655 
656 					/* Invalidate the draw RAM
657 					 * TODO: Not sure this is the right place for it -
658 					 * causes monitor mode draw tests to fail
659 					 */
660 					for (addr = 0; addr < 512; ++addr)
661 						m_draw_dpram[addr << 1] = 0x3ff000;
662 
663 					break;
664 				}
665 				case 0xf8:
666 				{
667 					/* 3D pipeline health LEDs toggle */
668 					break;
669 				}
670 				case 0xd8:
671 				{
672 					/* TODO: We shouldn't need this extra buffer - is there some sort of sync missing? */
673 					memcpy(m_frame_buffers[m_drawing_buffer].get(), m_tmp_buffer.get(), 512*1024*2);
674 					m_drawing_buffer ^= 1;
675 					m_vgb->set_input_line(0, ASSERT_LINE);
676 					break;
677 				}
678 				default:
679 					m_draw_state = STATE_DRAW_CMD_DATA;
680 			}
681 			break;
682 		}
683 		case STATE_DRAW_CMD_DATA:
684 		{
685 			switch (m_draw_cmd >> 24)
686 			{
687 				case 0x90: m_z_min = VTX_SEX(data); break;
688 				case 0x94: m_z_max = VTX_SEX(data); break;
689 				case 0x98: m_y_max = VTX_SEX(data); break;
690 				case 0x9c: m_x_min = VTX_SEX(data); break;
691 				case 0xa0: m_x_max = VTX_SEX(data); break;
692 				case 0xa4: m_y_min = VTX_SEX(data); break;
693 				case 0xb8:
694 				{
695 					m_draw_dpram[((m_draw_cmd & 0x1ff) << 1) | m_dpram_bank] = data & 0x00ffffff;
696 					break;
697 				}
698 				default:
699 					popmessage("Unknown 3D command: %x %x\n", m_draw_cmd, data);
700 			}
701 			m_draw_state = STATE_DRAW_CMD;
702 			break;
703 		}
704 		case STATE_DRAW_VTX_DATA:
705 		{
706 			if ((opcode == 0x85) || (opcode == 0x8a))
707 			{
708 				draw_triangles(data);
709 				m_draw_state = STATE_DRAW_CMD;
710 			}
711 			else
712 			{
713 				m_vtx_fifo[m_fifo_idx++] = VTX_SEX(data);
714 			}
715 			break;
716 		}
717 	}
718 }
719 
micro3d_alt_fifo_w(uint32_t data)720 void micro3d_state::micro3d_alt_fifo_w(uint32_t data)
721 {
722 	m_vtx_fifo[m_fifo_idx++] = VTX_SEX(data);
723 }
724 
micro3d_pipe_r()725 uint32_t micro3d_state::micro3d_pipe_r()
726 {
727 	m_drmath->set_input_line(AM29000_INTR1, CLEAR_LINE);
728 	return m_pipe_data;
729 }
730 
INTERRUPT_GEN_MEMBER(micro3d_state::micro3d_vblank)731 INTERRUPT_GEN_MEMBER(micro3d_state::micro3d_vblank)
732 {
733 //  mc68901_int_gen(machine(), GPIP7);
734 
735 	m_display_buffer = m_drawing_buffer ^ 1;
736 }
737