
/*
 * Copyright 1995, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 *
 * UNPUBLISHED -- Rights reserved under the copyright laws of the United
 * States.   Use of a copyright notice is precautionary only and does not
 * imply publication or disclosure.
 *
 * U.S. GOVERNMENT RESTRICTED RIGHTS LEGEND:
 * Use, duplication or disclosure by the Government is subject to restrictions
 * as set forth in FAR 52.227.19(c)(2) or subparagraph (c)(1)(ii) of the Rights
 * in Technical Data and Computer Software clause at DFARS 252.227-7013 and/or
 * in similar or successor clauses in the FAR, or the DOD or NASA FAR
 * Supplement.  Contractor/manufacturer is Silicon Graphics, Inc.,
 * 2011 N. Shoreline Blvd. Mountain View, CA 94039-7311.
 *
 * THE CONTENT OF THIS WORK CONTAINS CONFIDENTIAL AND PROPRIETARY
 * INFORMATION OF SILICON GRAPHICS, INC. ANY DUPLICATION, MODIFICATION,
 * DISTRIBUTION, OR DISCLOSURE IN ANY FORM, IN WHOLE, OR IN PART, IS STRICTLY
 * PROHIBITED WITHOUT THE PRIOR EXPRESS WRITTEN PERMISSION OF SILICON
 * GRAPHICS, INC.
 *
 */

/*---------------------------------------------------------------------*
        Copyright (C) 1997,1998 Nintendo. (Originated by SGI)
        
        $RCSfile: gtdemo.c,v $
        $Revision: 1.1.1.1 $
        $Date: 2003/01/17 23:03:19 $
 *---------------------------------------------------------------------*/

/*
 * File:	gtdemo.c
 * Creator:	hsa@sgi.com
 * Create Date:	Tue Oct 31 14:17:25 PST 1995
 *
 * This program demonstrates:
 *
 * 	- use of multiple graphics RCP tasks
 * 	- use of the turbo graphics code
 *	- host centroid sort for visibility determination
 *	- hierarchical character animation and complex host
 *	  matrix manipulations using floating point gu routines.
 *
 */

#include <ultra64.h>
#include <ramrom.h>	/* needed for argument passing into the app */

#include "gtdemo.h"
#include "controller.h"
#include "turbo.h"
#include "character.h"
#include "timer.h"

/*
 * Symbol genererated by "makerom" to indicate the end of the code segment
 * in virtual (and physical) memory
 */
extern char _codeSegmentEnd[];

/*
 * Symbols generated by "makerom" to tell us where the static segment is
 * in ROM.
 */
extern char _staticSegmentRomStart[], _staticSegmentRomEnd[];
char	*staticSegment;

/*
 * Stacks for the threads as well as message queues for synchronization
 */
u64	bootStack[STACKSIZE/sizeof(u64)];

static void	idle(void *);
static void	mainproc(void *);

static OSThread	idleThread;
static u64	idleThreadStack[STACKSIZE/sizeof(u64)];

static OSThread	mainThread;
static u64	mainThreadStack[STACKSIZE/sizeof(u64)];

/* this number (the depth of the message queue) needs to be equal
 * to the maximum number of possible overlapping PI requests.
 * For this app, 1 or 2 is probably plenty, other apps might
 * require a lot more.
 */
#define NUM_PI_MSGS     8
    
static OSMesg PiMessages[NUM_PI_MSGS];
static OSMesgQueue PiMessageQ;

OSMesgQueue	dmaMessageQ, rspMessageQ, rdpMessageQ, retraceMessageQ;
OSMesg		dmaMessageBuf, rspMessageBuf, rdpMessageBuf, retraceMessageBuf;
OSMesg		dummyMessage;
OSIoMesg	dmaIOMessageBuf;

/*
 * Dynamic segment in code space. Needs OS_K0_TO_PHYSICAL()...
 */
Dynamic dynamic;

/*
 * necessary for RSP tasks:
 */
u64 dram_stack[SP_DRAM_STACK_SIZE64];

/*
 * must be in BSS, not on the stack for this to work:
 */
