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