1
2 // HEADER FILES ------------------------------------------------------------
3
4 #include "doomtype.h"
5
6 #include "templates.h"
7 #include "i_system.h"
8 #include "i_video.h"
9 #include "v_video.h"
10 #include "v_pfx.h"
11 #include "stats.h"
12 #include "v_palette.h"
13 #include "sdlvideo.h"
14 #include "r_swrenderer.h"
15 #include "version.h"
16
17 #include <SDL.h>
18
19 #ifdef __APPLE__
20 #include <OpenGL/OpenGL.h>
21 #endif // __APPLE__
22
23 // MACROS ------------------------------------------------------------------
24
25 // TYPES -------------------------------------------------------------------
26
27 class SDLFB : public DFrameBuffer
28 {
29 DECLARE_CLASS(SDLFB, DFrameBuffer)
30 public:
31 SDLFB (int width, int height, bool fullscreen, SDL_Window *oldwin);
32 ~SDLFB ();
33
34 bool Lock (bool buffer);
35 void Unlock ();
36 bool Relock ();
37 void ForceBuffering (bool force);
38 bool IsValid ();
39 void Update ();
40 PalEntry *GetPalette ();
41 void GetFlashedPalette (PalEntry pal[256]);
42 void UpdatePalette ();
43 bool SetGamma (float gamma);
44 bool SetFlash (PalEntry rgb, int amount);
45 void GetFlash (PalEntry &rgb, int &amount);
46 void SetFullscreen (bool fullscreen);
47 int GetPageCount ();
48 bool IsFullscreen ();
49
50 friend class SDLVideo;
51
52 virtual void SetVSync (bool vsync);
53 virtual void ScaleCoordsFromWindow(SWORD &x, SWORD &y);
54
55 private:
56 PalEntry SourcePalette[256];
57 BYTE GammaTable[3][256];
58 PalEntry Flash;
59 int FlashAmount;
60 float Gamma;
61 bool UpdatePending;
62
63 SDL_Window *Screen;
64 SDL_Renderer *Renderer;
65 union
66 {
67 SDL_Texture *Texture;
68 SDL_Surface *Surface;
69 };
70
71 bool UsingRenderer;
72 bool NeedPalUpdate;
73 bool NeedGammaUpdate;
74 bool NotPaletted;
75
76 void UpdateColors ();
77 void ResetSDLRenderer ();
78
SDLFB()79 SDLFB () {}
80 };
81 IMPLEMENT_CLASS(SDLFB)
82
83 struct MiniModeInfo
84 {
85 WORD Width, Height;
86 };
87
88 // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
89
90 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
91
92 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
93
94 extern IVideo *Video;
95 extern bool GUICapture;
96
EXTERN_CVAR(Float,Gamma)97 EXTERN_CVAR (Float, Gamma)
98 EXTERN_CVAR (Int, vid_maxfps)
99 EXTERN_CVAR (Bool, cl_capfps)
100 EXTERN_CVAR (Bool, vid_vsync)
101
102 // PUBLIC DATA DEFINITIONS -------------------------------------------------
103
104 CVAR (Int, vid_adapter, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
105
106 CVAR (Int, vid_displaybits, 32, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
107
108 CVAR (Bool, vid_forcesurface, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
109
110 CUSTOM_CVAR (Float, rgamma, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
111 {
112 if (screen != NULL)
113 {
114 screen->SetGamma (Gamma);
115 }
116 }
117 CUSTOM_CVAR (Float, ggamma, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
118 {
119 if (screen != NULL)
120 {
121 screen->SetGamma (Gamma);
122 }
123 }
124 CUSTOM_CVAR (Float, bgamma, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
125 {
126 if (screen != NULL)
127 {
128 screen->SetGamma (Gamma);
129 }
130 }
131
132 // PRIVATE DATA DEFINITIONS ------------------------------------------------
133
134 // Dummy screen sizes to pass when windowed
135 static MiniModeInfo WinModes[] =
136 {
137 { 320, 200 },
138 { 320, 240 },
139 { 400, 225 }, // 16:9
140 { 400, 300 },
141 { 480, 270 }, // 16:9
142 { 480, 360 },
143 { 512, 288 }, // 16:9
144 { 512, 384 },
145 { 640, 360 }, // 16:9
146 { 640, 400 },
147 { 640, 480 },
148 { 720, 480 }, // 16:10
149 { 720, 540 },
150 { 800, 450 }, // 16:9
151 { 800, 480 },
152 { 800, 500 }, // 16:10
153 { 800, 600 },
154 { 848, 480 }, // 16:9
155 { 960, 600 }, // 16:10
156 { 960, 720 },
157 { 1024, 576 }, // 16:9
158 { 1024, 600 }, // 17:10
159 { 1024, 640 }, // 16:10
160 { 1024, 768 },
161 { 1088, 612 }, // 16:9
162 { 1152, 648 }, // 16:9
163 { 1152, 720 }, // 16:10
164 { 1152, 864 },
165 { 1280, 720 }, // 16:9
166 { 1280, 854 },
167 { 1280, 800 }, // 16:10
168 { 1280, 960 },
169 { 1280, 1024 }, // 5:4
170 { 1360, 768 }, // 16:9
171 { 1366, 768 },
172 { 1400, 787 }, // 16:9
173 { 1400, 875 }, // 16:10
174 { 1400, 1050 },
175 { 1440, 900 },
176 { 1440, 960 },
177 { 1440, 1080 },
178 { 1600, 900 }, // 16:9
179 { 1600, 1000 }, // 16:10
180 { 1600, 1200 },
181 { 1680, 1050 }, // 16:10
182 { 1920, 1080 },
183 { 1920, 1200 },
184 { 2048, 1536 },
185 { 2560, 1440 },
186 { 2560, 1600 },
187 { 2560, 2048 },
188 { 2880, 1800 },
189 { 3200, 1800 },
190 { 3840, 2160 },
191 { 3840, 2400 },
192 { 4096, 2160 },
193 { 5120, 2880 }
194 };
195
196 static cycle_t BlitCycles;
197 static cycle_t SDLFlipCycles;
198
199 // CODE --------------------------------------------------------------------
200
ScaleWithAspect(int & w,int & h,int Width,int Height)201 void ScaleWithAspect (int &w, int &h, int Width, int Height)
202 {
203 int resRatio = CheckRatio (Width, Height);
204 int screenRatio;
205 CheckRatio (w, h, &screenRatio);
206 if (resRatio == screenRatio)
207 return;
208
209 double yratio;
210 switch(resRatio)
211 {
212 case 0: yratio = 4./3.; break;
213 case 1: yratio = 16./9.; break;
214 case 2: yratio = 16./10.; break;
215 case 3: yratio = 17./10.; break;
216 case 4: yratio = 5./4.; break;
217 default: return;
218 }
219 double y = w/yratio;
220 if (y > h)
221 w = h*yratio;
222 else
223 h = y;
224 }
225
SDLVideo(int parm)226 SDLVideo::SDLVideo (int parm)
227 {
228 IteratorBits = 0;
229 }
230
~SDLVideo()231 SDLVideo::~SDLVideo ()
232 {
233 }
234
StartModeIterator(int bits,bool fs)235 void SDLVideo::StartModeIterator (int bits, bool fs)
236 {
237 IteratorMode = 0;
238 IteratorBits = bits;
239 }
240
NextMode(int * width,int * height,bool * letterbox)241 bool SDLVideo::NextMode (int *width, int *height, bool *letterbox)
242 {
243 if (IteratorBits != 8)
244 return false;
245
246 if ((unsigned)IteratorMode < sizeof(WinModes)/sizeof(WinModes[0]))
247 {
248 *width = WinModes[IteratorMode].Width;
249 *height = WinModes[IteratorMode].Height;
250 ++IteratorMode;
251 return true;
252 }
253 return false;
254 }
255
CreateFrameBuffer(int width,int height,bool fullscreen,DFrameBuffer * old)256 DFrameBuffer *SDLVideo::CreateFrameBuffer (int width, int height, bool fullscreen, DFrameBuffer *old)
257 {
258 static int retry = 0;
259 static int owidth, oheight;
260
261 PalEntry flashColor;
262 int flashAmount;
263
264 SDL_Window *oldwin = NULL;
265
266 if (old != NULL)
267 { // Reuse the old framebuffer if its attributes are the same
268 SDLFB *fb = static_cast<SDLFB *> (old);
269 if (fb->Width == width &&
270 fb->Height == height)
271 {
272 bool fsnow = (SDL_GetWindowFlags (fb->Screen) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
273
274 if (fsnow != fullscreen)
275 {
276 fb->SetFullscreen (fullscreen);
277 }
278 return old;
279 }
280
281 oldwin = fb->Screen;
282 fb->Screen = NULL;
283
284 old->GetFlash (flashColor, flashAmount);
285 old->ObjectFlags |= OF_YesReallyDelete;
286 if (screen == old) screen = NULL;
287 delete old;
288 }
289 else
290 {
291 flashColor = 0;
292 flashAmount = 0;
293 }
294
295 SDLFB *fb = new SDLFB (width, height, fullscreen, oldwin);
296
297 // If we could not create the framebuffer, try again with slightly
298 // different parameters in this order:
299 // 1. Try with the closest size
300 // 2. Try in the opposite screen mode with the original size
301 // 3. Try in the opposite screen mode with the closest size
302 // This is a somewhat confusing mass of recursion here.
303
304 while (fb == NULL || !fb->IsValid ())
305 {
306 if (fb != NULL)
307 {
308 delete fb;
309 }
310
311 switch (retry)
312 {
313 case 0:
314 owidth = width;
315 oheight = height;
316 case 2:
317 // Try a different resolution. Hopefully that will work.
318 I_ClosestResolution (&width, &height, 8);
319 break;
320
321 case 1:
322 // Try changing fullscreen mode. Maybe that will work.
323 width = owidth;
324 height = oheight;
325 fullscreen = !fullscreen;
326 break;
327
328 default:
329 // I give up!
330 I_FatalError ("Could not create new screen (%d x %d)", owidth, oheight);
331 }
332
333 ++retry;
334 fb = static_cast<SDLFB *>(CreateFrameBuffer (width, height, fullscreen, NULL));
335 }
336 retry = 0;
337
338 fb->SetFlash (flashColor, flashAmount);
339
340 return fb;
341 }
342
SetWindowedScale(float scale)343 void SDLVideo::SetWindowedScale (float scale)
344 {
345 }
346
347 // FrameBuffer implementation -----------------------------------------------
348
SDLFB(int width,int height,bool fullscreen,SDL_Window * oldwin)349 SDLFB::SDLFB (int width, int height, bool fullscreen, SDL_Window *oldwin)
350 : DFrameBuffer (width, height)
351 {
352 int i;
353
354 NeedPalUpdate = false;
355 NeedGammaUpdate = false;
356 UpdatePending = false;
357 NotPaletted = false;
358 FlashAmount = 0;
359
360 if (oldwin)
361 {
362 // In some cases (Mac OS X fullscreen) SDL2 doesn't like having multiple windows which
363 // appears to inevitably happen while compositor animations are running. So lets try
364 // to reuse the existing window.
365 Screen = oldwin;
366 SDL_SetWindowSize (Screen, width, height);
367 SetFullscreen (fullscreen);
368 }
369 else
370 {
371 FString caption;
372 caption.Format(GAMESIG " %s (%s)", GetVersionString(), GetGitTime());
373
374 Screen = SDL_CreateWindow (caption,
375 SDL_WINDOWPOS_UNDEFINED_DISPLAY(vid_adapter), SDL_WINDOWPOS_UNDEFINED_DISPLAY(vid_adapter),
376 width, height, (fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0)|SDL_WINDOW_RESIZABLE);
377
378 if (Screen == NULL)
379 return;
380 }
381
382 Renderer = NULL;
383 Texture = NULL;
384 ResetSDLRenderer ();
385
386 for (i = 0; i < 256; i++)
387 {
388 GammaTable[0][i] = GammaTable[1][i] = GammaTable[2][i] = i;
389 }
390
391 memcpy (SourcePalette, GPalette.BaseColors, sizeof(PalEntry)*256);
392 UpdateColors ();
393
394 #ifdef __APPLE__
395 SetVSync (vid_vsync);
396 #endif
397 }
398
399
~SDLFB()400 SDLFB::~SDLFB ()
401 {
402 if (Renderer)
403 {
404 if (Texture)
405 SDL_DestroyTexture (Texture);
406 SDL_DestroyRenderer (Renderer);
407 }
408
409 if(Screen)
410 {
411 SDL_DestroyWindow (Screen);
412 }
413 }
414
IsValid()415 bool SDLFB::IsValid ()
416 {
417 return DFrameBuffer::IsValid() && Screen != NULL;
418 }
419
GetPageCount()420 int SDLFB::GetPageCount ()
421 {
422 return 1;
423 }
424
Lock(bool buffered)425 bool SDLFB::Lock (bool buffered)
426 {
427 return DSimpleCanvas::Lock ();
428 }
429
Relock()430 bool SDLFB::Relock ()
431 {
432 return DSimpleCanvas::Lock ();
433 }
434
Unlock()435 void SDLFB::Unlock ()
436 {
437 if (UpdatePending && LockCount == 1)
438 {
439 Update ();
440 }
441 else if (--LockCount <= 0)
442 {
443 Buffer = NULL;
444 LockCount = 0;
445 }
446 }
447
Update()448 void SDLFB::Update ()
449 {
450 if (LockCount != 1)
451 {
452 if (LockCount > 0)
453 {
454 UpdatePending = true;
455 --LockCount;
456 }
457 return;
458 }
459
460 DrawRateStuff ();
461
462 #if !defined(__APPLE__) && !defined(__DragonFly__)
463 if(vid_maxfps && !cl_capfps)
464 {
465 SEMAPHORE_WAIT(FPSLimitSemaphore)
466 }
467 #endif
468
469 Buffer = NULL;
470 LockCount = 0;
471 UpdatePending = false;
472
473 BlitCycles.Reset();
474 SDLFlipCycles.Reset();
475 BlitCycles.Clock();
476
477 void *pixels;
478 int pitch;
479 if (UsingRenderer)
480 {
481 if (SDL_LockTexture (Texture, NULL, &pixels, &pitch))
482 return;
483 }
484 else
485 {
486 if (SDL_LockSurface (Surface))
487 return;
488
489 pixels = Surface->pixels;
490 pitch = Surface->pitch;
491 }
492
493 if (NotPaletted)
494 {
495 GPfx.Convert (MemBuffer, Pitch,
496 pixels, pitch, Width, Height,
497 FRACUNIT, FRACUNIT, 0, 0);
498 }
499 else
500 {
501 if (pitch == Pitch)
502 {
503 memcpy (pixels, MemBuffer, Width*Height);
504 }
505 else
506 {
507 for (int y = 0; y < Height; ++y)
508 {
509 memcpy ((BYTE *)pixels+y*pitch, MemBuffer+y*Pitch, Width);
510 }
511 }
512 }
513
514 if (UsingRenderer)
515 {
516 SDL_UnlockTexture (Texture);
517
518 SDLFlipCycles.Clock();
519 SDL_RenderClear(Renderer);
520 SDL_RenderCopy(Renderer, Texture, NULL, NULL);
521 SDL_RenderPresent(Renderer);
522 SDLFlipCycles.Unclock();
523 }
524 else
525 {
526 SDL_UnlockSurface (Surface);
527
528 SDLFlipCycles.Clock();
529 SDL_UpdateWindowSurface (Screen);
530 SDLFlipCycles.Unclock();
531 }
532
533 BlitCycles.Unclock();
534
535 if (NeedGammaUpdate)
536 {
537 bool Windowed = false;
538 NeedGammaUpdate = false;
539 CalcGamma ((Windowed || rgamma == 0.f) ? Gamma : (Gamma * rgamma), GammaTable[0]);
540 CalcGamma ((Windowed || ggamma == 0.f) ? Gamma : (Gamma * ggamma), GammaTable[1]);
541 CalcGamma ((Windowed || bgamma == 0.f) ? Gamma : (Gamma * bgamma), GammaTable[2]);
542 NeedPalUpdate = true;
543 }
544
545 if (NeedPalUpdate)
546 {
547 NeedPalUpdate = false;
548 UpdateColors ();
549 }
550 }
551
UpdateColors()552 void SDLFB::UpdateColors ()
553 {
554 if (NotPaletted)
555 {
556 PalEntry palette[256];
557
558 for (int i = 0; i < 256; ++i)
559 {
560 palette[i].r = GammaTable[0][SourcePalette[i].r];
561 palette[i].g = GammaTable[1][SourcePalette[i].g];
562 palette[i].b = GammaTable[2][SourcePalette[i].b];
563 }
564 if (FlashAmount)
565 {
566 DoBlending (palette, palette,
567 256, GammaTable[0][Flash.r], GammaTable[1][Flash.g], GammaTable[2][Flash.b],
568 FlashAmount);
569 }
570 GPfx.SetPalette (palette);
571 }
572 else
573 {
574 SDL_Color colors[256];
575
576 for (int i = 0; i < 256; ++i)
577 {
578 colors[i].r = GammaTable[0][SourcePalette[i].r];
579 colors[i].g = GammaTable[1][SourcePalette[i].g];
580 colors[i].b = GammaTable[2][SourcePalette[i].b];
581 }
582 if (FlashAmount)
583 {
584 DoBlending ((PalEntry *)colors, (PalEntry *)colors,
585 256, GammaTable[2][Flash.b], GammaTable[1][Flash.g], GammaTable[0][Flash.r],
586 FlashAmount);
587 }
588 SDL_SetPaletteColors (Surface->format->palette, colors, 0, 256);
589 }
590 }
591
GetPalette()592 PalEntry *SDLFB::GetPalette ()
593 {
594 return SourcePalette;
595 }
596
UpdatePalette()597 void SDLFB::UpdatePalette ()
598 {
599 NeedPalUpdate = true;
600 }
601
SetGamma(float gamma)602 bool SDLFB::SetGamma (float gamma)
603 {
604 Gamma = gamma;
605 NeedGammaUpdate = true;
606 return true;
607 }
608
SetFlash(PalEntry rgb,int amount)609 bool SDLFB::SetFlash (PalEntry rgb, int amount)
610 {
611 Flash = rgb;
612 FlashAmount = amount;
613 NeedPalUpdate = true;
614 return true;
615 }
616
GetFlash(PalEntry & rgb,int & amount)617 void SDLFB::GetFlash (PalEntry &rgb, int &amount)
618 {
619 rgb = Flash;
620 amount = FlashAmount;
621 }
622
623 // Q: Should I gamma adjust the returned palette?
GetFlashedPalette(PalEntry pal[256])624 void SDLFB::GetFlashedPalette (PalEntry pal[256])
625 {
626 memcpy (pal, SourcePalette, 256*sizeof(PalEntry));
627 if (FlashAmount)
628 {
629 DoBlending (pal, pal, 256, Flash.r, Flash.g, Flash.b, FlashAmount);
630 }
631 }
632
SetFullscreen(bool fullscreen)633 void SDLFB::SetFullscreen (bool fullscreen)
634 {
635 if (IsFullscreen() == fullscreen)
636 return;
637
638 SDL_SetWindowFullscreen (Screen, fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
639 if (!fullscreen)
640 {
641 // Restore proper window size
642 SDL_SetWindowSize (Screen, Width, Height);
643 }
644
645 ResetSDLRenderer ();
646 }
647
IsFullscreen()648 bool SDLFB::IsFullscreen ()
649 {
650 return (SDL_GetWindowFlags (Screen) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
651 }
652
ResetSDLRenderer()653 void SDLFB::ResetSDLRenderer ()
654 {
655 if (Renderer)
656 {
657 if (Texture)
658 SDL_DestroyTexture (Texture);
659 SDL_DestroyRenderer (Renderer);
660 }
661
662 UsingRenderer = !vid_forcesurface;
663 if (UsingRenderer)
664 {
665 Renderer = SDL_CreateRenderer (Screen, -1,SDL_RENDERER_ACCELERATED|SDL_RENDERER_TARGETTEXTURE|
666 (vid_vsync ? SDL_RENDERER_PRESENTVSYNC : 0));
667 if (!Renderer)
668 return;
669
670 SDL_SetRenderDrawColor(Renderer, 0, 0, 0, 255);
671
672 Uint32 fmt;
673 switch(vid_displaybits)
674 {
675 default: fmt = SDL_PIXELFORMAT_ARGB8888; break;
676 case 30: fmt = SDL_PIXELFORMAT_ARGB2101010; break;
677 case 24: fmt = SDL_PIXELFORMAT_RGB888; break;
678 case 16: fmt = SDL_PIXELFORMAT_RGB565; break;
679 case 15: fmt = SDL_PIXELFORMAT_ARGB1555; break;
680 }
681 Texture = SDL_CreateTexture (Renderer, fmt, SDL_TEXTUREACCESS_STREAMING, Width, Height);
682
683 {
684 NotPaletted = true;
685
686 Uint32 format;
687 SDL_QueryTexture(Texture, &format, NULL, NULL, NULL);
688
689 Uint32 Rmask, Gmask, Bmask, Amask;
690 int bpp;
691 SDL_PixelFormatEnumToMasks(format, &bpp, &Rmask, &Gmask, &Bmask, &Amask);
692 GPfx.SetFormat (bpp, Rmask, Gmask, Bmask);
693 }
694 }
695 else
696 {
697 Surface = SDL_GetWindowSurface (Screen);
698
699 if (Surface->format->palette == NULL)
700 {
701 NotPaletted = true;
702 GPfx.SetFormat (Surface->format->BitsPerPixel, Surface->format->Rmask, Surface->format->Gmask, Surface->format->Bmask);
703 }
704 else
705 NotPaletted = false;
706 }
707
708 // In fullscreen, set logical size according to animorphic ratio.
709 // Windowed modes are rendered to fill the window (usually 1:1)
710 if (IsFullscreen ())
711 {
712 int w, h;
713 SDL_GetWindowSize (Screen, &w, &h);
714 ScaleWithAspect (w, h, Width, Height);
715 SDL_RenderSetLogicalSize (Renderer, w, h);
716 }
717 }
718
SetVSync(bool vsync)719 void SDLFB::SetVSync (bool vsync)
720 {
721 #ifdef __APPLE__
722 if (CGLContextObj context = CGLGetCurrentContext())
723 {
724 // Apply vsync for native backend only (where OpenGL context is set)
725
726 #if MAC_OS_X_VERSION_MAX_ALLOWED < 1050
727 // Inconsistency between 10.4 and 10.5 SDKs:
728 // third argument of CGLSetParameter() is const long* on 10.4 and const GLint* on 10.5
729 // So, GLint typedef'ed to long instead of int to workaround this issue
730 typedef long GLint;
731 #endif // prior to 10.5
732
733 const GLint value = vsync ? 1 : 0;
734 CGLSetParameter(context, kCGLCPSwapInterval, &value);
735 }
736 #else
737 ResetSDLRenderer ();
738 #endif // __APPLE__
739 }
740
ScaleCoordsFromWindow(SWORD & x,SWORD & y)741 void SDLFB::ScaleCoordsFromWindow(SWORD &x, SWORD &y)
742 {
743 int w, h;
744 SDL_GetWindowSize (Screen, &w, &h);
745
746 // Detect if we're doing scaling in the Window and adjust the mouse
747 // coordinates accordingly. This could be more efficent, but I
748 // don't think performance is an issue in the menus.
749 if(IsFullscreen())
750 {
751 int realw = w, realh = h;
752 ScaleWithAspect (realw, realh, SCREENWIDTH, SCREENHEIGHT);
753 if (realw != SCREENWIDTH || realh != SCREENHEIGHT)
754 {
755 double xratio = (double)SCREENWIDTH/realw;
756 double yratio = (double)SCREENHEIGHT/realh;
757 if (realw < w)
758 {
759 x = (x - (w - realw)/2)*xratio;
760 y *= yratio;
761 }
762 else
763 {
764 y = (y - (h - realh)/2)*yratio;
765 x *= xratio;
766 }
767 }
768 }
769 else
770 {
771 x = (SWORD)(x*Width/w);
772 y = (SWORD)(y*Height/h);
773 }
774 }
775
ADD_STAT(blit)776 ADD_STAT (blit)
777 {
778 FString out;
779 out.Format ("blit=%04.1f ms flip=%04.1f ms",
780 BlitCycles.TimeMS(), SDLFlipCycles.TimeMS());
781 return out;
782 }
783