nkeynes@100: /** nkeynes@103: * $Id: render.c,v 1.2 2006-03-13 12:39:07 nkeynes Exp $ nkeynes@100: * nkeynes@100: * PVR2 Renderer support. This is where the real work happens. nkeynes@100: * nkeynes@100: * Copyright (c) 2005 Nathan Keynes. nkeynes@100: * nkeynes@100: * This program is free software; you can redistribute it and/or modify nkeynes@100: * it under the terms of the GNU General Public License as published by nkeynes@100: * the Free Software Foundation; either version 2 of the License, or nkeynes@100: * (at your option) any later version. nkeynes@100: * nkeynes@100: * This program is distributed in the hope that it will be useful, nkeynes@100: * but WITHOUT ANY WARRANTY; without even the implied warranty of nkeynes@100: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nkeynes@100: * GNU General Public License for more details. nkeynes@100: */ nkeynes@100: nkeynes@100: #include "pvr2/pvr2.h" nkeynes@100: #include "asic.h" nkeynes@103: nkeynes@103: nkeynes@103: #define POLY_COLOUR_ARGB8888 0x00000000 nkeynes@103: #define POLY_COLOUR_ARGBFLOAT 0x00000010 nkeynes@103: nkeynes@103: static int pvr2_poly_vertexes[4] = { 3, 4, 6, 8 }; nkeynes@103: static int pvr2_poly_type[4] = { GL_TRIANGLES, GL_QUADS, GL_TRIANGLE_STRIP, GL_TRIANGLE_STRIP }; nkeynes@103: static int pvr2_poly_depthmode[8] = { GL_NEVER, GL_LESS, GL_EQUAL, GL_LEQUAL, nkeynes@103: GL_GREATER, GL_NOTEQUAL, GL_GEQUAL, nkeynes@103: GL_ALWAYS }; nkeynes@103: static int pvr2_poly_srcblend[8] = { nkeynes@103: GL_ZERO, GL_ONE, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, nkeynes@103: GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, nkeynes@103: GL_ONE_MINUS_DST_ALPHA }; nkeynes@103: static int pvr2_poly_dstblend[8] = { nkeynes@103: GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, nkeynes@103: GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, nkeynes@103: GL_ONE_MINUS_DST_ALPHA }; nkeynes@103: static int pvr2_render_colour_format[8] = { nkeynes@103: COLFMT_ARGB1555, COLFMT_RGB565, COLFMT_ARGB4444, COLFMT_ARGB1555, nkeynes@103: COLFMT_RGB888, COLFMT_ARGB8888, COLFMT_ARGB8888, COLFMT_ARGB4444 }; nkeynes@103: nkeynes@103: #define POLY_STRIP_TYPE(poly) ( pvr2_poly_type[((poly->command)>>18)&0x03] ) nkeynes@103: #define POLY_STRIP_VERTEXES(poly) ( pvr2_poly_vertexes[((poly->command)>>18)&0x03] ) nkeynes@103: #define POLY_DEPTH_MODE(poly) ( pvr2_poly_depthmode[poly->poly_cfg>>29] ) nkeynes@103: #define POLY_DEPTH_WRITE(poly) (poly->poly_cfg&0x04000000) nkeynes@103: #define POLY_TEX_WIDTH(poly) ( 1<< (((poly->poly_mode >> 3) & 0x07 ) + 3) ) nkeynes@103: #define POLY_TEX_HEIGHT(poly) ( 1<< (((poly->poly_mode) & 0x07 ) + 3) ) nkeynes@103: #define POLY_BLEND_SRC(poly) ( pvr2_poly_srcblend[(poly->poly_mode) >> 29] ) nkeynes@103: #define POLY_BLEND_DEST(poly) ( pvr2_poly_dstblend[((poly->poly_mode)>>26)&0x07] ) nkeynes@100: nkeynes@100: extern uint32_t pvr2_frame_counter; nkeynes@100: nkeynes@100: /** nkeynes@103: * Describes a rendering buffer that's actually held in GL, for when we need nkeynes@103: * to fetch the bits back to vram. nkeynes@103: */ nkeynes@103: typedef struct pvr2_render_buffer { nkeynes@103: uint32_t render_addr; /* The actual address rendered to in pvr ram */ nkeynes@103: int width, height; nkeynes@103: int colour_format; nkeynes@103: } *pvr2_render_buffer_t; nkeynes@103: nkeynes@103: struct pvr2_render_buffer front_buffer; nkeynes@103: struct pvr2_render_buffer back_buffer; nkeynes@103: nkeynes@103: struct tile_descriptor { nkeynes@103: uint32_t header[6]; nkeynes@103: struct tile_pointers { nkeynes@103: uint32_t tile_id; nkeynes@103: uint32_t opaque_ptr; nkeynes@103: uint32_t opaque_mod_ptr; nkeynes@103: uint32_t trans_ptr; nkeynes@103: uint32_t trans_mod_ptr; nkeynes@103: uint32_t punchout_ptr; nkeynes@103: } tile[0]; nkeynes@103: }; nkeynes@103: nkeynes@103: /* Textured polygon */ nkeynes@103: struct pvr2_poly { nkeynes@103: uint32_t command; nkeynes@103: uint32_t poly_cfg; /* Bitmask */ nkeynes@103: uint32_t poly_mode; /* texture/blending mask */ nkeynes@103: uint32_t texture; /* texture data */ nkeynes@103: float alpha; nkeynes@103: float red; nkeynes@103: float green; nkeynes@103: float blue; nkeynes@103: }; nkeynes@103: nkeynes@103: struct pvr2_specular_highlight { nkeynes@103: float base_alpha; nkeynes@103: float base_red; nkeynes@103: float base_green; nkeynes@103: float base_blue; nkeynes@103: float offset_alpha; nkeynes@103: float offset_red; nkeynes@103: float offset_green; nkeynes@103: float offset_blue; nkeynes@103: }; nkeynes@103: nkeynes@103: nkeynes@103: struct pvr2_vertex_basic { nkeynes@103: uint32_t command; nkeynes@103: float x, y, z; nkeynes@103: float s,t; nkeynes@103: uint32_t col; nkeynes@103: float f; nkeynes@103: }; nkeynes@103: nkeynes@103: nkeynes@103: void pvr2_render_copy_to_sh4( pvr2_render_buffer_t buffer, nkeynes@103: gboolean backBuffer ); nkeynes@103: nkeynes@103: nkeynes@103: gboolean pvr2_render_init( void ) nkeynes@103: { nkeynes@103: front_buffer.render_addr = -1; nkeynes@103: back_buffer.render_addr = -1; nkeynes@103: } nkeynes@103: nkeynes@103: /** nkeynes@103: * Display a rendered frame if one is available. nkeynes@103: * @param address An address in PVR ram (0500000 range). nkeynes@103: * @return TRUE if a frame was available to be displayed, otherwise false. nkeynes@103: */ nkeynes@103: gboolean pvr2_render_display_frame( uint32_t address ) nkeynes@103: { nkeynes@103: if( front_buffer.render_addr == address ) { nkeynes@103: /* Current front buffer is already displayed, so do nothing nkeynes@103: * and tell the caller that all is well. nkeynes@103: */ nkeynes@103: return TRUE; nkeynes@103: } nkeynes@103: if( back_buffer.render_addr == address ) { nkeynes@103: /* The more useful case - back buffer is to be displayed. Swap nkeynes@103: * the buffers nkeynes@103: */ nkeynes@103: video_driver->display_back_buffer(); nkeynes@103: front_buffer = back_buffer; nkeynes@103: back_buffer.render_addr = -1; nkeynes@103: return TRUE; nkeynes@103: } nkeynes@103: return FALSE; nkeynes@103: } nkeynes@103: nkeynes@103: /** nkeynes@103: * Prepare the OpenGL context to receive instructions for a new frame. nkeynes@103: */ nkeynes@103: static void pvr2_render_prepare_context( sh4addr_t render_addr, nkeynes@103: uint32_t width, uint32_t height, nkeynes@103: uint32_t colour_format, nkeynes@103: gboolean texture_target ) nkeynes@103: { nkeynes@103: /* Select and initialize the render context */ nkeynes@103: video_driver->set_render_format( width, height, colour_format, texture_target ); nkeynes@103: nkeynes@103: if( back_buffer.render_addr != -1 && nkeynes@103: back_buffer.render_addr != render_addr ) { nkeynes@103: /* There's a current back buffer, and we're rendering somewhere else - nkeynes@103: * flush the back buffer back to vram and start a new back buffer nkeynes@103: */ nkeynes@103: pvr2_render_copy_to_sh4( &back_buffer, TRUE ); nkeynes@103: } nkeynes@103: nkeynes@103: if( front_buffer.render_addr == render_addr ) { nkeynes@103: /* In case we've been asked to render to the current front buffer - nkeynes@103: * invalidate the front buffer and render to the back buffer, ensuring nkeynes@103: * we swap at the next frame display. nkeynes@103: */ nkeynes@103: front_buffer.render_addr = -1; nkeynes@103: } nkeynes@103: back_buffer.render_addr = render_addr; nkeynes@103: back_buffer.width = width; nkeynes@103: back_buffer.height = height; nkeynes@103: back_buffer.colour_format = colour_format; nkeynes@103: nkeynes@103: /* Setup the display model */ nkeynes@103: glDrawBuffer(GL_BACK); nkeynes@103: glShadeModel(GL_SMOOTH); nkeynes@103: glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); nkeynes@103: glViewport( 0, 0, width, height ); nkeynes@103: glMatrixMode(GL_PROJECTION); nkeynes@103: glLoadIdentity(); nkeynes@103: glOrtho( 0, width, height, 0, 0, 65535 ); nkeynes@103: glMatrixMode(GL_MODELVIEW); nkeynes@103: glLoadIdentity(); nkeynes@103: nkeynes@103: /* Clear out the buffers */ nkeynes@103: glClearColor(0.0f, 0.0f, 0.0f, 0.0f); nkeynes@103: glClearDepth(1.0f); nkeynes@103: glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); nkeynes@103: } nkeynes@103: nkeynes@103: static void pvr2_render_display_list( uint32_t *display_list, uint32_t length ) nkeynes@103: { nkeynes@103: uint32_t *cmd_ptr = display_list; nkeynes@103: int expect_vertexes = 0; nkeynes@103: gboolean textured = FALSE; nkeynes@103: struct pvr2_poly *poly; nkeynes@103: while( cmd_ptr < display_list+length ) { nkeynes@103: switch( *cmd_ptr >> 24 ) { nkeynes@103: case PVR2_CMD_POLY_OPAQUE: nkeynes@103: poly = (struct pvr2_poly *)cmd_ptr; nkeynes@103: nkeynes@103: if( poly->command & PVR2_POLY_TEXTURED ) { nkeynes@103: uint32_t addr = PVR2_TEX_ADDR(poly->texture); nkeynes@103: int width = POLY_TEX_WIDTH(poly); nkeynes@103: int height = POLY_TEX_HEIGHT(poly); nkeynes@103: texcache_get_texture( addr, width, height, poly->texture ); nkeynes@103: textured = TRUE; nkeynes@103: glEnable( GL_TEXTURE_2D ); nkeynes@103: } else { nkeynes@103: textured = FALSE; nkeynes@103: glDisable( GL_TEXTURE_2D ); nkeynes@103: } nkeynes@103: glBlendFunc( POLY_BLEND_SRC(poly), POLY_BLEND_DEST(poly) ); nkeynes@103: if( poly->command & PVR2_POLY_SPECULAR ) { nkeynes@103: /* Second block expected */ nkeynes@103: } nkeynes@103: if( POLY_DEPTH_WRITE(poly) ) { nkeynes@103: glEnable( GL_DEPTH_TEST ); nkeynes@103: glDepthFunc( POLY_DEPTH_MODE(poly) ); nkeynes@103: } else { nkeynes@103: glDisable( GL_DEPTH_TEST ); nkeynes@103: } nkeynes@103: nkeynes@103: expect_vertexes = POLY_STRIP_VERTEXES( poly ); nkeynes@103: if( expect_vertexes == 3 ) nkeynes@103: glBegin( GL_TRIANGLES ); nkeynes@103: else if( expect_vertexes == 4 ) nkeynes@103: glBegin( GL_QUADS ); nkeynes@103: else nkeynes@103: glBegin( GL_TRIANGLE_STRIP ); nkeynes@103: break; nkeynes@103: case PVR2_CMD_VERTEX_LAST: nkeynes@103: case PVR2_CMD_VERTEX: nkeynes@103: if( expect_vertexes == 0 ) { nkeynes@103: ERROR( "Unexpected vertex!" ); nkeynes@103: return; nkeynes@103: } nkeynes@103: expect_vertexes--; nkeynes@103: struct pvr2_vertex_basic *vertex = (struct pvr2_vertex_basic *)cmd_ptr; nkeynes@103: if( textured ) { nkeynes@103: glTexCoord2f( vertex->s, vertex->t ); nkeynes@103: } nkeynes@103: glVertex3f( vertex->x, vertex->y, vertex->z ); nkeynes@103: nkeynes@103: if( expect_vertexes == 0 ) nkeynes@103: glEnd(); nkeynes@103: break; nkeynes@103: } nkeynes@103: cmd_ptr += 8; /* Next record */ nkeynes@103: } nkeynes@103: } nkeynes@103: nkeynes@103: /** nkeynes@103: * Render a complete scene into the OpenGL back buffer. nkeynes@103: * Note: this will probably need to be broken up eventually once timings are nkeynes@100: * determined. nkeynes@100: */ nkeynes@103: void pvr2_render_scene( ) nkeynes@100: { nkeynes@103: struct tile_descriptor *tile_desc = nkeynes@103: (struct tile_descriptor *)mem_get_region(PVR2_RAM_BASE + MMIO_READ( PVR2, TILEBASE )); nkeynes@100: nkeynes@103: uint32_t render_addr = MMIO_READ( PVR2, RENDADDR1 ); nkeynes@103: gboolean render_to_tex; nkeynes@103: if( render_addr & 0x01000000 ) { nkeynes@103: render_addr = (render_addr & 0x00FFFFFF) + PVR2_RAM_BASE_INT; nkeynes@103: /* Heuristic - if we're rendering to the interlaced region we're nkeynes@103: * probably creating a texture rather than rendering actual output. nkeynes@103: * We can optimise for this case a little nkeynes@103: */ nkeynes@103: render_to_tex = TRUE; nkeynes@103: } else { nkeynes@103: render_addr = (render_addr & 0x00FFFFFF) + PVR2_RAM_BASE; nkeynes@103: render_to_tex = FALSE; nkeynes@103: } nkeynes@103: uint32_t render_mode = MMIO_READ( PVR2, RENDMODE ); nkeynes@103: int width = 640; /* FIXME - get this from the tile buffer */ nkeynes@103: int height = 480; nkeynes@103: int colour_format = pvr2_render_colour_format[render_mode&0x07]; nkeynes@103: pvr2_render_prepare_context( render_addr, width, height, colour_format, nkeynes@103: render_to_tex ); nkeynes@103: nkeynes@103: uint32_t *display_list = nkeynes@103: (uint32_t *)mem_get_region(PVR2_RAM_BASE + MMIO_READ( PVR2, OBJBASE )); nkeynes@103: uint32_t display_length = *display_list++; nkeynes@103: nkeynes@103: int clip_x = MMIO_READ( PVR2, HCLIP ) & 0x03FF; nkeynes@103: int clip_y = MMIO_READ( PVR2, VCLIP ) & 0x03FF; nkeynes@103: int clip_width = ((MMIO_READ( PVR2, HCLIP ) >> 16) & 0x03FF) - clip_x + 1; nkeynes@103: int clip_height= ((MMIO_READ( PVR2, VCLIP ) >> 16) & 0x03FF) - clip_y + 1; nkeynes@103: nkeynes@103: if( clip_x == 0 && clip_y == 0 && clip_width == width && clip_height == height ) { nkeynes@103: glDisable( GL_SCISSOR_TEST ); nkeynes@103: } else { nkeynes@103: glEnable( GL_SCISSOR_TEST ); nkeynes@103: glScissor( clip_x, clip_y, clip_width, clip_height ); nkeynes@103: } nkeynes@103: nkeynes@103: /* Fog setup goes here */ nkeynes@103: nkeynes@103: /* Render the display list */ nkeynes@103: pvr2_render_display_list( display_list, display_length ); nkeynes@103: nkeynes@103: /* Post-render cleanup and update */ nkeynes@103: nkeynes@103: nkeynes@103: /* Generate end of render event */ nkeynes@100: asic_event( EVENT_PVR_RENDER_DONE ); nkeynes@100: DEBUG( "Rendered frame %d", pvr2_frame_counter ); nkeynes@100: } nkeynes@103: nkeynes@103: nkeynes@103: /** nkeynes@103: * Flush the indicated render buffer back to PVR. Caller is responsible for nkeynes@103: * tracking whether there is actually anything in the buffer. nkeynes@103: * nkeynes@103: * @param buffer A render buffer indicating the address to store to, and the nkeynes@103: * format the data needs to be in. nkeynes@103: * @param backBuffer TRUE to flush the back buffer, FALSE for nkeynes@103: * the front buffer. nkeynes@103: */ nkeynes@103: void pvr2_render_copy_to_sh4( pvr2_render_buffer_t buffer, nkeynes@103: gboolean backBuffer ) nkeynes@103: { nkeynes@103: if( buffer->render_addr == -1 ) nkeynes@103: return; nkeynes@103: GLenum type, format = GL_RGBA; nkeynes@103: int size = buffer->width * buffer->height; nkeynes@103: nkeynes@103: switch( buffer->colour_format ) { nkeynes@103: case COLFMT_RGB565: nkeynes@103: type = GL_UNSIGNED_SHORT_5_6_5; nkeynes@103: format = GL_RGB; nkeynes@103: size <<= 1; nkeynes@103: break; nkeynes@103: case COLFMT_RGB888: nkeynes@103: type = GL_UNSIGNED_INT; nkeynes@103: format = GL_RGB; nkeynes@103: size = (size<<1)+size; nkeynes@103: break; nkeynes@103: case COLFMT_ARGB1555: nkeynes@103: type = GL_UNSIGNED_SHORT_5_5_5_1; nkeynes@103: size <<= 1; nkeynes@103: break; nkeynes@103: case COLFMT_ARGB4444: nkeynes@103: type = GL_UNSIGNED_SHORT_4_4_4_4; nkeynes@103: size <<= 1; nkeynes@103: break; nkeynes@103: case COLFMT_ARGB8888: nkeynes@103: type = GL_UNSIGNED_INT_8_8_8_8; nkeynes@103: size <<= 2; nkeynes@103: break; nkeynes@103: } nkeynes@103: nkeynes@103: if( backBuffer ) { nkeynes@103: glFinish(); nkeynes@103: glReadBuffer( GL_BACK ); nkeynes@103: } else { nkeynes@103: glReadBuffer( GL_FRONT ); nkeynes@103: } nkeynes@103: nkeynes@103: if( buffer->render_addr & 0xFF000000 == 0x04000000 ) { nkeynes@103: /* Interlaced buffer. Go the double copy... :( */ nkeynes@103: char target[size]; nkeynes@103: glReadPixels( 0, 0, buffer->width, buffer->height, format, type, target ); nkeynes@103: pvr2_vram64_write( buffer->render_addr, target, size ); nkeynes@103: } else { nkeynes@103: /* Regular buffer - go direct */ nkeynes@103: char *target = mem_get_region( buffer->render_addr ); nkeynes@103: glReadPixels( 0, 0, buffer->width, buffer->height, format, type, target ); nkeynes@103: } nkeynes@103: } nkeynes@103: nkeynes@103: nkeynes@103: /** nkeynes@103: * Copy data from PVR ram into the GL render buffer. nkeynes@103: * nkeynes@103: * @param buffer A render buffer indicating the address to read from, and the nkeynes@103: * format the data is in. nkeynes@103: * @param backBuffer TRUE to write the back buffer, FALSE for nkeynes@103: * the front buffer. nkeynes@103: */ nkeynes@103: void pvr2_render_copy_from_sh4( pvr2_render_buffer_t buffer, nkeynes@103: gboolean backBuffer ) nkeynes@103: { nkeynes@103: if( buffer->render_addr == -1 ) nkeynes@103: return; nkeynes@103: GLenum type, format = GL_RGBA; nkeynes@103: int size = buffer->width * buffer->height; nkeynes@103: nkeynes@103: switch( buffer->colour_format ) { nkeynes@103: case COLFMT_RGB565: nkeynes@103: type = GL_UNSIGNED_SHORT_5_6_5; nkeynes@103: format = GL_RGB; nkeynes@103: size <<= 1; nkeynes@103: break; nkeynes@103: case COLFMT_RGB888: nkeynes@103: type = GL_UNSIGNED_INT; nkeynes@103: format = GL_RGB; nkeynes@103: size = (size<<1)+size; nkeynes@103: break; nkeynes@103: case COLFMT_ARGB1555: nkeynes@103: type = GL_UNSIGNED_SHORT_5_5_5_1; nkeynes@103: size <<= 1; nkeynes@103: break; nkeynes@103: case COLFMT_ARGB4444: nkeynes@103: type = GL_UNSIGNED_SHORT_4_4_4_4; nkeynes@103: size <<= 1; nkeynes@103: break; nkeynes@103: case COLFMT_ARGB8888: nkeynes@103: type = GL_UNSIGNED_INT_8_8_8_8; nkeynes@103: size <<= 2; nkeynes@103: break; nkeynes@103: } nkeynes@103: nkeynes@103: if( backBuffer ) { nkeynes@103: glDrawBuffer( GL_BACK ); nkeynes@103: } else { nkeynes@103: glDrawBuffer( GL_FRONT ); nkeynes@103: } nkeynes@103: nkeynes@103: glRasterPos2i( 0, 0 ); nkeynes@103: if( buffer->render_addr & 0xFF000000 == 0x04000000 ) { nkeynes@103: /* Interlaced buffer. Go the double copy... :( */ nkeynes@103: char target[size]; nkeynes@103: pvr2_vram64_read( target, buffer->render_addr, size ); nkeynes@103: glDrawPixels( buffer->width, buffer->height, nkeynes@103: format, type, target ); nkeynes@103: } else { nkeynes@103: /* Regular buffer - go direct */ nkeynes@103: char *target = mem_get_region( buffer->render_addr ); nkeynes@103: glDrawPixels( buffer->width, buffer->height, nkeynes@103: format, type, target ); nkeynes@103: } nkeynes@103: }