summaryrefslogtreecommitdiff
path: root/src/libs/graphics/dcqueue.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/graphics/dcqueue.c')
-rw-r--r--src/libs/graphics/dcqueue.c670
1 files changed, 670 insertions, 0 deletions
diff --git a/src/libs/graphics/dcqueue.c b/src/libs/graphics/dcqueue.c
new file mode 100644
index 0000000..70c0662
--- /dev/null
+++ b/src/libs/graphics/dcqueue.c
@@ -0,0 +1,670 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "port.h"
+#include "libs/threadlib.h"
+#include "libs/graphics/drawcmd.h"
+#include "libs/graphics/drawable.h"
+#include "libs/graphics/context.h"
+#include "libs/graphics/dcqueue.h"
+#include "libs/graphics/gfx_common.h"
+#include "libs/graphics/bbox.h"
+#include "libs/timelib.h"
+#include "libs/log.h"
+#include "libs/misc.h"
+ // for TFB_DEBUG_HALT
+
+
+static RecursiveMutex DCQ_Mutex;
+
+CondVar RenderingCond;
+
+TFB_DrawCommand DCQ[DCQ_MAX];
+
+TFB_DrawCommandQueue DrawCommandQueue;
+
+#define FPS_PERIOD (ONE_SECOND / 100)
+int RenderedFrames = 0;
+
+
+// Wait for the queue to be emptied.
+static void
+TFB_WaitForSpace (int requested_slots)
+{
+ int old_depth, i;
+ log_add (log_Debug, "DCQ overload (Size = %d, FullSize = %d, "
+ "Requested = %d). Sleeping until renderer is done.",
+ DrawCommandQueue.Size, DrawCommandQueue.FullSize,
+ requested_slots);
+ // Restore the DCQ locking level. I *think* this is
+ // always 1, but...
+ TFB_BatchReset ();
+ old_depth = GetRecursiveMutexDepth (DCQ_Mutex);
+ for (i = 0; i < old_depth; i++)
+ UnlockRecursiveMutex (DCQ_Mutex);
+ WaitCondVar (RenderingCond);
+ for (i = 0; i < old_depth; i++)
+ LockRecursiveMutex (DCQ_Mutex);
+ log_add (log_Debug, "DCQ clear (Size = %d, FullSize = %d). Continuing.",
+ DrawCommandQueue.Size, DrawCommandQueue.FullSize);
+}
+
+void
+Lock_DCQ (int slots)
+{
+ LockRecursiveMutex (DCQ_Mutex);
+ while (DrawCommandQueue.FullSize >= DCQ_MAX - slots)
+ {
+ TFB_WaitForSpace (slots);
+ }
+}
+
+void
+Unlock_DCQ (void)
+{
+ UnlockRecursiveMutex (DCQ_Mutex);
+}
+
+// Always have the DCQ locked when calling this.
+static void
+Synchronize_DCQ (void)
+{
+ if (!DrawCommandQueue.Batching)
+ {
+ int front = DrawCommandQueue.Front;
+ int back = DrawCommandQueue.InsertionPoint;
+ DrawCommandQueue.Back = DrawCommandQueue.InsertionPoint;
+ if (front <= back)
+ {
+ DrawCommandQueue.Size = (back - front);
+ }
+ else
+ {
+ DrawCommandQueue.Size = (back + DCQ_MAX - front);
+ }
+ DrawCommandQueue.FullSize = DrawCommandQueue.Size;
+ }
+}
+
+void
+TFB_BatchGraphics (void)
+{
+ LockRecursiveMutex (DCQ_Mutex);
+ DrawCommandQueue.Batching++;
+ UnlockRecursiveMutex (DCQ_Mutex);
+}
+
+void
+TFB_UnbatchGraphics (void)
+{
+ LockRecursiveMutex (DCQ_Mutex);
+ if (DrawCommandQueue.Batching)
+ {
+ DrawCommandQueue.Batching--;
+ }
+ Synchronize_DCQ ();
+ UnlockRecursiveMutex (DCQ_Mutex);
+}
+
+// Cancel all pending batch operations, making them unbatched. This will
+// cause a small amount of flicker when invoked, but prevents
+// batching problems from freezing the game.
+void
+TFB_BatchReset (void)
+{
+ LockRecursiveMutex (DCQ_Mutex);
+ DrawCommandQueue.Batching = 0;
+ Synchronize_DCQ ();
+ UnlockRecursiveMutex (DCQ_Mutex);
+}
+
+
+// Draw Command Queue Stuff
+
+void
+Init_DrawCommandQueue (void)
+{
+ DrawCommandQueue.Back = 0;
+ DrawCommandQueue.Front = 0;
+ DrawCommandQueue.InsertionPoint = 0;
+ DrawCommandQueue.Batching = 0;
+ DrawCommandQueue.FullSize = 0;
+ DrawCommandQueue.Size = 0;
+
+ TFB_BBox_Init (ScreenWidth, ScreenHeight);
+
+ DCQ_Mutex = CreateRecursiveMutex ("DCQ",
+ SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO);
+
+ RenderingCond = CreateCondVar ("DCQ empty",
+ SYNC_CLASS_TOPLEVEL | SYNC_CLASS_VIDEO);
+}
+
+void
+Uninit_DrawCommandQueue (void)
+{
+ if (RenderingCond)
+ {
+ DestroyCondVar (RenderingCond);
+ RenderingCond = 0;
+ }
+
+ if (DCQ_Mutex)
+ {
+ DestroyRecursiveMutex (DCQ_Mutex);
+ DCQ_Mutex = 0;
+ }
+}
+
+void
+TFB_DrawCommandQueue_Push (TFB_DrawCommand* Command)
+{
+ Lock_DCQ (1);
+ DCQ[DrawCommandQueue.InsertionPoint] = *Command;
+ DrawCommandQueue.InsertionPoint = (DrawCommandQueue.InsertionPoint + 1)
+ % DCQ_MAX;
+ DrawCommandQueue.FullSize++;
+ Synchronize_DCQ ();
+ Unlock_DCQ ();
+}
+
+int
+TFB_DrawCommandQueue_Pop (TFB_DrawCommand *target)
+{
+ LockRecursiveMutex (DCQ_Mutex);
+
+ if (DrawCommandQueue.Size == 0)
+ {
+ Unlock_DCQ ();
+ return (0);
+ }
+
+ if (DrawCommandQueue.Front == DrawCommandQueue.Back &&
+ DrawCommandQueue.Size != DCQ_MAX)
+ {
+ log_add (log_Debug, "Augh! Assertion failure in DCQ! "
+ "Front == Back, Size != DCQ_MAX");
+ DrawCommandQueue.Size = 0;
+ Unlock_DCQ ();
+ return (0);
+ }
+
+ *target = DCQ[DrawCommandQueue.Front];
+ DrawCommandQueue.Front = (DrawCommandQueue.Front + 1) % DCQ_MAX;
+
+ DrawCommandQueue.Size--;
+ DrawCommandQueue.FullSize--;
+ UnlockRecursiveMutex (DCQ_Mutex);
+
+ return 1;
+}
+
+void
+TFB_DrawCommandQueue_Clear ()
+{
+ LockRecursiveMutex (DCQ_Mutex);
+ DrawCommandQueue.Size = 0;
+ DrawCommandQueue.Front = 0;
+ DrawCommandQueue.Back = 0;
+ DrawCommandQueue.Batching = 0;
+ DrawCommandQueue.FullSize = 0;
+ DrawCommandQueue.InsertionPoint = 0;
+ UnlockRecursiveMutex (DCQ_Mutex);
+}
+
+static void
+checkExclusiveThread (TFB_DrawCommand* DrawCommand)
+{
+#ifdef DEBUG_DCQ_THREADS
+ static uint32 exclusiveThreadId;
+ extern uint32 SDL_ThreadID(void);
+
+ // Only one thread is currently allowed to enqueue commands
+ // This is not a technical limitation but rather a semantical one atm.
+ if (DrawCommand->Type == TFB_DRAWCOMMANDTYPE_REINITVIDEO)
+ { // TFB_DRAWCOMMANDTYPE_REINITVIDEO is an exception
+ // It is queued from the main() thread, which is safe to do
+ return;
+ }
+
+ if (!exclusiveThreadId)
+ exclusiveThreadId = SDL_ThreadID();
+ else
+ assert (SDL_ThreadID() == exclusiveThreadId);
+#else
+ (void) DrawCommand; // suppress unused warning
+#endif
+}
+
+void
+TFB_EnqueueDrawCommand (TFB_DrawCommand* DrawCommand)
+{
+ if (TFB_DEBUG_HALT)
+ {
+ return;
+ }
+
+ checkExclusiveThread (DrawCommand);
+
+ if (DrawCommand->Type <= TFB_DRAWCOMMANDTYPE_COPYTOIMAGE
+ && _CurFramePtr->Type == SCREEN_DRAWABLE)
+ {
+ static RECT scissor_rect;
+
+ // Set the clipping region.
+ // We allow drawing with no current context set, so the whole screen
+ if ((_pCurContext && !rectsEqual (scissor_rect, _pCurContext->ClipRect))
+ || (!_pCurContext && scissor_rect.extent.width != 0))
+ {
+ // Enqueue command to set the glScissor spec
+ TFB_DrawCommand DC;
+
+ if (_pCurContext)
+ scissor_rect = _pCurContext->ClipRect;
+ else
+ scissor_rect.extent.width = 0;
+
+ if (scissor_rect.extent.width)
+ {
+ DC.Type = TFB_DRAWCOMMANDTYPE_SCISSORENABLE;
+ DC.data.scissor.rect = scissor_rect;
+ }
+ else
+ {
+ DC.Type = TFB_DRAWCOMMANDTYPE_SCISSORDISABLE;
+ }
+
+ TFB_EnqueueDrawCommand(&DC);
+ }
+ }
+
+ TFB_DrawCommandQueue_Push (DrawCommand);
+}
+
+static void
+computeFPS (void)
+{
+ static TimeCount last_time;
+ static TimePeriod fps_counter;
+ TimeCount current_time;
+ TimePeriod delta_time;
+
+ current_time = GetTimeCounter ();
+ delta_time = current_time - last_time;
+ last_time = current_time;
+
+ fps_counter += delta_time;
+ if (fps_counter > FPS_PERIOD)
+ {
+ log_add (log_User, "fps %.2f, effective %.2f",
+ (float)ONE_SECOND / delta_time,
+ (float)ONE_SECOND * RenderedFrames / fps_counter);
+
+ fps_counter = 0;
+ RenderedFrames = 0;
+ }
+}
+
+// Only call from main() thread!!
+void
+TFB_FlushGraphics (void)
+{
+ int commands_handled;
+ BOOLEAN livelock_deterrence;
+
+ // This is technically a locking violation on DrawCommandQueue.Size,
+ // but it is likely to not be very destructive.
+ if (DrawCommandQueue.Size == 0)
+ {
+ static int last_fade = 255;
+ static int last_transition = 255;
+ int current_fade = GetFadeAmount ();
+ int current_transition = TransitionAmount;
+
+ if ((current_fade != 255 && current_fade != last_fade) ||
+ (current_transition != 255 &&
+ current_transition != last_transition) ||
+ (current_fade == 255 && last_fade != 255) ||
+ (current_transition == 255 && last_transition != 255))
+ {
+ TFB_SwapBuffers (TFB_REDRAW_FADING);
+ // if fading, redraw every frame
+ }
+ else
+ {
+ TaskSwitch ();
+ }
+
+ last_fade = current_fade;
+ last_transition = current_transition;
+ BroadcastCondVar (RenderingCond);
+ return;
+ }
+
+ if (GfxFlags & TFB_GFXFLAGS_SHOWFPS)
+ computeFPS ();
+
+ commands_handled = 0;
+ livelock_deterrence = FALSE;
+
+ if (DrawCommandQueue.FullSize > DCQ_FORCE_BREAK_SIZE)
+ {
+ TFB_BatchReset ();
+ }
+
+ if (DrawCommandQueue.Size > DCQ_FORCE_SLOWDOWN_SIZE)
+ {
+ Lock_DCQ (-1);
+ livelock_deterrence = TRUE;
+ }
+
+ TFB_BBox_Reset ();
+
+ for (;;)
+ {
+ TFB_DrawCommand DC;
+
+ if (!TFB_DrawCommandQueue_Pop (&DC))
+ {
+ // the Queue is now empty.
+ break;
+ }
+
+ ++commands_handled;
+ if (!livelock_deterrence && commands_handled + DrawCommandQueue.Size
+ > DCQ_LIVELOCK_MAX)
+ {
+ // log_add (log_Debug, "Initiating livelock deterrence!");
+ livelock_deterrence = TRUE;
+
+ Lock_DCQ (-1);
+ }
+
+ switch (DC.Type)
+ {
+ case TFB_DRAWCOMMANDTYPE_SETMIPMAP:
+ {
+ TFB_DrawCommand_SetMipmap *cmd = &DC.data.setmipmap;
+ TFB_DrawImage_SetMipmap (cmd->image, cmd->mipmap,
+ cmd->hotx, cmd->hoty);
+ break;
+ }
+
+ case TFB_DRAWCOMMANDTYPE_IMAGE:
+ {
+ TFB_DrawCommand_Image *cmd = &DC.data.image;
+ TFB_Image *DC_image = cmd->image;
+ const int x = cmd->x;
+ const int y = cmd->y;
+
+ TFB_DrawCanvas_Image (DC_image, x, y,
+ cmd->scale, cmd->scaleMode, cmd->colormap,
+ cmd->drawMode,
+ TFB_GetScreenCanvas (cmd->destBuffer));
+
+ if (cmd->destBuffer == TFB_SCREEN_MAIN)
+ {
+ LockMutex (DC_image->mutex);
+ if (cmd->scale)
+ TFB_BBox_RegisterCanvas (DC_image->ScaledImg,
+ x - DC_image->last_scale_hs.x,
+ y - DC_image->last_scale_hs.y);
+ else
+ TFB_BBox_RegisterCanvas (DC_image->NormalImg,
+ x - DC_image->NormalHs.x,
+ y - DC_image->NormalHs.y);
+ UnlockMutex (DC_image->mutex);
+ }
+
+ break;
+ }
+
+ case TFB_DRAWCOMMANDTYPE_FILLEDIMAGE:
+ {
+ TFB_DrawCommand_FilledImage *cmd = &DC.data.filledimage;
+ TFB_Image *DC_image = cmd->image;
+ const int x = cmd->x;
+ const int y = cmd->y;
+
+ TFB_DrawCanvas_FilledImage (DC_image, x, y,
+ cmd->scale, cmd->scaleMode, cmd->color,
+ cmd->drawMode,
+ TFB_GetScreenCanvas (cmd->destBuffer));
+
+ if (cmd->destBuffer == TFB_SCREEN_MAIN)
+ {
+ LockMutex (DC_image->mutex);
+ if (cmd->scale)
+ TFB_BBox_RegisterCanvas (DC_image->ScaledImg,
+ x - DC_image->last_scale_hs.x,
+ y - DC_image->last_scale_hs.y);
+ else
+ TFB_BBox_RegisterCanvas (DC_image->NormalImg,
+ x - DC_image->NormalHs.x,
+ y - DC_image->NormalHs.y);
+ UnlockMutex (DC_image->mutex);
+ }
+
+ break;
+ }
+
+ case TFB_DRAWCOMMANDTYPE_FONTCHAR:
+ {
+ TFB_DrawCommand_FontChar *cmd = &DC.data.fontchar;
+ TFB_Char *DC_char = cmd->fontchar;
+ const int x = cmd->x;
+ const int y = cmd->y;
+
+ TFB_DrawCanvas_FontChar (DC_char, cmd->backing, x, y,
+ cmd->drawMode, TFB_GetScreenCanvas (cmd->destBuffer));
+
+ if (cmd->destBuffer == TFB_SCREEN_MAIN)
+ {
+ RECT r;
+
+ r.corner.x = x - DC_char->HotSpot.x;
+ r.corner.y = y - DC_char->HotSpot.y;
+ r.extent.width = DC_char->extent.width;
+ r.extent.height = DC_char->extent.height;
+
+ TFB_BBox_RegisterRect (&r);
+ }
+
+ break;
+ }
+
+ case TFB_DRAWCOMMANDTYPE_LINE:
+ {
+ TFB_DrawCommand_Line *cmd = &DC.data.line;
+
+ if (cmd->destBuffer == TFB_SCREEN_MAIN)
+ {
+ TFB_BBox_RegisterPoint (cmd->x1, cmd->y1);
+ TFB_BBox_RegisterPoint (cmd->x2, cmd->y2);
+ }
+ TFB_DrawCanvas_Line (cmd->x1, cmd->y1, cmd->x2, cmd->y2,
+ cmd->color, cmd->drawMode,
+ TFB_GetScreenCanvas (cmd->destBuffer));
+ break;
+ }
+
+ case TFB_DRAWCOMMANDTYPE_RECTANGLE:
+ {
+ TFB_DrawCommand_Rect *cmd = &DC.data.rect;
+
+ if (cmd->destBuffer == TFB_SCREEN_MAIN)
+ TFB_BBox_RegisterRect (&cmd->rect);
+ TFB_DrawCanvas_Rect (&cmd->rect, cmd->color, cmd->drawMode,
+ TFB_GetScreenCanvas (cmd->destBuffer));
+
+ break;
+ }
+
+ case TFB_DRAWCOMMANDTYPE_SCISSORENABLE:
+ {
+ TFB_DrawCommand_Scissor *cmd = &DC.data.scissor;
+
+ TFB_DrawCanvas_SetClipRect (
+ TFB_GetScreenCanvas (TFB_SCREEN_MAIN), &cmd->rect);
+ TFB_BBox_SetClipRect (&DC.data.scissor.rect);
+ break;
+ }
+
+ case TFB_DRAWCOMMANDTYPE_SCISSORDISABLE:
+ TFB_DrawCanvas_SetClipRect (
+ TFB_GetScreenCanvas (TFB_SCREEN_MAIN), NULL);
+ TFB_BBox_SetClipRect (NULL);
+ break;
+
+ case TFB_DRAWCOMMANDTYPE_COPYTOIMAGE:
+ {
+ TFB_DrawCommand_CopyToImage *cmd = &DC.data.copytoimage;
+ TFB_Image *DC_image = cmd->image;
+ const POINT dstPt = {0, 0};
+
+ if (DC_image == 0)
+ {
+ log_add (log_Debug, "DCQ ERROR: COPYTOIMAGE passed null "
+ "image ptr");
+ break;
+ }
+ LockMutex (DC_image->mutex);
+ TFB_DrawCanvas_CopyRect (
+ TFB_GetScreenCanvas (cmd->srcBuffer), &cmd->rect,
+ DC_image->NormalImg, dstPt);
+ UnlockMutex (DC_image->mutex);
+ break;
+ }
+
+ case TFB_DRAWCOMMANDTYPE_COPY:
+ {
+ TFB_DrawCommand_Copy *cmd = &DC.data.copy;
+ const RECT r = cmd->rect;
+
+ if (cmd->destBuffer == TFB_SCREEN_MAIN)
+ TFB_BBox_RegisterRect (&cmd->rect);
+
+ TFB_DrawCanvas_CopyRect (
+ TFB_GetScreenCanvas (cmd->srcBuffer), &r,
+ TFB_GetScreenCanvas (cmd->destBuffer), r.corner);
+ break;
+ }
+
+ case TFB_DRAWCOMMANDTYPE_DELETEIMAGE:
+ {
+ TFB_Image *DC_image = DC.data.deleteimage.image;
+ TFB_DrawImage_Delete (DC_image);
+ break;
+ }
+
+ case TFB_DRAWCOMMANDTYPE_DELETEDATA:
+ {
+ void *data = DC.data.deletedata.data;
+ HFree (data);
+ break;
+ }
+
+ case TFB_DRAWCOMMANDTYPE_SENDSIGNAL:
+ ClearSemaphore (DC.data.sendsignal.sem);
+ break;
+
+ case TFB_DRAWCOMMANDTYPE_REINITVIDEO:
+ {
+ TFB_DrawCommand_ReinitVideo *cmd = &DC.data.reinitvideo;
+ int oldDriver = GraphicsDriver;
+ int oldFlags = GfxFlags;
+ int oldWidth = ScreenWidthActual;
+ int oldHeight = ScreenHeightActual;
+ if (TFB_ReInitGraphics (cmd->driver, cmd->flags,
+ cmd->width, cmd->height))
+ {
+ log_add (log_Error, "Could not provide requested mode: "
+ "reverting to last known driver.");
+ // We don't know what exactly failed, so roll it all back
+ if (TFB_ReInitGraphics (oldDriver, oldFlags,
+ oldWidth, oldHeight))
+ {
+ log_add (log_Fatal,
+ "Couldn't reinit at that point either. "
+ "Your video has been somehow tied in knots.");
+ exit (EXIT_FAILURE);
+ }
+ }
+ TFB_SwapBuffers (TFB_REDRAW_YES);
+ break;
+ }
+
+ case TFB_DRAWCOMMANDTYPE_CALLBACK:
+ {
+ DC.data.callback.callback (DC.data.callback.arg);
+ break;
+ }
+ }
+ }
+
+ if (livelock_deterrence)
+ Unlock_DCQ ();
+
+ TFB_SwapBuffers (TFB_REDRAW_NO);
+ RenderedFrames++;
+ BroadcastCondVar (RenderingCond);
+}
+
+void
+TFB_PurgeDanglingGraphics (void)
+{
+ Lock_DCQ (-1);
+
+ for (;;)
+ {
+ TFB_DrawCommand DC;
+
+ if (!TFB_DrawCommandQueue_Pop (&DC))
+ {
+ // the Queue is now empty.
+ break;
+ }
+
+ switch (DC.Type)
+ {
+ case TFB_DRAWCOMMANDTYPE_DELETEIMAGE:
+ {
+ TFB_Image *DC_image = DC.data.deleteimage.image;
+ TFB_DrawImage_Delete (DC_image);
+ break;
+ }
+ case TFB_DRAWCOMMANDTYPE_DELETEDATA:
+ {
+ void *data = DC.data.deletedata.data;
+ HFree (data);
+ break;
+ }
+ case TFB_DRAWCOMMANDTYPE_IMAGE:
+ {
+ TFB_ColorMap *cmap = DC.data.image.colormap;
+ if (cmap)
+ TFB_ReturnColorMap (cmap);
+ break;
+ }
+ case TFB_DRAWCOMMANDTYPE_SENDSIGNAL:
+ {
+ ClearSemaphore (DC.data.sendsignal.sem);
+ break;
+ }
+ }
+ }
+ Unlock_DCQ ();
+}