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