egra: better, faster, and more flexible vertical gradients in agg mini
[iv.d.git] / vl2 / core.d
blob4b84d5c72e272242d609eccd61056658b2fbcd65
1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module iv.vl2.core /*is aliced*/;
19 import iv.alice;
20 public import iv.sdl2.sdl;
22 private import core.sys.posix.time;
23 // idiotic phobos forgets 'nothrow' at it
24 extern(C) private int clock_gettime (clockid_t, timespec*) @trusted nothrow @nogc;
27 version(GNU) {
28 static import gcc.attribute;
29 private enum gcc_inline = gcc.attribute.attribute("forceinline");
30 private enum gcc_noinline = gcc.attribute.attribute("noinline");
31 private enum gcc_flatten = gcc.attribute.attribute("flatten");
32 } else {
33 // hackery for non-gcc compilers
34 private enum gcc_inline;
35 private enum gcc_noinline;
36 private enum gcc_flatten;
40 // ////////////////////////////////////////////////////////////////////////// //
41 /// generic VideoLib exception
42 class VideoLibError : Exception {
43 this (string msg, string file=__FILE__, usize line=__LINE__, Throwable next=null) @safe pure nothrow @nogc {
44 super(msg, file, line, next);
49 // ////////////////////////////////////////////////////////////////////////// //
50 /// output filter
51 enum VLFilter {
52 None,
53 Green,
54 BlackNWhite
58 /// is videolib initialized?
59 enum VLInitState {
60 Not, /// no
61 Partial, /// not fully (i.e. `vlInit()` failed in the middle)
62 Done /// completely initialized
66 // ////////////////////////////////////////////////////////////////////////// //
67 private shared VLInitState pvInited = VLInitState.Not;
69 /// is VideoLib properly initialized and videomode set?
70 @property VLInitState vlInitState () @trusted nothrow @nogc {
71 import core.atomic : atomicLoad;
72 return atomicLoad(pvInited);
75 // ////////////////////////////////////////////////////////////////////////// //
76 shared bool vlFullscreen = false; /// use fullscreen mode? this can be set to true before calling vlInit()
77 shared bool vlRealFullscreen = false; /// use 'real' fullscreen? defaul: false
78 shared bool vlVSync = false; /// wait VBL? this can be set to true before calling initVideo()
79 shared bool vlMag2x = true; // set to true to create double-sized window; default: true; have no effect after calling vlInit()
80 shared bool vlScanlines = true; // set to true to use 'scanline' filter in mag2x mode; default: true
81 shared VLFilter vlFilter = VLFilter.None; /// output filter; default: VLFilter.None
83 __gshared SDL_Window* sdlWindow = null; /// current SDL window; DON'T CHANGE THIS!
86 // ////////////////////////////////////////////////////////////////////////// //
87 private __gshared SDL_Renderer* sdlRenderer = null; // current SDL rendering context
88 private __gshared SDL_Texture* sdlScreen = null; // current SDL screen
90 private shared bool sdlFrameChangedFlag = false; // this will be set to false by vlPaintFrame()
92 package __gshared void function () vlInitHook = null;
93 package __gshared void function () vlDeinitHook = null;
96 // ////////////////////////////////////////////////////////////////////////// //
97 /// call this if you want VideoLib to call rebuild callback
98 void vlFrameChanged () @trusted nothrow @nogc {
99 import core.atomic : atomicStore;
100 atomicStore(sdlFrameChangedFlag, true);
103 @property bool vlIsFrameChanged () @trusted nothrow @nogc {
104 import core.atomic : atomicLoad;
105 return atomicLoad(sdlFrameChangedFlag);
108 /// screen dimensions, should be changed prior to calling vlInit()
109 private shared uint vsWidth = 320;
110 private shared uint vsHeight = 240;
112 /// get current screen width
113 @property uint vlWidth() () @trusted nothrow @nogc {
114 import core.atomic : atomicLoad;
115 return atomicLoad(vsWidth);
118 /// get current screen height
119 @property uint vlHeight() () @trusted nothrow @nogc {
120 import core.atomic : atomicLoad;
121 return atomicLoad(vsHeight);
124 /// set screen width; must be used before vlInit()
125 @property void vlWidth (int wdt) @trusted {
126 import core.atomic : atomicLoad, atomicStore;
127 if (atomicLoad(pvInited) != VLInitState.Not) throw new VideoLibError("trying to change screen width after initialization");
128 if (wdt < 1 || wdt > 8192) throw new VideoLibError("invalid screen width");
129 atomicStore(vsWidth, wdt);
132 /// set screen height; must be used before vlInit()
133 @property void vlHeight (int hgt) @trusted {
134 import core.atomic : atomicLoad, atomicStore;
135 if (atomicLoad(pvInited) != VLInitState.Not) throw new VideoLibError("trying to change screen height after initialization");
136 if (hgt < 1 || hgt > 8192) throw new VideoLibError("invalid screen height");
137 atomicStore(vsHeight, hgt);
140 private __gshared int effectiveMag2x; // effective vlMag2x
141 private __gshared SDL_Texture* sdlScr2x = null;
142 private __gshared int prevLogSizeWas1x = 0; // DON'T change to bool!
144 __gshared uint* vlVScr = null; /// current SDL 'virtual screen', ARGB format for LE
145 private __gshared uint* vscr2x = null; // this is used in magnifying blitters
148 // ////////////////////////////////////////////////////////////////////////// //
150 * Process CLI args from main().
152 * Params:
153 * args = command line arguments
155 * Returns:
156 * command line with processed arguments removed
158 void vlProcessArgs (ref string[] args) @trusted nothrow {
159 main_loop:
160 for (uint idx = 1; idx < args.length; ++idx) {
161 auto arg = args[idx];
162 if (arg == "--") break;
163 if (arg.length < 3 || arg[0] != '-' || arg[1] != '-') continue;
164 bool yes = true;
165 if (arg.length > 5 && arg[2..5] == "no-") {
166 yes = false;
167 arg = arg[5..$];
168 } else {
169 arg = arg[2..$];
171 switch (arg) {
172 case "fs": vlFullscreen = yes; break;
173 case "win": vlFullscreen = !yes; break;
174 case "realfs": vlRealFullscreen = yes; break;
175 case "winfs": vlRealFullscreen = !yes; break;
176 case "tv": vlScanlines = yes; break;
177 case "bw": if (yes) vlFilter = VLFilter.BlackNWhite; break;
178 case "green": if (yes) vlFilter = VLFilter.Green; break;
179 case "1x": vlMag2x = !yes; break;
180 case "2x": vlMag2x = yes; break;
181 case "vbl": vlVSync = yes; break;
182 case "vhelp":
183 if (yes) {
184 import core.stdc.stdlib : exit;
185 import std.exception : collectException;
186 import std.stdio : stdout;
187 collectException(stdout.write(
188 "video options (add \"no-\" to negate):\n"~
189 " --fs fullscreen\n"
190 " --win windowed\n"
191 " --realfs use \"real\" fullscreen\n"
192 " --winfs use \"windowed\" fullscreen\n"
193 " --tv scanlines filter\n"
194 " --bw black-and-white filter\n"
195 " --green green filter\n"
196 " --1x normal size\n"
197 " --2x magnify\n"
198 " --vbl vsync\n"));
199 exit(0);
201 break;
202 default: continue main_loop;
204 // remove option
205 foreach (immutable c; idx+1..args.length) args[c-1] = args[c];
206 args.length -= 1;
207 --idx; // compensate for removed element
212 private void sdlPrintError () @trusted nothrow @nogc {
213 import core.stdc.stdio : stderr, fprintf;
214 auto sdlError = SDL_GetError();
215 fprintf(stderr, "SDL ERROR: %s\n", sdlError);
219 private void sdlError (bool raise) @trusted {
220 if (raise) {
221 sdlPrintError();
222 throw new VideoLibError("SDL fucked");
228 * Initialize SDL, create drawing window and buffers, init other shit.
230 * Params:
231 * windowName = window caption
233 * Returns:
234 * nothing
236 * Throws:
237 * VideoLibError on error
239 void vlInit (string windowName=null) @trusted {
240 import core.atomic : atomicLoad, atomicStore;
242 final switch (atomicLoad(pvInited)) with (VLInitState) {
243 case Not: break;
244 case Partial: throw new VideoLibError("can't continue initialization");
245 case Done: return;
248 import core.exception : onOutOfMemoryError;
249 import core.stdc.stdlib : malloc, free;
250 import std.string : toStringz;
252 vlDeinitInternal();
253 if (windowName is null) windowName = "SDL Application";
255 effectiveMag2x = (vlMag2x ? 2 : 1);
256 prevLogSizeWas1x = 1;
258 if (vlVScr !is null) free(vlVScr);
259 vlVScr = cast(uint*)malloc(vsWidth*vsHeight*vlVScr[0].sizeof);
260 if (vlVScr is null) onOutOfMemoryError();
262 if (vscr2x !is null) free(vscr2x);
263 vscr2x = cast(uint*)malloc(vsWidth*effectiveMag2x*vsHeight*effectiveMag2x*vscr2x[0].sizeof);
264 if (vscr2x is null) onOutOfMemoryError();
266 SDL_Init(SDL_INIT_VIDEO|SDL_INIT_EVENTS|SDL_INIT_TIMER);
267 atomicStore(pvInited, VLInitState.Partial);
269 sdlWindow = SDL_CreateWindow(
270 windowName.toStringz,
271 SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
272 vsWidth*effectiveMag2x, vsHeight*effectiveMag2x,
273 (vlFullscreen ? (vlRealFullscreen ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP) : 0));
274 sdlError(!sdlWindow);
276 sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, (vlVSync ? SDL_RENDERER_PRESENTVSYNC : 0));
277 sdlError(!sdlRenderer);
279 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); // "nearest" or "linear"
280 SDL_RenderSetLogicalSize(sdlRenderer, vsWidth*(2-prevLogSizeWas1x), vsHeight*(2-prevLogSizeWas1x));
282 sdlScreen = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, vsWidth, vsHeight);
283 sdlError(!sdlScreen);
285 if (effectiveMag2x == 2) {
286 sdlScr2x = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, vsWidth*2, vsHeight*2);
287 sdlError(!sdlScr2x);
288 } else {
289 sdlScr2x = null;
292 if (vlInitHook !is null) vlInitHook();
294 atomicStore(pvInited, VLInitState.Done);
299 * Deinitialize SDL, free resources.
301 * Params:
302 * none
304 * Returns:
305 * nothing
307 private void vlDeinitInternal () /*@trusted nothrow @nogc*/ {
308 import core.atomic : atomicLoad, atomicStore;
309 import core.stdc.stdlib : free;
311 if (atomicLoad(pvInited) == VLInitState.Not) return;
313 if (vlDeinitHook !is null) vlDeinitHook();
315 if (sdlScreen !is null) { SDL_DestroyTexture(sdlScreen); sdlScreen = null; }
316 if (sdlScr2x !is null) { SDL_DestroyTexture(sdlScr2x); sdlScr2x = null; }
317 if (sdlRenderer !is null) { SDL_DestroyRenderer(sdlRenderer); sdlRenderer = null; }
318 if (sdlWindow) { SDL_DestroyWindow(sdlWindow); sdlWindow = null; }
319 if (vlVScr !is null) { free(vlVScr); vlVScr = null; }
320 if (vscr2x !is null) { free(vscr2x); vscr2x = null; }
322 atomicStore(pvInited, VLInitState.Not);
327 * Shutdown SDL, free resources. You don't need to call this explicitely.
329 * Params:
330 * none
332 * Returns:
333 * nothing
335 void vlDeinit () /*@trusted nothrow @nogc*/ {
336 import core.atomic : atomicLoad;
337 if (atomicLoad(pvInited) != VLInitState.Not) {
338 vlDeinitInternal();
339 SDL_Quit();
345 * Switches between fullscreen and windowed modes.
347 * Params:
348 * none
350 * Returns:
351 * nothing
353 void vlSwitchFullscreen () @trusted nothrow {
354 import core.atomic : atomicLoad, atomicStore;
355 bool ns = !atomicLoad(vlFullscreen);
356 if (SDL_SetWindowFullscreen(sdlWindow, (ns ? (atomicLoad(vlRealFullscreen) ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP) : 0)) != 0) {
357 sdlPrintError();
358 return;
360 if (!ns) {
361 SDL_SetWindowSize(sdlWindow, vsWidth*effectiveMag2x, vsHeight*effectiveMag2x);
362 SDL_SetWindowPosition(sdlWindow, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
364 atomicStore(vlFullscreen, ns);
369 * Post SDL_QUIT message. Can be called to initiate clean exit from main loop.
371 * Params:
372 * none
374 * Returns:
375 * nothing
377 void vlPostQuitMessage () @trusted nothrow @nogc {
378 import core.stdc.string : memset;
379 SDL_Event evt = void;
380 memset(&evt, 0, evt.sizeof);
381 evt.type = SDL_QUIT;
382 SDL_PushEvent(&evt);
386 // ////////////////////////////////////////////////////////////////////////// //
387 @gcc_inline ubyte clampToByte(T) (T n) @safe pure nothrow @nogc
388 if (__traits(isIntegral, T) && (T.sizeof == 2 || T.sizeof == 4))
390 static if (__VERSION__ > 2067) pragma(inline, true);
391 n &= -cast(int)(n >= 0);
392 return cast(ubyte)(n|((255-cast(int)n)>>31));
395 @gcc_inline ubyte clampToByte(T) (T n) @safe pure nothrow @nogc
396 if (__traits(isIntegral, T) && T.sizeof == 1)
398 static if (__VERSION__ > 2067) pragma(inline, true);
399 return cast(ubyte)n;
403 // ////////////////////////////////////////////////////////////////////////// //
404 alias Color = uint;
406 version(LittleEndian) {
407 /// vlRGBA struct to ease color components extraction/replacing
408 align(1) struct vlRGBA {
409 align(1):
410 ubyte b, g, r, a;
412 } else version(BigEndian) {
413 /// vlRGBA struct to ease color components extraction/replacing
414 align(1) struct vlRGBA {
415 align(1):
416 ubyte a, r, g, b;
418 } else {
419 static assert(0, "WTF?!");
422 static assert(vlRGBA.sizeof == Color.sizeof);
425 enum : Color {
426 vlAMask = 0xff000000u,
427 vlRMask = 0x00ff0000u,
428 vlGMask = 0x0000ff00u,
429 vlBMask = 0x000000ffu
432 enum : Color {
433 vlAShift = 24,
434 vlRShift = 16,
435 vlGShift = 8,
436 vlBShift = 0
440 enum Color vlcTransparent = vlAMask; /// completely transparent pixel color
444 * Is color completely transparent?
446 * Params:
447 * col = rgba color
449 * Returns:
450 * 'transparent' flag
452 @gcc_inline bool vlcIsTransparent(T : Color) (T col) @safe pure nothrow @nogc {
453 static if (__VERSION__ > 2067) pragma(inline, true);
454 return ((col&vlAMask) == vlAMask);
458 * Is color completely non-transparent?
460 * Params:
461 * col = rgba color
463 * Returns:
464 * 'transparent' flag
466 @gcc_inline bool isOpaque(T : Color) (T col) @safe pure nothrow @nogc {
467 static if (__VERSION__ > 2067) pragma(inline, true);
468 return ((col&vlAMask) == 0);
472 * Build opaque non-transparent rgb color from components.
474 * Params:
475 * r = red component [0..255]
476 * g = green component [0..255]
477 * b = blue component [0..255]
478 * a = transparency [0..255] (0: completely opaque; 255: completely transparent)
480 * Returns:
481 * rgba color
483 @gcc_inline Color rgb2col(TR, TG, TB, TA=ubyte) (TR r, TG g, TB b, TA a=0) @safe pure nothrow @nogc
484 if (__traits(isIntegral, TR) && __traits(isIntegral, TG) && __traits(isIntegral, TB) && __traits(isIntegral, TA)) {
485 static if (__VERSION__ > 2067) pragma(inline, true);
486 return
487 (clampToByte(a)<<vlAShift)|
488 (clampToByte(r)<<vlRShift)|
489 (clampToByte(g)<<vlGShift)|
490 (clampToByte(b)<<vlBShift);
494 * Build rgba color from components.
496 * Params:
497 * r = red component [0..255]
498 * g = green component [0..255]
499 * b = blue component [0..255]
500 * a = transparency [0..255] (0: completely opaque; 255: completely transparent)
502 * Returns:
503 * rgba color
505 alias rgba2col = rgb2col;
508 // generate some templates
509 private enum genRGBGetSet(string cname) =
510 "@gcc_inline ubyte rgb"~cname~"() (Color clr) @safe pure nothrow @nogc {\n"~
511 " static if (__VERSION__ > 2067) pragma(inline, true);\n"~
512 " return ((clr>>vl"~cname[0]~"Shift)&0xff);\n"~
513 "}\n"~
514 "@gcc_inline Color rgbSet"~cname~"(T) (Color clr, T v) @safe pure nothrow @nogc if (__traits(isIntegral, T)) {\n"~
515 " static if (__VERSION__ > 2067) pragma(inline, true);\n"~
516 " return (clr&~vl"~cname[0]~"Mask)|(clampToByte(v)<<vl"~cname[0]~"Shift);\n"~
517 "}\n";
519 mixin(genRGBGetSet!"Alpha");
520 mixin(genRGBGetSet!"Red");
521 mixin(genRGBGetSet!"Green");
522 mixin(genRGBGetSet!"Blue");
525 // ////////////////////////////////////////////////////////////////////////// //
526 private enum buildBlit1x(string name, string op, string wrt) =
527 `private void `~name~` () @trusted nothrow @nogc {`~
528 ` auto s = cast(const(ubyte)*)vlVScr;`~
529 ` auto d = cast(ubyte*)vscr2x;`~
530 ` foreach (immutable _; 0..vsWidth*vsHeight) {`~
531 ` ubyte i = `~op~`;`~
532 ` `~wrt~`;`~
533 ` s += 4;`~
534 ` d += 4;`~
535 ` }`~
536 `}`;
539 mixin(buildBlit1x!("blit1xBW", "(s[0]*28+s[1]*151+s[2]*77)/256", "d[0] = d[1] = d[2] = i"));
540 mixin(buildBlit1x!("blit1xGreen", "(s[0]*28+s[1]*151+s[2]*77)/256", "d[0] = d[2] = 0; d[1] = i"));
543 private enum buildBlit2x(string name, string op) =
544 `private void `~name~` () @trusted nothrow @nogc {`~
545 ` auto s = cast(const(ubyte)*)vlVScr;`~
546 ` auto d = cast(uint*)vscr2x;`~
547 ` immutable auto wdt = vsWidth;`~
548 ` immutable auto wdt2x = vsWidth*2;`~
549 ` foreach (immutable y; 0..vsHeight) {`~
550 ` foreach (immutable x; 0..wdt) {`~
552 ` immutable uint c1 = ((((c0&0x00ff00ff)*6)>>3)&0x00ff00ff)|(((c0&0x0000ff00)*6)>>3)&0x0000ff00;`~
553 ` d[0] = d[1] = c0;`~
554 ` d[wdt2x] = d[wdt2x+1] = c1;`~
555 ` s += 4;`~
556 ` d += 2;`~
557 ` }`~
558 // fix d: skip one scanline
559 ` d += wdt2x;`~
560 ` }`~
561 `}`;
564 mixin(buildBlit2x!("blit2xTV", "immutable uint c0 = (cast(immutable(uint)*)s)[0];"));
565 mixin(buildBlit2x!("blit2xTVBW", "immutable ubyte i = cast(ubyte)((s[0]*28+s[1]*151+s[2]*77)/256); immutable uint c0 = (i<<16)|(i<<8)|i;"));
566 mixin(buildBlit2x!("blit2xTVGreen", "immutable ubyte i = cast(ubyte)((s[0]*28+s[1]*151+s[2]*77)/256); immutable uint c0 = i<<8;"));
569 // ////////////////////////////////////////////////////////////////////////// //
571 * Paint frame contents.
573 * Params:
574 * none
576 * Returns:
577 * nothing
579 void vlPaintFrame () @trusted nothrow @nogc {
580 import core.atomic : atomicStore;
581 vlPaintFrameDefault();
582 atomicStore(sdlFrameChangedFlag, false);
587 * Paint virtual screen onto real screen or window.
589 * Params:
590 * none
592 * Returns:
593 * nothing
595 void vlPaintFrameDefault () @trusted nothrow @nogc {
596 import core.atomic : atomicLoad;
597 // fix 'logical size'
598 immutable flt = atomicLoad(vlFilter);
599 if (effectiveMag2x == 2 && atomicLoad(vlScanlines)) {
600 // mag2x and scanlines: size is 2x
601 if (prevLogSizeWas1x) {
602 prevLogSizeWas1x = 0;
603 SDL_RenderSetLogicalSize(sdlRenderer, vsWidth*2, vsHeight*2);
605 } else {
606 // any other case: size is 2x
607 if (!prevLogSizeWas1x) {
608 prevLogSizeWas1x = 1;
609 SDL_RenderSetLogicalSize(sdlRenderer, vsWidth, vsHeight);
612 // apply filters if any
613 if (effectiveMag2x == 2 && atomicLoad(vlScanlines)) {
614 // heavy case: scanline filter turned on
615 final switch (flt) with (VLFilter) {
616 case None: blit2xTV(); break;
617 case BlackNWhite: blit2xTVBW(); break;
618 case Green: blit2xTVGreen(); break;
620 SDL_UpdateTexture(sdlScr2x, null, vscr2x, cast(uint)(vsWidth*2*vscr2x[0].sizeof));
621 SDL_RenderCopy(sdlRenderer, sdlScr2x, null, null);
622 } else {
623 // light cases
624 if (flt == VLFilter.None) {
625 // easiest case
626 SDL_UpdateTexture(sdlScreen, null, vlVScr, cast(uint)(vsWidth*vlVScr[0].sizeof));
627 } else {
628 import core.stdc.string : memcpy;
629 final switch (flt) with (VLFilter) {
630 case None: memcpy(vscr2x, vlVScr, vsWidth*vsHeight*vlVScr[0].sizeof); break; // just in case
631 case BlackNWhite: blit1xBW(); break;
632 case Green: blit1xGreen(); break;
634 SDL_UpdateTexture(sdlScreen, null, vscr2x, cast(uint)(vsWidth*vscr2x[0].sizeof));
636 SDL_RenderCopy(sdlRenderer, sdlScreen, null, null);
638 SDL_RenderPresent(sdlRenderer);
642 // ////////////////////////////////////////////////////////////////////////// //
643 version(linux) {
644 private enum CLOCK_MONOTONIC_RAW = 4;
645 private enum CLOCK_MONOTONIC_COARSE = 6;
646 } else {
647 static assert(0, "sorry, only GNU/Linux for now; please, fix `vlGetTicks()` and company for your OS!");
651 shared static this () {
652 vlInitializeClock();
656 private __gshared timespec videolib_clock_stt;
657 private __gshared int vlClockType = CLOCK_MONOTONIC_COARSE;
660 private void vlInitializeClock () @trusted nothrow @nogc {
661 timespec cres = void;
662 bool inited = false;
663 if (clock_getres(vlClockType, &cres) == 0) {
664 if (cres.tv_sec == 0 && cres.tv_nsec <= cast(long)1000000*1 /*1 ms*/) inited = true;
666 if (!inited) {
667 vlClockType = CLOCK_MONOTONIC_RAW;
668 if (clock_getres(vlClockType, &cres) == 0) {
669 if (cres.tv_sec == 0 && cres.tv_nsec <= cast(long)1000000*1 /*1 ms*/) inited = true;
672 if (!inited) assert(0, "FATAL: can't initialize clock subsystem!");
673 if (clock_gettime(vlClockType, &videolib_clock_stt) != 0) {
674 assert(0, "FATAL: can't initialize clock subsystem!");
679 /** returns monitonically increasing time; starting value is UNDEFINED (i.e. can be any number)
680 * milliseconds; (0: no timer available) */
681 @gcc_inline ulong vlGetTicks () @trusted nothrow @nogc {
682 static if (__VERSION__ > 2067) pragma(inline, true);
683 timespec ts = void;
684 if (clock_gettime(vlClockType, &ts) != 0) assert(0, "FATAL: can't get real-time clock value!\n");
685 // ah, ignore nanoseconds in videolib_clock_stt->stt here: we need only 'differential' time, and it can start with something weird
686 return (cast(ulong)(ts.tv_sec-videolib_clock_stt.tv_sec))*1000+ts.tv_nsec/1000000+1;
690 /** returns monitonically increasing time; starting value is UNDEFINED (i.e. can be any number)
691 * microseconds; (0: no timer available) */
692 @gcc_inline ulong vlGetTicksMicro () @trusted nothrow @nogc {
693 static if (__VERSION__ > 2067) pragma(inline, true);
694 timespec ts = void;
695 if (clock_gettime(vlClockType, &ts) != 0) assert(0, "FATAL: can't get real-time clock value!\n");
696 // ah, ignore nanoseconds in videolib_clock_stt->stt here: we need only 'differential' time, and it can start with something weird
697 return (cast(ulong)(ts.tv_sec-videolib_clock_stt.tv_sec))*1000000+ts.tv_nsec/1000+1;
701 // ////////////////////////////////////////////////////////////////////////// //
702 // main loop
704 enum SDL_MOUSEDOUBLE = SDL_USEREVENT+1; /// same as SDL_MouseButtonEvent
705 enum SDL_FREEEVENT = SDL_USEREVENT+42; /// first event available for user
708 // ////////////////////////////////////////////////////////////////////////// //
710 * mechanics is like this:
711 * when it's time to show new frame, videolib will call vlOnUpdateCB().
712 * if vlFrameChanged is set (either from previous frame, or by vlOnUpdateCB(),
713 * videolib will call vlOnRebuildCB() and vlPaintFrame().
714 * note that videolib will reset vlFrameChanged after vlOnRebuildCB() called.
715 * also note that if vlOnUpdateCB() or vlOnRebuildCB() will not set vlFrameChanged,
716 * frame visuals will not be updated properly.
717 * you can either update and vlOnRebuildCB screen in vlOnUpdateCB(), or update state in
718 * vlOnUpdateCB() and rebuild virtual screen in vlOnRebuildCB().
719 * you can count on videolib never changes virtual screen contents by itself.
721 /** elapsedTicks: ms elapsed from the last call; will try to keep up with FPS; 0: first call in vlMainLoop()
722 * can call vlFrameChanged() flag so vlMainLoop() will call vlOnRebuildCB() or call vlOnRebuildCB() itself */
723 private {
724 __gshared void delegate (int elapsedTicks) vlOnUpdateCB = null;
725 __gshared void delegate () vlOnRebuildCB = null;
727 __gshared void delegate (in ref SDL_KeyboardEvent ev) vlOnKeyDownCB = null;
728 __gshared void delegate (in ref SDL_KeyboardEvent ev) vlOnKeyUpCB = null;
729 __gshared void delegate (dchar ch) vlOnTextInputCB = null;
731 __gshared void delegate (in ref SDL_MouseButtonEvent ev) vlOnMouseDownCB = null;
732 __gshared void delegate (in ref SDL_MouseButtonEvent ev) vlOnMouseUpCB = null;
733 __gshared void delegate (in ref SDL_MouseButtonEvent ev) vlOnMouseClickCB = null;
734 __gshared void delegate (in ref SDL_MouseButtonEvent ev) vlOnMouseDoubleCB = null; /// mouse double-click
735 __gshared void delegate (in ref SDL_MouseMotionEvent ev) vlOnMouseMotionCB = null;
736 __gshared void delegate (in ref SDL_MouseWheelEvent ev) vlOnMouseWheelCB = null;
740 @trusted nothrow @nogc {
741 private import std.functional : toDelegate;
742 @property void vlOnUpdate (void function (int elapsedTicks) cb) { vlOnUpdateCB = toDelegate(cb); }
743 @property void vlOnRebuild (void function () cb) { vlOnRebuildCB = toDelegate(cb); }
744 @property void vlOnKeyDown (void function (in ref SDL_KeyboardEvent ev) cb) { vlOnKeyDownCB = toDelegate(cb); }
745 @property void vlOnKeyUp (void function (in ref SDL_KeyboardEvent ev) cb) { vlOnKeyUpCB = toDelegate(cb); }
746 @property void vlOnTextInput (void function (dchar ch) cb) { vlOnTextInputCB = toDelegate(cb); }
747 @property void vlOnMouseDown (void function (in ref SDL_MouseButtonEvent ev) cb) { vlOnMouseDownCB = toDelegate(cb); }
748 @property void vlOnMouseUp (void function (in ref SDL_MouseButtonEvent ev) cb) { vlOnMouseUpCB = toDelegate(cb); }
749 @property void vlOnMouseClick (void function (in ref SDL_MouseButtonEvent ev) cb) { vlOnMouseClickCB = toDelegate(cb); }
750 @property void vlOnMouseDouble (void function (in ref SDL_MouseButtonEvent ev) cb) { vlOnMouseDoubleCB = toDelegate(cb); }
751 @property void vlOnMouseMotion (void function (in ref SDL_MouseMotionEvent ev) cb) { vlOnMouseMotionCB = toDelegate(cb); }
752 @property void vlOnMouseWheel (void function (in ref SDL_MouseWheelEvent ev) cb) { vlOnMouseWheelCB = toDelegate(cb); }
754 @property void vlOnUpdate (void delegate (int elapsedTicks) cb) { vlOnUpdateCB = cb; }
755 @property void vlOnRebuild (void delegate () cb) { vlOnRebuildCB = cb; }
756 @property void vlOnKeyDown (void delegate (in ref SDL_KeyboardEvent ev) cb) { vlOnKeyDownCB = cb; }
757 @property void vlOnKeyUp (void delegate (in ref SDL_KeyboardEvent ev) cb) { vlOnKeyUpCB = cb; }
758 @property void vlOnTextInput (void delegate (dchar ch) cb) { vlOnTextInputCB = cb; }
759 @property void vlOnMouseDown (void delegate (in ref SDL_MouseButtonEvent ev) cb) { vlOnMouseDownCB = cb; }
760 @property void vlOnMouseUp (void delegate (in ref SDL_MouseButtonEvent ev) cb) { vlOnMouseUpCB = cb; }
761 @property void vlOnMouseClick (void delegate (in ref SDL_MouseButtonEvent ev) cb) { vlOnMouseClickCB = cb; }
762 @property void vlOnMouseDouble (void delegate (in ref SDL_MouseButtonEvent ev) cb) { vlOnMouseDoubleCB = cb; }
763 @property void vlOnMouseMotion (void delegate (in ref SDL_MouseMotionEvent ev) cb) { vlOnMouseMotionCB = cb; }
764 @property void vlOnMouseWheel (void delegate (in ref SDL_MouseWheelEvent ev) cb) { vlOnMouseWheelCB = cb; }
766 @property auto vlOnUpdate () { return vlOnUpdateCB; }
767 @property auto vlOnRebuild () { return vlOnRebuildCB; }
768 @property auto vlOnKeyDown () { return vlOnKeyDownCB; }
769 @property auto vlOnKeyUp () { return vlOnKeyUpCB; }
770 @property auto vlOnTextInput () { return vlOnTextInputCB; }
771 @property auto vlOnMouseDown () { return vlOnMouseDownCB; }
772 @property auto vlOnMouseUp () { return vlOnMouseUpCB; }
773 @property auto vlOnMouseClick () { return vlOnMouseClickCB; }
774 @property auto vlOnMouseDouble () { return vlOnMouseDoubleCB; }
775 @property auto vlOnMouseMotion () { return vlOnMouseMotionCB; }
776 @property auto vlOnMouseWheel () { return vlOnMouseWheelCB; }
779 /// start receiving text input events
780 void startTextInput() () { SDL_StartTextInput(); }
782 /// stop receiving text input events
783 void stopTextInput() () { SDL_StopTextInput(); }
786 // ////////////////////////////////////////////////////////////////////////// //
787 /* doubleclick processing */
788 // first press (count is 0):
789 // start it, set count to 1, send "press"
790 // first release (count is 1):
791 // checkCond pass: set count to 2, send "release", send "clicked"
792 // checkCond fail: set count to 0, send "release"
793 // second press (count is 2):
794 // checkCond pass: set count to 3, eat press
795 // checkCond fail: start it, set count to 1, send "press"
796 // second release (count is 3):
797 // checkCond pass: set count to 0, eat release, send "double"
798 // checkCond fail: set count to 0, eat release
799 private struct VLDblClickInfo {
800 enum Action {
801 Pass, // send normal event
802 Eat, // don't send any event
803 Click, // send normal event, then click
804 Double // send double
807 int count;
808 int x, y;
809 ulong pressTime; // in msecs
811 void reset () @safe nothrow @nogc { count = 0; }
813 bool checkConds (in ref SDL_Event ev) @trusted nothrow @nogc {
814 if (count) {
815 import std.math : abs;
816 return
817 abs(ev.button.x-x) <= vlDblTreshold &&
818 abs(ev.button.y-y) <= vlDblTreshold &&
819 vlGetTicks()-pressTime <= vlDblTimeout;
820 } else {
821 return false;
825 Action processDown (in ref SDL_Event ev) @trusted nothrow @nogc {
826 void register () @trusted nothrow @nogc {
827 count = 1;
828 x = ev.button.x;
829 y = ev.button.y;
830 pressTime = vlGetTicks();
833 if (count == 0) {
834 // first press
835 register();
836 return Action.Pass;
837 } else {
838 // second press
839 assert(count == 2);
840 if (checkConds(ev)) {
841 count = 3;
842 return Action.Eat;
843 } else {
844 register();
845 return Action.Pass;
850 Action processUp (in ref SDL_Event ev) @trusted nothrow @nogc {
851 if (count == 0) {
852 return Action.Pass;
853 } else if (checkConds(ev)) {
854 if (count == 1) {
855 //if (w.mWantDouble) count = 2; else reset();
856 count = 2;
857 return Action.Click;
858 } else if (count == 3) {
859 reset();
860 return Action.Double;
861 } else {
862 assert(0);
864 } else {
865 Action res = (count == 1 ? Action.Pass : Action.Eat);
866 reset();
867 return res;
873 // ////////////////////////////////////////////////////////////////////////// //
874 private __gshared VLDblClickInfo[32] vlDblClickInfo; // button_number-1
875 public __gshared uint vlDblTimeout = 400; /// milliseconds; set to 0 to disable doubleclicks
876 public __gshared uint vlDblTreshold = 4; /// mouse cursor should not move more than `vlDblTreshold` pixels to register dblclick
879 // return `true` if event was eaten
880 private bool vlProcessDblClick (in ref SDL_Event ev) {
881 if (vlDblTimeout < 1) return false;
882 if (ev.type != SDL_MOUSEBUTTONDOWN && ev.type != SDL_MOUSEBUTTONUP) return false;
883 if (ev.button.button < 1 || ev.button.button >= vlDblClickInfo.length) return false;
884 if (ev.type == SDL_MOUSEBUTTONDOWN) {
885 // button pressed
886 immutable act = vlDblClickInfo[ev.button.button-1].processDown(ev);
887 bool doEat = true;
888 switch (act) with (VLDblClickInfo.Action) {
889 case Eat:
890 break;
891 case Click:
892 if (vlOnMouseDownCB !is null) vlOnMouseDownCB(ev.button);
893 if (vlOnMouseClickCB !is null) vlOnMouseClickCB(ev.button);
894 break;
895 case Double:
896 if (vlOnMouseDoubleCB !is null) vlOnMouseDoubleCB(ev.button);
897 break;
898 default:
899 doEat = false;
900 break;
902 if (!doEat && vlOnMouseDownCB !is null) vlOnMouseDownCB(ev.button);
903 } else {
904 // button released
905 immutable act = vlDblClickInfo[ev.button.button-1].processUp(ev);
906 bool doEat = true;
907 switch (act) with (VLDblClickInfo.Action) {
908 case Eat:
909 break;
910 case Click:
911 if (vlOnMouseUpCB !is null) vlOnMouseUpCB(ev.button);
912 if (vlOnMouseClickCB !is null) vlOnMouseClickCB(ev.button);
913 break;
914 case Double:
915 if (vlOnMouseDoubleCB !is null) vlOnMouseDoubleCB(ev.button);
916 break;
917 default:
918 doEat = false;
919 break;
921 if (!doEat && vlOnMouseUpCB !is null) vlOnMouseUpCB(ev.button);
923 return true;
927 // ////////////////////////////////////////////////////////////////////////// //
928 /** returns !0 to stop event loop; <0: from event preprocessor; >0: from SDL_QUIT */
929 int vlProcessEvent (ref SDL_Event ev) {
930 if (vlMag2x) {
931 if (ev.type == SDL_MOUSEBUTTONDOWN || ev.type == SDL_MOUSEBUTTONUP) {
932 ev.button.x /= 2;
933 ev.button.y /= 2;
934 } else if (ev.type == SDL_MOUSEMOTION) {
935 ev.motion.x /= 2;
936 ev.motion.y /= 2;
939 if (vlProcessDblClick(ev)) return 0; // process mouse
940 if (ev.type == SDL_QUIT) return 1;
941 switch (ev.type) {
942 case SDL_KEYDOWN: if (vlOnKeyDownCB !is null) vlOnKeyDownCB(ev.key); break;
943 case SDL_KEYUP: if (vlOnKeyUpCB !is null) vlOnKeyUpCB(ev.key); break;
944 case SDL_MOUSEBUTTONDOWN: if (vlOnMouseDownCB !is null) vlOnMouseDownCB(ev.button); break;
945 case SDL_MOUSEBUTTONUP: if (vlOnMouseUpCB !is null) vlOnMouseUpCB(ev.button); break;
946 //case SDL_MOUSEDOUBLE: if (vlOnMouseDoubleCB !is null) vlOnMouseDoubleCB(ev.button); break;
947 case SDL_MOUSEMOTION: if (vlOnMouseMotionCB !is null) vlOnMouseMotionCB(ev.motion); break;
948 case SDL_MOUSEWHEEL: if (vlOnMouseWheelCB !is null) vlOnMouseWheelCB(ev.wheel); break;
949 case SDL_TEXTINPUT:
950 if (vlOnTextInputCB !is null) {
951 //TODO: UTF-8 decode!
952 if (ev.text.text[0] && ev.text.text[0] < 127) {
953 vlOnTextInputCB(ev.text.text[0]);
956 break;
957 case SDL_USEREVENT:
958 //TODO: forward other user events
959 //(ev.user.code)
960 break;
961 case SDL_WINDOWEVENT:
962 switch (ev.window.event) {
963 case SDL_WINDOWEVENT_EXPOSED:
964 vlFrameChanged(); // this will force repaint
965 break;
966 default:
968 break;
969 default:
971 return 0;
975 private __gshared int pvCurFPS = 35; /// desired FPS (default: 35)
976 private __gshared ulong pvLastUpdateCall = 0;
977 private __gshared ulong pvNextUpdateCall = 0;
978 private __gshared uint pvMSecsInFrame = 1000/35;
980 @property uint vlMSecsPerFrame () @trusted nothrow @nogc { return pvMSecsInFrame; } /// return number of milliseconds in frame
982 @property uint vlFPS () @trusted nothrow @nogc { return pvCurFPS; } /// return current FPS
984 /// set desired FPS
985 @property void vlFPS (uint newfps) @trusted nothrow @nogc {
986 if (newfps < 1) newfps = 1;
987 if (newfps > 500) newfps = 500;
988 pvMSecsInFrame = 1000/newfps;
989 if (pvMSecsInFrame == 0) pvMSecsInFrame = 1;
990 pvCurFPS = newfps;
994 /// update and rebuild frame if necessary
995 /// will not paint frame to screen
996 void vlCheckFrameUpdate () {
997 auto tm = vlGetTicks();
998 if (tm >= pvNextUpdateCall) {
999 if (vlOnUpdateCB !is null) vlOnUpdateCB(cast(int)(tm-pvLastUpdateCall));
1000 pvLastUpdateCall = tm;//vlGetTicks();
1001 while (tm >= pvNextUpdateCall) {
1002 // do something with skipped frames
1003 pvNextUpdateCall += pvMSecsInFrame;
1006 import core.atomic : cas;
1007 bool cf = cas(&sdlFrameChangedFlag, true, false); // cf is true if sdlFrameChangedFlag was true
1008 if (cf) {
1009 vlFrameChanged();
1010 if (vlOnRebuildCB !is null) vlOnRebuildCB();
1015 /** will start and stop FPS timer automatically */
1016 void vlMainLoop () {
1017 bool doQuit = false;
1018 SDL_Event ev;
1019 pvLastUpdateCall = vlGetTicks();
1020 pvNextUpdateCall = pvLastUpdateCall+pvMSecsInFrame;
1021 if (vlOnUpdateCB !is null) vlOnUpdateCB(0);
1022 if (vlOnRebuildCB !is null) vlOnRebuildCB();
1023 vlPaintFrame();
1024 while (!doQuit) {
1025 if (!SDL_PollEvent(null)) {
1026 // no events, see if we have to wait for the next frame
1027 auto now = vlGetTicks();
1028 if (now < pvNextUpdateCall) {
1029 // have some time to waste
1030 int n = cast(int)(pvNextUpdateCall-now);
1031 if (!SDL_WaitEventTimeout(null, n)) goto noevent;
1034 while (SDL_PollEvent(&ev)) {
1035 if (vlProcessEvent(ev)) { doQuit = true; break; }
1037 if (doQuit) break;
1038 noevent:
1039 vlCheckFrameUpdate();
1040 if (vlIsFrameChanged) vlPaintFrame(); // this will reset sdlFrameChangedFlag again
1045 // ////////////////////////////////////////////////////////////////////////// //
1046 shared static ~this () {
1047 vlDeinit();