1 /*---------------------------------------------------------------------------*
2   Project:  Dolphin/Revolution gx demo
3   File:     mgt-triple-buf.c
4 
5   Copyright 1998-2006 Nintendo.  All rights reserved.
6 
7   These coded instructions, statements, and computer programs contain
8   proprietary information of Nintendo of America Inc. and/or Nintendo
9   Company Ltd., and are protected by Federal copyright law.  They may
10   not be disclosed to third parties or copied or duplicated in any form,
11   in whole or in part, without the prior written consent of Nintendo.
12  *---------------------------------------------------------------------------*/
13 
14 #include <demo.h>
15 #include <stdlib.h>
16 
17 /*---------------------------------------------------------------------------*
18  * Quick and dirty queue implementation.
19  *---------------------------------------------------------------------------*/
20 
21 typedef struct QItem_
22 {
23     void* writePtr;
24     void* dataPtr;
25     void* copyXFB;
26 } QItem;
27 
28 #define QUEUE_MAX 5
29 #define QUEUE_EMPTY QUEUE_MAX
30 
31 typedef struct Queue_
32 {
33     QItem entry[QUEUE_MAX];
34     u16 top;
35     u16 bot;
36 } Queue;
37 
38 /*---------------------------------------------------------------------------*
39  * Data needed for triple-buffering.
40  *---------------------------------------------------------------------------*/
41 
42 static Queue RenderQ;   // Queue for frames in FIFO
43 static Queue DoneQ;     // Queue for frames finished already
44 
45 static void* myXFB1;    // Pointers to the two XFB's
46 static void* myXFB2;
47 static void* copyXFB;   // Which XFB to copy to next
48 static void* dispXFB;   // Which XFB is being displayed now
49 
50 static GXBool BPSet  = GX_FALSE;        // Is the FIFO breakpoint set?
51 static GXBool BPWait = GX_FALSE;        // Is breakpoint reset waiting on VBlank?
52 static GXBool BPGo   = GX_FALSE;        // Indicates breakpoint should be released
53 
54 static u16 lastVCBToken = 0;    // Last sync token the VBlank callback saw
55 static u16 newToken = 1;        // Value to use for new sync token.
56 
57 static OSThreadQueue waitingDoneRender; // Threads waiting for frames to finish
58 
59 static OSThread CUThread;               // OS data for clean-up thread
60 static u8       CUThreadStack[4096];    // Stack for clean-up thread
61 
62 extern void*   DemoFrameBuffer1;        // Where to find XFB info
63 extern void*   DemoFrameBuffer2;
64 
65 /*---------------------------------------------------------------------------*
66    Data for drawing routine.
67 
68    The macro ATTRIBUTE_ALIGN provides a convenient way to align initialized
69    arrays.  Alignment of vertex arrays to 32B IS NOT required, but may result
70    in a slight performance improvement.
71  *---------------------------------------------------------------------------*/
72 f32 Verts_f32[] ATTRIBUTE_ALIGN(32) =
73 {
74 //      x, y, z
75     -1.0f, -1.0f, -1.0f,    // 0:0
76      1.0f, -1.0f, -1.0f,    // 0:1
77      1.0f,  1.0f, -1.0f,    // 0:2
78     -1.0f,  1.0f, -1.0f,    // 0:3
79     -1.0f, -1.0f, -1.0f,    // 1:0
80     -1.0f, -1.0f,  1.0f,    // 1:1
81      1.0f, -1.0f,  1.0f,    // 1:2
82      1.0f, -1.0f, -1.0f,    // 1:3
83     -1.0f, -1.0f, -1.0f,    // 2:0
84     -1.0f,  1.0f, -1.0f,    // 2:1
85     -1.0f,  1.0f,  1.0f,    // 2:2
86     -1.0f, -1.0f,  1.0f,    // 2:3
87      1.0f,  1.0f,  1.0f,    // 3:0
88      1.0f, -1.0f,  1.0f,    // 3:1
89     -1.0f, -1.0f,  1.0f,    // 3:2
90     -1.0f,  1.0f,  1.0f,    // 3:3
91      1.0f,  1.0f,  1.0f,    // 4:0
92     -1.0f,  1.0f,  1.0f,    // 4:1
93     -1.0f,  1.0f, -1.0f,    // 4:2
94      1.0f,  1.0f, -1.0f,    // 4:3
95      1.0f,  1.0f,  1.0f,    // 5:0
96      1.0f,  1.0f, -1.0f,    // 5:1
97      1.0f, -1.0f, -1.0f,    // 5:2
98      1.0f, -1.0f,  1.0f,    // 5:3
99 };
100 
101 u8 Colors_rgba8[] ATTRIBUTE_ALIGN(32) =
102 {
103   //  r,   g,   b,   a
104     128, 128, 128, 255, // 0
105     128, 128, 128, 255, // 1
106     128, 128, 128, 255, // 2
107     128, 128, 128, 255, // 3
108     255, 255, 255, 255, // 0
109     255, 255, 255, 255, // 1
110     255, 255, 255, 255, // 2
111     255, 255, 255, 255, // 3
112       0,   0,   0, 255, // 0
113       0,   0,   0, 255, // 1
114       0,   0,   0, 255, // 2
115       0,   0,   0, 255, // 3
116     128, 128, 128, 255, // 0
117     128, 128, 128, 255, // 1
118     128, 128, 128, 255, // 2
119     128, 128, 128, 255, // 3
120     255, 255, 255, 255, // 0
121     255, 255, 255, 255, // 1
122     255, 255, 255, 255, // 2
123     255, 255, 255, 255, // 3
124       0,   0,   0, 255, // 0
125       0,   0,   0, 255, // 1
126       0,   0,   0, 255, // 2
127       0,   0,   0, 255, // 3
128 };
129 
130 static u32 ticks = 0;   // animation time counter
131 
132 /*---------------------------------------------------------------------------*
133    Forward references
134  *---------------------------------------------------------------------------*/
135 
136 void   main            ( void );
137 void   CameraInit      ( Mtx v );
138 void   DrawInit        ( void );
139 void   DrawTick        ( Mtx v );
140 void   AnimTick        ( void );
141 void   PrintIntro      ( void );
142 
143 /*---------------------------------------------------------------------------*/
144 
145 void   BPCallback      ( void );
146 void   VIPreCallback   ( u32 retraceCount );
147 void   VIPostCallback  ( u32 retraceCount );
148 void*  CleanupThread   ( void* param );
149 void   SetNextBreakPt  ( void );
150 
151 void   init_queue  (Queue *q);
152 void   enqueue     (Queue *q, QItem *qitm);
153 QItem  dequeue     (Queue *q);
154 QItem  queue_front (Queue *q);
155 GXBool queue_empty (Queue *q);
156 u32    queue_length(Queue *q);
157 
158 /*---------------------------------------------------------------------------*
159    Breakpoint Interrupt Callback
160  *---------------------------------------------------------------------------*/
161 
BPCallback(void)162 void BPCallback ( void )
163 {
164     QItem qitm;
165 
166     qitm = queue_front(&RenderQ);
167 
168     // Check whether or not the just-finished frame can be
169     // copied already or if it must wait (due to lack of a
170     // free XFB).  If it must wait, set a flag for the VBlank
171     // interrupt callback to take care of it.
172 
173     if (qitm.copyXFB == dispXFB)
174     {
175         BPWait = GX_TRUE;
176     }
177     else
178     {
179         SetNextBreakPt();
180     }
181 }
182 
183 /*---------------------------------------------------------------------------*
184    Routine to move breakpoint ahead, deal with finished frames.
185  *---------------------------------------------------------------------------*/
186 
SetNextBreakPt(void)187 void SetNextBreakPt ( void )
188 {
189     QItem qitm;
190 
191     // Move entry from RenderQ to DoneQ.
192 
193     qitm = dequeue(&RenderQ);
194 
195     enqueue(&DoneQ, &qitm);
196 
197     OSWakeupThread( &waitingDoneRender );
198 
199     // Move breakpoint to next entry, if any.
200 
201     if (queue_empty(&RenderQ))
202     {
203         GXDisableBreakPt();
204         BPSet = GX_FALSE;
205     }
206     else
207     {
208         qitm = queue_front(&RenderQ);
209         GXEnableBreakPt( qitm.writePtr );
210     }
211 }
212 
213 /*---------------------------------------------------------------------------*
214    VI Pre Callback (VBlank interrupt)
215 
216    The VI Pre callback should be kept minimal, since the VI registers
217    must be set before too much time passes.  Additional bookkeeping is
218    done in the VI Post callback.
219 
220  *---------------------------------------------------------------------------*/
221 
VIPreCallback(u32 retraceCount)222 void VIPreCallback ( u32 retraceCount )
223 {
224     #pragma unused (retraceCount)
225     u16 token;
226 
227     // We don't need to worry about missed tokens, since
228     // the breakpoint holds up the tokens, and the logic only
229     // allows one token out the gate at a time.
230 
231     token = GXReadDrawSync();
232 
233     // We actually need to use only 1 bit from the sync token.
234 
235     if (token == (u16) (lastVCBToken+1))
236     {
237         lastVCBToken = token;
238 
239         dispXFB = (dispXFB == myXFB1) ? myXFB2 : myXFB1;
240 
241         VISetNextFrameBuffer( dispXFB );
242         VIFlush();
243 
244         BPGo = GX_TRUE;
245     }
246 }
247 
248 /*---------------------------------------------------------------------------*
249    VI Post Callback (VBlank interrupt)
250  *---------------------------------------------------------------------------*/
251 
VIPostCallback(u32 retraceCount)252 void VIPostCallback ( u32 retraceCount )
253 {
254     #pragma unused (retraceCount)
255 
256     if (BPWait && BPGo)
257     {
258         SetNextBreakPt();
259         BPWait = GX_FALSE;
260         BPGo = GX_FALSE;
261     }
262 }
263 
264 /*---------------------------------------------------------------------------*
265    Cleanup Thread
266  *---------------------------------------------------------------------------*/
267 
CleanupThread(void * param)268 void* CleanupThread ( void* param )
269 {
270     #pragma unused (param)
271     QItem qitm;
272 
273     while(1) {
274 
275         OSSleepThread( &waitingDoneRender );
276 
277         qitm = dequeue(&DoneQ);
278 
279         // Take qitm.dataPtr and do any necessary cleanup.
280         // That is, free up any data that only needed to be
281         // around for the GP to read while rendering the frame.
282     }
283 }
284 
285 /*---------------------------------------------------------------------------*
286    Application main loop
287  *---------------------------------------------------------------------------*/
288 
main(void)289 void main ( void )
290 {
291     Mtx         v;   // view matrix
292     PADStatus   pad[PAD_MAX_CONTROLLERS]; // game pad state
293     GXFifoObj   fifoInfo;
294 
295     void* tmp_read;
296     void* tmp_write;
297     QItem qitm;
298     int   enabled;
299 
300     srand(1);
301 
302     DEMOInit(NULL);    // Init os, pad, gx, vi
303 
304     init_queue(&RenderQ);
305     init_queue(&DoneQ);
306 
307     OSInitThreadQueue( &waitingDoneRender );
308 
309     // Creates a new thread. The thread is suspended by default.
310     OSCreateThread(
311         &CUThread,                          // ptr to the thread to init
312         CleanupThread,                      // ptr to the start routine
313         0,                                  // param passed to start routine
314         CUThreadStack+sizeof(CUThreadStack),// initial stack address
315         sizeof CUThreadStack,
316         14,                                 // scheduling priority
317         OS_THREAD_ATTR_DETACH);             // detached by default
318 
319     // Starts the thread
320     OSResumeThread(&CUThread);
321 
322     myXFB1 = DemoFrameBuffer1;
323     myXFB2 = DemoFrameBuffer2;
324     dispXFB = myXFB1;
325     copyXFB = myXFB2;
326 
327     (void) VISetPreRetraceCallback(VIPreCallback);
328     (void) VISetPostRetraceCallback(VIPostCallback);
329     (void) GXSetBreakPtCallback(BPCallback);
330 
331     // The screen won't actually unblank until the first frame has
332     // been displayed (until VIFlush is called and retrace occurs).
333     VISetBlack(FALSE);
334 
335     pad[0].button = 0;
336 
337     CameraInit(v); // Initialize the camera.
338     DrawInit();    // Define my vertex formats and set array pointers.
339     PrintIntro();  // Print demo directions
340 
341     while(!(pad[0].button & PAD_BUTTON_MENU))
342     {
343         DEMOBeforeRender();
344 
345         // We must keep latency down while still keeping the FIFO full.
346         // We allow only two frames to be in the FIFO at once.
347 
348         // This is a critical section that requires no interrupts to
349         // happen in between the "if" and the "sleep".  The sleep will
350         // re-enable interrupts, allowing one to wake up this thread.
351 
352         enabled = OSDisableInterrupts();
353         if (queue_length(&RenderQ) > 1)
354         {
355             OSSleepThread( &waitingDoneRender );
356         }
357         OSRestoreInterrupts(enabled);
358 
359         // Sample inputs
360         PADRead(pad);
361 
362         // Decide what to draw
363         if (!(pad[0].button & PAD_BUTTON_X))
364         {
365             AnimTick();
366         }
367 
368         // Draw it
369         DrawTick(v);
370 
371         // End of frame code:
372         GXFlush();
373 
374         GXGetCPUFifo(&fifoInfo);
375         GXGetFifoPtrs(&fifoInfo, &tmp_read, &tmp_write);
376 
377         // Create new render queue item
378         qitm.writePtr = tmp_write;
379         qitm.dataPtr = NULL;        // pointer to frame-related user data
380         qitm.copyXFB = copyXFB;
381 
382         // Technically, you can work this such that you don't
383         // need the OSDisabled interrupts.  You need to rework
384         // the enqueue/dequeue routines a bit, though, to make
385         // them non-interfere with each other.
386 
387         enabled = OSDisableInterrupts();
388         enqueue(&RenderQ, &qitm);
389         OSRestoreInterrupts(enabled);
390 
391         if (BPSet == GX_FALSE) {
392 
393             BPSet = GX_TRUE;
394             GXEnableBreakPt( tmp_write );
395         }
396 
397         GXSetDrawSync( newToken );
398         GXCopyDisp( copyXFB, GX_TRUE);
399         GXFlush();
400 
401         newToken++;
402         copyXFB = (copyXFB == myXFB1) ? myXFB2 : myXFB1;
403     }
404 
405     OSHalt("End of test");
406 }
407 
408 
409 /*---------------------------------------------------------------------------*
410    Functions
411  *---------------------------------------------------------------------------*/
412 
413 /*---------------------------------------------------------------------------*
414     Name:           CameraInit
415 
416     Description:    Initialize the projection matrix and load into hardware.
417                     Initialize the view matrix.
418 
419     Arguments:      v      view matrix
420 
421     Returns:        none
422  *---------------------------------------------------------------------------*/
CameraInit(Mtx v)423 static void CameraInit ( Mtx v )
424 {
425     Mtx44   p;      // projection matrix
426     Vec     up      = {0.0F, 0.0F, 1.0F};
427     Vec     camLoc  = {0.25F, 4.0F, 0.5F};
428     Vec     objPt   = {0.0F, 0.0F, 0.0F};
429     f32     left    = 0.0375F;
430     f32     top     = 0.050F;
431     f32     znear   = 0.1F;
432     f32     zfar    = 10.0F;
433 
434     MTXFrustum(p, left, -left, -top, top, znear, zfar);
435     GXSetProjection(p, GX_PERSPECTIVE);
436 
437     MTXLookAt(v, &camLoc, &up, &objPt);
438 }
439 
440 /*---------------------------------------------------------------------------*
441     Name:           DrawInit
442 
443     Description:    Initializes the vertex attribute format 0, and sets
444                     the array pointers and strides for the indexed data.
445 
446     Arguments:      none
447 
448     Returns:        none
449  *---------------------------------------------------------------------------*/
DrawInit(void)450 static void DrawInit( void )
451 {
452     GXColor blue = {0, 0, 255, 0};
453 
454     GXSetCopyClear(blue, 0x00ffffff);
455 
456     // Set current vertex descriptor to enable position and color0.
457     // Both use 8b index to access their data arrays.
458     GXClearVtxDesc();
459     GXSetVtxDesc(GX_VA_POS, GX_INDEX8);
460     GXSetVtxDesc(GX_VA_CLR0, GX_INDEX8);
461 
462     // Position has 3 elements (x,y,z), each of type f32
463     GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
464 
465     // Color 0 has 4 components (r, g, b, a), each component is 8b.
466     GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
467 
468     // stride = 3 elements (x,y,z) each of type s16
469     GXSetArray(GX_VA_POS, Verts_f32, 3*sizeof(f32));
470     // stride = 4 elements (r,g,b,a) each of type u8
471     GXSetArray(GX_VA_CLR0, Colors_rgba8, 4*sizeof(u8));
472 
473     // Initialize lighting, texgen, and tev parameters
474     GXSetNumChans(1); // default, color = vertex color
475     GXSetNumTexGens(0); // no texture in this demo
476     GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
477     GXSetTevOp(GX_TEVSTAGE0, GX_PASSCLR);
478 }
479 
480 /*---------------------------------------------------------------------------*
481     Name:           Vertex
482 
483     Description:    Create my vertex format
484 
485     Arguments:      t        8-bit triangle index
486             v        8-bit vertex index
487 
488     Returns:        none
489  *---------------------------------------------------------------------------*/
Vertex(u8 t,u8 v)490 static inline void Vertex( u8 t, u8 v )
491 {
492     u8 qv = (u8) (4 * t + v);
493     GXPosition1x8(qv);
494     GXColor1x8(qv);
495 }
496 
497 /*---------------------------------------------------------------------------*
498     Name:           DrawTick
499 
500     Description:    Draw the model once.
501 
502     Arguments:      v        view matrix
503 
504     Returns:        none
505  *---------------------------------------------------------------------------*/
DrawTick(Mtx v)506 static void DrawTick( Mtx v )
507 {
508     Mtx m;  // Model matrix.
509     Mtx mv; // Modelview matrix.
510     u8  iQuad;  // index of quad
511     u8  iVert;  // index of vertex
512     u32 i,n;
513 
514     // Code for testing different alignments of the breakpoint
515     n=(u32) (rand() % 10);
516     for(i=0; i<n; i++)
517     {
518         GXCmd1u8(GX_NOP);
519     }
520 
521     GXSetNumTexGens( 0 );
522     GXSetNumTevStages( 1 );
523     GXSetTevOp( GX_TEVSTAGE0, GX_PASSCLR );
524 
525     // model has a rotation about z axis
526     MTXRotDeg(m, 'z', ticks);
527     MTXConcat(v, m, mv);
528     GXLoadPosMtxImm(mv, GX_PNMTX0);
529 
530     // Test constant timing
531     n=1;
532 
533     for(i=0; i<n; i++) {
534 
535     GXBegin(GX_QUADS, GX_VTXFMT0, 24);
536 
537     // for all triangles of octahedron, ...
538     for (iQuad = 0; iQuad < 6; ++iQuad)
539     {
540             // for all vertices of triangle, ...
541             for (iVert = 0; iVert < 4; ++iVert)
542             {
543                 Vertex(iQuad, iVert);
544             }
545     }
546 
547     GXEnd();
548     }
549 }
550 
551 /*---------------------------------------------------------------------------*
552     Name:           AnimTick
553 
554     Description:    Computes next time step.
555 
556     Arguments:      none
557 
558     Returns:        none
559  *---------------------------------------------------------------------------*/
AnimTick(void)560 static void AnimTick( void )
561 {
562     ticks = (u32) (OSTicksToMilliseconds(OSGetTick()) / 10);
563 }
564 
565 /*---------------------------------------------------------------------------*
566     Name:            PrintIntro
567 
568     Description:    Prints the directions on how to use this demo.
569 
570     Arguments:        none
571 
572     Returns:        none
573  *---------------------------------------------------------------------------*/
PrintIntro(void)574 static void PrintIntro( void )
575 {
576     OSReport("\n\n********************************\n");
577     OSReport("Press the X button to pause the animation\n");
578     OSReport("To quit:\n");
579     OSReport("     Hit the menu button\n");
580     OSReport("********************************\n");
581 }
582 
583 /*---------------------------------------------------------------------------*
584  * Quick and dirty queue implementation.
585  *---------------------------------------------------------------------------*/
586 
init_queue(Queue * q)587 void init_queue(Queue *q)
588 {
589     q->top = QUEUE_EMPTY;
590 }
591 
enqueue(Queue * q,QItem * qitm)592 void enqueue(Queue *q, QItem *qitm)
593 {
594     if (q->top == QUEUE_EMPTY)
595     {
596         q->top = q->bot = 0;
597     }
598     else
599     {
600         q->top = (u16) ((q->top+1) % QUEUE_MAX);
601 
602         if (q->top == q->bot)
603         {   // error, overflow
604             OSHalt("queue overflow");
605         }
606     }
607 
608     q->entry[q->top] = *qitm;
609 }
610 
dequeue(Queue * q)611 QItem dequeue(Queue *q)
612 {
613     u16 bot = q->bot;
614 
615     if (q->top == QUEUE_EMPTY)
616     {   // error, underflow
617         OSHalt("queue underflow");
618     }
619 
620     if (q->bot == q->top)
621     {
622         q->top = QUEUE_EMPTY;
623     }
624     else
625     {
626         q->bot = (u16) ((q->bot+1) % QUEUE_MAX);
627     }
628 
629     return q->entry[bot];
630 }
631 
queue_front(Queue * q)632 QItem queue_front(Queue *q)
633 {
634     if (q->top == QUEUE_EMPTY)
635     {   // error, queue empty
636         OSHalt("queue_top: queue empty");
637     }
638 
639     return q->entry[q->bot];
640 }
641 
queue_empty(Queue * q)642 GXBool queue_empty(Queue *q)
643 {
644     return q->top == QUEUE_EMPTY;
645 }
646 
queue_length(Queue * q)647 u32 queue_length(Queue *q)
648 {
649     if (q->top == QUEUE_EMPTY) return 0;
650 
651     if (q->top >= q->bot)
652         return (u32) ((s32) q->top - q->bot + 1);
653     else
654         return (u32) ((s32) (q->top + QUEUE_MAX) - q->bot + 1);
655 }
656