OSTask	tlist =
{
    M_GFXTASK,			/* task type */
    OS_TASK_DP_WAIT,		/* task flags */
    NULL,			/* boot ucode pointer (fill in later) */
    0,				/* boot ucode size (fill in later) */
    NULL,			/* task ucode pointer (fill in later) */
    SP_UCODE_SIZE,		/* task ucode size */
    NULL,			/* task ucode data pointer (fill in later) */
    SP_UCODE_DATA_SIZE,		/* task ucode data size */
    &(dram_stack[0]),		/* task dram stack pointer */
    SP_DRAM_STACK_SIZE8,	/* task dram stack size */
    NULL,			/* task output buffer ptr (not always used) */
    NULL,			/* task output buffer size ptr */
    NULL,			/* task data pointer (fill in later) */
    0,				/* task data size (fill in later) */
    NULL,			/* task yield buffer ptr (not used here) */
    0				/* task yield buffer size (not used here) */
};

Gfx		*glistp;	/* global for test case procs */


/*
 * global variables for arguments, to control test cases
 */
static int	debugflag = 0;
static int      draw_buffer = 0;
static void	*cfb_ptrs[2];
static u64	ramrombuf[RAMROM_MSG_SIZE/sizeof(u64)];
    
static void	setup_view(Dynamic *dynamicp);
extern void	draw_floor(Dynamic *);

int	controllerSlot;
u32	frame_count;


OSPiHandle	*handler;

boot(void)
{

#ifdef DEBUG
    int i, *pr;
    char *ap;
    u32 *argp;
    u32 argbuf[16];
#endif

    /* notice that you can't call osSyncPrintf() until you set
     * up an idle thread.
     */
    
    osInitialize();

    handler = osCartRomInit();

#ifdef DEBUG
    argp = (u32 *)RAMROM_APP_WRITE_ADDR;
    for (i=0; i<sizeof(argbuf)/4; i++, argp++) {
	osEPiReadIo(handler, (u32)argp, &argbuf[i]); /* Assume no DMA */
    }
    /* Parse the options */
    ap = (char *)argbuf;
    while (*ap != '\0') {
	while (*ap == ' ')
	    ap++;
	if ( *ap == '-' && *(ap+1) == 'd') {
	    debugflag = 1;
	    ap += 2;
	}
    }
#endif
    
    osCreateThread(&idleThread, 1, idle, (void *)0,
		   idleThreadStack+STACKSIZE/sizeof(u64), 10);
    osStartThread(&idleThread);

    /* never reached */
}

static void
idle(void *arg)
{
    /* Initialize video */
    osCreateViManager(OS_PRIORITY_VIMGR);
    osViSetMode(&osViModeTable[OS_VI_NTSC_LAN1]);
    osViSetSpecialFeatures(OS_VI_GAMMA_ON|OS_VI_GAMMA_DITHER_ON|OS_VI_DIVOT_ON|OS_VI_DITHER_FILTER_ON);
    
    /*
     * Start PI Mgr for access to cartridge
     */
    osCreatePiManager((OSPri)OS_PRIORITY_PIMGR, &PiMessageQ, PiMessages, 
		      NUM_PI_MSGS);
    
    /*
     * at this point, AND NOT BEFORE, we can now do an
     * osSyncPrintf()
     */
    
    /*
     * Create main thread
     */
    osCreateThread(&mainThread, 3, mainproc, arg,
		   mainThreadStack+STACKSIZE/sizeof(u64), 10);
    
    if (!debugflag)
	osStartThread(&mainThread);

    /*
     * Become the idle thread
     */
    osSetThreadPri( 0, 0 );
    for (;;);
}



/*
 * This is the main routine of the app.
 */
