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