2008-09-16 07:42:21 +02:00
/****************************************************************************
2008-09-17 04:27:55 +02:00
* Visual Boy Advance GX
*
* Tantric September 2008
* softdev 2007
*
* video . cpp
*
2009-04-08 09:08:12 +02:00
* Video routines
2008-09-17 04:27:55 +02:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2009-04-08 09:08:12 +02:00
2008-09-16 07:42:21 +02:00
# include <gccore.h>
2009-04-08 09:08:12 +02:00
# include <ogcsys.h>
2008-09-16 07:42:21 +02:00
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
2009-04-08 09:08:12 +02:00
# include <unistd.h>
2008-10-10 07:28:16 +02:00
# include "vba.h"
2009-04-08 09:08:12 +02:00
# include "menu.h"
# include "input.h"
2008-09-16 07:42:21 +02:00
2009-03-04 08:01:04 +01:00
s32 CursorX , CursorY ;
bool CursorVisible ;
bool CursorValid ;
bool TiltScreen = false ;
float TiltAngle = 0 ;
2008-10-10 07:28:16 +02:00
u32 FrameTimer = 0 ;
2008-09-26 10:10:36 +02:00
2008-09-16 07:42:21 +02:00
/*** External 2D Video ***/
/*** 2D Video Globals ***/
2008-12-30 08:32:42 +01:00
static GXRModeObj * vmode = NULL ; // Graphics Mode Object
2009-09-16 09:55:07 +02:00
unsigned int * xfb [ 2 ] = { NULL , NULL } ; // Framebuffers
2008-09-26 03:01:08 +02:00
int whichfb = 0 ; // Frame buffer toggle
2008-09-16 07:42:21 +02:00
2009-04-08 09:08:12 +02:00
static Mtx GXmodelView2D ;
u8 * gameScreenTex = NULL ; // a GX texture screen capture of the game
u8 * gameScreenTex2 = NULL ; // a GX texture screen capture of the game (copy)
2009-09-16 09:55:07 +02:00
int screenheight = 480 ;
int screenwidth = 640 ;
2008-09-16 07:42:21 +02:00
/*** 3D GX ***/
# define DEFAULT_FIFO_SIZE ( 256 * 1024 )
2008-09-26 10:10:36 +02:00
static u8 gp_fifo [ DEFAULT_FIFO_SIZE ] ATTRIBUTE_ALIGN ( 32 ) ;
2008-12-30 08:32:42 +01:00
static unsigned int copynow = GX_FALSE ;
2008-09-16 07:42:21 +02:00
/*** Texture memory ***/
2008-09-26 03:01:08 +02:00
static u8 * texturemem = NULL ;
2008-09-16 07:42:21 +02:00
static int texturesize ;
2008-12-30 08:32:42 +01:00
static GXTexObj texobj ;
2008-09-16 07:42:21 +02:00
static Mtx view ;
2008-10-25 09:26:02 +02:00
static int vwidth , vheight ;
2008-12-30 08:32:42 +01:00
static int updateScaling ;
2008-10-25 09:26:02 +02:00
bool progressive = false ;
2008-09-16 07:42:21 +02:00
/* New texture based scaler */
typedef struct tagcamera
{
2009-07-10 23:30:13 +02:00
guVector pos ;
guVector up ;
guVector view ;
2008-09-16 07:42:21 +02:00
}
camera ;
/*** Square Matrix
This structure controls the size of the image on the screen .
* * */
static s16 square [ ] ATTRIBUTE_ALIGN ( 32 ) = {
2008-11-05 08:21:50 +01:00
/*
* X , Y , Z
* Values set are for roughly 4 : 3 aspect
*/
- 200 , 200 , 0 , // 0
200 , 200 , 0 , // 1
200 , - 200 , 0 , // 2
- 200 , - 200 , 0 // 3
2008-09-16 07:42:21 +02:00
} ;
static camera cam = { { 0.0F , 0.0F , 0.0F } ,
{ 0.0F , 0.5F , 0.0F } ,
{ 0.0F , 0.0F , - 0.5F }
} ;
2008-10-10 07:28:16 +02:00
/****************************************************************************
* VideoThreading
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# define TSTACK 16384
2008-12-30 01:10:12 +01:00
static lwp_t vbthread ;
2008-10-10 07:28:16 +02:00
static unsigned char vbstack [ TSTACK ] ;
/****************************************************************************
* vbgetback
*
* This callback enables the emulator to keep running while waiting for a
* vertical blank .
*
* Putting LWP to good use : )
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static void *
vbgetback ( void * arg )
{
while ( 1 )
{
VIDEO_WaitVSync ( ) ; /**< Wait for video vertical blank */
LWP_SuspendThread ( vbthread ) ;
}
return NULL ;
}
/****************************************************************************
* copy_to_xfb
*
* Stock code to copy the GX buffer to the current display mode .
* Also increments the frameticker , as it ' s called for each vb .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2009-05-06 08:36:59 +02:00
static inline void
2008-10-10 07:28:16 +02:00
copy_to_xfb ( u32 arg )
{
if ( copynow = = GX_TRUE )
{
GX_CopyDisp ( xfb [ whichfb ] , GX_TRUE ) ;
GX_Flush ( ) ;
copynow = GX_FALSE ;
}
FrameTimer + + ;
}
/****************************************************************************
* Scaler Support Functions
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2009-05-06 08:36:59 +02:00
static inline void draw_init ( void )
2008-10-10 07:28:16 +02:00
{
2008-10-11 00:33:57 +02:00
GX_ClearVtxDesc ( ) ;
GX_SetVtxDesc ( GX_VA_POS , GX_INDEX8 ) ;
GX_SetVtxDesc ( GX_VA_CLR0 , GX_INDEX8 ) ;
GX_SetVtxDesc ( GX_VA_TEX0 , GX_DIRECT ) ;
2008-10-10 07:28:16 +02:00
2008-10-11 00:33:57 +02:00
GX_SetVtxAttrFmt ( GX_VTXFMT0 , GX_VA_POS , GX_POS_XYZ , GX_S16 , 0 ) ;
GX_SetVtxAttrFmt ( GX_VTXFMT0 , GX_VA_CLR0 , GX_CLR_RGBA , GX_RGBA8 , 0 ) ;
GX_SetVtxAttrFmt ( GX_VTXFMT0 , GX_VA_TEX0 , GX_TEX_ST , GX_F32 , 0 ) ;
2008-10-10 07:28:16 +02:00
2008-10-11 00:33:57 +02:00
GX_SetArray ( GX_VA_POS , square , 3 * sizeof ( s16 ) ) ;
2008-10-10 07:28:16 +02:00
2008-10-11 00:33:57 +02:00
GX_SetNumTexGens ( 1 ) ;
2009-04-08 09:08:12 +02:00
GX_SetNumChans ( 0 ) ;
2008-10-11 00:33:57 +02:00
GX_SetTexCoordGen ( GX_TEXCOORD0 , GX_TG_MTX2x4 , GX_TG_TEX0 , GX_IDENTITY ) ;
2008-10-10 07:28:16 +02:00
2009-04-08 09:08:12 +02:00
GX_SetTevOp ( GX_TEVSTAGE0 , GX_REPLACE ) ;
GX_SetTevOrder ( GX_TEVSTAGE0 , GX_TEXCOORD0 , GX_TEXMAP0 , GX_COLORNULL ) ;
memset ( & view , 0 , sizeof ( Mtx ) ) ;
guLookAt ( view , & cam . pos , & cam . up , & cam . view ) ;
GX_LoadPosMtxImm ( view , GX_PNMTX0 ) ;
2008-10-28 07:51:58 +01:00
GX_InvVtxCache ( ) ; // update vertex cache
2008-11-05 08:21:50 +01:00
GX_InitTexObj ( & texobj , texturemem , vwidth , vheight , GX_TF_RGB565 ,
GX_CLAMP , GX_CLAMP , GX_FALSE ) ;
if ( ! ( GCSettings . render & 1 ) )
GX_InitTexObjLOD ( & texobj , GX_NEAR , GX_NEAR_MIP_NEAR , 2.5 , 9.0 , 0.0 , GX_FALSE , GX_FALSE , GX_ANISO_1 ) ; // original/unfiltered video mode: force texture filtering OFF
2008-10-10 07:28:16 +02:00
}
2009-05-06 08:36:59 +02:00
static inline void draw_vert ( u8 pos , u8 c , f32 s , f32 t )
2008-10-10 07:28:16 +02:00
{
GX_Position1x8 ( pos ) ;
GX_Color1x8 ( c ) ;
GX_TexCoord2f32 ( s , t ) ;
}
2009-05-06 08:36:59 +02:00
static inline void draw_square ( Mtx v )
2008-10-10 07:28:16 +02:00
{
Mtx m ; // model matrix.
Mtx mv ; // modelview matrix.
2009-03-04 08:01:04 +01:00
if ( TiltScreen )
{
guMtxRotDeg ( m , ' z ' , - TiltAngle ) ;
guMtxScaleApply ( m , m , 0.8 , 0.8 , 1 ) ;
}
else
{
guMtxIdentity ( m ) ;
}
2008-10-10 07:28:16 +02:00
guMtxTransApply ( m , m , 0 , 0 , - 100 ) ;
guMtxConcat ( v , m , mv ) ;
GX_LoadPosMtxImm ( mv , GX_PNMTX0 ) ;
GX_Begin ( GX_QUADS , GX_VTXFMT0 , 4 ) ;
draw_vert ( 0 , 0 , 0.0 , 0.0 ) ;
draw_vert ( 1 , 0 , 1.0 , 0.0 ) ;
draw_vert ( 2 , 0 , 1.0 , 1.0 ) ;
draw_vert ( 3 , 0 , 0.0 , 1.0 ) ;
GX_End ( ) ;
}
2009-04-08 09:08:12 +02:00
# ifdef HW_RVL
2009-05-06 08:36:59 +02:00
static inline void draw_cursor ( Mtx v )
2009-03-04 08:01:04 +01:00
{
2009-04-03 01:41:09 +02:00
if ( ! CursorVisible | | ! CursorValid )
2009-03-04 08:01:04 +01:00
return ;
2009-04-08 09:08:12 +02:00
GX_InitTexObj ( & texobj , pointer [ 0 ] - > GetImage ( ) , 96 , 96 , GX_TF_RGBA8 , GX_CLAMP , GX_CLAMP , GX_FALSE ) ;
2009-04-03 01:41:09 +02:00
GX_LoadTexObj ( & texobj , GX_TEXMAP0 ) ;
GX_SetBlendMode ( GX_BM_BLEND , GX_BL_DSTALPHA , GX_BL_INVSRCALPHA , GX_LO_CLEAR ) ;
GX_SetTevOp ( GX_TEVSTAGE0 , GX_REPLACE ) ;
GX_SetVtxDesc ( GX_VA_TEX0 , GX_DIRECT ) ;
2009-03-04 08:01:04 +01:00
Mtx m ; // model matrix.
guMtxIdentity ( m ) ;
2009-04-03 01:41:09 +02:00
guMtxScaleApply ( m , m , 0.070f , 0.10f , 0.06f ) ;
// I needed to hack this position
guMtxTransApply ( m , m , CursorX - 315 , 220 - CursorY , - 100 ) ;
2009-03-04 08:01:04 +01:00
GX_LoadPosMtxImm ( m , GX_PNMTX0 ) ;
GX_Begin ( GX_QUADS , GX_VTXFMT0 , 4 ) ;
2009-04-06 09:41:45 +02:00
2009-04-03 01:41:09 +02:00
// I needed to hack the texture coords to cut out the opaque bit around the outside
draw_vert ( 0 , 0 , 0.4 , 0.45 ) ;
draw_vert ( 1 , 0 , 0.76 , 0.45 ) ;
draw_vert ( 2 , 0 , 0.76 , 0.97 ) ;
draw_vert ( 3 , 0 , 0.4 , 0.97 ) ;
2009-04-06 09:41:45 +02:00
2009-03-04 08:01:04 +01:00
GX_End ( ) ;
2009-04-03 01:41:09 +02:00
GX_ClearVtxDesc ( ) ;
GX_SetVtxDesc ( GX_VA_POS , GX_INDEX8 ) ;
GX_SetVtxDesc ( GX_VA_CLR0 , GX_INDEX8 ) ;
GX_SetVtxDesc ( GX_VA_TEX0 , GX_DIRECT ) ;
GX_SetVtxAttrFmt ( GX_VTXFMT0 , GX_VA_POS , GX_POS_XYZ , GX_S16 , 0 ) ;
GX_SetVtxAttrFmt ( GX_VTXFMT0 , GX_VA_CLR0 , GX_CLR_RGBA , GX_RGBA8 , 0 ) ;
GX_SetVtxAttrFmt ( GX_VTXFMT0 , GX_VA_TEX0 , GX_TEX_ST , GX_F32 , 0 ) ;
GX_SetArray ( GX_VA_POS , square , 3 * sizeof ( s16 ) ) ;
GX_SetNumTexGens ( 1 ) ;
GX_SetTexCoordGen ( GX_TEXCOORD0 , GX_TG_MTX2x4 , GX_TG_TEX0 , GX_IDENTITY ) ;
GX_InvVtxCache ( ) ; // update vertex cache
GX_InitTexObj ( & texobj , texturemem , vwidth , vheight , GX_TF_RGB565 ,
GX_CLAMP , GX_CLAMP , GX_FALSE ) ;
if ( ! ( GCSettings . render & 1 ) )
GX_InitTexObjLOD ( & texobj , GX_NEAR , GX_NEAR_MIP_NEAR , 2.5 , 9.0 , 0.0 , GX_FALSE , GX_FALSE , GX_ANISO_1 ) ; // original/unfiltered video mode: force texture filtering OFF
2009-03-04 08:01:04 +01:00
}
2009-04-08 09:08:12 +02:00
# endif
2009-03-04 08:01:04 +01:00
2009-04-08 09:08:12 +02:00
/****************************************************************************
* StopGX
*
* Stops GX ( when exiting )
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void StopGX ( )
{
GX_AbortFrame ( ) ;
GX_Flush ( ) ;
2009-03-04 08:01:04 +01:00
2009-04-08 09:08:12 +02:00
VIDEO_SetBlack ( TRUE ) ;
VIDEO_Flush ( ) ;
2008-09-16 07:42:21 +02:00
}
2008-09-26 10:10:36 +02:00
/****************************************************************************
2009-09-16 09:55:07 +02:00
* FindVideoMode
2009-04-08 09:08:12 +02:00
*
2009-04-22 20:24:05 +02:00
* Finds the optimal video mode , or uses the user - specified one
* Also configures original video modes
2009-04-08 09:08:12 +02:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2009-09-16 09:55:07 +02:00
static GXRModeObj * FindVideoMode ( )
2008-09-26 10:10:36 +02:00
{
2009-09-16 09:55:07 +02:00
GXRModeObj * mode ;
2009-04-22 20:24:05 +02:00
// choose the desired video mode
switch ( GCSettings . videomode )
{
case 1 : // NTSC (480i)
2009-09-16 09:55:07 +02:00
mode = & TVNtsc480IntDf ;
2009-04-22 20:24:05 +02:00
break ;
case 2 : // Progressive (480p)
2009-09-16 09:55:07 +02:00
mode = & TVNtsc480Prog ;
2009-04-22 20:24:05 +02:00
break ;
case 3 : // PAL (50Hz)
2009-09-16 09:55:07 +02:00
mode = & TVPal528IntDf ;
2009-04-22 20:24:05 +02:00
break ;
case 4 : // PAL (60Hz)
2009-09-16 09:55:07 +02:00
mode = & TVEurgb60Hz480IntDf ;
2009-04-22 20:24:05 +02:00
break ;
default :
2009-09-16 09:55:07 +02:00
mode = VIDEO_GetPreferredMode ( NULL ) ;
2009-04-22 20:24:05 +02:00
# ifdef HW_DOL
/* we have component cables, but the preferred mode is interlaced
* why don ' t we switch into progressive ?
* on the Wii , the user can do this themselves on their Wii Settings */
if ( VIDEO_HaveComponentCable ( ) )
2009-09-16 09:55:07 +02:00
mode = & TVNtsc480Prog ;
2009-04-22 20:24:05 +02:00
# endif
break ;
}
2008-10-11 00:33:57 +02:00
2009-04-22 20:24:05 +02:00
// configure original modes (not implemented)
2009-09-16 09:55:07 +02:00
switch ( mode - > viTVMode > > 2 )
2008-10-25 09:26:02 +02:00
{
2009-01-02 23:26:58 +01:00
case VI_PAL :
// 576 lines (PAL 50hz)
2008-10-25 09:26:02 +02:00
break ;
case VI_NTSC :
// 480 lines (NTSC 60hz)
break ;
default :
// 480 lines (PAL 60Hz)
break ;
}
// check for progressive scan
2009-09-16 09:55:07 +02:00
if ( mode - > viTVMode = = VI_TVMODE_NTSC_PROG )
2008-10-25 09:26:02 +02:00
progressive = true ;
2009-04-22 20:24:05 +02:00
else
progressive = false ;
2008-10-25 09:26:02 +02:00
2009-04-08 09:08:12 +02:00
# ifdef HW_RVL
2008-11-05 08:21:50 +01:00
// widescreen fix
2009-04-08 09:08:12 +02:00
if ( CONF_GetAspectRatio ( ) = = CONF_ASPECT_16_9 )
2008-11-05 18:29:49 +01:00
{
2009-09-16 09:55:07 +02:00
mode - > viWidth = VI_MAX_WIDTH_PAL ;
2008-11-05 18:29:49 +01:00
}
2009-04-08 09:08:12 +02:00
# endif
2008-11-05 08:21:50 +01:00
2009-09-16 09:55:07 +02:00
return mode ;
2009-04-22 20:24:05 +02:00
}
/****************************************************************************
2009-09-16 09:55:07 +02:00
* SetupVideoMode
2009-04-22 20:24:05 +02:00
*
2009-09-16 09:55:07 +02:00
* Sets up the given video mode
2009-04-22 20:24:05 +02:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2009-09-16 09:55:07 +02:00
static void SetupVideoMode ( GXRModeObj * mode )
2009-04-22 20:24:05 +02:00
{
2009-09-16 09:55:07 +02:00
if ( vmode = = mode )
return ;
VIDEO_SetPostRetraceCallback ( NULL ) ;
copynow = GX_FALSE ;
VIDEO_Configure ( mode ) ;
VIDEO_Flush ( ) ;
2008-09-26 10:10:36 +02:00
2009-04-08 09:08:12 +02:00
// Allocate the video buffers
2009-09-16 09:55:07 +02:00
if ( xfb [ 0 ] ) free ( MEM_K1_TO_K0 ( xfb [ 0 ] ) ) ;
if ( xfb [ 1 ] ) free ( MEM_K1_TO_K0 ( xfb [ 1 ] ) ) ;
xfb [ 0 ] = ( u32 * ) MEM_K0_TO_K1 ( SYS_AllocateFramebuffer ( mode ) ) ;
xfb [ 1 ] = ( u32 * ) MEM_K0_TO_K1 ( SYS_AllocateFramebuffer ( mode ) ) ;
2008-10-28 07:51:58 +01:00
2008-10-10 07:28:16 +02:00
// Clear framebuffers etc.
2009-09-16 09:55:07 +02:00
VIDEO_ClearFrameBuffer ( mode , xfb [ 0 ] , COLOR_BLACK ) ;
VIDEO_ClearFrameBuffer ( mode , xfb [ 1 ] , COLOR_BLACK ) ;
2008-10-10 07:28:16 +02:00
VIDEO_SetNextFramebuffer ( xfb [ 0 ] ) ;
2008-09-26 10:10:36 +02:00
2009-04-08 09:08:12 +02:00
VIDEO_SetBlack ( FALSE ) ;
2009-09-16 09:55:07 +02:00
VIDEO_Flush ( ) ;
VIDEO_WaitVSync ( ) ;
if ( mode - > viTVMode & VI_NON_INTERLACE )
2008-10-10 07:28:16 +02:00
VIDEO_WaitVSync ( ) ;
2009-09-16 09:55:07 +02:00
else
while ( VIDEO_GetNextField ( ) )
VIDEO_WaitVSync ( ) ;
VIDEO_SetPostRetraceCallback ( ( VIRetraceCallback ) copy_to_xfb ) ;
vmode = mode ;
}
2008-09-16 07:42:21 +02:00
2009-09-16 09:55:07 +02:00
/****************************************************************************
* InitializeVideo
*
* This function MUST be called at startup .
* - also sets up menu video mode
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2008-09-16 07:42:21 +02:00
2009-09-16 09:55:07 +02:00
void
InitializeVideo ( )
{
2009-10-13 07:05:16 +02:00
VIDEO_Init ( ) ;
2009-09-16 09:55:07 +02:00
GXRModeObj * rmode = FindVideoMode ( ) ;
SetupVideoMode ( rmode ) ;
2009-10-13 07:05:16 +02:00
LWP_CreateThread ( & vbthread , vbgetback , NULL , vbstack , TSTACK , 68 ) ;
// Initialise GX
GXColor background = { 0 , 0 , 0 , 0xff } ;
memset ( gp_fifo , 0 , DEFAULT_FIFO_SIZE ) ;
GX_Init ( & gp_fifo , DEFAULT_FIFO_SIZE ) ;
GX_SetCopyClear ( background , 0x00ffffff ) ;
GX_SetDispCopyGamma ( GX_GM_1_0 ) ;
GX_SetCullMode ( GX_CULL_NONE ) ;
GX_CopyDisp ( xfb [ whichfb ] , GX_TRUE ) ; // reset xfb
GX_Flush ( ) ;
2009-04-08 09:08:12 +02:00
# ifdef HW_RVL
pointer [ 0 ] = new GuiImageData ( player1_point_png ) ;
pointer [ 1 ] = new GuiImageData ( player2_point_png ) ;
pointer [ 2 ] = new GuiImageData ( player3_point_png ) ;
pointer [ 3 ] = new GuiImageData ( player4_point_png ) ;
# endif
2008-09-16 07:42:21 +02:00
}
2009-04-08 09:08:12 +02:00
2009-05-06 08:36:59 +02:00
static inline void UpdateScaling ( )
2008-10-25 09:26:02 +02:00
{
2008-11-05 08:21:50 +01:00
int xscale ;
int yscale ;
2008-11-05 18:29:49 +01:00
2009-03-04 08:01:04 +01:00
float TvAspectRatio ;
float GameboyAspectRatio ;
float MaxStretchRatio = 1.6f ;
if ( GCSettings . scaling = = 1 )
MaxStretchRatio = 1.3f ;
else if ( GCSettings . scaling = = 2 )
MaxStretchRatio = 1.6f ;
else
MaxStretchRatio = 1.0f ;
# ifdef HW_RVL
if ( CONF_GetAspectRatio ( ) = = CONF_ASPECT_16_9 )
TvAspectRatio = 16.0f / 9.0f ;
else
TvAspectRatio = 4.0f / 3.0f ;
# else
if ( GCSettings . scaling = = 3 )
TvAspectRatio = 16.0f / 9.0f ;
else
TvAspectRatio = 4.0f / 3.0f ;
# endif
if ( vwidth = = 240 ) // GBA
GameboyAspectRatio = 240.0f / 160.0f ; // assumes square pixels on GB Advance
else // GB or GBC
GameboyAspectRatio = 160.0f / 144.0f ; // assumes square pixels on GB Colour
if ( TvAspectRatio > GameboyAspectRatio )
2008-11-05 08:21:50 +01:00
{
2009-03-04 08:01:04 +01:00
yscale = 240 ; // half of TV resolution 640x480
float StretchRatio = TvAspectRatio / GameboyAspectRatio ;
if ( StretchRatio > MaxStretchRatio )
StretchRatio = MaxStretchRatio ;
xscale = 240.0f * GameboyAspectRatio * StretchRatio * ( ( 4.0f / 3.0f ) / TvAspectRatio ) ;
2008-11-05 08:21:50 +01:00
}
2009-03-04 08:01:04 +01:00
else
2008-11-05 08:21:50 +01:00
{
2009-03-04 08:01:04 +01:00
xscale = 320 ; // half of TV resolution 640x480
float StretchRatio = GameboyAspectRatio / TvAspectRatio ;
if ( StretchRatio > MaxStretchRatio )
StretchRatio = MaxStretchRatio ;
yscale = 320.0f / GameboyAspectRatio * StretchRatio / ( ( 4.0f / 3.0f ) / TvAspectRatio ) ;
2008-11-05 08:21:50 +01:00
}
2008-10-25 09:26:02 +02:00
// change zoom
xscale * = GCSettings . ZoomLevel ;
yscale * = GCSettings . ZoomLevel ;
2008-11-05 08:21:50 +01:00
// Set new aspect
2009-01-26 08:11:02 +01:00
square [ 0 ] = square [ 9 ] = - xscale + GCSettings . xshift ;
square [ 3 ] = square [ 6 ] = xscale + GCSettings . xshift ;
square [ 1 ] = square [ 4 ] = yscale - GCSettings . yshift ;
square [ 7 ] = square [ 10 ] = - yscale - GCSettings . yshift ;
2008-11-07 17:49:25 +01:00
DCFlushRange ( square , 32 ) ; // update memory BEFORE the GPU accesses it!
2008-10-25 09:26:02 +02:00
2008-10-28 07:51:58 +01:00
draw_init ( ) ;
2008-10-25 09:26:02 +02:00
2008-11-05 08:21:50 +01:00
memset ( & view , 0 , sizeof ( Mtx ) ) ;
guLookAt ( view , & cam . pos , & cam . up , & cam . view ) ;
GX_SetViewport ( 0 , 0 , vmode - > fbWidth , vmode - > efbHeight , 0 , 1 ) ;
updateScaling = 0 ;
2008-10-25 09:26:02 +02:00
}
2008-10-10 07:28:16 +02:00
/****************************************************************************
* ResetVideo_Emu
*
* Reset the video / rendering mode for the emulator rendering
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
ResetVideo_Emu ( )
{
2008-12-30 08:32:42 +01:00
Mtx44 p ;
2009-09-16 09:55:07 +02:00
GXRModeObj * rmode = FindVideoMode ( ) ;
2008-10-10 07:28:16 +02:00
2009-09-16 09:55:07 +02:00
SetupVideoMode ( rmode ) ; // reconfigure VI
2009-06-27 22:06:26 +02:00
2008-10-28 07:51:58 +01:00
// reconfigure GX
2008-10-10 07:28:16 +02:00
GX_SetViewport ( 0 , 0 , rmode - > fbWidth , rmode - > efbHeight , 0 , 1 ) ;
GX_SetDispCopyYScale ( ( f32 ) rmode - > xfbHeight / ( f32 ) rmode - > efbHeight ) ;
GX_SetScissor ( 0 , 0 , rmode - > fbWidth , rmode - > efbHeight ) ;
2008-11-05 08:21:50 +01:00
2008-10-10 07:28:16 +02:00
GX_SetDispCopySrc ( 0 , 0 , rmode - > fbWidth , rmode - > efbHeight ) ;
GX_SetDispCopyDst ( rmode - > fbWidth , rmode - > xfbHeight ) ;
2008-10-28 07:51:58 +01:00
GX_SetCopyFilter ( rmode - > aa , rmode - > sample_pattern , ( GCSettings . render = = 1 ) ? GX_TRUE : GX_FALSE , rmode - > vfilter ) ; // deflickering filter only for filtered mode
2008-11-05 08:21:50 +01:00
2008-10-10 07:28:16 +02:00
GX_SetFieldMode ( rmode - > field_rendering , ( ( rmode - > viHeight = = 2 * rmode - > xfbHeight ) ? GX_ENABLE : GX_DISABLE ) ) ;
2009-09-15 08:11:20 +02:00
if ( rmode - > aa )
GX_SetPixelFmt ( GX_PF_RGB565_Z16 , GX_ZC_LINEAR ) ;
else
GX_SetPixelFmt ( GX_PF_RGB8_Z24 , GX_ZC_LINEAR ) ;
2008-11-05 08:21:50 +01:00
GX_SetCullMode ( GX_CULL_NONE ) ;
GX_SetDispCopyGamma ( GX_GM_1_0 ) ;
2009-04-08 09:08:12 +02:00
GX_SetBlendMode ( GX_BM_BLEND , GX_BL_DSTALPHA , GX_BL_INVSRCALPHA , GX_LO_CLEAR ) ;
2008-10-24 08:16:15 +02:00
2009-04-10 04:06:00 +02:00
guOrtho ( p , 480 / 2 , - ( 480 / 2 ) , - ( 640 / 2 ) , 640 / 2 , 100 , 1000 ) ; // matrix, t, b, l, r, n, f
2008-11-05 08:21:50 +01:00
GX_LoadProjectionMtx ( p , GX_ORTHOGRAPHIC ) ;
2008-10-25 09:26:02 +02:00
2008-10-28 07:51:58 +01:00
// reinitialize texture
GX_InvalidateTexAll ( ) ;
GX_InitTexObj ( & texobj , texturemem , vwidth , vheight , GX_TF_RGB565 , GX_CLAMP , GX_CLAMP , GX_FALSE ) ; // initialize the texture obj we are going to use
if ( ! ( GCSettings . render & 1 ) )
GX_InitTexObjLOD ( & texobj , GX_NEAR , GX_NEAR_MIP_NEAR , 2.5 , 9.0 , 0.0 , GX_FALSE , GX_FALSE , GX_ANISO_1 ) ; // original/unfiltered video mode: force texture filtering OFF
2008-11-05 08:21:50 +01:00
GX_Flush ( ) ;
2009-04-08 09:08:12 +02:00
draw_init ( ) ;
2008-11-05 08:21:50 +01:00
2008-10-28 07:51:58 +01:00
// set aspect ratio
2008-11-05 08:21:50 +01:00
updateScaling = 1 ;
2008-10-10 07:28:16 +02:00
}
2009-01-14 07:10:02 +01:00
void GX_Render_Init ( int width , int height )
2008-10-11 00:33:57 +02:00
{
if ( texturemem )
free ( texturemem ) ;
/*** Allocate 32byte aligned texture memory ***/
texturesize = ( width * height ) * 2 ;
texturemem = ( u8 * ) memalign ( 32 , texturesize ) ;
memset ( texturemem , 0 , texturesize ) ;
/*** Setup for first call to scaler ***/
2008-10-11 10:06:36 +02:00
vwidth = width ;
vheight = height ;
2008-10-10 07:28:16 +02:00
}
2008-09-16 07:42:21 +02:00
/****************************************************************************
* GX_Render
*
* Pass in a buffer , width and height to update as a tiled RGB565 texture
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void GX_Render ( int width , int height , u8 * buffer , int pitch )
{
2008-10-10 07:28:16 +02:00
int h , w ;
2008-09-16 07:42:21 +02:00
long long int * dst = ( long long int * ) texturemem ;
long long int * src1 = ( long long int * ) buffer ;
long long int * src2 = ( long long int * ) ( buffer + pitch ) ;
long long int * src3 = ( long long int * ) ( buffer + ( pitch * 2 ) ) ;
long long int * src4 = ( long long int * ) ( buffer + ( pitch * 3 ) ) ;
int rowpitch = ( pitch > > 3 ) * 3 ;
int rowadjust = ( pitch % 8 ) * 4 ;
2008-10-10 07:28:16 +02:00
char * ra = NULL ;
2008-09-16 07:42:21 +02:00
vwidth = width ;
vheight = height ;
2008-10-10 07:28:16 +02:00
// Ensure previous vb has complete
while ( ( LWP_ThreadIsSuspended ( vbthread ) = = 0 ) | | ( copynow = = GX_TRUE ) )
2008-10-25 09:26:02 +02:00
usleep ( 50 ) ;
2008-10-10 07:28:16 +02:00
2008-09-16 07:42:21 +02:00
whichfb ^ = 1 ;
2008-10-11 00:33:57 +02:00
if ( updateScaling )
UpdateScaling ( ) ;
2008-10-09 08:48:46 +02:00
2008-10-28 07:51:58 +01:00
// clear texture objects
2008-11-05 08:21:50 +01:00
GX_InvVtxCache ( ) ;
2008-10-28 07:51:58 +01:00
GX_InvalidateTexAll ( ) ;
2008-11-05 08:21:50 +01:00
GX_SetTevOp ( GX_TEVSTAGE0 , GX_DECAL ) ;
GX_SetTevOrder ( GX_TEVSTAGE0 , GX_TEXCOORD0 , GX_TEXMAP0 , GX_COLOR0A0 ) ;
2008-10-28 07:51:58 +01:00
2008-10-10 07:28:16 +02:00
for ( h = 0 ; h < vheight ; h + = 4 )
2008-09-16 07:42:21 +02:00
{
for ( w = 0 ; w < ( vwidth > > 2 ) ; w + + )
{
* dst + + = * src1 + + ;
* dst + + = * src2 + + ;
* dst + + = * src3 + + ;
* dst + + = * src4 + + ;
}
src1 + = rowpitch ;
src2 + = rowpitch ;
src3 + = rowpitch ;
src4 + = rowpitch ;
if ( rowadjust )
{
ra = ( char * ) src1 ;
src1 = ( long long int * ) ( ra + rowadjust ) ;
ra = ( char * ) src2 ;
src2 = ( long long int * ) ( ra + rowadjust ) ;
ra = ( char * ) src3 ;
src3 = ( long long int * ) ( ra + rowadjust ) ;
ra = ( char * ) src4 ;
src4 = ( long long int * ) ( ra + rowadjust ) ;
}
2008-10-10 07:28:16 +02:00
}
2008-09-16 07:42:21 +02:00
2008-10-28 07:51:58 +01:00
// load texture into GX
2008-09-16 07:42:21 +02:00
DCFlushRange ( texturemem , texturesize ) ;
2008-11-05 08:21:50 +01:00
GX_SetNumChans ( 1 ) ;
2009-06-15 10:22:16 +02:00
GX_SetZMode ( GX_TRUE , GX_LEQUAL , GX_TRUE ) ;
GX_SetColorUpdate ( GX_TRUE ) ;
2008-11-05 08:21:50 +01:00
GX_LoadTexObj ( & texobj , GX_TEXMAP0 ) ;
2008-09-16 07:42:21 +02:00
2009-03-04 08:01:04 +01:00
draw_square ( view ) ; // render textured quad
2009-04-08 09:08:12 +02:00
# ifdef HW_RVL
2009-03-04 08:01:04 +01:00
draw_cursor ( view ) ; // render cursor
2009-04-08 09:08:12 +02:00
# endif
2008-09-16 07:42:21 +02:00
GX_DrawDone ( ) ;
2009-06-15 10:22:16 +02:00
if ( ScreenshotRequested )
{
ScreenshotRequested = 0 ;
TakeScreenshot ( ) ;
ConfigRequested = 1 ;
}
2008-11-05 08:21:50 +01:00
2008-10-28 07:51:58 +01:00
// EFB is ready to be copied into XFB
2008-09-16 07:42:21 +02:00
VIDEO_SetNextFramebuffer ( xfb [ whichfb ] ) ;
VIDEO_Flush ( ) ;
2008-10-10 07:28:16 +02:00
copynow = GX_TRUE ;
2008-09-26 10:10:36 +02:00
2008-10-10 07:28:16 +02:00
// Return to caller, don't waste time waiting for vb
LWP_ResumeThread ( vbthread ) ;
2008-09-16 07:42:21 +02:00
}
/****************************************************************************
2008-10-09 08:48:46 +02:00
* Zoom Functions
2008-09-16 07:42:21 +02:00
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
2008-10-09 08:48:46 +02:00
zoom ( float speed )
2008-09-16 07:42:21 +02:00
{
2008-10-24 08:16:15 +02:00
if ( GCSettings . ZoomLevel > 1 )
GCSettings . ZoomLevel + = ( speed / - 100.0 ) ;
2008-10-09 08:48:46 +02:00
else
2008-10-24 08:16:15 +02:00
GCSettings . ZoomLevel + = ( speed / - 200.0 ) ;
2008-09-16 07:42:21 +02:00
2008-10-24 08:16:15 +02:00
if ( GCSettings . ZoomLevel < 0.5 )
GCSettings . ZoomLevel = 0.5 ;
else if ( GCSettings . ZoomLevel > 2.0 )
GCSettings . ZoomLevel = 2.0 ;
2008-09-16 07:42:21 +02:00
}
void
2008-10-09 08:48:46 +02:00
zoom_reset ( )
2008-09-16 07:42:21 +02:00
{
2008-10-24 08:16:15 +02:00
GCSettings . ZoomLevel = 1.0 ;
2008-10-09 08:48:46 +02:00
}
2009-03-04 08:01:04 +01:00
2009-04-08 09:08:12 +02:00
/****************************************************************************
* TakeScreenshot
*
* Copies the current screen into a GX texture
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void TakeScreenshot ( )
{
int texSize = vmode - > fbWidth * vmode - > efbHeight * 4 ;
if ( gameScreenTex ) free ( gameScreenTex ) ;
gameScreenTex = ( u8 * ) memalign ( 32 , texSize ) ;
if ( gameScreenTex = = NULL ) return ;
GX_SetTexCopySrc ( 0 , 0 , vmode - > fbWidth , vmode - > efbHeight ) ;
GX_SetTexCopyDst ( vmode - > fbWidth , vmode - > efbHeight , GX_TF_RGBA8 , GX_FALSE ) ;
GX_CopyTex ( gameScreenTex , GX_FALSE ) ;
GX_PixModeSync ( ) ;
DCFlushRange ( gameScreenTex , texSize ) ;
# ifdef HW_RVL
if ( gameScreenTex2 ) free ( gameScreenTex2 ) ;
gameScreenTex2 = ( u8 * ) memalign ( 32 , texSize ) ;
if ( gameScreenTex2 = = NULL ) return ;
GX_CopyTex ( gameScreenTex2 , GX_FALSE ) ;
GX_PixModeSync ( ) ;
DCFlushRange ( gameScreenTex2 , texSize ) ;
# endif
}
/****************************************************************************
* ResetVideo_Menu
*
* Reset the video / rendering mode for the menu
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void
ResetVideo_Menu ( )
{
Mtx44 p ;
f32 yscale ;
u32 xfbHeight ;
2009-09-16 09:55:07 +02:00
GXRModeObj * rmode = FindVideoMode ( ) ;
2009-04-08 09:08:12 +02:00
2009-09-16 09:55:07 +02:00
SetupVideoMode ( rmode ) ; // reconfigure VI
2009-06-27 22:06:26 +02:00
2009-04-08 09:08:12 +02:00
// clears the bg to color and clears the z buffer
GXColor background = { 0 , 0 , 0 , 255 } ;
GX_SetCopyClear ( background , 0x00ffffff ) ;
yscale = GX_GetYScaleFactor ( vmode - > efbHeight , vmode - > xfbHeight ) ;
xfbHeight = GX_SetDispCopyYScale ( yscale ) ;
GX_SetScissor ( 0 , 0 , vmode - > fbWidth , vmode - > efbHeight ) ;
GX_SetDispCopySrc ( 0 , 0 , vmode - > fbWidth , vmode - > efbHeight ) ;
GX_SetDispCopyDst ( vmode - > fbWidth , xfbHeight ) ;
GX_SetCopyFilter ( vmode - > aa , vmode - > sample_pattern , GX_TRUE , vmode - > vfilter ) ;
GX_SetFieldMode ( vmode - > field_rendering , ( ( vmode - > viHeight = = 2 * vmode - > xfbHeight ) ? GX_ENABLE : GX_DISABLE ) ) ;
if ( vmode - > aa )
GX_SetPixelFmt ( GX_PF_RGB565_Z16 , GX_ZC_LINEAR ) ;
else
GX_SetPixelFmt ( GX_PF_RGB8_Z24 , GX_ZC_LINEAR ) ;
// setup the vertex descriptor
// tells the flipper to expect direct data
GX_ClearVtxDesc ( ) ;
GX_InvVtxCache ( ) ;
GX_InvalidateTexAll ( ) ;
GX_SetVtxDesc ( GX_VA_TEX0 , GX_NONE ) ;
GX_SetVtxDesc ( GX_VA_POS , GX_DIRECT ) ;
GX_SetVtxDesc ( GX_VA_CLR0 , GX_DIRECT ) ;
GX_SetVtxAttrFmt ( GX_VTXFMT0 , GX_VA_POS , GX_POS_XYZ , GX_F32 , 0 ) ;
GX_SetVtxAttrFmt ( GX_VTXFMT0 , GX_VA_CLR0 , GX_CLR_RGBA , GX_RGBA8 , 0 ) ;
GX_SetVtxAttrFmt ( GX_VTXFMT0 , GX_VA_TEX0 , GX_TEX_ST , GX_F32 , 0 ) ;
GX_SetZMode ( GX_FALSE , GX_LEQUAL , GX_TRUE ) ;
GX_SetNumChans ( 1 ) ;
GX_SetNumTexGens ( 1 ) ;
GX_SetTevOp ( GX_TEVSTAGE0 , GX_PASSCLR ) ;
GX_SetTevOrder ( GX_TEVSTAGE0 , GX_TEXCOORD0 , GX_TEXMAP0 , GX_COLOR0A0 ) ;
GX_SetTexCoordGen ( GX_TEXCOORD0 , GX_TG_MTX2x4 , GX_TG_TEX0 , GX_IDENTITY ) ;
guMtxIdentity ( GXmodelView2D ) ;
guMtxTransApply ( GXmodelView2D , GXmodelView2D , 0.0F , 0.0F , - 50.0F ) ;
GX_LoadPosMtxImm ( GXmodelView2D , GX_PNMTX0 ) ;
guOrtho ( p , 0 , 479 , 0 , 639 , 0 , 300 ) ;
GX_LoadProjectionMtx ( p , GX_ORTHOGRAPHIC ) ;
GX_SetViewport ( 0 , 0 , vmode - > fbWidth , vmode - > efbHeight , 0 , 1 ) ;
GX_SetBlendMode ( GX_BM_BLEND , GX_BL_SRCALPHA , GX_BL_INVSRCALPHA , GX_LO_CLEAR ) ;
GX_SetAlphaUpdate ( GX_TRUE ) ;
}
/****************************************************************************
* Menu_Render
*
* Renders everything current sent to GX , and flushes video
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void Menu_Render ( )
{
whichfb ^ = 1 ; // flip framebuffer
GX_SetZMode ( GX_TRUE , GX_LEQUAL , GX_TRUE ) ;
GX_SetColorUpdate ( GX_TRUE ) ;
GX_CopyDisp ( xfb [ whichfb ] , GX_TRUE ) ;
2009-10-12 21:38:49 +02:00
GX_DrawDone ( ) ;
2009-04-08 09:08:12 +02:00
VIDEO_SetNextFramebuffer ( xfb [ whichfb ] ) ;
VIDEO_Flush ( ) ;
VIDEO_WaitVSync ( ) ;
}
/****************************************************************************
* Menu_DrawImg
*
* Draws the specified image on screen using GX
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void Menu_DrawImg ( f32 xpos , f32 ypos , u16 width , u16 height , u8 data [ ] ,
f32 degrees , f32 scaleX , f32 scaleY , u8 alpha )
{
if ( data = = NULL )
return ;
GXTexObj texObj ;
GX_InitTexObj ( & texObj , data , width , height , GX_TF_RGBA8 , GX_CLAMP , GX_CLAMP , GX_FALSE ) ;
GX_LoadTexObj ( & texObj , GX_TEXMAP0 ) ;
GX_InvalidateTexAll ( ) ;
GX_SetTevOp ( GX_TEVSTAGE0 , GX_MODULATE ) ;
GX_SetVtxDesc ( GX_VA_TEX0 , GX_DIRECT ) ;
Mtx m , m1 , m2 , mv ;
width * = .5 ;
height * = .5 ;
guMtxIdentity ( m1 ) ;
guMtxScaleApply ( m1 , m1 , scaleX , scaleY , 1.0 ) ;
2009-07-10 23:30:13 +02:00
guVector axis = ( guVector ) { 0 , 0 , 1 } ;
2009-04-08 09:08:12 +02:00
guMtxRotAxisDeg ( m2 , & axis , degrees ) ;
guMtxConcat ( m2 , m1 , m ) ;
guMtxTransApply ( m , m , xpos + width , ypos + height , 0 ) ;
guMtxConcat ( GXmodelView2D , m , mv ) ;
GX_LoadPosMtxImm ( mv , GX_PNMTX0 ) ;
GX_Begin ( GX_QUADS , GX_VTXFMT0 , 4 ) ;
GX_Position3f32 ( - width , - height , 0 ) ;
GX_Color4u8 ( 0xFF , 0xFF , 0xFF , alpha ) ;
GX_TexCoord2f32 ( 0 , 0 ) ;
GX_Position3f32 ( width , - height , 0 ) ;
GX_Color4u8 ( 0xFF , 0xFF , 0xFF , alpha ) ;
GX_TexCoord2f32 ( 1 , 0 ) ;
GX_Position3f32 ( width , height , 0 ) ;
GX_Color4u8 ( 0xFF , 0xFF , 0xFF , alpha ) ;
GX_TexCoord2f32 ( 1 , 1 ) ;
GX_Position3f32 ( - width , height , 0 ) ;
GX_Color4u8 ( 0xFF , 0xFF , 0xFF , alpha ) ;
GX_TexCoord2f32 ( 0 , 1 ) ;
GX_End ( ) ;
GX_LoadPosMtxImm ( GXmodelView2D , GX_PNMTX0 ) ;
GX_SetTevOp ( GX_TEVSTAGE0 , GX_PASSCLR ) ;
GX_SetVtxDesc ( GX_VA_TEX0 , GX_NONE ) ;
}
/****************************************************************************
* Menu_DrawRectangle
*
* Draws a rectangle at the specified coordinates using GX
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void Menu_DrawRectangle ( f32 x , f32 y , f32 width , f32 height , GXColor color , u8 filled )
{
u8 fmt ;
long n ;
int i ;
f32 x2 = x + width ;
f32 y2 = y + height ;
2009-07-10 23:30:13 +02:00
guVector v [ ] = { { x , y , 0.0f } , { x2 , y , 0.0f } , { x2 , y2 , 0.0f } , { x , y2 , 0.0f } , { x , y , 0.0f } } ;
2009-04-08 09:08:12 +02:00
if ( ! filled )
{
fmt = GX_LINESTRIP ;
n = 5 ;
}
else
{
fmt = GX_TRIANGLEFAN ;
n = 4 ;
}
GX_Begin ( fmt , GX_VTXFMT0 , n ) ;
for ( i = 0 ; i < n ; i + + )
{
GX_Position3f32 ( v [ i ] . x , v [ i ] . y , v [ i ] . z ) ;
GX_Color4u8 ( color . r , color . g , color . b , color . a ) ;
}
GX_End ( ) ;
}