static void
mainproc(void *arg)
{
    OSTask	*tlistp;
    Dynamic	*dynamicp;
    
    cfb_ptrs[0] = &(cfb_16_a[0]);
    cfb_ptrs[1] = &(cfb_16_b[0]);

    /*
     * Setup the message queues
     */
    osCreateMesgQueue(&dmaMessageQ, &dmaMessageBuf, 1);
    
    osCreateMesgQueue(&rspMessageQ, &rspMessageBuf, 1);
    osSetEventMesg(OS_EVENT_SP, &rspMessageQ, dummyMessage);
    
    osCreateMesgQueue(&rdpMessageQ, &rdpMessageBuf, 1);
    osSetEventMesg(OS_EVENT_DP, &rdpMessageQ, dummyMessage);
    
    osCreateMesgQueue(&retraceMessageQ, &retraceMessageBuf, 1);
    osViSetEvent(&retraceMessageQ, dummyMessage, 1);
    
    /* Stick the static segment right after the code/data segment */
    staticSegment = _codeSegmentEnd;

    dmaIOMessageBuf.hdr.pri      = OS_MESG_PRI_NORMAL;
    dmaIOMessageBuf.hdr.retQueue = &dmaMessageQ;
    dmaIOMessageBuf.dramAddr     = staticSegment;
    dmaIOMessageBuf.devAddr      = (u32)_staticSegmentRomStart;
    dmaIOMessageBuf.size         = _staticSegmentRomEnd-_staticSegmentRomStart;

    osEPiStartDma(handler, &dmaIOMessageBuf, OS_READ);
    
    /* Wait for DMA to finish */
    (void)osRecvMesg(&dmaMessageQ, &dummyMessage, OS_MESG_BLOCK);
    
    /* Initialize controller */
    controllerSlot = initControllers();

    /* Initialize timers. */
    TimerInit();

#ifdef DEBUG
    /* print helpful info to the screen: */
    osSyncPrintf("\nControls:\n\n");
    osSyncPrintf("\tjoystick:\tmove man around.\n");
    osSyncPrintf("\tarrow left:\tpunch left.\n");
    osSyncPrintf("\tarrow right:\tpunch right.\n");
    osSyncPrintf("\tA:\t\tselect a different man.\n");
    osSyncPrintf("\tB:\t\ttoggle sorting/z-buffering.\n");
    osSyncPrintf("\t+ up:\t\tmove eye in.\n");
    osSyncPrintf("\t+ down:\t\tmove eye out.\n");

    osSyncPrintf("\nTimer Bars:\t(each green tick is 60Hz)\n\n");
    osSyncPrintf("\tdark blue:\tbackground CPU time.\n");
    osSyncPrintf("\tlight blue:\tbackground RCP time.\n");
    osSyncPrintf("\tdark red:\tforeground CPU time.\n");
    osSyncPrintf("\tlight red:\tforeground RCP time.\n");
    osSyncPrintf("\n");
#endif

    /* Main game loop */
    while (1) {
	
	bg_cpu_start = osGetTime();

	readController(controllerSlot);

	/* which guy do we control? */
	dynamic.char_control = which_char;

	if (do_reset) {
	    dynamic.char_control = 0;
	    dynamic.free_node = 0;
	    dynamic.characters[0] = create_character(&dynamic, 0, 60, 0);
	    dynamic.characters[1] = create_character(&dynamic, 80, 60, -20);
	    dynamic.characters[2] = create_character(&dynamic, -80, 60, 20);
	    dynamic.characters[3] = create_character(&dynamic, 40, 60, -120);
	    do_reset = FALSE;
	}

	/* pointers to build the display list. */
	tlistp = &tlist;
	dynamicp = &dynamic;
	glistp = &(dynamicp->glist[0]);
	
	/* Tell RCP where each segment is */
	gSPSegment(glistp++, 0, 0x0);	/* K0 (physical) address segment */
	gSPSegment(glistp++, STATIC_SEGMENT,
		   osVirtualToPhysical(staticSegment));
	
	gSPDisplayList(glistp++, rdpinit_dl);
	gSPDisplayList(glistp++, rspinit_dl);

	
	if (do_turbo_z) {
	    /*
	     * clear z, z = max z, dz = 0
	     */
	    gDPSetDepthImage(glistp++, OS_K0_TO_PHYSICAL(zbuffer));
	    gDPSetCycleType(glistp++, G_CYC_FILL);
	    gDPSetColorImage(glistp++, G_IM_FMT_RGBA, G_IM_SIZ_16b, SCREEN_WD,
			     OS_K0_TO_PHYSICAL(zbuffer));
	    gDPSetFillColor(glistp++, (GPACK_ZDZ(G_MAXFBZ,0) << 16 | 
				       GPACK_ZDZ(G_MAXFBZ,0)));
	    gDPFillRectangle(glistp++, 0, 0, SCREEN_WD-1, SCREEN_HT-1);
	}

	/* Clear framebuffer */
	gDPPipeSync(glistp++);
	gDPSetCycleType(glistp++, G_CYC_FILL);
	gDPSetColorImage(glistp++, G_IM_FMT_RGBA, G_IM_SIZ_16b, SCREEN_WD, 
			 OS_K0_TO_PHYSICAL(cfb_ptrs[draw_buffer]));
	gDPSetFillColor(glistp++, 
			((GPACK_RGBA5551(100, 100, 200, 1) << 16) |
			 (GPACK_RGBA5551(100, 100, 200, 1))));
	gDPFillRectangle(glistp++, 0, 0, SCREEN_WD-1, SCREEN_HT-1);
	gDPPipeSync(glistp++);
	gDPSetCycleType(glistp++, G_CYC_1CYCLE);
	/* end of standard display list part. */

	setup_view(dynamicp);
	
	draw_floor(dynamicp);

	/* all done with the background for this frame. */
	gDPFullSync(glistp++);
	gSPEndDisplayList(glistp++);
	
	/* Build background graphics task: */
	tlistp->t.ucode_boot = (u64 *) rspbootTextStart;
	tlistp->t.ucode_boot_size = ((int)rspbootTextEnd - 
				     (int)rspbootTextStart);

	if (use_fifo_ucode) {
	    tlistp->t.ucode = (u64 *) gspFast3D_fifoTextStart;
	    tlistp->t.ucode_data = (u64 *) gspFast3D_fifoDataStart;
	    tlistp->t.output_buff = rdp_output;
	    tlistp->t.output_buff_size = (u64 *) ((int)rdp_output + 
						  (int)(0x400 << 3));
	} else {
	    /* RSP output over XBUS to RDP: */
	    tlistp->t.ucode = (u64 *) gspFast3DTextStart;
	    tlistp->t.ucode_data = (u64 *) gspFast3DDataStart;
	}

	/* initial display list: */
	tlistp->t.data_ptr = (u64 *) dynamicp->glist;
	tlistp->t.data_size = ((int)(glistp - dynamicp->glist) *
			       sizeof (Gfx));

	/*
	 * Can just flush 16KB and forget about each individual pieces
	 * of data to flush.
	 */
	osWritebackDCacheAll();
	osSpTaskLoad(tlistp);
	bg_cpu_end = osGetTime();
	bg_rcp_start = osGetTime();
	osSpTaskStartGo(tlistp);
	
	/* compute turbo work while we wait for RCP to draw */
	turbo_compute(dynamicp);

	/* wait for DP completion */
	(void)osRecvMesg(&rdpMessageQ, &dummyMessage, OS_MESG_BLOCK);
	bg_rcp_end = osGetTime();

	/* do turbo stuff here: */
	turbo_draw(dynamicp, staticSegment);

	/* wait for DP completion */
	(void)osRecvMesg(&rdpMessageQ, &dummyMessage, OS_MESG_BLOCK);
	fg_rcp_end = osGetTime();

	/* setup to swap buffers */
	osViSwapBuffer(cfb_ptrs[draw_buffer]);

	/* Make sure there isn't an old retrace in queue 
	 * (assumes queue has a depth of 1) 
	 */
	if (MQ_IS_FULL(&retraceMessageQ))
	    (void)osRecvMesg(&retraceMessageQ, &dummyMessage, OS_MESG_BLOCK);
	
	/* Wait for Vertical retrace to finish swap buffers */
	(void)osRecvMesg(&retraceMessageQ, &dummyMessage, OS_MESG_BLOCK);
	draw_buffer ^= 1;

	frame_count++;
	TimerFinish();
    }
}


