// license:BSD-3-Clause // copyright-holders:R. Belmont, Olivier Galibert, ElSemi, Angelo Salese /********************************************************************************************************************************* Sega Model 2 Geometry Engine and 3D Rasterizer Emulation General Notes: - The 3D rendering system has 3 main parts: 1) The Geometry Engine: Model 2B and 2C hardware upload the geometry code to a DSP. The original and 2A hardware have the geometry code in an internal ROM inside the DSP itself. The simulation written here for the geometry engine is based off the disassembly of the code uploaded to a 2B game. 2) The Z-Sort and Clip Hardware: Not much information about this hardware. There are a couple of variables that can be passed to this stage, like a master Z-Clip enable register, and a Z-Sort mode variable. 3) The Hardware Renderer: Not much information about the inner workings of this hardware. Based on the available tidbits from the 2B manual, it is a double-buffered device (can be toggled between page flipping and single buffer mode), and can also be switched to generate output at 60Hz or 30Hz. - Most of the information used to write this code come from 3 main sources: 1) The Geometry Engine code disassembly from a 2B game. 2) The Model 2B-CRX Manual. 3) ElSemi's Direct3D implementation of the geometrizer engine. - The emulation strategy used here is to decouple the geometrizer code from the Z-Sort/Clip/Renderer code, so that for original and 2A games, the HLE version of the geometrizer can be used, while for 2B and 2C games, the original geometrizer DSP can be emulated, and data can be pushed to the Z-Sort/Clip/Renderer code. Geometry Engine Notes and Known Bugs: - Top Skater seem to use a slightly different geometry code that has a secondary transformation matrix. Once we implement the real DSP code upload and emulation for 2B and 2C games, this should not be an issue. Z-Sort Notes and Known Bugs: - The Z-Sort algorithm is not entirely understood. The manual states that the Z-Sort works internally in 1.4.8 bit format, but then it states that the Z-Sort Mode register value is added to the floating point z value, and converted to a 16 bit format (most likely 4.12). - The system allows for Z-Sort override, by means of specifying whether a polygon will use the same Z value ordinal as the previous polygon, or the calculated minimum or maximum from it's points. This allows for a full object to be in front of another, even though the first object might have some z coordinates that are bigger than the second object's z coordinates. - The current implementation takes the effective computed z value for the polygon and converts it into a 4.12 fixed point representation, used as an index into an array of linked polygons. Every polygon with the same z value is linked with any previous polygon that had that same z value: z-value index linked list of polygons 0000 triangle3->triangle2->triangle1 0001 triangle22->triangle8->triangle7 .... FFFF triangle33->triangle11->triangle9 As we add polygons to the array, we also keep track of the minimum and maximum z-value indexes seen this frame. When it's time to render, we start and the max z-value seen, and iterate through the array going down to the minimum z-value seen, clipping and rendering the linked polygons as we go. Unfortunately, it seems there's something not quite right, as there are some visible Z-Sort problems in various games. Perhaps the linked list of polygons need to be presorted by a unknown factor before rendering. Clip Notes and Known Bugs: - The Z-Clip happens at z=0. Since dividing by 0 during projection would give bogus values, we scale by a distance of 1, thus our projection equation turns into vd.x = (d*vs.x) / (d+vs.z) where d = 1, since we know we won't have polygons with a z < 0. This, however, poses the problem that for objects originally between 0.0 and 1.0 are not going to be scaled properly. We still need to find a solution for this. - A small offset need to be added horizontally and vertically to the viewport and center variables for certain games (like the original Model 2 games). The coordinate system has been worked out from the 2B specifications, but the older games need a slight adjustment. Hardware Renderer Notes and Known Bugs: - Texturing code could use a real good speed optimization. - The U and V coordinates provided by the game are in 13.3 fixed point format. - The luma/texel combination algorithm is not known. There are currently some small color glitches here and there, and this might be the culprit. - The log tables and distance coefficients are used to calculate the number of texels per world unit that need to be used to render a texture. Textures can also be provided with smaller levels of details and a LOD bit selector in the texture header tells the rasterizer which texture map to use. The rasterizer then can average two texture maps to do mip mapping. More information can be found on the 2B manual, on the 'Texturing' and 'Data Format' chapters. This is currently unemulated. We always use the texture data from the bigger texture map. - The rasterizer supports up to 128x128 'microtex' textures, which are supposed to be higher resolution textures used to display more detail when a texture is real close to the viewer. This is currently unemulated. *********************************************************************************************************************************/ #include "emu.h" #include "includes/model2.h" #include #define pz p[0] #define pu p[1] #define pv p[2] /******************************************* * * Generic 3D Math Functions * *******************************************/ static inline void transform_point( poly_vertex *point, float *matrix ) { float tx = (point->x * matrix[0]) + (point->y * matrix[3]) + (point->pz * matrix[6]) + (matrix[9]); float ty = (point->x * matrix[1]) + (point->y * matrix[4]) + (point->pz * matrix[7]) + (matrix[10]); float tz = (point->x * matrix[2]) + (point->y * matrix[5]) + (point->pz * matrix[8]) + (matrix[11]); point->x = tx; point->y = ty; point->pz = tz; } static inline void transform_vector( poly_vertex *vector, float *matrix ) { float tx = (vector->x * matrix[0]) + (vector->y * matrix[3]) + (vector->pz * matrix[6]); float ty = (vector->x * matrix[1]) + (vector->y * matrix[4]) + (vector->pz * matrix[7]); float tz = (vector->x * matrix[2]) + (vector->y * matrix[5]) + (vector->pz * matrix[8]); vector->x = tx; vector->y = ty; vector->pz = tz; } static inline void normalize_vector( poly_vertex *vector ) { float n = sqrt( (vector->x * vector->x) + (vector->y * vector->y) + (vector->pz * vector->pz) ); if ( n ) { float oon = 1.0f / n; vector->x *= oon; vector->y *= oon; vector->pz *= oon; } } static inline float dot_product( poly_vertex *v1, poly_vertex *v2 ) { return (v1->x * v2->x) + (v1->y * v2->y) + (v1->pz * v2->pz); } static inline void vector_cross3( poly_vertex *dst, poly_vertex *v0, poly_vertex *v1, poly_vertex *v2 ) { poly_vertex p1, p2; p1.x = v1->x - v0->x; p1.y = v1->y - v0->y; p1.pz = v1->pz - v0->pz; p2.x = v2->x - v0->x; p2.y = v2->y - v0->y; p2.pz = v2->pz - v0->pz; dst->x = (p1.y * p2.pz) - (p1.pz * p2.y); dst->y = (p1.pz * p2.x) - (p1.x * p2.pz); dst->pz = (p1.x * p2.y) - (p1.y * p2.x); } static inline void apply_focus( geo_state *geo, poly_vertex *p0) { p0->x *= geo->focus.x; p0->y *= geo->focus.y; } /* 1.8.23 float to 4.12 float converter, courtesy of Aaron Giles */ inline uint16_t model2_state::float_to_zval( float floatval ) { int32_t fpint = f2u(floatval); int32_t exponent = ((fpint >> 23) & 0xff) - 127; uint32_t mantissa = fpint & 0x7fffff; /* round the low bits and reduce to 12 */ mantissa += 0x400; if (mantissa > 0x7fffff) { exponent++; mantissa = (mantissa & 0x7fffff) >> 1; } mantissa >>= 11; /* if negative, clamp to 0 */ if (fpint < 0) return 0x0000; /* the rest depends on the exponent */ /* less than -12 is too small, return 0 */ if ( exponent < -12 ) return 0x0000; /* between -12 and 0 create a denormal with exponent of 0 */ if ( exponent < 0 ) return (mantissa | 0x1000) >> -exponent; /* between 0 and 14 create a FP value with exponent + 1 */ if ( exponent < 15 ) return (( exponent + 1 ) << 12) | mantissa; /* above 14 is too large */ return 0xffff; } static int32_t clip_polygon(poly_vertex *v, int32_t num_vertices, poly_vertex *vout) { poly_vertex *cur, *out; float curdot, nextdot, scale; int32_t i, curin, nextin, nextvert, outcount; plane clip_plane; clip_plane.normal.x = 0.0f; clip_plane.normal.y = 0.0f; clip_plane.normal.pz = 1.0f; clip_plane.distance = 0.0f; outcount = 0; cur = v; out = vout; curdot = dot_product( cur, &clip_plane.normal ); curin = (curdot >= clip_plane.distance) ? 1 : 0; for( i = 0; i < num_vertices; i++ ) { nextvert = (i + 1) % num_vertices; /* if the current point is inside the plane, add it */ if ( curin ) memcpy( &out[outcount++], cur, sizeof( poly_vertex ) ); nextdot = dot_product( &v[nextvert], &clip_plane.normal ); nextin = (nextdot >= clip_plane.distance) ? 1 : 0; /* Add a clipped vertex if one end of the current edge is inside the plane and the other is outside */ // TODO: displaying Honey in Fighting Vipers and Bean in Sonic the Fighters somehow causes a NaN dot product here, // causing MAME to hardlock in the renderer routine. They are also causing lots of invalid polygon renders // which might be related. if ( curin != nextin && std::isnan(curdot) == false && std::isnan(nextdot) == false ) { scale = (clip_plane.distance - curdot) / (nextdot - curdot); out[outcount].x = cur->x + ((v[nextvert].x - cur->x) * scale); out[outcount].y = cur->y + ((v[nextvert].y - cur->y) * scale); out[outcount].pz = cur->pz + ((v[nextvert].pz - cur->pz) * scale); out[outcount].pu = (uint16_t)((float)cur->pu + (((float)v[nextvert].pu - (float)cur->pu) * scale)); out[outcount].pv = (uint16_t)((float)cur->pv + (((float)v[nextvert].pv - (float)cur->pv) * scale)); outcount++; } curdot = nextdot; curin = nextin; cur++; } return outcount; } inline bool model2_state::check_culling( raster_state *raster, uint32_t attr, float min_z, float max_z ) { /* if doubleside is disabled */ if ( ((attr >> 17) & 1) == 0 ) { /* if it's the backface, cull it */ if ( raster->command_buffer[9] & 0x00800000 ) return true; } /* if the linktype is 0, then we can also cull it */ if ( ((attr >> 8) & 3) == 0 ) return true; /* if the minimum z value is bigger than the master z clip value, don't render */ if ( (int32_t)(1.0/min_z) > raster->master_z_clip ) return true; /* if the maximum z value is < 0 then we can safely clip the entire polygon */ if ( max_z < 0 ) return true; return false; } /***********************************************************************************************/ /******************************************* * * Hardware 3D Rasterizer Initialization * *******************************************/ void model2_state::raster_init( memory_region *texture_rom ) { m_raster = make_unique_clear(); m_raster->texture_rom = (uint16_t *)texture_rom->base(); m_raster->texture_rom_mask = (texture_rom->bytes() / 2) - 1; save_item(NAME(m_raster->min_z)); save_item(NAME(m_raster->max_z)); // save_item(NAME(m_raster->tri_list)); save_item(NAME(m_raster->tri_list_index)); save_item(NAME(m_raster->command_buffer)); save_item(NAME(m_raster->command_index)); save_item(NAME(m_raster->cur_command)); save_item(NAME(m_raster->master_z_clip)); save_item(NAME(m_raster->triangle_z)); save_item(NAME(m_raster->z_adjust)); save_item(NAME(m_raster->reverse)); save_item(NAME(m_raster->viewport)); save_item(NAME(m_raster->center)); save_item(NAME(m_raster->center_sel)); save_item(NAME(m_raster->texture_ram)); save_item(NAME(m_raster->log_ram)); } /******************************************* * * Hardware 3D Rasterizer Z-Clip selection * *******************************************/ WRITE32_MEMBER(model2_state::model2_3d_zclip_w) { m_raster->master_z_clip = data; } // TODO: only Sky Target seems to use this for unknown purpose READ32_MEMBER(model2_state::polygon_count_r) { // printf("%08x\n",m_raster->tri_list_index); return m_raster->tri_list_index; } /******************************************* * * Hardware 3D Rasterizer Processing * *******************************************/ void model2_state::model2_3d_process_quad( raster_state *raster, uint32_t attr ) { quad_m2 object; uint16_t *th, *tp; int32_t tho; uint32_t i; bool cull; float zvalue; float min_z, max_z; /* extract P0(n-1) */ object.v[1].x = u2f( raster->command_buffer[2] << 8 ); object.v[1].y = u2f( raster->command_buffer[3] << 8 ); object.v[1].pz = u2f( raster->command_buffer[4] << 8 ); /* extract P1(n-1) */ object.v[0].x = u2f( raster->command_buffer[5] << 8 ); object.v[0].y = u2f( raster->command_buffer[6] << 8 ); object.v[0].pz = u2f( raster->command_buffer[7] << 8 ); /* extract P0(n) */ object.v[2].x = u2f( raster->command_buffer[11] << 8 ); object.v[2].y = u2f( raster->command_buffer[12] << 8 ); object.v[2].pz = u2f( raster->command_buffer[13] << 8 ); /* extract P1(n) */ object.v[3].x = u2f( raster->command_buffer[14] << 8 ); object.v[3].y = u2f( raster->command_buffer[15] << 8 ); object.v[3].pz = u2f( raster->command_buffer[16] << 8 ); /* always calculate the min z and max z value */ min_z = object.v[0].pz; if ( object.v[1].pz < min_z ) min_z = object.v[1].pz; if ( object.v[2].pz < min_z ) min_z = object.v[2].pz; if ( object.v[3].pz < min_z ) min_z = object.v[3].pz; max_z = object.v[0].pz; if ( object.v[1].pz > max_z ) max_z = object.v[1].pz; if ( object.v[2].pz > max_z ) max_z = object.v[2].pz; if ( object.v[3].pz > max_z ) max_z = object.v[3].pz; /* read in the texture information */ /* texture point data */ if ( raster->command_buffer[0] & 0x800000 ) tp = &raster->texture_ram[raster->command_buffer[0] & 0xFFFF]; else tp = &raster->texture_rom[raster->command_buffer[0] & raster->texture_rom_mask]; object.v[0].pv = *tp++; object.v[0].pu = *tp++; object.v[1].pv = *tp++; object.v[1].pu = *tp++; object.v[2].pv = *tp++; object.v[2].pu = *tp++; object.v[3].pv = *tp++; object.v[3].pu = *tp++; /* update the address */ raster->command_buffer[0] += 8; /* texture header data */ if ( raster->command_buffer[1] & 0x800000 ) th = &raster->texture_ram[raster->command_buffer[1] & 0xFFFF]; else th = &raster->texture_rom[raster->command_buffer[1] & raster->texture_rom_mask]; object.texheader[0] = *th++; object.texheader[1] = *th++; object.texheader[2] = *th++; object.texheader[3] = *th++; /* extract the texture header offset */ tho = (attr >> 12) & 0x1F; /* adjust for sign */ if ( tho & 0x10 ) tho |= -16; /* update the address */ raster->command_buffer[1] += tho * 4; /* set the luma value of this quad */ object.luma = (raster->command_buffer[9] >> 15) & 0xFF; /* determine whether we can cull this quad */ cull = check_culling(raster,attr,min_z,max_z); /* set the object's z value */ switch((attr >> 10) & 3) { case 0: // old value zvalue = raster->triangle_z; break; case 1: // min z zvalue = min_z; break; case 2: // max z zvalue = max_z; break; case 3: // error default: zvalue = 1e10; break; } raster->triangle_z = zvalue; if ( cull == false ) { int32_t clipped_verts; poly_vertex verts[10]; /* do near z clipping */ clipped_verts = clip_polygon( object.v, 4, verts); if ( clipped_verts > 2 ) { triangle *ztri; /* adjust and set the object z-sort value */ object.z = float_to_zval( zvalue + raster->z_adjust ); /* get our list read to add the triangles */ ztri = raster->tri_sorted_list[object.z]; if ( ztri != nullptr ) { while( ztri->next != nullptr ) ztri = (triangle *)ztri->next; } /* go through the clipped vertex list, adding triangles */ for( i = 2; i < clipped_verts; i++ ) { triangle *tri; tri = &raster->tri_list[raster->tri_list_index++]; if ( raster->tri_list_index >= MAX_TRIANGLES ) { fatalerror( "SEGA 3D: Max triangle limit exceeded\n" ); } /* copy the object information */ tri->z = object.z; tri->texheader[0] = object.texheader[0]; tri->texheader[1] = object.texheader[1]; tri->texheader[2] = object.texheader[2]; tri->texheader[3] = object.texheader[3]; tri->luma = object.luma; /* set the viewport */ tri->viewport[0] = raster->viewport[0]; tri->viewport[1] = raster->viewport[1]; tri->viewport[2] = raster->viewport[2]; tri->viewport[3] = raster->viewport[3]; /* set the center */ tri->center[0] = raster->center[raster->center_sel][0]; tri->center[1] = raster->center[raster->center_sel][1]; memcpy( &tri->v[0], &verts[0], sizeof( poly_vertex ) ); memcpy( &tri->v[1], &verts[i-1], sizeof( poly_vertex ) ); memcpy( &tri->v[2], &verts[i], sizeof( poly_vertex ) ); /* add to our sorted list */ tri->next = nullptr; if ( ztri == nullptr ) { raster->tri_sorted_list[object.z] = tri; } else { ztri->next = tri; } ztri = tri; } /* keep around the min and max z values for this frame */ if ( object.z < raster->min_z ) raster->min_z = object.z; if ( object.z > raster->max_z ) raster->max_z = object.z; } } /* update linking */ switch( ((attr >> 8) & 3) ) { case 0: case 2: { /* reuse P0(n) and P1(n) */ for( i = 0; i < 6; i++ ) /* P0(n) -> P0(n-1) */ raster->command_buffer[2+i] = raster->command_buffer[11+i]; /* P1(n) -> P1(n-1) */ } break; case 1: { /* reuse P0(n-1) and P0(n) */ for( i = 0; i < 3; i++ ) raster->command_buffer[5+i] = raster->command_buffer[11+i]; /* P0(n) -> P1(n-1) */ } break; case 3: { /* reuse P1(n-1) and P1(n) */ for( i = 0; i < 3; i++ ) raster->command_buffer[2+i] = raster->command_buffer[14+i]; /* P1(n) -> P1(n-1) */ } break; } } void model2_state::model2_3d_process_triangle( raster_state *raster, uint32_t attr ) { triangle object; uint16_t *th, *tp; int32_t tho; uint32_t i; bool cull; float zvalue; float min_z, max_z; /* extract P0(n-1) */ object.v[1].x = u2f( raster->command_buffer[2] << 8 ); object.v[1].y = u2f( raster->command_buffer[3] << 8 ); object.v[1].pz = u2f( raster->command_buffer[4] << 8 ); /* extract P1(n-1) */ object.v[0].x = u2f( raster->command_buffer[5] << 8 ); object.v[0].y = u2f( raster->command_buffer[6] << 8 ); object.v[0].pz = u2f( raster->command_buffer[7] << 8 ); /* extract P0(n) */ object.v[2].x = u2f( raster->command_buffer[11] << 8 ); object.v[2].y = u2f( raster->command_buffer[12] << 8 ); object.v[2].pz = u2f( raster->command_buffer[13] << 8 ); /* for triangles, the rope of P1(n) is achieved by P0(n-1) (linktype 3) */ raster->command_buffer[14] = raster->command_buffer[11]; raster->command_buffer[15] = raster->command_buffer[12]; raster->command_buffer[16] = raster->command_buffer[13]; /* always calculate the min z and max z values */ min_z = object.v[0].pz; if ( object.v[1].pz < min_z ) min_z = object.v[1].pz; if ( object.v[2].pz < min_z ) min_z = object.v[2].pz; max_z = object.v[0].pz; if ( object.v[1].pz > max_z ) max_z = object.v[1].pz; if ( object.v[2].pz > max_z ) max_z = object.v[2].pz; /* read in the texture information */ /* texture point data */ if ( raster->command_buffer[0] & 0x800000 ) tp = &raster->texture_ram[raster->command_buffer[0] & 0xFFFF]; else tp = &raster->texture_rom[raster->command_buffer[0] & raster->texture_rom_mask]; object.v[0].pv = *tp++; object.v[0].pu = *tp++; object.v[1].pv = *tp++; object.v[1].pu = *tp++; object.v[2].pv = *tp++; object.v[2].pu = *tp++; /* update the address */ raster->command_buffer[0] += 6; /* texture header data */ if ( raster->command_buffer[1] & 0x800000 ) th = &raster->texture_ram[raster->command_buffer[1] & 0xFFFF]; else th = &raster->texture_rom[raster->command_buffer[1] & raster->texture_rom_mask]; object.texheader[0] = *th++; object.texheader[1] = *th++; object.texheader[2] = *th++; object.texheader[3] = *th++; /* extract the texture header offset */ tho = (attr >> 12) & 0x1F; /* adjust for sign */ if ( tho & 0x10 ) tho |= -16; /* update the address */ raster->command_buffer[1] += tho * 4; /* set the luma value of this quad */ object.luma = (raster->command_buffer[9] >> 15) & 0xFF; /* determine whether we can cull this quad */ cull = check_culling(raster,attr,min_z,max_z); /* set the object's z value */ switch((attr >> 10) & 3) { case 0: // old value zvalue = raster->triangle_z; break; case 1: // min z zvalue = min_z; break; case 2: // max z zvalue = max_z; break; case 3: // error default: zvalue = 1e10; break; } raster->triangle_z = zvalue; /* if we're not culling, do z-clip and add to out triangle list */ if ( cull == false ) { int32_t clipped_verts; poly_vertex verts[10]; /* do near z clipping */ clipped_verts = clip_polygon( object.v, 3, verts); if ( clipped_verts > 2 ) { triangle *ztri; /* adjust and set the object z-sort value */ object.z = float_to_zval( zvalue + raster->z_adjust ); /* get our list read to add the triangles */ ztri = raster->tri_sorted_list[object.z]; if ( ztri != nullptr ) { while( ztri->next != nullptr ) ztri = (triangle *)ztri->next; } /* go through the clipped vertex list, adding triangles */ for( i = 2; i < clipped_verts; i++ ) { triangle *tri; tri = &raster->tri_list[raster->tri_list_index++]; if ( raster->tri_list_index >= MAX_TRIANGLES ) { fatalerror( "SEGA 3D: Max triangle limit exceeded\n" ); } /* copy the object information */ tri->z = object.z; tri->texheader[0] = object.texheader[0]; tri->texheader[1] = object.texheader[1]; tri->texheader[2] = object.texheader[2]; tri->texheader[3] = object.texheader[3]; tri->luma = object.luma; /* set the viewport */ tri->viewport[0] = raster->viewport[0]; tri->viewport[1] = raster->viewport[1]; tri->viewport[2] = raster->viewport[2]; tri->viewport[3] = raster->viewport[3]; /* set the center */ tri->center[0] = raster->center[raster->center_sel][0]; tri->center[1] = raster->center[raster->center_sel][1]; memcpy( &tri->v[0], &verts[0], sizeof( poly_vertex ) ); memcpy( &tri->v[1], &verts[i-1], sizeof( poly_vertex ) ); memcpy( &tri->v[2], &verts[i], sizeof( poly_vertex ) ); /* add to our sorted list */ tri->next = nullptr; if ( ztri == nullptr ) { raster->tri_sorted_list[object.z] = tri; } else { ztri->next = tri; } ztri = tri; } /* keep around the min and max z values for this frame */ if ( object.z < raster->min_z ) raster->min_z = object.z; if ( object.z > raster->max_z ) raster->max_z = object.z; } } /* update linking */ switch( ((attr >> 8) & 3) ) { case 0: case 2: { /* reuse P0(n) and P1(n) */ for( i = 0; i < 6; i++ ) /* P0(n) -> P0(n-1) */ raster->command_buffer[2+i] = raster->command_buffer[11+i]; /* P1(n) -> P1(n-1) */ } break; case 1: { /* reuse P0(n-1) and P0(n) */ for( i = 0; i < 3; i++ ) raster->command_buffer[5+i] = raster->command_buffer[11+i]; /* P0(n) -> P1(n-1) */ } break; case 3: { /* reuse P1(n-1) and P1(n) */ for( i = 0; i < 3; i++ ) raster->command_buffer[2+i] = raster->command_buffer[14+i]; /* P1(n) -> P1(n-1) */ } break; } } /***********************************************************************************************/ /***********************************************************************************************/ void model2_renderer::model2_3d_render(triangle *tri, const rectangle &cliprect) { model2_renderer *poly = m_state.m_poly.get(); m2_poly_extra_data& extra = poly->object_data_alloc(); uint8_t renderer; /* select renderer based on attributes (bit15 = checker, bit14 = textured, bit13 = transparent */ renderer = (tri->texheader[0] >> 13) & 7; /* calculate and clip to viewport */ rectangle vp(tri->viewport[0] + m_xoffs, tri->viewport[2] + m_xoffs, (384-tri->viewport[3]) + m_yoffs, (384-tri->viewport[1]) + m_yoffs); vp &= cliprect; extra.state = &m_state; extra.lumabase = ((tri->texheader[1] & 0xFF) << 7) + ((tri->luma >> 5) ^ 0x7); extra.colorbase = (tri->texheader[3] >> 6) & 0x3FF; if (renderer & 2) { extra.texwidth = 32 << ((tri->texheader[0] >> 0) & 0x7); extra.texheight = 32 << ((tri->texheader[0] >> 3) & 0x7); extra.texx = 32 * ((tri->texheader[2] >> 0) & 0x1f); extra.texy = 32 * (((tri->texheader[2] >> 6) & 0x1f) + ( tri->texheader[2] & 0x20 )); /* TODO: Virtua Striker contradicts with this. */ extra.texmirrorx = 0;//(tri->texheader[0] >> 9) & 1; extra.texmirrory = 0;//(tri->texheader[0] >> 8) & 1; extra.texsheet = (tri->texheader[2] & 0x1000) ? m_state.m_textureram1 : m_state.m_textureram0; tri->v[0].pz = 1.0f / (1.0f + tri->v[0].pz); tri->v[0].pu = tri->v[0].pu * tri->v[0].pz * (1.0f / 8.0f); tri->v[0].pv = tri->v[0].pv * tri->v[0].pz * (1.0f / 8.0f); tri->v[1].pz = 1.0f / (1.0f + tri->v[1].pz); tri->v[1].pu = tri->v[1].pu * tri->v[1].pz * (1.0f / 8.0f); tri->v[1].pv = tri->v[1].pv * tri->v[1].pz * (1.0f / 8.0f); tri->v[2].pz = 1.0f / (1.0f + tri->v[2].pz); tri->v[2].pu = tri->v[2].pu * tri->v[2].pz * (1.0f / 8.0f); tri->v[2].pv = tri->v[2].pv * tri->v[2].pz * (1.0f / 8.0f); // Note : The class model2_renderer has an array of function pointers in it named m_renderfuncs, in theory this simply // needs to be passed into the render_triangle function as such model2_renderer::m_renderfuncs[renderer], but // I was unable to make it work when converting to the new polygon rasterizer interface. switch (renderer) { case 0: render_triangle(vp, render_delegate(&model2_renderer::model2_3d_render_0, this), 3, tri->v[0], tri->v[1], tri->v[2]); break; case 1: render_triangle(vp, render_delegate(&model2_renderer::model2_3d_render_1, this), 3, tri->v[0], tri->v[1], tri->v[2]); break; case 2: render_triangle(vp, render_delegate(&model2_renderer::model2_3d_render_2, this), 3, tri->v[0], tri->v[1], tri->v[2]); break; case 3: render_triangle(vp, render_delegate(&model2_renderer::model2_3d_render_3, this), 3, tri->v[0], tri->v[1], tri->v[2]); break; case 4: render_triangle(vp, render_delegate(&model2_renderer::model2_3d_render_4, this), 3, tri->v[0], tri->v[1], tri->v[2]); break; case 5: render_triangle(vp, render_delegate(&model2_renderer::model2_3d_render_5, this), 3, tri->v[0], tri->v[1], tri->v[2]); break; case 6: render_triangle(vp, render_delegate(&model2_renderer::model2_3d_render_6, this), 3, tri->v[0], tri->v[1], tri->v[2]); break; case 7: render_triangle(vp, render_delegate(&model2_renderer::model2_3d_render_7, this), 3, tri->v[0], tri->v[1], tri->v[2]); break; } } else { switch (renderer) { case 0: render_triangle(vp, render_delegate(&model2_renderer::model2_3d_render_0, this), 0, tri->v[0], tri->v[1], tri->v[2]); break; case 1: render_triangle(vp, render_delegate(&model2_renderer::model2_3d_render_1, this), 0, tri->v[0], tri->v[1], tri->v[2]); break; case 2: render_triangle(vp, render_delegate(&model2_renderer::model2_3d_render_2, this), 0, tri->v[0], tri->v[1], tri->v[2]); break; case 3: render_triangle(vp, render_delegate(&model2_renderer::model2_3d_render_3, this), 0, tri->v[0], tri->v[1], tri->v[2]); break; case 4: render_triangle(vp, render_delegate(&model2_renderer::model2_3d_render_4, this), 0, tri->v[0], tri->v[1], tri->v[2]); break; case 5: render_triangle(vp, render_delegate(&model2_renderer::model2_3d_render_5, this), 0, tri->v[0], tri->v[1], tri->v[2]); break; case 6: render_triangle(vp, render_delegate(&model2_renderer::model2_3d_render_6, this), 0, tri->v[0], tri->v[1], tri->v[2]); break; case 7: render_triangle(vp, render_delegate(&model2_renderer::model2_3d_render_7, this), 0, tri->v[0], tri->v[1], tri->v[2]); break; } } } /* Projection: According to the 2B Manual the screen coordinates are: (8,474) (504,474) +--------------------------------+ | | | | | | | | | | | | | | | | +--------------------------------+ (8,90) (504,90) */ WRITE16_MEMBER(model2_state::horizontal_sync_w) { m_crtc_xoffset = 84 + (int16_t)data; // printf("H %04x %d %d\n",data,(int16_t)data,m_crtc_xoffset); m_poly->set_xoffset(m_crtc_xoffset); } WRITE16_MEMBER(model2_state::vertical_sync_w) { m_crtc_yoffset = 130 + (int16_t)data; // printf("V %04x %d %d\n",data,(int16_t)data,m_crtc_yoffset); m_poly->set_yoffset(m_crtc_yoffset); } /* 3D Rasterizer projection: projects a triangle into screen coordinates */ inline void model2_state::model2_3d_project( triangle *tri ) { uint16_t i; for( i = 0; i < 3; i++ ) { /* project the vertices */ tri->v[i].x = m_crtc_xoffset + tri->center[0] + (tri->v[i].x / (1.0f+tri->v[i].pz)); tri->v[i].y = ((384 - tri->center[1])+m_crtc_yoffset) - (tri->v[i].y / (1.0f+tri->v[i].pz)); } } /* 3D Rasterizer frame start: Resets frame variables */ void model2_state::model2_3d_frame_start( void ) { raster_state *raster = m_raster.get(); /* reset the triangle list index */ raster->tri_list_index = 0; /* reset the sorted z list */ memset( raster->tri_sorted_list, 0, 0x10000 * sizeof( triangle * ) ); /* reset the min-max sortable Z values */ raster->min_z = 0xFFFF; raster->max_z = 0; } void model2_state::model2_3d_frame_end( bitmap_rgb32 &bitmap, const rectangle &cliprect ) { raster_state *raster = m_raster.get(); int32_t z; /* if we have nothing to render, bail */ if ( raster->tri_list_index == 0 ) return; m_poly->destmap().fill(0x00000000, cliprect); /* go through the Z levels, and render each bucket */ for( z = raster->max_z; z >= raster->min_z; z-- ) { /* see if we have items at this z level */ if ( raster->tri_sorted_list[z] != nullptr ) { /* get a pointer to the first triangle */ triangle *tri = raster->tri_sorted_list[z]; /* and loop clipping and rendering each triangle */ while( tri != nullptr ) { /* project and render */ model2_3d_project( tri ); m_poly->model2_3d_render(tri, cliprect); tri = (triangle *)tri->next; } } } m_poly->wait("End of frame"); copybitmap_trans(bitmap, m_poly->destmap(), 0, 0, 0, 0, cliprect, 0x00000000); } // direct framebuffer drawing (enabled with render test mode, Last Bronx title screen) // pretty sure test mode cuts off DSP framebuffer drawing/clear, according to the manual description too void model2_state::draw_framebuffer( bitmap_rgb32 &bitmap, const rectangle &cliprect ) { uint16_t *fbvram = &(m_screen->frame_number() & 1 ? m_fbvramB[0] : m_fbvramA[0]); // TODO: halved crtc values? int xoffs = (-m_crtc_xoffset)/2; int yoffs = m_crtc_yoffset/2; for (int y = cliprect.min_y; y <= cliprect.max_y; ++y) { for (int x = cliprect.min_x; x <= cliprect.max_x; x++) { int r,g,b; int offset = (x + xoffs) + (y + yoffs)*512; b = (fbvram[offset] >> 0) & 0x1f; r = (fbvram[offset] >> 5) & 0x1f; g = (fbvram[offset] >> 10) & 0x1f; r = pal5bit(r); g = pal5bit(g); b = pal5bit(b); bitmap.pix32(y, x) = r << 16 | g << 8 | b; } } } /* 3D Rasterizer main data input port */ void model2_state::model2_3d_push( raster_state *raster, uint32_t input ) { /* see if we have a command in progress */ if ( raster->cur_command != 0 ) { raster->command_buffer[raster->command_index++] = input; switch( raster->cur_command ) { case 0x00: /* NOP */ break; case 0x01: /* Polygon Data */ { uint32_t attr; /* start by looking if we have the basic input data */ if ( raster->command_index < 9 ) return; /* get the attributes */ attr = raster->command_buffer[8]; /* see if we're done */ if ( (attr & 3) == 0 ) { raster->cur_command = 0; return; } /* see if it's a quad or a triangle */ if ( attr & 1 ) { /* it's a quad, wait for the rest of the points */ if ( raster->command_index < 17 ) return; /* we have a full quad info, fill up our quad structure */ model2_3d_process_quad( raster, attr ); /* back up and wait for more data */ raster->command_index = 8; } else { /* it's a triangle, wait for the rest of the point */ if ( raster->command_index < 14 ) return; /* we have a full quad info, fill up our quad structure */ model2_3d_process_triangle( raster, attr ); /* back up and wait for more data */ raster->command_index = 8; } } break; case 0x03: /* Window Data */ { uint32_t i; /* make sure we have all the data */ if ( raster->command_index < 6 ) return; /* coordinates are 12 bit signed */ /* extract the viewport start x */ raster->viewport[0] = (raster->command_buffer[0] >> 12) & 0xFFF; if ( raster->viewport[0] & 0x800 ) raster->viewport[0] = -( 0x800 - (raster->viewport[0] & 0x7FF) ); /* extract the viewport start y */ raster->viewport[1] = raster->command_buffer[0] & 0xFFF; if ( raster->viewport[1] & 0x800 ) raster->viewport[1] = -( 0x800 - (raster->viewport[1] & 0x7FF) ); /* extract the viewport end x */ raster->viewport[2] = (raster->command_buffer[1] >> 12) & 0xFFF; if ( raster->viewport[2] & 0x800 ) raster->viewport[2] = -( 0x800 - (raster->viewport[2] & 0x7FF) ); /* extract the viewport end y */ raster->viewport[3] = raster->command_buffer[1] & 0xFFF; if ( raster->viewport[3] & 0x800 ) raster->viewport[3] = -( 0x800 - (raster->viewport[3] & 0x7FF) ); /* extract the centers */ for( i = 0; i < 4; i++ ) { /* center x */ raster->center[i][0] = (raster->command_buffer[2+i] >> 12) & 0xFFF; if ( raster->center[i][0] & 0x800 ) raster->center[i][0] = -( 0x800 - (raster->center[i][0] & 0x7FF) ); /* center y */ raster->center[i][1] = raster->command_buffer[2+i] & 0xFFF; if ( raster->center[i][1] & 0x800 ) raster->center[i][1] = -( 0x800 - (raster->center[i][1] & 0x7FF) ); } /* done with this command */ raster->cur_command = 0; } break; case 0x04: /* Texture/Log Data write */ { /* make sure we have enough data */ if ( raster->command_index < 2 ) return; /* see if the count is non-zero */ if ( raster->command_buffer[1] > 0 ) { /* see if we have data available */ if ( raster->command_index >= 3 ) { /* get the address */ uint32_t address = raster->command_buffer[0]; /* do the write */ if ( address & 0x800000 ) raster->texture_ram[address&0xFFFF] = raster->command_buffer[2]; else raster->log_ram[address&0xFFFF] = raster->command_buffer[2]; /* increment the address and decrease the count */ raster->command_buffer[0]++; raster->command_buffer[1]--; /* decrease the index, so we keep placing data in the same slot */ raster->command_index--; } } /* see if we're done with this command */ if ( raster->command_buffer[1] == 0 ) raster->cur_command = 0; } break; case 0x08: /* ZSort mode */ { /* save the zsort mode value */ raster->z_adjust = u2f( raster->command_buffer[0] << 8 ); /* done with this command */ raster->cur_command = 0; } break; default: { fatalerror( "SEGA 3D: Unknown rasterizer command %08x\n", raster->cur_command ); } } } else { /* new command */ raster->cur_command = input & 0x0F; raster->command_index = 0; /* see if it's object data */ if ( raster->cur_command == 1 ) { /* extract reverse bit */ raster->reverse = (input >> 4) & 1; /* extract center select */ raster->center_sel = ( input >> 6 ) & 3; /* reset the triangle z value */ // Zero Gunner sets backgrounds with "previous z value" mode at the start of the display list, // needs this to be this big in order to work properly raster->triangle_z = 1e10; } } } /***********************************************************************************************/ /******************************************* * * Geometry Engine Initialization * *******************************************/ void model2_state::geo_init(memory_region *polygon_rom) { m_geo = make_unique_clear(); m_geo->state = this; m_geo->raster = m_raster.get(); m_geo->polygon_rom = (uint32_t *)polygon_rom->base(); m_geo->polygon_rom_mask = (polygon_rom->bytes() / 4) - 1; save_item(NAME(m_geo->mode)); save_item(NAME(m_geo->matrix)); save_item(NAME(m_geo->lod)); save_item(NAME(m_geo->coef_table)); save_item(NAME(m_geo->polygon_ram0)); save_item(NAME(m_geo->polygon_ram1)); } /******************************************* * * Geometry Engine Polygon Parsers * *******************************************/ /* Parse Polygons: Normals Present, No Specular case */ void model2_state::geo_parse_np_ns( geo_state *geo, uint32_t *input, uint32_t count ) { raster_state *raster = geo->raster; poly_vertex point, normal; uint32_t attr, i; /* read the 1st point */ point.x = u2f( *input++ ); point.y = u2f( *input++ ); point.pz = u2f( *input++ ); /* transform with the current matrix */ transform_point( &point, geo->matrix ); /* apply focus */ apply_focus( geo, &point ); /* push it to the 3d rasterizer */ model2_3d_push( raster, f2u(point.x) >> 8 ); model2_3d_push( raster, f2u(point.y) >> 8 ); model2_3d_push( raster, f2u(point.pz) >> 8 ); /* read the 2nd point */ point.x = u2f( *input++ ); point.y = u2f( *input++ ); point.pz = u2f( *input++ ); /* transform with the current matrix */ transform_point( &point, geo->matrix ); /* apply focus */ apply_focus( geo, &point ); /* push it to the 3d rasterizer */ model2_3d_push( raster, f2u(point.x) >> 8 ); model2_3d_push( raster, f2u(point.y) >> 8 ); model2_3d_push( raster, f2u(point.pz) >> 8 ); /* loop through the following links */ for( i = 0; i < count; i++ ) { /* read in the attributes */ attr = *input++; /* push to the 3d rasterizer */ model2_3d_push( raster, attr & 0x0003FFFF ); /* read in the normal */ normal.x = u2f(*input++); normal.y = u2f(*input++); normal.pz = u2f(*input++); /* transform with the current matrix */ transform_vector( &normal, geo->matrix ); if ( (attr & 3) != 0 ) /* quad or triangle */ { float dotl, dotp, luminance, distance; float coef, face; int32_t luma; texture_parameter * texparam; /* read in the next point */ point.x = u2f( *input++ ); point.y = u2f( *input++ ); point.pz = u2f( *input++ ); /* transform with the current matrix */ transform_point( &point, geo->matrix ); /* calculate the dot product of the normal and the light vector */ dotl = dot_product( &normal, &geo->light ); /* calculate the dot product of the normal and the point */ dotp = dot_product( &normal, &point ); /* apply focus */ apply_focus( geo, &point ); /* determine whether this is the front or the back of the polygon */ face = 0x100; /* rear */ if ( dotp >= 0 ) face = 0; /* front */ /* get the texture parameters */ texparam = &geo->texture_parameters[(attr>>18) & 0x1f]; /* calculate luminance */ if ( (dotl * dotp) < 0 ) luminance = 0; else luminance = fabs( dotl ); luminance = (luminance * texparam->diffuse) + texparam->ambient; luma = (int32_t)luminance; if ( luma > 255 ) luma = 255; if ( luma < 0 ) luma = 0; /* add the face bit to the luma */ luma += face; /* extract distance coefficient */ coef = geo->coef_table[attr>>27]; /* calculate texture level of detail */ distance = coef * fabs( dotp ) * geo->lod; /* push to the 3d rasterizer */ model2_3d_push( raster, luma << 15 ); model2_3d_push( raster, f2u(distance) >> 8 ); model2_3d_push( raster, f2u(point.x) >> 8 ); model2_3d_push( raster, f2u(point.y) >> 8 ); model2_3d_push( raster, f2u(point.pz) >> 8 ); /* if it's a quad, push one more point */ if ( attr & 1 ) { /* read in the next point */ point.x = u2f( *input++ ); point.y = u2f( *input++ ); point.pz = u2f( *input++ ); /* transform with the current matrix */ transform_point( &point, geo->matrix ); /* apply focus */ apply_focus( geo, &point ); /* push to the 3d rasterizer */ model2_3d_push( raster, f2u(point.x) >> 8 ); model2_3d_push( raster, f2u(point.y) >> 8 ); model2_3d_push( raster, f2u(point.pz) >> 8 ); } else /* triangle */ { /* skip the next 3 points */ input += 3; } } else /* we're done */ { break; } } /* notify the 3d rasterizer we're done */ model2_3d_push( raster, 0 ); } /* Parse Polygons: Normals Present, Specular case */ void model2_state::geo_parse_np_s( geo_state *geo, uint32_t *input, uint32_t count ) { raster_state *raster = geo->raster; poly_vertex point, normal; uint32_t attr, i; /* read the 1st point */ point.x = u2f( *input++ ); point.y = u2f( *input++ ); point.pz = u2f( *input++ ); /* transform with the current matrix */ transform_point( &point, geo->matrix ); /* apply focus */ apply_focus( geo, &point ); /* push it to the 3d rasterizer */ model2_3d_push( raster, f2u(point.x) >> 8 ); model2_3d_push( raster, f2u(point.y) >> 8 ); model2_3d_push( raster, f2u(point.pz) >> 8 ); /* read the 2nd point */ point.x = u2f( *input++ ); point.y = u2f( *input++ ); point.pz = u2f( *input++ ); /* transform with the current matrix */ transform_point( &point, geo->matrix ); /* apply focus */ apply_focus( geo, &point ); /* push it to the 3d rasterizer */ model2_3d_push( raster, f2u(point.x) >> 8 ); model2_3d_push( raster, f2u(point.y) >> 8 ); model2_3d_push( raster, f2u(point.pz) >> 8 ); /* loop through the following links */ for( i = 0; i < count; i++ ) { /* read in the attributes */ attr = *input++; /* push to the 3d rasterizer */ model2_3d_push( raster, attr & 0x0003FFFF ); /* read in the normal */ normal.x = u2f(*input++); normal.y = u2f(*input++); normal.pz = u2f(*input++); /* transform with the current matrix */ transform_vector( &normal, geo->matrix ); if ( (attr & 3) != 0 ) /* quad or triangle */ { float dotl, dotp, luminance, distance, specular; float coef, face; int32_t luma; texture_parameter * texparam; /* read in the next point */ point.x = u2f( *input++ ); point.y = u2f( *input++ ); point.pz = u2f( *input++ ); /* transform with the current matrix */ transform_point( &point, geo->matrix ); /* calculate the dot product of the normal and the light vector */ dotl = dot_product( &normal, &geo->light ); /* calculate the dot product of the normal and the point */ dotp = dot_product( &normal, &point ); /* apply focus */ apply_focus( geo, &point ); /* determine whether this is the front or the back of the polygon */ face = 0x100; /* rear */ if ( dotp >= 0 ) face = 0; /* front */ /* get the texture parameters */ texparam = &geo->texture_parameters[(attr>>18) & 0x1f]; /* calculate luminance and specular */ if ( (dotl * dotp) < 0 ) luminance = 0; else luminance = fabs( dotl ); specular = ((2*dotl) * normal.pz) - geo->light.pz; if ( specular < 0 ) specular = 0; if ( texparam->specular_control == 0 ) specular = 0; if ( (texparam->specular_control >> 1) != 0 ) specular *= specular; if ( (texparam->specular_control >> 2) != 0 ) specular *= specular; if ( ((texparam->specular_control+1) >> 3) != 0 ) specular *= specular; specular *= texparam->specular_scale; luminance = (luminance * texparam->diffuse) + texparam->ambient + specular; luma = (int32_t)luminance; if ( luma > 255 ) luma = 255; if ( luma < 0 ) luma = 0; /* add the face bit to the luma */ luma += face; /* extract distance coefficient */ coef = geo->coef_table[attr>>27]; /* calculate texture level of detail */ distance = coef * fabs( dotp ) * geo->lod; /* push to the 3d rasterizer */ model2_3d_push( raster, luma << 15 ); model2_3d_push( raster, f2u(distance) >> 8 ); model2_3d_push( raster, f2u(point.x) >> 8 ); model2_3d_push( raster, f2u(point.y) >> 8 ); model2_3d_push( raster, f2u(point.pz) >> 8 ); /* if it's a quad, push one more point */ if ( attr & 1 ) { /* read in the next point */ point.x = u2f( *input++ ); point.y = u2f( *input++ ); point.pz = u2f( *input++ ); /* transform with the current matrix */ transform_point( &point, geo->matrix ); /* apply focus */ apply_focus( geo, &point ); /* push to the 3d rasterizer */ model2_3d_push( raster, f2u(point.x) >> 8 ); model2_3d_push( raster, f2u(point.y) >> 8 ); model2_3d_push( raster, f2u(point.pz) >> 8 ); } else /* triangle */ { /* skip the next 3 points */ input += 3; } } else /* we're done */ { break; } } /* notify the 3d rasterizer we're done */ model2_3d_push( raster, 0 ); } /* Parse Polygons: No Normals, No Specular case */ void model2_state::geo_parse_nn_ns( geo_state *geo, uint32_t *input, uint32_t count ) { raster_state *raster = geo->raster; poly_vertex point, normal, p0, p1, p2, p3; uint32_t attr, i; /* read the 1st point */ point.x = u2f( *input++ ); point.y = u2f( *input++ ); point.pz = u2f( *input++ ); /* transform with the current matrix */ transform_point( &point, geo->matrix ); /* save for normal calculation */ p0.x = point.x; p0.y = point.y; p0.pz = point.pz; /* apply focus */ apply_focus( geo, &point ); /* push it to the 3d rasterizer */ model2_3d_push( raster, f2u(point.x) >> 8 ); model2_3d_push( raster, f2u(point.y) >> 8 ); model2_3d_push( raster, f2u(point.pz) >> 8 ); /* read the 2nd point */ point.x = u2f( *input++ ); point.y = u2f( *input++ ); point.pz = u2f( *input++ ); /* transform with the current matrix */ transform_point( &point, geo->matrix ); /* save for normal calculation */ p1.x = point.x; p1.y = point.y; p1.pz = point.pz; /* apply focus */ apply_focus( geo, &point ); /* push it to the 3d rasterizer */ model2_3d_push( raster, f2u(point.x) >> 8 ); model2_3d_push( raster, f2u(point.y) >> 8 ); model2_3d_push( raster, f2u(point.pz) >> 8 ); /* loop through the following links */ for( i = 0; i < count; i++ ) { /* read in the attributes */ attr = *input++; /* push to the 3d rasterizer */ model2_3d_push( raster, attr & 0x0003FFFF ); if ( (attr & 3) != 0 ) /* quad or triangle */ { float dotl, dotp, luminance, distance; float coef, face; int32_t luma; texture_parameter * texparam; /* Skip normal */ input += 3; /* read in the next point */ point.x = u2f( *input++ ); point.y = u2f( *input++ ); point.pz = u2f( *input++ ); /* transform with the current matrix */ transform_point( &point, geo->matrix ); /* save for normal calculation */ p2.x = point.x; p2.y = point.y; p2.pz = point.pz; /* compute the normal */ vector_cross3( &normal, &p0, &p1, &p2 ); /* normalize it */ normalize_vector( &normal ); /* calculate the dot product of the normal and the light vector */ dotl = dot_product( &normal, &geo->light ); /* calculate the dot product of the normal and the point */ dotp = dot_product( &normal, &point ); /* apply focus */ apply_focus( geo, &point ); /* determine whether this is the front or the back of the polygon */ face = 0x100; /* rear */ if ( dotp >= 0 ) face = 0; /* front */ /* get the texture parameters */ texparam = &geo->texture_parameters[(attr>>18) & 0x1f]; /* calculate luminance */ if ( (dotl * dotp) < 0 ) luminance = 0; else luminance = fabs( dotl ); luminance = (luminance * texparam->diffuse) + texparam->ambient; luma = (int32_t)luminance; if ( luma > 255 ) luma = 255; if ( luma < 0 ) luma = 0; /* add the face bit to the luma */ luma += face; /* extract distance coefficient */ coef = geo->coef_table[attr>>27]; /* calculate texture level of detail */ distance = coef * fabs( dotp ) * geo->lod; /* push to the 3d rasterizer */ model2_3d_push( raster, luma << 15 ); model2_3d_push( raster, f2u(distance) >> 8 ); model2_3d_push( raster, f2u(point.x) >> 8 ); model2_3d_push( raster, f2u(point.y) >> 8 ); model2_3d_push( raster, f2u(point.pz) >> 8 ); /* if it's a quad, push one more point */ if ( attr & 1 ) { /* read in the next point */ point.x = u2f( *input++ ); point.y = u2f( *input++ ); point.pz = u2f( *input++ ); /* transform with the current matrix */ transform_point( &point, geo->matrix ); /* save for normal calculation */ p3.x = point.x; p3.y = point.y; p3.pz = point.pz; /* apply focus */ apply_focus( geo, &point ); /* push to the 3d rasterizer */ model2_3d_push( raster, f2u(point.x) >> 8 ); model2_3d_push( raster, f2u(point.y) >> 8 ); model2_3d_push( raster, f2u(point.pz) >> 8 ); } else { /* skip the next 3 points */ input += 3; /* for triangles, the rope of P1(n) is achieved by P0(n-1) (linktype 3) */ p3.x = p2.x; p3.y = p2.y; p3.pz = p2.pz; } } else /* we're done */ { break; } /* link type */ switch( (attr>>8) & 3 ) { case 0: case 2: { /* reuse P0(n) and P1(n) */ p0.x = p2.x; p0.y = p2.y; p0.pz = p2.pz; p1.x = p3.x; p1.y = p3.y; p1.pz = p3.pz; } break; case 1: { /* reuse P0(n-1) and P0(n) */ p1.x = p2.x; p1.y = p2.y; p1.pz = p2.pz; } break; case 3: { /* reuse P1(n-1) and P1(n) */ p0.x = p3.x; p0.y = p3.y; p0.pz = p3.pz; } break; } } /* notify the 3d rasterizer we're done */ model2_3d_push( raster, 0 ); } /* Parse Polygons: No Normals, Specular case */ void model2_state::geo_parse_nn_s( geo_state *geo, uint32_t *input, uint32_t count ) { raster_state *raster = geo->raster; poly_vertex point, normal, p0, p1, p2, p3; uint32_t attr, i; /* read the 1st point */ point.x = u2f( *input++ ); point.y = u2f( *input++ ); point.pz = u2f( *input++ ); /* transform with the current matrix */ transform_point( &point, geo->matrix ); /* save for normal calculation */ p0.x = point.x; p0.y = point.y; p0.pz = point.pz; /* apply focus */ apply_focus( geo, &point ); /* push it to the 3d rasterizer */ model2_3d_push( raster, f2u(point.x) >> 8 ); model2_3d_push( raster, f2u(point.y) >> 8 ); model2_3d_push( raster, f2u(point.pz) >> 8 ); /* read the 2nd point */ point.x = u2f( *input++ ); point.y = u2f( *input++ ); point.pz = u2f( *input++ ); /* transform with the current matrix */ transform_point( &point, geo->matrix ); /* save for normal calculation */ p1.x = point.x; p1.y = point.y; p1.pz = point.pz; /* apply focus */ apply_focus( geo, &point ); /* push it to the 3d rasterizer */ model2_3d_push( raster, f2u(point.x) >> 8 ); model2_3d_push( raster, f2u(point.y) >> 8 ); model2_3d_push( raster, f2u(point.pz) >> 8 ); /* loop through the following links */ for( i = 0; i < count; i++ ) { /* read in the attributes */ attr = *input++; /* push to the 3d rasterizer */ model2_3d_push( raster, attr & 0x0003FFFF ); if ( (attr & 3) != 0 ) /* quad or triangle */ { float dotl, dotp, luminance, distance, specular; float coef, face; int32_t luma; texture_parameter * texparam; /* Skip normal */ input += 3; /* read in the next point */ point.x = u2f( *input++ ); point.y = u2f( *input++ ); point.pz = u2f( *input++ ); /* transform with the current matrix */ transform_point( &point, geo->matrix ); /* save for normal calculation */ p2.x = point.x; p2.y = point.y; p2.pz = point.pz; /* compute the normal */ vector_cross3( &normal, &p0, &p1, &p2 ); /* normalize it */ normalize_vector( &normal ); /* calculate the dot product of the normal and the light vector */ dotl = dot_product( &normal, &geo->light ); /* calculate the dot product of the normal and the point */ dotp = dot_product( &normal, &point ); /* apply focus */ apply_focus( geo, &point ); /* determine whether this is the front or the back of the polygon */ face = 0x100; /* rear */ if ( dotp >= 0 ) face = 0; /* front */ /* get the texture parameters */ texparam = &geo->texture_parameters[(attr>>18) & 0x1f]; /* calculate luminance and specular */ if ( (dotl * dotp) < 0 ) luminance = 0; else luminance = fabs( dotl ); specular = ((2*dotl) * normal.pz) - geo->light.pz; if ( specular < 0 ) specular = 0; if ( texparam->specular_control == 0 ) specular = 0; if ( (texparam->specular_control >> 1) != 0 ) specular *= specular; if ( (texparam->specular_control >> 2) != 0 ) specular *= specular; if ( ((texparam->specular_control+1) >> 3) != 0 ) specular *= specular; specular *= texparam->specular_scale; luminance = (luminance * texparam->diffuse) + texparam->ambient + specular; luma = (int32_t)luminance; if ( luma > 255 ) luma = 255; if ( luma < 0 ) luma = 0; /* add the face bit to the luma */ luma += face; /* extract distance coefficient */ coef = geo->coef_table[attr>>27]; /* calculate texture level of detail */ distance = coef * fabs( dotp ) * geo->lod; /* push to the 3d rasterizer */ model2_3d_push( raster, luma << 15 ); model2_3d_push( raster, f2u(distance) >> 8 ); model2_3d_push( raster, f2u(point.x) >> 8 ); model2_3d_push( raster, f2u(point.y) >> 8 ); model2_3d_push( raster, f2u(point.pz) >> 8 ); /* if it's a quad, push one more point */ if ( attr & 1 ) { /* read in the next point */ point.x = u2f( *input++ ); point.y = u2f( *input++ ); point.pz = u2f( *input++ ); /* transform with the current matrix */ transform_point( &point, geo->matrix ); /* save for normal calculation */ p3.x = point.x; p3.y = point.y; p3.pz = point.pz; /* apply focus */ apply_focus( geo, &point ); /* push to the 3d rasterizer */ model2_3d_push( raster, f2u(point.x) >> 8 ); model2_3d_push( raster, f2u(point.y) >> 8 ); model2_3d_push( raster, f2u(point.pz) >> 8 ); } else { /* skip the next 3 points */ input += 3; /* for triangles, the rope of P1(n) is achieved by P0(n-1) (linktype 3) */ p3.x = p2.x; p3.y = p2.y; p3.pz = p2.pz; } } else /* we're done */ { break; } /* link type */ switch( (attr>>8) & 3 ) { case 0: case 2: { /* reuse P0(n) and P1(n) */ p0.x = p2.x; p0.y = p2.y; p0.pz = p2.pz; p1.x = p3.x; p1.y = p3.y; p1.pz = p3.pz; } break; case 1: { /* reuse P0(n-1) and P0(n) */ p1.x = p2.x; p1.y = p2.y; p1.pz = p2.pz; } break; case 3: { /* reuse P1(n-1) and P1(n) */ p0.x = p3.x; p0.y = p3.y; p0.pz = p3.pz; } break; } } /* notify the 3d rasterizer we're done */ model2_3d_push( raster, 0 ); } /******************************************* * * Geometry Engine Commands * *******************************************/ /* Command 00: NOP */ uint32_t *model2_state::geo_nop( geo_state *geo, uint32_t opcode, uint32_t *input ) { raster_state *raster = geo->raster; /* push the opcode to the 3d rasterizer */ model2_3d_push( raster, opcode >> 23 ); return input; } /* Command 01: Object Data */ uint32_t *model2_state::geo_object_data( geo_state *geo, uint32_t opcode, uint32_t *input ) { raster_state *raster = geo->raster; uint32_t tpa = *input++; /* Texture Point Address */ uint32_t tha = *input++; /* Texture Header Address */ uint32_t oba = *input++; /* Object Address */ uint32_t obc = *input++; /* Object Count */ uint32_t *obp; /* Object Pointer */ /* push the initial set of data to the 3d rasterizer */ model2_3d_push( raster, opcode >> 23 ); model2_3d_push( raster, tpa ); model2_3d_push( raster, tha ); /* select where we're reading polygon information from */ if ( oba & 0x01000000 ) { /* Fast polygon RAM */ obp = &geo->polygon_ram1[oba & 0x7FFF]; } else if ( oba & 0x00800000 ) { /* Polygon ROM */ obp = &geo->polygon_rom[oba & geo->polygon_rom_mask]; } else { /* Slow Polygon RAM */ obp = &geo->polygon_ram0[oba & 0x7FFF]; } // if count == 0 then rolls over to max size // Virtual On & Gunblade NY if(obc == 0) obc = 0xfffff; switch( geo->mode & 3 ) { /* Normals present, No Specular */ case 0: geo_parse_np_ns( geo, obp, obc ); break; /* Normals present, Specular */ case 1: geo_parse_np_s( geo, obp, obc ); break; /* No Normals present, No Specular */ case 2: geo_parse_nn_ns( geo, obp, obc ); break; /* No Normals present, Specular */ case 3: geo_parse_nn_s( geo, obp, obc ); break; } /* move by 4 parameters */ return input; } /* Command 02: Direct Data */ uint32_t *model2_state::geo_direct_data( geo_state *geo, uint32_t opcode, uint32_t *input ) { raster_state *raster = geo->raster; uint32_t tpa = *input++; /* Texture Point Address */ uint32_t tha = *input++; /* Texture Header Address */ uint32_t attr; /* push the initial set of data to the 3d rasterizer */ model2_3d_push( raster, (opcode >> 23) - 1 ); model2_3d_push( raster, tpa ); model2_3d_push( raster, tha ); /* push the initial points */ model2_3d_push( raster, (*input++) >> 8 ); /* x */ model2_3d_push( raster, (*input++) >> 8 ); /* y */ model2_3d_push( raster, (*input++) >> 8 ); /* z */ model2_3d_push( raster, (*input++) >> 8 ); /* x */ model2_3d_push( raster, (*input++) >> 8 ); /* y */ model2_3d_push( raster, (*input++) >> 8 ); /* z */ do { /* read in the attributes */ attr = *input++; if ( (attr & 3) == 0 ) break; /* push attributes */ model2_3d_push( raster, attr & 0x00FFFFFF ); /* push luma */ model2_3d_push( raster, (*input++) >> 8 ); /* push distance */ model2_3d_push( raster, (*input++) >> 8 ); /* push the next point */ model2_3d_push( raster, (*input++) >> 8 ); /* x */ model2_3d_push( raster, (*input++) >> 8 ); /* y */ model2_3d_push( raster, (*input++) >> 8 ); /* z */ /* if it's a quad, output another point */ if ( attr & 1 ) { model2_3d_push( raster, (*input++) >> 8 ); /* x */ model2_3d_push( raster, (*input++) >> 8 ); /* y */ model2_3d_push( raster, (*input++) >> 8 ); /* z */ } } while( 1 ); /* we're done */ model2_3d_push( raster, 0 ); return input; } /* Command 03: Window Data */ uint32_t *model2_state::geo_window_data( geo_state *geo, uint32_t opcode, uint32_t *input ) { raster_state *raster = geo->raster; uint32_t x, y, i; /* start by pushing the opcode */ model2_3d_push( raster, opcode >> 23 ); /* we're going to move 6 coordinates to the 3d rasterizer: - starting coordinate - completion coordinate - vanishing point 0 (eye mode 0) - vanishing point 1 (eye mode 1) - vanishing point 2 (eye mode 2) - vanishing point 3 (eye mode 3) */ for( i = 0; i < 6; i++ ) { /* read in the coordinate */ y = *input++; /* convert to the 3d rasterizer format (00XXXYYY) */ x = ( y & 0x0FFF0000 ) >> 4 ; y &= 0xFFF; /* push it */ model2_3d_push( raster, x | y ); } return input; } /* Command 04: Texture Data Write */ uint32_t *model2_state::geo_texture_data( geo_state *geo, uint32_t opcode, uint32_t *input ) { raster_state *raster = geo->raster; uint32_t i, count; /* start by pushing the opcode */ model2_3d_push( raster, opcode >> 23 ); /* push the starting address/dsp id */ model2_3d_push( raster, *input++ ); /* get the count */ count = *input++; /* push the count */ model2_3d_push( raster, count ); /* loop and send the data */ for( i = 0; i < count; i++ ) model2_3d_push( raster, *input++ ); return input; } /* Command 05: Polygon Data */ uint32_t *model2_state::geo_polygon_data( geo_state *geo, uint32_t opcode, uint32_t *input ) { uint32_t address, count, i; uint32_t *p; (void)opcode; /* read in the address */ address = *input++; /* prepare the pointer */ if ( address & 0x01000000 ) { /* Fast polygon RAM */ p = &geo->polygon_ram1[address & 0x7FFF]; } else { /* Slow Polygon RAM */ p = &geo->polygon_ram0[address & 0x7FFF]; } /* read the count */ count = *input++; /* move the data */ for( i = 0; i < count; i++ ) *p++ = *input++; return input; } /* Command 06: Texture Parameters */ uint32_t *model2_state::geo_texture_parameters( geo_state *geo, uint32_t opcode, uint32_t *input ) { uint32_t index, count, i, param; (void)opcode; /* read in the index */ index = *input++; /* read in the conut */ count = *input++; for( i = 0; i < count; i++ ) { /* read in the texture parameters */ param = *input++; geo->texture_parameters[index].diffuse = (float)( param & 0xFF ); geo->texture_parameters[index].ambient = (float)( (param >> 8) & 0xFF ); geo->texture_parameters[index].specular_control = (param >> 24) & 0xFF; geo->texture_parameters[index].specular_scale = (float)( (param >> 16) & 0xFF ); /* read in the distance coefficient */ geo->coef_table[index] = u2f(*input++); index = (index + 1) & 0x1F; } return input; } /* Command 07: Geo Mode */ uint32_t *model2_state::geo_mode( geo_state *geo, uint32_t opcode, uint32_t *input ) { (void)opcode; /* read in the mode */ geo->mode = *input++; return input; } /* Command 08: ZSort Mode */ uint32_t *model2_state::geo_zsort_mode( geo_state *geo, uint32_t opcode, uint32_t *input ) { raster_state *raster = geo->raster; /* push the opcode */ model2_3d_push( raster, opcode >> 23 ); /* push the mode */ model2_3d_push( raster, (*input++) >> 8 ); return input; } /* Command 09: Focal Distance */ uint32_t *model2_state::geo_focal_distance( geo_state *geo, uint32_t opcode, uint32_t *input ) { (void)opcode; /* read the x focus value */ geo->focus.x = u2f( *input++ ); /* read the y focus value */ geo->focus.y = u2f( *input++ ); return input; } /* Command 0A: Light Source Vector Write */ uint32_t *model2_state::geo_light_source( geo_state *geo, uint32_t opcode, uint32_t *input ) { (void)opcode; /* read the x light value */ geo->light.x = u2f( *input++ ); /* read the y light value */ geo->light.y = u2f( *input++ ); /* read the z light value */ geo->light.pz = u2f( *input++ ); return input; } /* Command 0B: Transformation Matrix Write */ uint32_t *model2_state::geo_matrix_write( geo_state *geo, uint32_t opcode, uint32_t *input ) { uint32_t i; (void)opcode; /* read in the transformation matrix */ for( i = 0; i < 12; i++ ) geo->matrix[i] = u2f( *input++ ); return input; } /* Command 0C: Parallel Transfer Vector Write */ uint32_t *model2_state::geo_translate_write( geo_state *geo, uint32_t opcode, uint32_t *input ) { uint32_t i; (void)opcode; /* read in the translation vector */ for( i = 0; i < 3; i++ ) geo->matrix[i+9] = u2f( *input++ ); return input; } /* Command 0D: Geo Data Memory Push (undocumented, unsupported) */ uint32_t *model2_state::geo_data_mem_push( geo_state *geo, uint32_t opcode, uint32_t *input ) { uint32_t address, count, i; /* This command pushes data stored in the Geometry DSP's RAM to the hardware 3D rasterizer. Since we don't emulate the DSP, we don't know what the RAM contents are. Eventually, we could check for the address, and if it happens to point to a polygon ROM, we could potentially emulate it partially. No games are known to use this command yet. */ (void)opcode; /* read in the address */ address = *input++; /* read in the count */ count = *input++; logerror( "SEGA GEO: Executing unsupported geo_data_mem_push (address = %08x, count = %08x)\n", address, count ); (void)i; /* for( i = 0; i < count; i++ ) model2_3d_push( 0 ); */ return input; } /* Command 0E: Geo Test */ uint32_t *model2_state::geo_test( geo_state *geo, uint32_t opcode, uint32_t *input ) { uint32_t data, blocks, address, count, checksum, i; (void)opcode; /* fifo test */ data = 1; for( i = 0; i < 32; i++ ) { if ( *input++ != data ) { /* TODO: Set Red LED on */ logerror( "SEGA GEO: FIFO Test failed\n" ); } data <<= 1; } /* get the number of checksums we have to run */ blocks = *input++; for( i = 0; i < blocks; i++ ) { uint32_t sum_even, sum_odd, j; /* read in the address */ address = (*input++) & 0x7FFFFF; /* read in the count */ count = *input++; /* read in the checksum */ checksum = *input++; /* reset the checksum counters */ sum_even = 0; sum_odd = 0; for( j = 0; j < count; j++ ) { data = geo->polygon_rom[address++]; address &= geo->polygon_rom_mask; sum_even += data >> 16; sum_even &= 0xFFFF; sum_odd += data & 0xFFFF; sum_odd &= 0xFFFF; } sum_even += checksum >> 16; sum_even &= 0xFFFF; sum_odd += checksum & 0xFFFF; sum_odd &= 0xFFFF; if ( sum_even != 0 || sum_odd != 0 ) { /* TODO: Set Green LED on */ logerror( "SEGA GEO: Polygon ROM Test failed\n" ); } } return input; } /* Command 0F: End */ uint32_t *model2_state::geo_end( geo_state *geo, uint32_t opcode, uint32_t *input ) { raster_state *raster = geo->raster; (void)opcode; /* signal the end of this data block the rasterizer */ model2_3d_push( raster, 0xFF000000 ); /* signal end by returning nullptr */ return nullptr; } /* Command 10: Dummy */ uint32_t *model2_state::geo_dummy( geo_state *geo, uint32_t opcode, uint32_t *input ) { // uint32_t data; (void)opcode; /* do the dummy read cycle */ // data = *input++; input++; return input; } /* Command 14: Log Data Write */ uint32_t *model2_state::geo_log_data( geo_state *geo, uint32_t opcode, uint32_t *input ) { raster_state *raster = geo->raster; uint32_t i, count; /* start by pushing the opcode */ model2_3d_push( raster, opcode >> 23 ); /* push the starting address/dsp id */ model2_3d_push( raster, *input++ ); /* get the count */ count = *input++; /* push the count */ model2_3d_push( raster, count << 2 ); /* loop and send the data */ for( i = 0; i < count; i++ ) { uint32_t data = *input++; model2_3d_push( raster, data & 0xff ); model2_3d_push( raster, (data >> 8) & 0xff ); model2_3d_push( raster, (data >> 16) & 0xff ); model2_3d_push( raster, (data >> 24) & 0xff ); } return input; } /* Command 16: LOD */ uint32_t *model2_state::geo_lod( geo_state *geo, uint32_t opcode, uint32_t *input ) { (void)opcode; /* read in the LOD */ geo->lod = u2f(*input++); return input; } /* Command 1D: Code Upload (undocumented, unsupported) */ uint32_t *model2_state::geo_code_upload( geo_state *geo, uint32_t opcode, uint32_t *input ) { uint32_t count, i; /* This command uploads code to program memory and optionally runs it. Probably used for debugging. No games are known to use this command yet. */ logerror( "SEGA GEO: Uploading debug code (unimplemented)\n" ); (void)opcode; /* read in the flags */ // flags = *input++; input++; /* read in the count */ count = *input++; for( i = 0; i < count; i++ ) { uint64_t code; /* read the top part of the opcode */ code = *input++; code <<= 32; /* the bottom part comes in two pieces */ code |= *input++; code |= (*input++) << 16; } /* Bit 10 of flags indicate whether to run iummediately after upload */ /* if ( flags & 0x400 ) code_jump(); */ return input; } /* Command 1E: Code Jump (undocumented, unsupported) */ uint32_t *model2_state::geo_code_jump( geo_state *geo, uint32_t opcode, uint32_t *input ) { // uint32_t address; /* This command jumps to a specified address in program memory. Code can be uploaded with function 1D. Probably used for debugging. No games are known to use this command yet. */ logerror( "SEGA GEO: Jumping to debug code (unimplemented)\n" ); (void)opcode; // address = *input++ & 0x3FF; input++; /* code_jump( address ) */ return input; } uint32_t *model2_state::geo_process_command( geo_state *geo, uint32_t opcode, uint32_t *input, bool *end_code ) { switch( (opcode >> 23) & 0x1f ) { case 0x00: input = geo_nop( geo, opcode, input ); break; case 0x01: input = geo_object_data( geo, opcode, input ); break; case 0x02: input = geo_direct_data( geo, opcode, input ); break; case 0x03: input = geo_window_data( geo, opcode, input ); break; case 0x04: input = geo_texture_data( geo, opcode, input ); break; case 0x05: input = geo_polygon_data( geo, opcode, input ); break; case 0x06: input = geo_texture_parameters( geo, opcode, input ); break; case 0x07: input = geo_mode( geo, opcode, input ); break; case 0x08: input = geo_zsort_mode( geo, opcode, input ); break; case 0x09: input = geo_focal_distance( geo, opcode, input ); break; case 0x0A: input = geo_light_source( geo, opcode, input ); break; case 0x0B: input = geo_matrix_write( geo, opcode, input ); break; case 0x0C: input = geo_translate_write( geo, opcode, input ); break; case 0x0D: input = geo_data_mem_push( geo, opcode, input ); break; case 0x0E: input = geo_test( geo, opcode, input ); break; case 0x0F: input = geo_end( geo, opcode, input ); *end_code = true; break; case 0x10: input = geo_dummy( geo, opcode, input ); break; case 0x11: input = geo_object_data( geo, opcode, input ); break; case 0x12: input = geo_direct_data( geo, opcode, input ); break; case 0x13: input = geo_window_data( geo, opcode, input ); break; case 0x14: input = geo_log_data( geo, opcode, input ); break; case 0x15: input = geo_polygon_data( geo, opcode, input ); break; case 0x16: input = geo_lod( geo, opcode, input ); break; case 0x17: input = geo_mode( geo, opcode, input ); break; case 0x18: input = geo_zsort_mode( geo, opcode, input ); break; case 0x19: input = geo_focal_distance( geo, opcode, input ); break; case 0x1A: input = geo_light_source( geo, opcode, input ); break; case 0x1B: input = geo_matrix_write( geo, opcode, input ); break; case 0x1C: input = geo_translate_write( geo, opcode, input ); break; case 0x1D: input = geo_code_upload( geo, opcode, input ); break; case 0x1E: input = geo_code_jump( geo, opcode, input ); break; case 0x1F: input = geo_end( geo, opcode, input ); *end_code = true; break; } return input; } void model2_state::geo_parse( void ) { uint32_t address = (m_geo_read_start_address & 0x1ffff)/4; uint32_t *input = &m_bufferram[address]; uint32_t opcode; bool end_code = false; while( end_code == false && (input - m_bufferram) < 0x20000/4 ) { /* read in the opcode */ opcode = *input++; /* if it's a jump opcode, do the jump */ if ( opcode & 0x80000000 ) { // TODO: daytona with master network enabled hardlocks by trying a jump with 0xffff0080 as opcode // bad timings for geo_parse? if(opcode & 0x078000000 ) return; /* get the address */ address = (opcode & 0x1FFFF) / 4; /* update our pointer */ input = &m_bufferram[address]; /* go again */ continue; } /* process it */ input = geo_process_command( m_geo.get(), opcode, input, &end_code ); } } /***********************************************************************************************/ void model2_state::video_start() { const rectangle &visarea = m_screen->visible_area(); int width = visarea.width(); int height = visarea.height(); m_sys24_bitmap.allocate(width, height+4); m_poly = std::make_unique(*this); /* initialize the hardware rasterizer */ raster_init( memregion("textures") ); /* initialize the geometry engine */ geo_init( memregion("polygons") ); /* init various video-related pointers */ m_palram = make_unique_clear(0x4000/2); m_colorxlat = make_unique_clear(0xc000/2); m_lumaram = make_unique_clear(0x10000/2); m_fbvramA = make_unique_clear(0x80000/2); m_fbvramB = make_unique_clear(0x80000/2); // convert (supposedly) 3d sRGB color space into linear // TODO: might be slightly different algorithm (Daytona USA road/cars, VF2 character skins) for(int i=0;i<256;i++) { double raw_value; raw_value = 255.0 * pow((double)(i) / 255.0,2.2); m_gamma_table[i] = (uint8_t)raw_value; // printf("%02x: %02x %lf\n",i,m_gamma_table[i],raw_value); } save_item(NAME(m_render_test_mode)); save_item(NAME(m_render_unk)); save_item(NAME(m_render_mode)); save_pointer(NAME(m_palram), 0x4000/2); save_pointer(NAME(m_colorxlat), 0xc000/2); save_pointer(NAME(m_lumaram), 0x10000/2); save_pointer(NAME(m_gamma_table), 256); } uint32_t model2_state::screen_update_model2(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) { //logerror("--- frame ---\n"); bitmap.fill(m_palette->pen(0), cliprect); m_sys24_bitmap.fill(0, cliprect); for(int layer = 3; layer >= 0; layer--) m_tiles->draw(screen, m_sys24_bitmap, cliprect, layer<<1, 0, 0); copybitmap_trans(bitmap, m_sys24_bitmap, 0, 0, 0, 0, cliprect, 0); /* tell the rasterizer we're starting a frame */ if(m_render_test_mode == true) draw_framebuffer( bitmap, cliprect ); else { model2_3d_frame_start(); /* let the geometry engine do it's thing */ // TODO: move it from here geo_parse(); /* have the rasterizer output the frame */ model2_3d_frame_end( bitmap, cliprect ); } m_sys24_bitmap.fill(0, cliprect); for (int layer = 3; layer >= 0; layer--) m_tiles->draw(screen, m_sys24_bitmap, cliprect, (layer<<1) | 1, 0, 0); copybitmap_trans(bitmap, m_sys24_bitmap, 0, 0, 0, 0, cliprect, 0); return 0; } // called from machine/model2.cpp trilist command // TODO: fix forward declaration mess and move this function there instead void model2_state::tri_list_dump(FILE *dst) { uint32_t i; for( i = 0; i < m_raster->tri_list_index; i++ ) { fprintf( dst, "index: %d\n", i ); fprintf( dst, "v0.x = %f, v0.y = %f, v0.z = %f\n", m_raster->tri_list[i].v[0].x, m_raster->tri_list[i].v[0].y, m_raster->tri_list[i].v[0].pz ); fprintf( dst, "v1.x = %f, v1.y = %f, v1.z = %f\n", m_raster->tri_list[i].v[1].x, m_raster->tri_list[i].v[1].y, m_raster->tri_list[i].v[1].pz ); fprintf( dst, "v2.x = %f, v2.y = %f, v2.z = %f\n", m_raster->tri_list[i].v[2].x, m_raster->tri_list[i].v[2].y, m_raster->tri_list[i].v[2].pz ); fprintf( dst, "tri z: %04x\n", m_raster->tri_list[i].z ); fprintf( dst, "texheader - 0: %04x\n", m_raster->tri_list[i].texheader[0] ); fprintf( dst, "texheader - 1: %04x\n", m_raster->tri_list[i].texheader[1] ); fprintf( dst, "texheader - 2: %04x\n", m_raster->tri_list[i].texheader[2] ); fprintf( dst, "texheader - 3: %04x\n", m_raster->tri_list[i].texheader[3] ); fprintf( dst, "luma: %02x\n", m_raster->tri_list[i].luma ); fprintf( dst, "vp.sx: %04x\n", m_raster->tri_list[i].viewport[0] ); fprintf( dst, "vp.sy: %04x\n", m_raster->tri_list[i].viewport[1] ); fprintf( dst, "vp.ex: %04x\n", m_raster->tri_list[i].viewport[2] ); fprintf( dst, "vp.ey: %04x\n", m_raster->tri_list[i].viewport[3] ); fprintf( dst, "vp.swx: %04x\n", m_raster->tri_list[i].center[0] ); fprintf( dst, "vp.swy: %04x\n", m_raster->tri_list[i].center[1] ); fprintf( dst, "\n---\n\n" ); } fprintf( dst, "min_z = %04x, max_z = %04x\n", m_raster->min_z, m_raster->max_z ); fclose( dst ); }