// license:BSD-3-Clause
// copyright-holders:Philip Bennett
/***************************************************************************
Microprose Games 3D hardware
TODO:
* Fix buffering issue
* Find correct noise-fill LFSR arrangement
****************************************************************************/
#include "emu.h"
#include "includes/micro3d.h"
#include "audio/micro3d.h"
#include "cpu/am29000/am29000.h"
/*************************************
*
* Defines
*
*************************************/
#define VTX_SEX(x) ((x) | ((x) & (1 << 29) ? 0xc0000000 : 0))
enum
{
STATE_DRAW_CMD,
STATE_DRAW_CMD_DATA,
STATE_DRAW_VTX_DATA
};
/*************************************
*
* Video initialisation
*
*************************************/
void micro3d_state::video_start()
{
/* Allocate 512x12 x 2 3D frame buffers */
m_frame_buffers[0] = std::make_unique<uint16_t[]>(1024 * 512);
m_frame_buffers[1] = std::make_unique<uint16_t[]>(1024 * 512);
m_tmp_buffer = std::make_unique<uint16_t[]>(1024 * 512);
}
void micro3d_state::video_reset()
{
m_pipeline_state = 0;
m_creg = 0;
m_drawing_buffer = 0;
m_display_buffer = 1;
}
/*************************************
*
* 2D graphics
*
*************************************/
TMS340X0_SCANLINE_IND16_CB_MEMBER(micro3d_state::scanline_update)
{
uint16_t *src = &m_sprite_vram[(params->rowaddr << 8) & 0x7fe00];
uint16_t *dest = &bitmap.pix16(scanline);
int coladdr = params->coladdr;
int sd_11_7 = (m_creg & 0x1f) << 7;
int x;
uint16_t *frame_src;
scanline = std::max((scanline - params->veblnk), 0);
frame_src = m_frame_buffers[m_display_buffer].get() + (scanline << 10);
/* TODO: XFER3DK - X/Y offsets for 3D */
/* Copy the non-blanked portions of this scanline */
for (x = params->heblnk; x < params->hsblnk; x += 2)
{
uint16_t pix = src[coladdr++ & 0x1ff];
/*
TODO: The upper four bits of the 3D buffer affect priority
0xfxx forces 3D priority?
*/
if (pix & 0x80)
dest[x + 0] = sd_11_7 | (pix & 0x7f);
else
dest[x + 0] = *frame_src & 0xfff;
pix >>= 8;
frame_src++;
if (pix & 0x80)
dest[x + 1] = sd_11_7 | (pix & 0x7f);
else
dest[x + 1] = *frame_src & 0xfff;
frame_src++;
}
}
WRITE16_MEMBER(micro3d_state::micro3d_creg_w)
{
if (~data & 0x80)
m_vgb->set_input_line(0, CLEAR_LINE);
m_creg = data;
}
WRITE16_MEMBER(micro3d_state::micro3d_xfer3dk_w)
{
m_xfer3dk = data;
}
WRITE_LINE_MEMBER(micro3d_state::tms_interrupt)
{
// mc68901_int_gen(device->machine(), GPIP4);
}
/*************************************
*
* 3D graphics
*
*************************************/
int micro3d_state::inside(micro3d_vtx *v, enum planes plane)
{
switch (plane)
{
case CLIP_Z_MIN: return v->z >= m_z_min;
case CLIP_Z_MAX: return v->z <= m_z_max;
case CLIP_X_MIN: return v->x >= m_x_min;
case CLIP_X_MAX: return v->x <= m_x_max;
case CLIP_Y_MIN: return v->y >= m_y_min;
case CLIP_Y_MAX: return v->y <= m_y_max;
}
return 0;
}
// Calculate where two points intersect
micro3d_state::micro3d_vtx micro3d_state::intersect(micro3d_vtx *v1, micro3d_vtx *v2, enum planes plane)
{
float m = 0.0;
if (v1->x != v2->x)
m = float(v1->y - v2->y) / float(v1->x - v2->x);
micro3d_vtx vo = { 0, 0, 0 };
switch (plane)
{
case CLIP_Z_MIN:
{
float mxz, myz;
if (v1->z != v2->z)
{
mxz = float(v1->x - v2->x) / float(v1->z - v2->z);
myz = float(v1->y - v2->y) / float(v1->z - v2->z);
}
else
{
mxz = 0.0;
myz = 0.0;
}
vo.x = v2->x + (m_z_min - v2->z) * mxz;
vo.y = v2->y + (m_z_min - v2->z) * myz;
vo.z = m_z_min;
break;
}
case CLIP_Z_MAX:
{
float mxz, myz;
if (v1->z != v2->z)
{
mxz = float(v1->x - v2->x) / float(v1->z - v2->z);
myz = float(v1->y - v2->y) / float(v1->z - v2->z);
}
else
{
mxz = 0.0;
myz = 0.0;
}
vo.x = v2->x + (m_z_max - v2->z) * mxz;
vo.y = v2->y + (m_z_max - v2->z) * myz;
vo.z = m_z_max;
break;
}
case CLIP_X_MIN:
{
vo.x = m_x_min;
vo.y = v2->y + (m_x_min - v2->x) * m;
vo.z = 0;
break;
}
case CLIP_X_MAX:
{
vo.x = m_x_max;
vo.y = v2->y + (m_x_max - v2->x) * m;
vo.z = 0;
break;
}
case CLIP_Y_MIN:
{
if (v1->x != v2->x)
vo.x = v2->x + (m_y_min - v2->y) / m;
else
vo.x = v2->x;
vo.y = m_y_min;
vo.z = 0;
break;
}
case CLIP_Y_MAX:
{
if (v1->x != v2->x)
vo.x = v2->x + (m_y_max - v2->y) / m;
else
vo.x = v2->x;
vo.y = m_y_max;
vo.z = 0;
break;
}
}
return vo;
}
inline void micro3d_state::write_span(uint32_t y, uint32_t x)
{
uint32_t *draw_dpram = m_draw_dpram;
int addr = y << 1;
if (draw_dpram[addr] == 0x3ff000)
{
draw_dpram[addr] = (x << 12) | x;
}
else
{
/* Check start */
if (x < (m_draw_dpram[addr] & 0x3ff))
{
draw_dpram[addr] &= ~0x3ff;
draw_dpram[addr] |= x;
}
/* Check end */
if (x > (draw_dpram[addr] >> 12))
{
draw_dpram[addr] &= ~0x3ff000;
draw_dpram[addr] |= (x << 12);
}
}
}
/* This is the same algorithm used in the 3D tests */
void micro3d_state::draw_line(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2)
{
uint32_t tmp2;
uint32_t acc;
uint32_t y_inc;
uint32_t dx;
uint32_t dy;
if (x2 < x1)
{
uint32_t tmp;
tmp = x1;
x1 = x2;
x2 = tmp;
tmp = y1;
y1 = y2;
y2 = tmp;
}
dx = x2 - x1;
if (y2 >= y1)
dy = y2 - y1;
else
dy = y1 - y2;
write_span(y1, x1);
if (dx == 0 && dy == 0)
return;
if (y1 <= y2)
y_inc = 1;
else
y_inc = -1;
if (dx > dy)
{
if (y2 != y1)
{
tmp2 = dy << 1;
acc = tmp2 - dx;
dy = (dy - dx) << 1;
do
{
if (~acc & 0x80000000)
{
write_span(y1, x1);
y1 += y_inc;
x1++;
acc += dy;
write_span(y1, x1);
}
else
{
acc += tmp2;
x1++;
}
} while (y2 != y1);
}
if (x2 != x1)
write_span(y1, x2);
}
else
{
tmp2 = dx << 1;
acc = tmp2 - dy;
dy = (dx - dy) << 1;
if (y1 != y2)
{
do
{
if (acc & 0x80000000)
{
acc += tmp2;
write_span(y1, x1);
y1 += y_inc;
write_span(y1, x1);
}
else
{
write_span(y1, x1);
x1++;
y1 += y_inc;
write_span(y1, x1);
acc += dy;
}
} while (y1 != y2);
}
if (x2 != x1)
write_span(y1, x2);
}
}
void micro3d_state::rasterise_spans(uint32_t min_y, uint32_t max_y, uint32_t attr)
{
int y;
int color = attr & 0xfff;
if ((attr >> 24) == 0x85)
{
for (y = min_y; y <= max_y; ++y)
{
int x;
int addr = y << 1;
uint16_t *dest = &m_tmp_buffer[y * 1024];
if (m_draw_dpram[addr] == 0x3ff000)
{
continue;
}
else
{
int start = m_draw_dpram[addr] & 0x3ff;
int end = (m_draw_dpram[addr] >> 12) & 0x3ff;
for (x = start; x <= end; ++x)
dest[x] = color;
}
}
}
else
{
/*
I don't know the LFSR arrangement inside the DRAW2 ASIC
but here are some possible tap arrangements
*/
static const uint8_t taps[8][4] =
{
{9, 8, 7, 2},
{9, 8, 6, 5},
{9, 8, 5, 4},
{9, 8, 5, 1},
{9, 8, 4, 2},
{9, 7, 6, 4},
{9, 7, 5, 2},
{9, 6, 5, 3},
};
int noise_val = (attr >> 12) & 0x3ff;
int noise_taps = 0;
for (y = min_y; y <= max_y; ++y)
{
int x;
int addr = y << 1;
uint16_t *dest = &m_tmp_buffer[y * 1024];
if (m_draw_dpram[addr] == 0x3ff000)
{
continue;
}
else
{
int start = m_draw_dpram[addr] & 0x3ff;
int end = (m_draw_dpram[addr] >> 12) & 0x3ff;
for (x = start; x <= end; ++x)
{
int fb;
if (noise_val & 0x1)
dest[x] = color;
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));
noise_val = ((noise_val << 1) | fb) & 0x1ff;
}
}
}
}
}
int micro3d_state::clip_triangle(micro3d_vtx *v, micro3d_vtx *vout, int num_vertices, enum planes plane)
{
micro3d_vtx clip_out[10];
int i;
int prev_i = num_vertices - 1;
int clip_verts = 0;
for (i = 0; i < num_vertices; ++i)
{
int v1_in = inside(&v[i], plane);
int v2_in = inside(&v[prev_i], plane);
/* Edge is inside */
if (v1_in && v2_in)
{
clip_out[clip_verts++] = v[i];
}
/* Edge is leaving */
else if (v1_in && !v2_in)
{
clip_out[clip_verts++] = intersect(&v[i], &v[prev_i], plane);
clip_out[clip_verts++] = v[i];
}
/* Edge is entering */
else if (!v1_in && v2_in)
{
clip_out[clip_verts++] = intersect(&v[i], &v[prev_i], plane);
}
prev_i = i;
}
memcpy(&vout[0], &clip_out[0], sizeof(vout[0]) * clip_verts);
return clip_verts;
}
void micro3d_state::draw_triangles(uint32_t attr)
{
int i;
bool triangles = false;
int vertices = m_fifo_idx / 3;
int min_y = 0x3ff;
int max_y = 0;
/* This satisifes the burst write test */
if (vertices == 0)
{
int y;
int val = ((m_x_mid + 16) << 12) | m_x_mid;
for (y = m_y_mid; y <= m_y_mid + 16; ++y)
m_draw_dpram[y << 1] = val;
return;
}
/* Draw triangles as fans */
for (i = 2; i < vertices; ++i)
{
int k;
int clip_vertices = 3;
micro3d_vtx vo, vm, vn;
micro3d_vtx vclip_list[10];
vo.x = m_vtx_fifo[0];
vo.y = m_vtx_fifo[1];
vo.z = m_vtx_fifo[2];
vm.x = m_vtx_fifo[(i - 1) * 3 + 0];
vm.y = m_vtx_fifo[(i - 1) * 3 + 1];
vm.z = m_vtx_fifo[(i - 1) * 3 + 2];
vn.x = m_vtx_fifo[i * 3 + 0];
vn.y = m_vtx_fifo[i * 3 + 1];
vn.z = m_vtx_fifo[i * 3 + 2];
vclip_list[0] = vo;
vclip_list[1] = vm;
vclip_list[2] = vn;
/* Clip against near Z and far Z planes */
clip_vertices = clip_triangle(vclip_list, vclip_list, clip_vertices, CLIP_Z_MIN);
clip_vertices = clip_triangle(vclip_list, vclip_list, clip_vertices, CLIP_Z_MAX);
/* Perform perspective divide */
for (k = 0; k < clip_vertices; ++k)
{
vclip_list[k].x = vclip_list[k].x * m_z_min / vclip_list[k].z;
vclip_list[k].y = vclip_list[k].y * m_z_min / vclip_list[k].z;
vclip_list[k].z = 0;
}
/* Perform screen-space clipping */
clip_vertices = clip_triangle(vclip_list, vclip_list, clip_vertices, CLIP_Y_MAX);
clip_vertices = clip_triangle(vclip_list, vclip_list, clip_vertices, CLIP_X_MIN);
clip_vertices = clip_triangle(vclip_list, vclip_list, clip_vertices, CLIP_X_MAX);
clip_vertices = clip_triangle(vclip_list, vclip_list, clip_vertices, CLIP_Y_MIN);
/* Rasterise */
if (clip_vertices >= 3)
{
micro3d_vtx a = vclip_list[0];
micro3d_vtx b = vclip_list[1];
triangles = true;
a.x += m_x_mid;
a.y += m_y_mid;
b.x += m_x_mid;
b.y += m_y_mid;
/* Keep track of the y-extents so we don't have to scan every line later */
if (a.y < min_y)
min_y = a.y;
if (a.y > max_y)
max_y = a.y;
if (b.y < min_y)
min_y = b.y;
if (b.y > max_y)
max_y = b.y;
/* Draw the first line of the triangle/fan */
draw_line(a.x, a.y, b.x, b.y);
for (k = 2; k < clip_vertices; ++k)
{
micro3d_vtx c = vclip_list[k];
c.x += m_x_mid;
c.y += m_y_mid;
if (c.y < min_y)
min_y = c.y;
if (c.y > max_y)
max_y = c.y;
draw_line(b.x, b.y, c.x, c.y);
draw_line(a.x, a.y, c.x, c.y);
b = c;
}
}
}
if (triangles == true)
rasterise_spans(min_y, max_y, attr);
}
/******************************************************************************
MPGDRAW commands
80000000 Start triangle list [...]
85000xxx End of vertex list - draw (xxx = colour) [0]
8Axxxxxx End of vertex list - random fill shading (xxx = colour) [0]
90000000 Set clipping Z min [1]
94000000 Set clipping Z max [1]
98000000 Set clipping Y max [1]
9c000000 Set clipping X min [1]
a0000000 Set clipping X max [1]
a4000001 Set clipping Y min [1] (what does 1 mean?)
d8000000 End of frame (will swap render buffer) [0]
f8000000 Toggle health LED [0]
b8000000-1fe Draw ASIC DPRAM address
b80003ff Data
bc000000-1fc DPRAM address for read access
******************************************************************************/
WRITE32_MEMBER(micro3d_state::micro3d_fifo_w)
{
uint32_t opcode = data >> 24;
switch (m_draw_state)
{
case STATE_DRAW_CMD:
{
m_draw_cmd = data;
switch (opcode)
{
case 0xb4:
{
m_x_mid = data & 0x3ff;
m_y_mid = (data >> 10) & 0x3ff;
break;
}
case 0xc8:
{
m_dpram_bank ^= 1;
break;
}
case 0xbc:
{
uint32_t dpram_r_addr = (((data & 0x01ff) << 1) | m_dpram_bank);
m_pipe_data = m_draw_dpram[dpram_r_addr];
m_drmath->set_input_line(AM29000_INTR1, ASSERT_LINE);
break;
}
case 0x80:
{
int addr;
m_fifo_idx = 0;
m_draw_state = STATE_DRAW_VTX_DATA;
/* Invalidate the draw RAM
* TODO: Not sure this is the right place for it -
* causes monitor mode draw tests to fail
*/
for (addr = 0; addr < 512; ++addr)
m_draw_dpram[addr << 1] = 0x3ff000;
break;
}
case 0xf8:
{
/* 3D pipeline health LEDs toggle */
break;
}
case 0xd8:
{
/* TODO: We shouldn't need this extra buffer - is there some sort of sync missing? */
memcpy(m_frame_buffers[m_drawing_buffer].get(), m_tmp_buffer.get(), 512*1024*2);
m_drawing_buffer ^= 1;
m_vgb->set_input_line(0, ASSERT_LINE);
break;
}
default:
m_draw_state = STATE_DRAW_CMD_DATA;
}
break;
}
case STATE_DRAW_CMD_DATA:
{
switch (m_draw_cmd >> 24)
{
case 0x90: m_z_min = VTX_SEX(data); break;
case 0x94: m_z_max = VTX_SEX(data); break;
case 0x98: m_y_max = VTX_SEX(data); break;
case 0x9c: m_x_min = VTX_SEX(data); break;
case 0xa0: m_x_max = VTX_SEX(data); break;
case 0xa4: m_y_min = VTX_SEX(data); break;
case 0xb8:
{
m_draw_dpram[((m_draw_cmd & 0x1ff) << 1) | m_dpram_bank] = data & 0x00ffffff;
break;
}
default:
popmessage("Unknown 3D command: %x %x\n", m_draw_cmd, data);
}
m_draw_state = STATE_DRAW_CMD;
break;
}
case STATE_DRAW_VTX_DATA:
{
if ((opcode == 0x85) || (opcode == 0x8a))
{
draw_triangles(data);
m_draw_state = STATE_DRAW_CMD;
}
else
{
m_vtx_fifo[m_fifo_idx++] = VTX_SEX(data);
}
break;
}
}
}
WRITE32_MEMBER(micro3d_state::micro3d_alt_fifo_w)
{
m_vtx_fifo[m_fifo_idx++] = VTX_SEX(data);
}
READ32_MEMBER(micro3d_state::micro3d_pipe_r)
{
m_drmath->set_input_line(AM29000_INTR1, CLEAR_LINE);
return m_pipe_data;
}
INTERRUPT_GEN_MEMBER(micro3d_state::micro3d_vblank)
{
// mc68901_int_gen(machine(), GPIP7);
m_display_buffer = m_drawing_buffer ^ 1;
}