static void
setup_view(Dynamic *dynamicp)
{
    float		cost, sint, x, y, z;

    cost = cosf(world_theta);
    sint = sinf(world_theta);

    /*
     * Viewing parameters are common for all objects in this frame:
     */
    guPerspectiveF(dynamicp->projection, &dynamicp->perspNorm,
		  33, 320.0/240.0, 100, 2000, 1.0);
    x = sint*view_dist;
    y = (400.0/5.0);
    z = cost*view_dist;
    guLookAtF(dynamicp->viewing, 
	     x, y, z,
	     0, 40, 0,
	     0, 1, 0);

    guMtxF2L(dynamicp->projection, &dynamicp->projectionL);
    guMtxF2L(dynamicp->viewing, &dynamicp->viewingL);
	
    gSPMatrix(glistp++, &dynamicp->projectionL,
	      G_MTX_PROJECTION|G_MTX_LOAD|G_MTX_NOPUSH);
    gSPMatrix(glistp++, &dynamicp->viewingL,
	      G_MTX_PROJECTION|G_MTX_MUL|G_MTX_NOPUSH);
    gSPPerspNormalize(glistp++, dynamicp->perspNorm);

    if (do_view_rotate) {
	world_theta += view_rotate_inc;
	if (world_theta > 6.28) {
	    world_theta -= 6.28;
	}
    }
}

