diff options
Diffstat (limited to 'src/libs/graphics/dcqueue.c')
-rw-r--r-- | src/libs/graphics/dcqueue.c | 670 |
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 (); +} |