1 /*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15 */
16
17 #include "port.h"
18 #include "libs/threadlib.h"
19 #include "libs/graphics/drawcmd.h"
20 #include "libs/graphics/drawable.h"
21 #include "libs/graphics/context.h"
22 #include "libs/graphics/dcqueue.h"
23 #include "libs/graphics/gfx_common.h"
24 #include "libs/graphics/bbox.h"
25 #include "libs/timelib.h"
26 #include "libs/log.h"
27 #include "libs/misc.h"
28 // for TFB_DEBUG_HALT
29
30
31 static RecursiveMutex DCQ_Mutex;
32
33 CondVar RenderingCond;
34
35 TFB_DrawCommand DCQ[DCQ_MAX];
36
37 TFB_DrawCommandQueue DrawCommandQueue;
38
39 #define FPS_PERIOD (ONE_SECOND / 100)
40 int RenderedFrames = 0;
41
42
43 // Wait for the queue to be emptied.
44 static void
TFB_WaitForSpace(int requested_slots)45 TFB_WaitForSpace (int requested_slots)
46 {
47 int old_depth, i;
48 log_add (log_Debug, "DCQ overload (Size = %d, FullSize = %d, "
49 "Requested = %d). Sleeping until renderer is done.",
50 DrawCommandQueue.Size, DrawCommandQueue.FullSize,
51 requested_slots);
52 // Restore the DCQ locking level. I *think* this is
53 // always 1, but...
54 TFB_BatchReset ();
55 old_depth = GetRecursiveMutexDepth (DCQ_Mutex);
56 for (i = 0; i < old_depth; i++)
57 UnlockRecursiveMutex (DCQ_Mutex);
58 WaitCondVar (RenderingCond);
59 for (i = 0; i < old_depth; i++)
60 LockRecursiveMutex (DCQ_Mutex);
61 log_add (log_Debug, "DCQ clear (Size = %d, FullSize = %d). Continuing.",
62 DrawCommandQueue.Size, DrawCommandQueue.FullSize);
63 }
64
65 void
Lock_DCQ(int slots)66 Lock_DCQ (int slots)
67 {
68 LockRecursiveMutex (DCQ_Mutex);
69 while (DrawCommandQueue.FullSize >= DCQ_MAX - slots)
70 {
71 TFB_WaitForSpace (slots);
72 }
73 }
74
75 void
Unlock_DCQ(void)76 Unlock_DCQ (void)
77 {
78 UnlockRecursiveMutex (DCQ_Mutex);
79 }
80
81 // Always have the DCQ locked when calling this.
82 static void
Synchronize_DCQ(void)83 Synchronize_DCQ (void)
84 {
85 if (!DrawCommandQueue.Batching)
86 {
87 int front = DrawCommandQueue.Front;
88 int back = DrawCommandQueue.InsertionPoint;
89 DrawCommandQueue.Back = DrawCommandQueue.InsertionPoint;
90 if (front <= back)
91 {
92 DrawCommandQueue.Size = (back - front);
93 }
94 else
95 {
96 DrawCommandQueue.Size = (back + DCQ_MAX - front);
97 }
98 DrawCommandQueue.FullSize = DrawCommandQueue.Size;
99 }
100 }
101
102 void
TFB_BatchGraphics(void)103 TFB_BatchGraphics (void)
104 {
105 LockRecursiveMutex (DCQ_Mutex);
106 DrawCommandQueue.Batching++;
107 UnlockRecursiveMutex (DCQ_Mutex);
108 }
109
110 void
TFB_UnbatchGraphics(void)111 TFB_UnbatchGraphics (void)
112 {
113 LockRecursiveMutex (DCQ_Mutex);
114 if (DrawCommandQueue.Batching)
115 {
116 DrawCommandQueue.Batching--;
117 }
118 Synchronize_DCQ ();
119 UnlockRecursiveMutex (DCQ_Mutex);
120 }
121
122 // Cancel all pending batch operations, making them unbatched. This will
123 // cause a small amount of flicker when invoked, but prevents
124 // batching problems from freezing the game.
125 void
TFB_BatchReset(void)126 TFB_BatchReset (void)
127 {
128 LockRecursiveMutex (DCQ_Mutex);
129 DrawCommandQueue.Batching = 0;
130 Synchronize_DCQ ();
131 UnlockRecursiveMutex (DCQ_Mutex);
132 }
133
134
135 // Draw Command Queue Stuff
136
137 void
Init_DrawCommandQueue(void)138 Init_DrawCommandQueue (void)
139 {
140 DrawCommandQueue.Back = 0;
141 DrawCommandQueue.Front = 0;
142 DrawCommandQueue.InsertionPoint = 0;
143 DrawCommandQueue.Batching = 0;
144 DrawCommandQueue.FullSize = 0;
145 DrawCommandQueue.Size = 0;
146
147 TFB_BBox_Init (ScreenWidth, ScreenHeight);
148
149 DCQ_Mutex = CreateRecursiveMutex ("DCQ",
150 SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO);
151
152 RenderingCond = CreateCondVar ("DCQ empty",
153 SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO);
154 }
155
156 void
Uninit_DrawCommandQueue(void)157 Uninit_DrawCommandQueue (void)
158 {
159 if (RenderingCond)
160 {
161 DestroyCondVar (RenderingCond);
162 RenderingCond = 0;
163 }
164
165 if (DCQ_Mutex)
166 {
167 DestroyRecursiveMutex (DCQ_Mutex);
168 DCQ_Mutex = 0;
169 }
170 }
171
172 void
TFB_DrawCommandQueue_Push(TFB_DrawCommand * Command)173 TFB_DrawCommandQueue_Push (TFB_DrawCommand* Command)
174 {
175 Lock_DCQ (1);
176 DCQ[DrawCommandQueue.InsertionPoint] = *Command;
177 DrawCommandQueue.InsertionPoint = (DrawCommandQueue.InsertionPoint + 1)
178 % DCQ_MAX;
179 DrawCommandQueue.FullSize++;
180 Synchronize_DCQ ();
181 Unlock_DCQ ();
182 }
183
184 int
TFB_DrawCommandQueue_Pop(TFB_DrawCommand * target)185 TFB_DrawCommandQueue_Pop (TFB_DrawCommand *target)
186 {
187 LockRecursiveMutex (DCQ_Mutex);
188
189 if (DrawCommandQueue.Size == 0)
190 {
191 Unlock_DCQ ();
192 return (0);
193 }
194
195 if (DrawCommandQueue.Front == DrawCommandQueue.Back &&
196 DrawCommandQueue.Size != DCQ_MAX)
197 {
198 log_add (log_Debug, "Augh! Assertion failure in DCQ! "
199 "Front == Back, Size != DCQ_MAX");
200 DrawCommandQueue.Size = 0;
201 Unlock_DCQ ();
202 return (0);
203 }
204
205 *target = DCQ[DrawCommandQueue.Front];
206 DrawCommandQueue.Front = (DrawCommandQueue.Front + 1) % DCQ_MAX;
207
208 DrawCommandQueue.Size--;
209 DrawCommandQueue.FullSize--;
210 UnlockRecursiveMutex (DCQ_Mutex);
211
212 return 1;
213 }
214
215 void
TFB_DrawCommandQueue_Clear()216 TFB_DrawCommandQueue_Clear ()
217 {
218 LockRecursiveMutex (DCQ_Mutex);
219 DrawCommandQueue.Size = 0;
220 DrawCommandQueue.Front = 0;
221 DrawCommandQueue.Back = 0;
222 DrawCommandQueue.Batching = 0;
223 DrawCommandQueue.FullSize = 0;
224 DrawCommandQueue.InsertionPoint = 0;
225 UnlockRecursiveMutex (DCQ_Mutex);
226 }
227
228 static void
checkExclusiveThread(TFB_DrawCommand * DrawCommand)229 checkExclusiveThread (TFB_DrawCommand* DrawCommand)
230 {
231 #ifdef DEBUG_DCQ_THREADS
232 static uint32 exclusiveThreadId;
233 extern uint32 SDL_ThreadID(void);
234
235 // Only one thread is currently allowed to enqueue commands
236 // This is not a technical limitation but rather a semantical one atm.
237 if (DrawCommand->Type == TFB_DRAWCOMMANDTYPE_REINITVIDEO)
238 { // TFB_DRAWCOMMANDTYPE_REINITVIDEO is an exception
239 // It is queued from the main() thread, which is safe to do
240 return;
241 }
242
243 if (!exclusiveThreadId)
244 exclusiveThreadId = SDL_ThreadID();
245 else
246 assert (SDL_ThreadID() == exclusiveThreadId);
247 #else
248 (void) DrawCommand; // suppress unused warning
249 #endif
250 }
251
252 void
TFB_EnqueueDrawCommand(TFB_DrawCommand * DrawCommand)253 TFB_EnqueueDrawCommand (TFB_DrawCommand* DrawCommand)
254 {
255 if (TFB_DEBUG_HALT)
256 {
257 return;
258 }
259
260 checkExclusiveThread (DrawCommand);
261
262 if (DrawCommand->Type <= TFB_DRAWCOMMANDTYPE_COPYTOIMAGE
263 && _CurFramePtr->Type == SCREEN_DRAWABLE)
264 {
265 static RECT scissor_rect;
266
267 // Set the clipping region.
268 // We allow drawing with no current context set, so the whole screen
269 if ((_pCurContext && !rectsEqual (scissor_rect, _pCurContext->ClipRect))
270 || (!_pCurContext && scissor_rect.extent.width != 0))
271 {
272 // Enqueue command to set the glScissor spec
273 TFB_DrawCommand DC;
274
275 if (_pCurContext)
276 scissor_rect = _pCurContext->ClipRect;
277 else
278 scissor_rect.extent.width = 0;
279
280 if (scissor_rect.extent.width)
281 {
282 DC.Type = TFB_DRAWCOMMANDTYPE_SCISSORENABLE;
283 DC.data.scissor.rect = scissor_rect;
284 }
285 else
286 {
287 DC.Type = TFB_DRAWCOMMANDTYPE_SCISSORDISABLE;
288 }
289
290 TFB_EnqueueDrawCommand(&DC);
291 }
292 }
293
294 TFB_DrawCommandQueue_Push (DrawCommand);
295 }
296
297 static void
computeFPS(void)298 computeFPS (void)
299 {
300 static TimeCount last_time;
301 static TimePeriod fps_counter;
302 TimeCount current_time;
303 TimePeriod delta_time;
304
305 current_time = GetTimeCounter ();
306 delta_time = current_time - last_time;
307 last_time = current_time;
308
309 fps_counter += delta_time;
310 if (fps_counter > FPS_PERIOD)
311 {
312 log_add (log_User, "fps %.2f, effective %.2f",
313 (float)ONE_SECOND / delta_time,
314 (float)ONE_SECOND * RenderedFrames / fps_counter);
315
316 fps_counter = 0;
317 RenderedFrames = 0;
318 }
319 }
320
321 // Only call from main() thread!!
322 void
TFB_FlushGraphics(void)323 TFB_FlushGraphics (void)
324 {
325 int commands_handled;
326 BOOLEAN livelock_deterrence;
327
328 // This is technically a locking violation on DrawCommandQueue.Size,
329 // but it is likely to not be very destructive.
330 if (DrawCommandQueue.Size == 0)
331 {
332 static int last_fade = 255;
333 static int last_transition = 255;
334 int current_fade = GetFadeAmount ();
335 int current_transition = TransitionAmount;
336
337 if ((current_fade != 255 && current_fade != last_fade) ||
338 (current_transition != 255 &&
339 current_transition != last_transition) ||
340 (current_fade == 255 && last_fade != 255) ||
341 (current_transition == 255 && last_transition != 255))
342 {
343 TFB_SwapBuffers (TFB_REDRAW_FADING);
344 // if fading, redraw every frame
345 }
346 else
347 {
348 TaskSwitch ();
349 }
350
351 last_fade = current_fade;
352 last_transition = current_transition;
353 BroadcastCondVar (RenderingCond);
354 return;
355 }
356
357 if (GfxFlags & TFB_GFXFLAGS_SHOWFPS)
358 computeFPS ();
359
360 commands_handled = 0;
361 livelock_deterrence = FALSE;
362
363 if (DrawCommandQueue.FullSize > DCQ_FORCE_BREAK_SIZE)
364 {
365 TFB_BatchReset ();
366 }
367
368 if (DrawCommandQueue.Size > DCQ_FORCE_SLOWDOWN_SIZE)
369 {
370 Lock_DCQ (-1);
371 livelock_deterrence = TRUE;
372 }
373
374 TFB_BBox_Reset ();
375
376 for (;;)
377 {
378 TFB_DrawCommand DC;
379
380 if (!TFB_DrawCommandQueue_Pop (&DC))
381 {
382 // the Queue is now empty.
383 break;
384 }
385
386 ++commands_handled;
387 if (!livelock_deterrence && commands_handled + DrawCommandQueue.Size
388 > DCQ_LIVELOCK_MAX)
389 {
390 // log_add (log_Debug, "Initiating livelock deterrence!");
391 livelock_deterrence = TRUE;
392
393 Lock_DCQ (-1);
394 }
395
396 switch (DC.Type)
397 {
398 case TFB_DRAWCOMMANDTYPE_SETMIPMAP:
399 {
400 TFB_DrawCommand_SetMipmap *cmd = &DC.data.setmipmap;
401 TFB_DrawImage_SetMipmap (cmd->image, cmd->mipmap,
402 cmd->hotx, cmd->hoty);
403 break;
404 }
405
406 case TFB_DRAWCOMMANDTYPE_IMAGE:
407 {
408 TFB_DrawCommand_Image *cmd = &DC.data.image;
409 TFB_Image *DC_image = cmd->image;
410 const int x = cmd->x;
411 const int y = cmd->y;
412
413 TFB_DrawCanvas_Image (DC_image, x, y,
414 cmd->scale, cmd->scaleMode, cmd->colormap,
415 cmd->drawMode,
416 TFB_GetScreenCanvas (cmd->destBuffer));
417
418 if (cmd->destBuffer == TFB_SCREEN_MAIN)
419 {
420 LockMutex (DC_image->mutex);
421 if (cmd->scale)
422 TFB_BBox_RegisterCanvas (DC_image->ScaledImg,
423 x - DC_image->last_scale_hs.x,
424 y - DC_image->last_scale_hs.y);
425 else
426 TFB_BBox_RegisterCanvas (DC_image->NormalImg,
427 x - DC_image->NormalHs.x,
428 y - DC_image->NormalHs.y);
429 UnlockMutex (DC_image->mutex);
430 }
431
432 break;
433 }
434
435 case TFB_DRAWCOMMANDTYPE_FILLEDIMAGE:
436 {
437 TFB_DrawCommand_FilledImage *cmd = &DC.data.filledimage;
438 TFB_Image *DC_image = cmd->image;
439 const int x = cmd->x;
440 const int y = cmd->y;
441
442 TFB_DrawCanvas_FilledImage (DC_image, x, y,
443 cmd->scale, cmd->scaleMode, cmd->color,
444 cmd->drawMode,
445 TFB_GetScreenCanvas (cmd->destBuffer));
446
447 if (cmd->destBuffer == TFB_SCREEN_MAIN)
448 {
449 LockMutex (DC_image->mutex);
450 if (cmd->scale)
451 TFB_BBox_RegisterCanvas (DC_image->ScaledImg,
452 x - DC_image->last_scale_hs.x,
453 y - DC_image->last_scale_hs.y);
454 else
455 TFB_BBox_RegisterCanvas (DC_image->NormalImg,
456 x - DC_image->NormalHs.x,
457 y - DC_image->NormalHs.y);
458 UnlockMutex (DC_image->mutex);
459 }
460
461 break;
462 }
463
464 case TFB_DRAWCOMMANDTYPE_FONTCHAR:
465 {
466 TFB_DrawCommand_FontChar *cmd = &DC.data.fontchar;
467 TFB_Char *DC_char = cmd->fontchar;
468 const int x = cmd->x;
469 const int y = cmd->y;
470
471 TFB_DrawCanvas_FontChar (DC_char, cmd->backing, x, y,
472 cmd->drawMode, TFB_GetScreenCanvas (cmd->destBuffer));
473
474 if (cmd->destBuffer == TFB_SCREEN_MAIN)
475 {
476 RECT r;
477
478 r.corner.x = x - DC_char->HotSpot.x;
479 r.corner.y = y - DC_char->HotSpot.y;
480 r.extent.width = DC_char->extent.width;
481 r.extent.height = DC_char->extent.height;
482
483 TFB_BBox_RegisterRect (&r);
484 }
485
486 break;
487 }
488
489 case TFB_DRAWCOMMANDTYPE_LINE:
490 {
491 TFB_DrawCommand_Line *cmd = &DC.data.line;
492
493 if (cmd->destBuffer == TFB_SCREEN_MAIN)
494 {
495 TFB_BBox_RegisterPoint (cmd->x1, cmd->y1);
496 TFB_BBox_RegisterPoint (cmd->x2, cmd->y2);
497 }
498 TFB_DrawCanvas_Line (cmd->x1, cmd->y1, cmd->x2, cmd->y2,
499 cmd->color, cmd->drawMode,
500 TFB_GetScreenCanvas (cmd->destBuffer));
501 break;
502 }
503
504 case TFB_DRAWCOMMANDTYPE_RECTANGLE:
505 {
506 TFB_DrawCommand_Rect *cmd = &DC.data.rect;
507
508 if (cmd->destBuffer == TFB_SCREEN_MAIN)
509 TFB_BBox_RegisterRect (&cmd->rect);
510 TFB_DrawCanvas_Rect (&cmd->rect, cmd->color, cmd->drawMode,
511 TFB_GetScreenCanvas (cmd->destBuffer));
512
513 break;
514 }
515
516 case TFB_DRAWCOMMANDTYPE_SCISSORENABLE:
517 {
518 TFB_DrawCommand_Scissor *cmd = &DC.data.scissor;
519
520 TFB_DrawCanvas_SetClipRect (
521 TFB_GetScreenCanvas (TFB_SCREEN_MAIN), &cmd->rect);
522 TFB_BBox_SetClipRect (&DC.data.scissor.rect);
523 break;
524 }
525
526 case TFB_DRAWCOMMANDTYPE_SCISSORDISABLE:
527 TFB_DrawCanvas_SetClipRect (
528 TFB_GetScreenCanvas (TFB_SCREEN_MAIN), NULL);
529 TFB_BBox_SetClipRect (NULL);
530 break;
531
532 case TFB_DRAWCOMMANDTYPE_COPYTOIMAGE:
533 {
534 TFB_DrawCommand_CopyToImage *cmd = &DC.data.copytoimage;
535 TFB_Image *DC_image = cmd->image;
536 const POINT dstPt = {0, 0};
537
538 if (DC_image == 0)
539 {
540 log_add (log_Debug, "DCQ ERROR: COPYTOIMAGE passed null "
541 "image ptr");
542 break;
543 }
544 LockMutex (DC_image->mutex);
545 TFB_DrawCanvas_CopyRect (
546 TFB_GetScreenCanvas (cmd->srcBuffer), &cmd->rect,
547 DC_image->NormalImg, dstPt);
548 UnlockMutex (DC_image->mutex);
549 break;
550 }
551
552 case TFB_DRAWCOMMANDTYPE_COPY:
553 {
554 TFB_DrawCommand_Copy *cmd = &DC.data.copy;
555 const RECT r = cmd->rect;
556
557 if (cmd->destBuffer == TFB_SCREEN_MAIN)
558 TFB_BBox_RegisterRect (&cmd->rect);
559
560 TFB_DrawCanvas_CopyRect (
561 TFB_GetScreenCanvas (cmd->srcBuffer), &r,
562 TFB_GetScreenCanvas (cmd->destBuffer), r.corner);
563 break;
564 }
565
566 case TFB_DRAWCOMMANDTYPE_DELETEIMAGE:
567 {
568 TFB_Image *DC_image = DC.data.deleteimage.image;
569 TFB_DrawImage_Delete (DC_image);
570 break;
571 }
572
573 case TFB_DRAWCOMMANDTYPE_DELETEDATA:
574 {
575 void *data = DC.data.deletedata.data;
576 HFree (data);
577 break;
578 }
579
580 case TFB_DRAWCOMMANDTYPE_SENDSIGNAL:
581 ClearSemaphore (DC.data.sendsignal.sem);
582 break;
583
584 case TFB_DRAWCOMMANDTYPE_REINITVIDEO:
585 {
586 TFB_DrawCommand_ReinitVideo *cmd = &DC.data.reinitvideo;
587 int oldDriver = GraphicsDriver;
588 int oldFlags = GfxFlags;
589 int oldWidth = ScreenWidthActual;
590 int oldHeight = ScreenHeightActual;
591 if (TFB_ReInitGraphics (cmd->driver, cmd->flags,
592 cmd->width, cmd->height))
593 {
594 log_add (log_Error, "Could not provide requested mode: "
595 "reverting to last known driver.");
596 // We don't know what exactly failed, so roll it all back
597 if (TFB_ReInitGraphics (oldDriver, oldFlags,
598 oldWidth, oldHeight))
599 {
600 log_add (log_Fatal,
601 "Couldn't reinit at that point either. "
602 "Your video has been somehow tied in knots.");
603 exit (EXIT_FAILURE);
604 }
605 }
606 TFB_SwapBuffers (TFB_REDRAW_YES);
607 break;
608 }
609
610 case TFB_DRAWCOMMANDTYPE_CALLBACK:
611 {
612 DC.data.callback.callback (DC.data.callback.arg);
613 break;
614 }
615 }
616 }
617
618 if (livelock_deterrence)
619 Unlock_DCQ ();
620
621 TFB_SwapBuffers (TFB_REDRAW_NO);
622 RenderedFrames++;
623 BroadcastCondVar (RenderingCond);
624 }
625
626 void
TFB_PurgeDanglingGraphics(void)627 TFB_PurgeDanglingGraphics (void)
628 {
629 Lock_DCQ (-1);
630
631 for (;;)
632 {
633 TFB_DrawCommand DC;
634
635 if (!TFB_DrawCommandQueue_Pop (&DC))
636 {
637 // the Queue is now empty.
638 break;
639 }
640
641 switch (DC.Type)
642 {
643 case TFB_DRAWCOMMANDTYPE_DELETEIMAGE:
644 {
645 TFB_Image *DC_image = DC.data.deleteimage.image;
646 TFB_DrawImage_Delete (DC_image);
647 break;
648 }
649 case TFB_DRAWCOMMANDTYPE_DELETEDATA:
650 {
651 void *data = DC.data.deletedata.data;
652 HFree (data);
653 break;
654 }
655 case TFB_DRAWCOMMANDTYPE_IMAGE:
656 {
657 TFB_ColorMap *cmap = DC.data.image.colormap;
658 if (cmap)
659 TFB_ReturnColorMap (cmap);
660 break;
661 }
662 case TFB_DRAWCOMMANDTYPE_SENDSIGNAL:
663 {
664 ClearSemaphore (DC.data.sendsignal.sem);
665 break;
666 }
667 }
668 }
669 Unlock_DCQ ();
670 }
671