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 // severely outdated! do not use!
18 module iv
.videolib
/*is aliced*/;
20 version(videolib_opengl
) {} else { version = videolib_sdl
; }
22 //public import iv.gccattrs;
28 public import iv
.sdl2
.sdl
;
29 version(videolib_opengl
) import iv
.opengl
.gl
;
31 private import core
.sys
.posix
.time
;
32 // idiotic phobos forgets 'nothrow' at it
33 extern(C
) private int clock_gettime (clockid_t
, timespec
*) @trusted nothrow @nogc;
36 // ////////////////////////////////////////////////////////////////////////// //
37 /// generic VideoLib exception
38 class VideoLibError
: Exception
{
39 this (string msg
, string file
=__FILE__
, usize line
=__LINE__
, Throwable next
=null) @safe pure nothrow =>
40 super(msg
, file
, line
, next
);
51 private shared bool pvInited
= false; // vill be set to 'true' if initVideo() initializes at least something
52 private shared bool pvInitedOk
= false; // vill be set to 'true' if initVideo() initializes everything
54 /// is VideoLib properly initialized and videomode set?
55 @property bool isInited () @trusted nothrow @nogc {
56 import core
.atomic
: atomicLoad
;
57 return atomicLoad(pvInitedOk
);
60 shared bool useFullscreen
= false; /// use fullscreen mode? this can be set to true before calling initVideo()
61 shared bool useOpenGL
= false; /// allow OpenGL? this can be set to true before calling initVideo()
62 shared bool noDefaultOpenGLContext
= false; /// don't create OpenGL context for useOpenGL?
63 shared bool useVSync
= false; /// wait VBL? this can be set to true before calling initVideo()
64 shared bool useRealFullscreen
= false; /// use 'real' fullscreen? defaul: false
66 shared bool useMag2x
= true; // set to true to create double-sized window; default: true; have no effect after calling initVideo()
67 shared bool useScanlines
= true; // set to true to use 'scanline' filter in mag2x mode; default: true
68 shared VideoFilter useFilter
= VideoFilter
.None
; /// output filter; default: VideoFilter.None
70 __gshared SDL_Window
* sdlWindow
= null; /// current SDL window; DON'T CHANGE THIS!
72 alias PaintHookType
= void function () @trusted;
73 __gshared PaintHookType paintHook
= null; /// set this to override standard paintFrame()
74 //shared static this () => paintHook = &paintFrameDefault;
76 version(videolib_sdl
) {
77 private __gshared SDL_Renderer
* sdlRenderer
= null; // current SDL rendering context
78 private __gshared SDL_Texture
* sdlScreen
= null; // current SDL screen
80 version(videolib_opengl
) {
81 private __gshared SDL_GLContext sdlGLCtx
= null;
84 private shared bool sdlFrameChangedFlag
= false; // this will be set to false by paintFrame()
86 /// call this if you want VideoLib to call rebuild callback
87 void frameChanged () @trusted nothrow @nogc {
88 import core
.atomic
: atomicStore
;
89 atomicStore(sdlFrameChangedFlag
, true);
92 @property bool isFrameChanged () @trusted nothrow @nogc {
93 import core
.atomic
: atomicLoad
;
94 return atomicLoad(sdlFrameChangedFlag
);
97 /// screen dimensions, should be changed prior to calling initVideo()
98 private shared int vsWidth
= 320;
99 private shared int vsHeight
= 240;
101 /*@gcc_inline*/ @property int vlWidth() () @trusted nothrow @nogc => vsWidth
; /// get current screen width
102 /*@gcc_inline*/ @property int vlHeight() () @trusted nothrow @nogc => vsHeight
; /// get current screen height
104 /// set screen width; must be used before initVideo()
105 @property void vlWidth (int wdt
) @trusted {
106 import core
.atomic
: atomicLoad
, atomicStore
;
107 if (atomicLoad(pvInited
)) throw new VideoLibError("trying to change screen width after initialization");
108 if (wdt
< 1 || wdt
> 8192) throw new VideoLibError("invalid screen width");
109 atomicStore(vsWidth
, wdt
);
112 /// set screen height; must be used before initVideo()
113 @property void vlHeight (int hgt
) @trusted {
114 import core
.atomic
: atomicLoad
, atomicStore
;
115 if (atomicLoad(pvInited
)) throw new VideoLibError("trying to change screen height after initialization");
116 if (hgt
< 1 || hgt
> 8192) throw new VideoLibError("invalid screen height");
117 atomicStore(vsHeight
, hgt
);
120 private __gshared
int effectiveMag2x
; // effective useMag2x
121 private __gshared SDL_Texture
* sdlScr2x
= null;
122 private __gshared
int prevLogSizeWas1x
= 0; // DON'T change to bool!
124 __gshared
uint* vscr
= null; /// current SDL 'virtual screen', ARGB format for LE
125 private __gshared
uint* vscr2x
= null; // this is used in magnifying blitters
126 private __gshared VLOverlay ovlVScr
= null;
128 /*@gcc_inline*/ VLOverlay
vscrOvl() () @trusted nothrow @nogc => ovlVScr
;
131 // ////////////////////////////////////////////////////////////////////////// //
133 * Process CLI args from main().
136 * args = command line arguments
139 * command line with processed arguments removed
141 void processArgs (ref string
[] args
) @trusted nothrow {
142 for (auto f
= 1; f
< args
.length
; ++f
) {
144 if (arg
== "--") break;
145 else if (arg
== "--fs") useFullscreen
= true;
146 else if (arg
== "--win") useFullscreen
= false;
147 else if (arg
== "--realfs") useRealFullscreen
= true;
148 else if (arg
== "--winfs") useRealFullscreen
= false;
149 else if (arg
== "--tv") useScanlines
= true;
150 else if (arg
== "--notv") useScanlines
= false;
151 else if (arg
== "--bw") useFilter
= VideoFilter
.BlackNWhite
;
152 else if (arg
== "--green") useFilter
= VideoFilter
.Green
;
153 else if (arg
== "--1x") useMag2x
= false;
154 else if (arg
== "--2x") useMag2x
= true;
155 else if (arg
== "--vbl") useVSync
= true;
156 else if (arg
== "--novbl") useVSync
= false;
158 foreach (auto c
; f
+1..args
.length
) args
[c
-1] = args
[c
];
159 args
.length
= args
.length
-1;
160 --f
; // compensate for removed element
165 private void sdlPrintError () @trusted nothrow @nogc {
166 import core
.stdc
.stdio
: stderr
, fprintf
;
167 auto sdlError
= SDL_GetError();
168 fprintf(stderr
, "SDL ERROR: %s\n", sdlError
);
172 private void sdlError (bool raise
) @trusted {
175 throw new VideoLibError("SDL fucked");
181 * Initialize SDL, create drawing window and buffers, init other shit.
184 * windowName = window caption
190 * VideoLibError on error
192 void initVideo (string windowName
=null) @trusted {
194 import core
.exception
: onOutOfMemoryError
;
195 import core
.stdc
.stdlib
: malloc
, free
;
196 import std
.string
: toStringz
;
199 if (windowName
is null) windowName
= "SDL Application";
200 version(videolib_opengl
) {
201 enum uint winflags
= SDL_WINDOW_OPENGL
;
202 //enforce(glClear !is null);
203 //enforce(glPixelZoom !is null);
204 //enforce(glDrawPixels !is null);
206 enum uint winflags
= 0;
209 effectiveMag2x
= (useMag2x ?
2 : 1);
210 prevLogSizeWas1x
= 1;
212 if (ovlVScr
!is null) ovlVScr
.mVScr
= null;
214 if (vscr
!is null) free(vscr
);
215 vscr
= cast(uint*)malloc(vsWidth
*vsHeight
*vscr
[0].sizeof
);
216 if (vscr
is null) onOutOfMemoryError();
218 if (vscr2x
!is null) free(vscr2x
);
219 vscr2x
= cast(uint*)malloc(vsWidth
*effectiveMag2x
*vsHeight
*effectiveMag2x
*vscr2x
[0].sizeof
);
220 if (vscr2x
is null) onOutOfMemoryError();
222 if (ovlVScr
is null) {
223 ovlVScr
= new VLOverlay(vscr
, vsWidth
, vsHeight
);
225 ovlVScr
.mVScr
= vscr
;
226 ovlVScr
.mWidth
= vsWidth
;
227 ovlVScr
.mHeight
= vsHeight
;
229 ovlVScr
.resetClipOfs();
231 SDL_Init(SDL_INIT_VIDEO|SDL_INIT_EVENTS|SDL_INIT_TIMER
);
233 version(videolib_opengl
) {
234 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION
, 1);
235 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION
, 1);
236 //SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
238 sdlWindow
= SDL_CreateWindow(windowName
.toStringz
, SDL_WINDOWPOS_UNDEFINED
, SDL_WINDOWPOS_UNDEFINED
, vsWidth
*effectiveMag2x
, vsHeight
*effectiveMag2x
,
239 winflags|
(useFullscreen ?
(useRealFullscreen ? SDL_WINDOW_FULLSCREEN
: SDL_WINDOW_FULLSCREEN_DESKTOP
) : 0)|
(useOpenGL ? SDL_WINDOW_OPENGL
: 0));
240 sdlError(!sdlWindow
);
241 version(videolib_opengl
) {
242 if (!noDefaultOpenGLContext
) sdlGLCtx
= SDL_GL_CreateContext(sdlWindow
);
243 SDL_GL_SetSwapInterval(useVSync ?
1: 0);
245 sdlRenderer
= SDL_CreateRenderer(sdlWindow
, -1, (useVSync ? SDL_RENDERER_PRESENTVSYNC
: 0));
246 sdlError(!sdlRenderer
);
247 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY
, "nearest"); // "nearest" or "linear"
248 SDL_RenderSetLogicalSize(sdlRenderer
, vsWidth
*(2-prevLogSizeWas1x
), vsHeight
*(2-prevLogSizeWas1x
));
249 sdlScreen
= SDL_CreateTexture(sdlRenderer
, SDL_PIXELFORMAT_ARGB8888
, SDL_TEXTUREACCESS_STREAMING
, vsWidth
, vsHeight
);
250 sdlError(!sdlScreen
);
251 if (effectiveMag2x
== 2) {
252 sdlScr2x
= SDL_CreateTexture(sdlRenderer
, SDL_PIXELFORMAT_ARGB8888
, SDL_TEXTUREACCESS_STREAMING
, vsWidth
*2, vsHeight
*2);
264 * Deinitialize SDL, free resources.
272 private void deinitInternal () @trusted nothrow @nogc {
274 import core
.stdc
.stdlib
: free
;
276 if (vscr
!is null) free(vscr
);
277 if (vscr2x
!is null) free(vscr2x
);
280 if (ovlVScr
!is null) {
281 ovlVScr
.mVScr
= null;
282 ovlVScr
.resetClipOfs();
284 version(videolib_opengl
) {
285 if (sdlGLCtx
!is null) {
286 SDL_GL_DeleteContext(sdlGLCtx
);
290 if (sdlScreen
) SDL_DestroyTexture(sdlScreen
);
291 if (sdlScr2x
) SDL_DestroyTexture(sdlScr2x
);
292 if (sdlRenderer
) SDL_DestroyRenderer(sdlRenderer
);
298 SDL_DestroyWindow(sdlWindow
);
301 pvInited
= pvInitedOk
= false;
307 * Shutdown SDL, free resources. You don't need to call this explicitely.
315 void deinitVideo () @trusted nothrow @nogc {
324 * Switches between fullscreen and windowed modes.
332 void switchFullscreen () @trusted nothrow {
333 useFullscreen
= !useFullscreen
;
334 if (SDL_SetWindowFullscreen(sdlWindow
, (useFullscreen ?
(useRealFullscreen ? SDL_WINDOW_FULLSCREEN
: SDL_WINDOW_FULLSCREEN_DESKTOP
) : 0)) != 0) {
336 useFullscreen
= !useFullscreen
;
339 if (!useFullscreen
) {
340 SDL_SetWindowSize(sdlWindow
, vsWidth
*effectiveMag2x
, vsHeight
*effectiveMag2x
);
341 SDL_SetWindowPosition(sdlWindow
, SDL_WINDOWPOS_CENTERED
, SDL_WINDOWPOS_CENTERED
);
347 * Post SDL_QUIT message. Can be called to initiate clean exit from main loop.
355 void postQuitMessage () @trusted nothrow @nogc {
356 import core
.stdc
.string
: memset
;
358 memset(&evt
, 0, evt
.sizeof
);
364 // ////////////////////////////////////////////////////////////////////////// //
365 /*@gcc_inline*/ ubyte clampToByte() (int n
) @safe pure nothrow @nogc {
366 n
&= -cast(int)(n
>= 0);
367 return cast(ubyte)(n|
((255-n
)>>31));
373 version(LittleEndian
) {
374 /// RGBA struct to ease color components extraction/replacing
375 align(1) struct RGBA
{
379 } else version(BigEndian
) {
380 /// RGBA struct to ease color components extraction/replacing
381 align(1) struct RGBA
{
386 static assert(0, "WTF?!");
389 static assert(RGBA
.sizeof
== Color
.sizeof
);
407 enum Color Transparent
= AMask
; /// completely transparent pixel color
411 * Is color completely transparent?
419 /*@gcc_inline*/ bool isTransparent(T
: Color
) (T col
) @safe pure nothrow @nogc => ((col
&AMask
) == AMask
);
423 * Is color completely non-transparent?
431 /*@gcc_inline*/ bool isOpaque(T
: Color
) (T col
) @safe pure nothrow @nogc => ((col
&AMask
) == 0);
435 * Build opaque non-transparent rgb color from components.
438 * r = red component [0..255]
439 * g = green component [0..255]
440 * b = blue component [0..255]
445 /*@gcc_inline*/ Color
rgb2col(TR
, TG
, TB
) (TR r
, TG g
, TB b
) @safe pure nothrow @nogc
446 if (__traits(isIntegral
, TR
) && __traits(isIntegral
, TG
) && __traits(isIntegral
, TB
)) =>
447 ((r
&0xff)<<RShift
)|
((g
&0xff)<<GShift
)|
((b
&0xff)<<BShift
);
450 * Build rgba color from components.
453 * r = red component [0..255]
454 * g = green component [0..255]
455 * b = blue component [0..255]
456 * a = transparency [0..255] (0: completely opaque; 255: completely transparent)
461 /*@gcc_inline*/ Color
rgba2col(TR
, TG
, TB
, TA
) (TR r
, TG g
, TB b
, TA a
) @safe pure nothrow @nogc
462 if (__traits(isIntegral
, TR
) && __traits(isIntegral
, TG
) && __traits(isIntegral
, TB
) && __traits(isIntegral
, TA
)) =>
463 ((a
&0xff)<<AShift
)|
((r
&0xff)<<RShift
)|
((g
&0xff)<<GShift
)|
((b
&0xff)<<BShift
);
465 // generate some templates
466 private enum genRGBGetSet(string cname
) =
467 `/*@gcc_inline*/ ubyte rgb`~cname
~`() (Color clr) @safe pure nothrow @nogc => ((clr>>`~cname
[0]~`Shift)&0xff);`~
468 `/*@gcc_inline*/ Color rgbSet`~cname
~`(T) (Color clr, T v) @safe pure nothrow @nogc if (__traits(isIntegral, T)) =>`~
469 `(clr&~`~cname
[0]~`Mask)|((v&0xff)<<`~cname
[0]~`Shift);`;
471 mixin(genRGBGetSet
!"Alpha");
472 mixin(genRGBGetSet
!"Red");
473 mixin(genRGBGetSet
!"Green");
474 mixin(genRGBGetSet
!"Blue");
477 // ////////////////////////////////////////////////////////////////////////// //
480 * Blit rectangle to virtual screen. Does clipping. Ignores alpha.
483 * x = destination x coordinate
484 * y = destination y coordinate
485 * rx = source x coordinate
486 * ry = source y coordinate
496 void blitRect (int x, int y, int rx, int ry, int rw, int rh, const(Color)[] data, usize dw, usize dh) {
497 if (rw < 1 || rh < 1 || dw < 1 || dh < 1) return;
498 if (x >= vsWidth || y >= vsHeight) return;
499 // clip rx and ry left and top
501 if ((rw += rx) <= 0) return;
505 if ((rh += ry) <= 0) return;
508 if (rx >= dw || ry >= dh) return;
509 if (rw > dw) rw = dw;
510 if (rh > dh) rh = dh;
517 // ////////////////////////////////////////////////////////////////////////// //
518 private enum buildBlit1x(string name
, string op
, string wrt
) =
519 `private void `~name
~` () @trusted nothrow @nogc {`~
520 ` auto s = cast(immutable(ubyte)*)vscr;`~
521 ` auto d = cast(ubyte*)vscr2x;`~
522 ` foreach (; 0..vsWidth*vsHeight) {`~
523 ` ubyte i = `~op
~`;`~
531 mixin(buildBlit1x
!("blit1xBW", "(s[0]*28+s[1]*151+s[2]*77)/256", "d[0] = d[1] = d[2] = i"));
532 mixin(buildBlit1x
!("blit1xGreen", "(s[0]*28+s[1]*151+s[2]*77)/256", "d[0] = d[2] = 0; d[1] = i"));
535 private enum buildBlit2x(string name
, string op
) =
536 `private void `~name
~` () @trusted nothrow @nogc {`~
537 ` auto s = cast(immutable(ubyte)*)vscr;`~
538 ` auto d = cast(uint*)vscr2x;`~
539 ` immutable auto wdt = vsWidth;`~
540 ` immutable auto wdt2x = vsWidth*2;`~
541 ` foreach (immutable y; 0..vsHeight) {`~
542 ` foreach (immutable x; 0..wdt) {`~
544 ` immutable uint c1 = ((((c0&0x00ff00ff)*6)>>3)&0x00ff00ff)|(((c0&0x0000ff00)*6)>>3)&0x0000ff00;`~
545 ` d[0] = d[1] = c0;`~
546 ` d[wdt2x] = d[wdt2x+1] = c1;`~
550 // fix d: skip one scanline
556 mixin(buildBlit2x
!("blit2xTV", "immutable uint c0 = (cast(immutable(uint)*)s)[0];"));
557 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;"));
558 mixin(buildBlit2x
!("blit2xTVGreen", "immutable ubyte i = cast(ubyte)((s[0]*28+s[1]*151+s[2]*77)/256); immutable uint c0 = i<<8;"));
561 // ////////////////////////////////////////////////////////////////////////// //
563 * Paint frame contents. Using paintHook()
571 void paintFrame () @trusted {
572 if (paintHook
!is null) {
577 import core
.atomic
: atomicStore
;
578 atomicStore(sdlFrameChangedFlag
, false);
583 * Paint virtual screen onto real screen or window.
591 void paintFrameDefault () @trusted {
592 version(videolib_sdl
) {
594 // fix 'logical size'
595 if (effectiveMag2x
== 2 && useScanlines
) {
596 // mag2x and scanlines: size is 2x
597 if (prevLogSizeWas1x
) {
598 prevLogSizeWas1x
= 0;
599 SDL_RenderSetLogicalSize(sdlRenderer
, vsWidth
*2, vsHeight
*2);
602 // any other case: size is 2x
603 if (!prevLogSizeWas1x
) {
604 prevLogSizeWas1x
= 1;
605 SDL_RenderSetLogicalSize(sdlRenderer
, vsWidth
, vsHeight
);
608 // apply filters if any
609 if (effectiveMag2x
== 2 && useScanlines
) {
610 // heavy case: scanline filter turned on
611 final switch (useFilter
) {
612 case VideoFilter
.None
: blit2xTV(); break;
613 case VideoFilter
.BlackNWhite
: blit2xTVBW(); break;
614 case VideoFilter
.Green
: blit2xTVGreen(); break;
616 SDL_UpdateTexture(sdlScr2x
, null, vscr2x
, cast(uint)(vsWidth
*2*vscr2x
[0].sizeof
));
617 SDL_RenderCopy(sdlRenderer
, sdlScr2x
, null, null);
620 if (useFilter
== VideoFilter
.None
) {
622 SDL_UpdateTexture(sdlScreen
, null, vscr
, cast(uint)(vsWidth
*vscr
[0].sizeof
));
624 import core
.stdc
.string
: memcpy
;
625 final switch (useFilter
) {
626 case VideoFilter
.None
: memcpy(vscr2x
, vscr
, vsWidth
*vsHeight
*vscr
[0].sizeof
); break; // just in case
627 case VideoFilter
.BlackNWhite
: blit1xBW(); break;
628 case VideoFilter
.Green
: blit1xGreen(); break;
630 SDL_UpdateTexture(sdlScreen
, null, vscr2x
, cast(uint)(vsWidth
*vscr2x
[0].sizeof
));
632 SDL_RenderCopy(sdlRenderer
, sdlScreen
, null, null);
634 SDL_RenderPresent(sdlRenderer
);
637 glClear(GL_COLOR_BUFFER_BIT
);
638 glTranslatef(0, vsHeight
, 0);
639 // apply filters if any
640 if (effectiveMag2x
== 2 && useScanlines
) {
641 // heavy case: scanline filter turned on
642 glPixelZoom(1.0f, 1.0f); // scale
643 final switch (useFilter
) {
644 case VideoFilter
.None
: blit2xTV(); break;
645 case VideoFilter
.BlackNWhite
: blit2xTVBW(); break;
646 case VideoFilter
.Green
: blit2xTVGreen(); break;
648 glDrawPixels(vsWidth
*2, vsHeight
*2, GL_BGRA
, GL_UNSIGNED_BYTE
, vscr
);
652 glPixelZoom(effectiveMag2x
, effectiveMag2x
);
653 if (useFilter
== VideoFilter
.None
) {
655 glDrawPixels(vsWidth
, vsHeight
, GL_BGRA
, GL_UNSIGNED_BYTE
, vscr
);
657 import core
.stdc
.string
: memcpy
;
658 final switch (useFilter
) {
659 case VideoFilter
.None
: memcpy(vscr2x
, vscr
, vsWidth
*vsHeight
*vscr
[0].sizeof
); break; // just in case
660 case VideoFilter
.BlackNWhite
: blit1xBW(); break;
661 case VideoFilter
.Green
: blit1xGreen(); break;
663 glDrawPixels(vsWidth
, vsHeight
, GL_BGRA
, GL_UNSIGNED_BYTE
, vscr2x
);
666 SDL_GL_SwapWindow(sdlWindow
);
671 // ////////////////////////////////////////////////////////////////////////// //
673 private import core
.sys
.posix
.time
: CLOCK_MONOTONIC_RAW
;
675 private import core
.sys
.linux
.time
: CLOCK_MONOTONIC_RAW
;
678 private __gshared
int vl_k8clock_initialized
= 0;
679 private __gshared timespec videolib_clock_stt
;
682 private void initializeClock () @trusted nothrow @nogc {
684 vl_k8clock_initialized
= -1;
685 if (clock_getres(CLOCK_MONOTONIC_RAW
, &cres
) != 0) {
686 //fprintf(stderr, "ERROR: can't get clock resolution!\n");
689 //fprintf(stderr, "CLOCK_MONOTONIC: %ld:%ld\n", cres.tv_sec, cres.tv_nsec);
690 if (cres
.tv_sec
> 0 || cres
.tv_nsec
> cast(long)1000000*10 /*10 ms*/) {
691 //fprintf(stderr, "ERROR: real-time clock resolution is too low!\n");
694 if (clock_gettime(CLOCK_MONOTONIC_RAW
, &videolib_clock_stt
) != 0) {
695 //fprintf(stderr, "ERROR: real-time clock initialization failed!\n");
698 vl_k8clock_initialized
= 1;
702 /** returns monitonically increasing time; starting value is UNDEFINED (i.e. can be any number)
703 * milliseconds; (0: no timer available) */
704 ulong getTicks () @trusted nothrow @nogc {
705 if (vl_k8clock_initialized
> 0) {
707 if (clock_gettime(CLOCK_MONOTONIC_RAW
, &ts
) != 0) {
708 //fprintf(stderr, "ERROR: can't get real-time clock value!\n");
711 // ah, ignore nanoseconds in videolib_clock_stt->stt here: we need only 'differential' time, and it can start with something weird
712 return (cast(ulong)(ts
.tv_sec
-videolib_clock_stt
.tv_sec
))*1000+ts
.tv_nsec
/1000000+1;
718 /** returns monitonically increasing time; starting value is UNDEFINED (i.e. can be any number)
719 * microseconds; (0: no timer available) */
720 ulong ticksMicro () @trusted nothrow @nogc {
721 if (vl_k8clock_initialized
> 0) {
723 if (clock_gettime(CLOCK_MONOTONIC_RAW
, &ts
) != 0) {
724 //fprintf(stderr, "ERROR: can't get real-time clock value!\n");
727 // ah, ignore nanoseconds in videolib_clock_stt->stt here: we need only 'differential' time, and it can start with something weird
728 return (cast(ulong)(ts
.tv_sec
-videolib_clock_stt
.tv_sec
))*1000000+ts
.tv_nsec
/1000+1;
734 // ////////////////////////////////////////////////////////////////////////// //
737 enum SDL_MOUSEDOUBLE
= SDL_USEREVENT
+1; /// same as SDL_MouseButtonEvent
738 enum SDL_FREEEVENT
= SDL_USEREVENT
+42; /// first event available for user
741 // ////////////////////////////////////////////////////////////////////////// //
742 /* return >0 if event was eaten by handler; <0 to exit loop and 0 to go on */
743 /* this is hooks for vlwidgets */
744 __gshared
int function (ref SDL_Event ev
) preprocessEventsHook
= null; //DO NOT USE! WILL BE DEPRECATED!
748 * mechanics is like this:
749 * when it's time to show new frame, videolib will call onUpdateCB().
750 * if frameChanged is set (either from previous frame, or by onUpdateCB(),
751 * videolib will call onRebuildCB() and paintFrame().
752 * note that videolib will reset frameChanged after onRebuildCB() called.
753 * also note that if onUpdateCB() or onRebuildCB() will not set frameChanged,
754 * frame visuals will not be updated properly.
755 * you can either update and onRebuildCB screen in onUpdateCB(), or update state in
756 * onUpdateCB() and rebuild virtual screen in onRebuildCB().
757 * you can count on videolib never changes virtual screen contents by itself.
759 /** elapsedTicks: ms elapsed from the last call; will try to keep up with FPS; 0: first call in mainLoop()
760 * can call frameChanged() flag so mainLoop() will call onRebuildCB() or call onRebuildCB() itself */
762 __gshared
void delegate (int elapsedTicks
) onUpdateCB
= null;
763 __gshared
void delegate () onRebuildCB
= null;
765 __gshared
void delegate (in ref SDL_KeyboardEvent ev
) onKeyDownCB
= null;
766 __gshared
void delegate (in ref SDL_KeyboardEvent ev
) onKeyUpCB
= null;
767 __gshared
void delegate (dchar ch
) onTextInputCB
= null;
769 __gshared
void delegate (in ref SDL_MouseButtonEvent ev
) onMouseDownCB
= null;
770 __gshared
void delegate (in ref SDL_MouseButtonEvent ev
) onMouseUpCB
= null;
771 __gshared
void delegate (in ref SDL_MouseButtonEvent ev
) onMouseDoubleCB
= null; /// mouse double-click
772 __gshared
void delegate (in ref SDL_MouseMotionEvent ev
) onMouseMotionCB
= null;
773 __gshared
void delegate (in ref SDL_MouseWheelEvent ev
) onMouseWheelCB
= null;
777 @trusted nothrow @nogc {
778 private import std
.functional
: toDelegate
;
779 @property void onUpdate (void function (int elapsedTicks
) cb
) => onUpdateCB
= toDelegate(cb
);
780 @property void onRebuild (void function () cb
) => onRebuildCB
= toDelegate(cb
);
781 @property void onKeyDown (void function (in ref SDL_KeyboardEvent ev
) cb
) => onKeyDownCB
= toDelegate(cb
);
782 @property void onKeyUp (void function (in ref SDL_KeyboardEvent ev
) cb
) => onKeyUpCB
= toDelegate(cb
);
783 @property void onTextInput (void function (dchar ch
) cb
) => onTextInputCB
= toDelegate(cb
);
784 @property void onMouseDown (void function (in ref SDL_MouseButtonEvent ev
) cb
) => onMouseDownCB
= toDelegate(cb
);
785 @property void onMouseUp (void function (in ref SDL_MouseButtonEvent ev
) cb
) => onMouseUpCB
= toDelegate(cb
);
786 @property void onMouseDouble (void function (in ref SDL_MouseButtonEvent ev
) cb
) => onMouseDoubleCB
= toDelegate(cb
);
787 @property void onMouseMotion (void function (in ref SDL_MouseMotionEvent ev
) cb
) => onMouseMotionCB
= toDelegate(cb
);
788 @property void onMouseWheel (void function (in ref SDL_MouseWheelEvent ev
) cb
) => onMouseWheelCB
= toDelegate(cb
);
790 @property void onUpdate (void delegate (int elapsedTicks
) cb
) => onUpdateCB
= cb
;
791 @property void onRebuild (void delegate () cb
) => onRebuildCB
= cb
;
792 @property void onKeyDown (void delegate (in ref SDL_KeyboardEvent ev
) cb
) => onKeyDownCB
= cb
;
793 @property void onKeyUp (void delegate (in ref SDL_KeyboardEvent ev
) cb
) => onKeyUpCB
= cb
;
794 @property void onTextInput (void delegate (dchar ch
) cb
) => onTextInputCB
= cb
;
795 @property void onMouseDown (void delegate (in ref SDL_MouseButtonEvent ev
) cb
) => onMouseDownCB
= cb
;
796 @property void onMouseUp (void delegate (in ref SDL_MouseButtonEvent ev
) cb
) => onMouseUpCB
= cb
;
797 @property void onMouseDouble (void delegate (in ref SDL_MouseButtonEvent ev
) cb
) => onMouseDoubleCB
= cb
;
798 @property void onMouseMotion (void delegate (in ref SDL_MouseMotionEvent ev
) cb
) => onMouseMotionCB
= cb
;
799 @property void onMouseWheel (void delegate (in ref SDL_MouseWheelEvent ev
) cb
) => onMouseWheelCB
= cb
;
801 @property auto onUpdate () => onUpdateCB
;
802 @property auto onRebuild () => onRebuildCB
;
803 @property auto onKeyDown () => onKeyDownCB
;
804 @property auto onKeyUp () => onKeyUpCB
;
805 @property auto onTextInput () => onTextInputCB
;
806 @property auto onMouseDown () => onMouseDownCB
;
807 @property auto onMouseUp () => onMouseUpCB
;
808 @property auto onMouseDouble () => onMouseDoubleCB
;
809 @property auto onMouseMotion () => onMouseMotionCB
;
810 @property auto onMouseWheel () => onMouseWheelCB
;
813 /// start receiving text input events
814 void startTextInput() () => SDL_StartTextInput();
816 /// stop receiving text input events
817 void stopTextInput() () => SDL_StopTextInput();
820 /* doubleclick processing */
821 // for 3 buttons (SDL_BUTTON_XXX-1)
825 private ulong[3] mdbClickTick
; // autoinit to 0
826 private int[3] mdbClickX
, mdbClickY
; // autoinit to 0
827 private int[3] mdbSkipUp
; // 1: there was one click; 2: skip next 'mouse up' event for this button
828 int mdbThreshold
= 2; /// double-click threshold
829 uint mdbTime
= 500; /// default double-click time, milliseconds (ticks)
832 // returns true: forget this event
833 private bool mdbProcess (in ref SDL_Event ev
) @trusted nothrow @nogc {
834 if (ev
.type
== SDL_MOUSEBUTTONDOWN || ev
.type
== SDL_MOUSEBUTTONUP
) {
835 immutable int idx
= ev
.button
.button
-1;
836 if (idx
< 0 || idx
> 2) return false; // we don't know about other buttons
837 if (ev
.type
== SDL_MOUSEBUTTONDOWN
) {
839 if (mdbSkipUp
[idx
] == 0) {
841 mdbClickTick
[idx
] = getTicks();
842 mdbClickX
[idx
] = ev
.button
.x
;
843 mdbClickY
[idx
] = ev
.button
.y
;
845 } else if (mdbSkipUp
[idx
] == 1) {
846 import std
.math
: abs
;
847 // second click, check for double
848 ulong now
= getTicks();
850 if (now
>= mdbClickTick
[idx
] && now
-mdbClickTick
[idx
] <= mdbTime
) {
851 mdbClickTick
[idx
] = 0;
852 if (abs(mdbClickX
[idx
]-ev
.button
.x
) <= mdbThreshold
&& abs(mdbClickY
[idx
]-ev
.button
.y
) <= mdbThreshold
) {
854 SDL_Event event
= ev
;
855 event
.type
= SDL_MOUSEDOUBLE
;
856 event
.button
.state
= SDL_PRESSED
;
857 SDL_PushEvent(&event
);
859 return true; // fuck this event
862 mdbClickTick
[idx
] = now
;
863 mdbClickX
[idx
] = ev
.button
.x
;
864 mdbClickY
[idx
] = ev
.button
.y
;
868 mdbClickTick
[idx
] = 0;
873 if (mdbSkipUp
[idx
] >= 2) {
874 // this is 'up' event from generated doubleclick, fuck it
884 /** returns !0 to stop event loop; <0: from event preprocessor; >0: from SDL_QUIT */
885 int processEvent (ref SDL_Event ev
) {
887 if (ev
.type
== SDL_MOUSEBUTTONDOWN || ev
.type
== SDL_MOUSEBUTTONUP
) {
890 } else if (ev
.type
== SDL_MOUSEMOTION
) {
895 if (mdbProcess(ev
)) return 0; // process doubleclicks
896 if (preprocessEventsHook
!is null) {
897 int res
= preprocessEventsHook(ev
);
898 if (res
< 0) return -1;
899 if (res
> 0) return 0;
901 if (ev
.type
== SDL_QUIT
) return 1;
903 case SDL_KEYDOWN
: if (onKeyDownCB
!is null) onKeyDownCB(ev
.key
); break;
904 case SDL_KEYUP
: if (onKeyUpCB
!is null) onKeyUpCB(ev
.key
); break;
905 case SDL_MOUSEBUTTONDOWN
: if (onMouseDownCB
!is null) onMouseDownCB(ev
.button
); break;
906 case SDL_MOUSEBUTTONUP
: if (onMouseUpCB
!is null) onMouseUpCB(ev
.button
); break;
907 case SDL_MOUSEDOUBLE
: if (onMouseDoubleCB
!is null) onMouseDoubleCB(ev
.button
); break;
908 case SDL_MOUSEMOTION
: if (onMouseMotionCB
!is null) onMouseMotionCB(ev
.motion
); break;
909 case SDL_MOUSEWHEEL
: if (onMouseWheelCB
!is null) onMouseWheelCB(ev
.wheel
); break;
911 if (onTextInputCB
!is null) {
912 //TODO: UTF-8 decode!
913 if (ev
.text
.text
[0] && ev
.text
.text
[0] < 127) {
914 onTextInputCB(ev
.text
.text
[0]);
919 //TODO: forward other user events
922 case SDL_WINDOWEVENT
:
923 switch (ev
.window
.event
) {
924 case SDL_WINDOWEVENT_EXPOSED
:
925 frameChanged(); // this will force repaint
936 private __gshared
int curFPS
= 35; /// desired FPS (default: 35)
937 private __gshared
ulong lastUpdateCall
= 0;
938 private __gshared
ulong nextUpdateCall
= 0;
939 private __gshared
int msecsInFrame
= 1000/35;
941 @property int currentFPS () @trusted nothrow @nogc => curFPS
; /// return current FPS
942 @property int msecsPerFrame () @trusted nothrow @nogc => msecsInFrame
; /// return number of milliseconds in frame
946 void setFPS (int newfps
) @trusted nothrow @nogc {
947 if (newfps
< 1) newfps
= 1;
948 if (newfps
> 500) newfps
= 500;
949 msecsInFrame
= 1000/newfps
;
950 if (msecsInFrame
== 0) msecsInFrame
= 1;
955 /// update and rebuild frame if necessary
956 /// will not paint frame to screen
957 void sdlCheckFrameUpdate () {
958 auto tm
= getTicks();
959 if (tm
>= nextUpdateCall
) {
960 if (onUpdateCB
!is null) onUpdateCB(cast(int)(tm
-lastUpdateCall
));
961 lastUpdateCall
= tm
;//getTicks();
962 while (tm
>= nextUpdateCall
) {
963 // do something with skipped frames
964 nextUpdateCall
+= msecsInFrame
;
967 import core
.atomic
: cas
;
968 bool cf
= cas(&sdlFrameChangedFlag
, true, false); // cf is true if sdlFrameChangedFlag was true
971 if (onRebuildCB
!is null) onRebuildCB();
976 /** will start and stop FPS timer automatically */
980 lastUpdateCall
= getTicks();
981 nextUpdateCall
= lastUpdateCall
+msecsInFrame
;
982 if (onUpdateCB
!is null) onUpdateCB(0);
983 if (onRebuildCB
!is null) onRebuildCB();
986 if (!SDL_PollEvent(null)) {
987 // no events, see if we have to wait for the next frame
988 auto now
= getTicks();
989 if (now
< nextUpdateCall
) {
990 // have some time to waste
991 int n
= cast(int)(nextUpdateCall
-now
);
992 if (!SDL_WaitEventTimeout(null, n
)) goto noevent
;
995 while (SDL_PollEvent(&ev
)) {
996 if (processEvent(ev
)) { doQuit
= true; break; }
1000 sdlCheckFrameUpdate();
1001 if (isFrameChanged
) paintFrame(); // this will reset sdlFrameChangedFlag again
1006 // ////////////////////////////////////////////////////////////////////////// //
1009 int mWidth
, mHeight
;
1010 int mClipX0
, mClipY0
;
1011 int mClipX1
, mClipY1
;
1014 bool dontFree
= false;
1017 this (void* avscr
, int wdt
, int hgt
) @trusted nothrow @nogc {
1018 mVScr
= cast(uint*)avscr
;
1026 this(TW
, TH
) (TW wdt
, TH hgt
) if (__traits(isIntegral
, TW
) && __traits(isIntegral
, TH
)) {
1030 ~this () @trusted nothrow @nogc => free();
1033 void resize(TW
, TH
) (TW wdt
, TH hgt
) if (__traits(isIntegral
, TW
) && __traits(isIntegral
, TH
)) {
1034 import core
.exception
: onOutOfMemoryError
;
1035 import core
.stdc
.stdlib
: malloc
, realloc
, free
;
1036 if (wdt
< 1 || wdt
> 16384 || hgt
< 1 || hgt
> 16384) throw new VideoLibError("VLOverlay: invalid size");
1037 if (dontFree
) throw new VideoLibError("VLOverlay: can't resize predefined overlay");
1038 if (mVScr
is null) {
1039 mWidth
= cast(int)wdt
;
1040 mHeight
= cast(int)hgt
;
1041 mVScr
= cast(uint*)malloc(mWidth
*mHeight
*mVScr
[0].sizeof
);
1042 if (mVScr
is null) onOutOfMemoryError();
1043 } else if (mWidth
!= cast(int)wdt || mHeight
!= cast(int)hgt
) {
1044 mWidth
= cast(int)wdt
;
1045 mHeight
= cast(int)hgt
;
1046 auto scr
= cast(uint*)realloc(mVScr
, mWidth
*mHeight
*mVScr
[0].sizeof
);
1047 if (scr
is null) { this.free(); onOutOfMemoryError(); }
1053 /// WARNING! this will trash virtual screen!
1054 @property void width(T
) (T w
) if (__traits(isIntegral
, T
)) => resize(w
, mHeight
);
1055 @property void height(T
) (T h
) if (__traits(isIntegral
, T
)) => resize(mWidth
, h
);
1059 @property bool valid () const pure => (mVScr
!is null);
1061 void free () @trusted {
1062 if (!dontFree
&& mVScr
!is null) {
1063 import core
.stdc
.stdlib
: free
;
1071 * Draw (possibly semi-transparent) pixel onto virtual screen; mix colors.
1081 /*@gcc_inline*/ void putPixel(TX
, TY
) (TX x
, TY y
, Color col
) @trusted
1082 if (__traits(isIntegral
, TX
) && __traits(isIntegral
, TY
))
1084 immutable long xx
= cast(long)x
+mXOfs
;
1085 immutable long yy
= cast(long)y
+mYOfs
;
1086 if ((col
&AMask
) != AMask
&& xx
>= mClipX0
&& yy
>= mClipY0
&& xx
<= mClipX1
&& yy
<= mClipY1
) {
1087 uint* da = mVScr
+yy
*mWidth
+xx
;
1089 immutable uint a
= 256-(col
>>24); // to not loose bits
1090 immutable uint dc
= (*da)&0xffffff;
1091 immutable uint srb
= (col
&0xff00ff);
1092 immutable uint sg
= (col
&0x00ff00);
1093 immutable uint drb
= (dc
&0xff00ff);
1094 immutable uint dg
= (dc
&0x00ff00);
1095 immutable uint orb
= (drb
+(((srb
-drb
)*a
+0x800080)>>8))&0xff00ff;
1096 immutable uint og
= (dg
+(((sg
-dg
)*a
+0x008000)>>8))&0x00ff00;
1105 * Draw (possibly semi-transparent) pixel onto virtual screen; don't mix colors.
1115 /*@gcc_inline*/ void setPixel(TX
, TY
) (TX x
, TY y
, Color col
) @trusted
1116 if (__traits(isIntegral
, TX
) && __traits(isIntegral
, TY
))
1118 immutable long xx
= cast(long)x
+mXOfs
;
1119 immutable long yy
= cast(long)y
+mYOfs
;
1120 if (xx
>= mClipX0
&& yy
>= mClipY0
&& xx
<= mClipX1
&& yy
<= mClipY1
) {
1121 uint* da = mVScr
+yy
*mWidth
+xx
;
1126 void resetClipOfs () @safe {
1127 if (mVScr
!is null) {
1128 mClipX0
= mClipY0
= mXOfs
= mYOfs
= 0;
1130 mClipY1
= mHeight
-1;
1132 // all functions checks clipping, and this is not valid region
1133 // so we can omit VScr checks
1134 mClipX0
= mClipY0
= -42;
1135 mClipX1
= mClipY1
= -666;
1139 @property int width () const @safe pure => mWidth
;
1140 @property int height () const @safe pure => mHeight
;
1142 @property int xOfs () const @safe pure => mXOfs
;
1143 @property void xOfs (int v
) @safe => mXOfs
= v
;
1145 @property int yOfs () const @safe pure => mYOfs
;
1146 @property void yOfs (int v
) @safe => mYOfs
= v
;
1148 void getOfs (ref int x
, ref int y
) const @safe pure { x
= mXOfs
; y
= mYOfs
; }
1149 void setOfs (in int x
, in int y
) @safe { mXOfs
= x
; mYOfs
= y
; }
1156 void getOfs (ref Ofs ofs
) const @safe pure { ofs
.x
= mXOfs
; ofs
.y
= mYOfs
; }
1157 void setOfs (in ref Ofs ofs
) @safe { mXOfs
= ofs
.x
; mYOfs
= ofs
.y
; }
1158 void resetOfs () @safe => mXOfs
= mYOfs
= 0;
1165 void getClip (ref int x0
, ref int y0
, ref int wdt
, ref int hgt
) const @safe pure {
1166 if (mVScr
!is null) {
1169 wdt
= mClipX1
-mClipX0
+1;
1170 hgt
= mClipY1
-mClipY0
+1;
1172 x0
= y0
= wdt
= hgt
= 0;
1176 void setClip (in int x0
, in int y0
, in int wdt
, in int hgt
) @safe {
1177 if (mVScr
!is null) {
1180 mClipX1
= (wdt
> 0 ? x0
+wdt
-1 : x0
-1);
1181 mClipY1
= (hgt
> 0 ? y0
+hgt
-1 : y0
-1);
1182 if (mClipX0
< 0) mClipX0
= 0;
1183 if (mClipY0
< 0) mClipY0
= 0;
1184 if (mClipX0
>= mWidth
) mClipX0
= mWidth
-1;
1185 if (mClipY0
>= mHeight
) mClipY0
= mHeight
-1;
1186 if (mClipX1
< 0) mClipX1
= 0;
1187 if (mClipY1
< 0) mClipY1
= 0;
1188 if (mClipX1
>= mWidth
) mClipX1
= mWidth
-1;
1189 if (mClipY1
>= mHeight
) mClipY1
= mHeight
-1;
1193 void resetClip () @safe {
1194 if (mVScr
!is null) {
1195 mClipX0
= mClipY0
= 0;
1197 mClipY1
= mHeight
-1;
1199 // all functions checks clipping, and this is not valid region
1200 // so we can omit VScr checks
1201 mClipX0
= mClipY0
= -42;
1202 mClipX1
= mClipY1
= -666;
1206 void getClip (ref Clip clip
) const @safe pure => getClip(clip
.x
, clip
.y
, clip
.w
, clip
.h
);
1207 void setClip (in ref Clip clip
) @safe => setClip(clip
.x
, clip
.y
, clip
.w
, clip
.h
);
1209 void clipIntrude (int dx
, int dy
) @safe {
1210 if (mVScr
!is null) {
1218 void clipExtrude (int dx
, int dy
) @safe => clipIntrude(-dx
, -dy
);
1220 // //////////////////////////////////////////////////////////////////////// //
1222 * Draw 6x8 character onto virtual screen in KOI8 encoding.
1228 * col = foreground color
1229 * bkcol = background color
1234 void drawChar (int x
, int y
, char ch
, Color col
, Color bkcol
=Transparent
) @trusted {
1236 foreach (immutable int dy
; 0..8) {
1237 ubyte b
= vlFont6
[pos
++];
1238 foreach (immutable int dx
; 0..6) {
1239 Color c
= (b
&0x80 ? col
: bkcol
);
1240 if (!isTransparent(c
)) putPixel(x
+dx
, y
+dy
, c
);
1246 void drawStr (int x
, int y
, string
str, Color col
, Color bkcol
=Transparent
) @trusted {
1247 foreach (immutable char ch
; str) {
1248 drawChar(x
, y
, ch
, col
, bkcol
);
1253 static int charWidthProp (char ch
) @trusted pure => (vlFontPropWidth
[ch
]&0x0f);
1255 int strWidthProp (string
str) @trusted pure {
1257 foreach (immutable char ch
; str) wdt
+= (vlFontPropWidth
[ch
]&0x0f)+1;
1258 if (wdt
> 0) --wdt
; // don't count last empty pixel
1262 int drawCharProp (int x
, int y
, char ch
, Color col
, Color bkcol
=Transparent
) @trusted {
1264 immutable int wdt
= (vlFontPropWidth
[ch
]&0x0f);
1265 foreach (immutable int dy
; 0..8) {
1266 ubyte b
= (vlFont6
[pos
++]<<(vlFontPropWidth
[ch
]>>4))&0xff;
1267 foreach (immutable int dx
; 0..wdt
) {
1268 Color c
= (b
&0x80 ? col
: bkcol
);
1269 if (!isTransparent(c
)) putPixel(x
+dx
, y
+dy
, c
);
1276 int drawStrProp (int x
, int y
, string
str, Color col
, Color bkcol
=Transparent
) @trusted {
1279 foreach (immutable char ch
; str) {
1281 if (!isTransparent(bkcol
)) foreach (int dy
; 0..8) putPixel(x
, y
+dy
, bkcol
);
1285 x
+= drawCharProp(x
, y
, ch
, col
, bkcol
);
1290 void drawOutlineStr (int x
, int y
, string text
, Color col
, Color outcol
) @trusted {
1291 foreach (immutable int dy
; -1..2) {
1292 foreach (immutable int dx
; -1..2) {
1293 if (dx || dy
) drawStr(x
+dx
, y
+dy
, text
, outcol
, Transparent
);
1296 drawStr(x
, y
, text
, col
, Transparent
);
1299 int drawOutlineProp (int x
, int y
, string text
, Color col
, Color outcol
) @trusted {
1300 foreach (immutable int dy
; -1..2) {
1301 foreach (immutable int dx
; -1..2) {
1302 if (dx || dy
) drawStrProp(x
+dx
, y
+dy
, text
, outcol
, Transparent
);
1305 return drawStrProp(x
, y
, text
, col
, Transparent
);
1308 // ////////////////////////////////////////////////////////////////////////// //
1309 void clear (Color col
) @trusted {
1310 if (mVScr
!is null) {
1311 if (dontFree
) col
&= 0xffffff;
1312 mVScr
[0..mWidth
*mHeight
] = col
;
1316 void hline (int x0
, int y0
, int len
, Color col
) @trusted {
1317 if (isOpaque(col
) && len
> 0 && mVScr
!is null) {
1320 if (y0
>= mClipY0
&& x0
<= mClipX1
&& y0
<= mClipY1
&& x0
+len
> mClipX0
) {
1321 if (x0
< mClipX0
) { if ((len
+= (x0
-mClipX0
)) <= 0) return; x0
= mClipX0
; }
1322 if (x0
+len
-1 > mClipX1
) len
= mClipX1
-x0
+1;
1323 immutable usize ofs
= y0
*mWidth
+x0
;
1324 mVScr
[ofs
..ofs
+len
] = col
;
1327 while (len
-- > 0) putPixel(x0
++, y0
, col
);
1331 void vline (int x0
, int y0
, int len
, Color col
) @trusted {
1332 while (len
-- > 0) putPixel(x0
, y0
++, col
);
1337 void drawLine(bool lastPoint) (int x0, int y0, int x1, int y1, Color col) @trusted {
1338 import std.math : abs;
1339 int dx = abs(x1-x0), sx = (x0 < x1 ? 1 : -1);
1340 int dy = -abs(y1-y0), sy = (y0 < y1 ? 1 : -1);
1341 int err = dx+dy, e2; // error value e_xy
1343 static if (lastPoint) putPixel(x0, y0, col);
1344 if (x0 == x1 && y0 == y1) break;
1345 static if (!lastPoint) putPixel(x0, y0, col);
1347 if (e2 >= dy) { err += dy; x0 += sx; } // e_xy+e_x > 0
1348 if (e2 <= dx) { err += dx; y0 += sy; } // e_xy+e_y < 0
1353 // as the paper on which this code is based in not available to public,
1354 // i say "fuck you!"
1355 // knowledge must be publicly available; the ones who hides the knowledge
1356 // are not deserving any credits.
1357 void drawLine(bool lastPoint
) (int x0
, int y0
, int x1
, int y1
, immutable Color col
) {
1358 enum swap(string a
, string b
) = "{int tmp_="~a
~";"~a
~"="~b
~";"~b
~"=tmp_;}";
1360 if ((col
&AMask
) == AMask || mClipX0
> mClipX1 || mClipY0
> mClipY1 || mVScr
is null) return;
1362 if (x0
== x1
&& y0
== y1
) {
1363 static if (lastPoint
) putPixel(x0
, y0
, col
);
1367 x0
+= mXOfs
; x1
+= mXOfs
;
1368 y0
+= mYOfs
; y1
+= mYOfs
;
1371 int wx0
= mClipX0
, wy0
= mClipY0
, wx1
= mClipX1
, wy1
= mClipY1
;
1373 int stx
, sty
; // "steps" for x and y axes
1374 int dsx
, dsy
; // "lengthes" for x and y axes
1375 int dx2
, dy2
; // "double lengthes" for x and y axes
1376 int xd
, yd
; // current coord
1377 int e
; // "error" (as in bresenham algo)
1383 // from left to right
1384 if (x0
> wx1 || x1
< wx0
) return; // out of screen
1385 stx
= 1; // going right
1387 // from right to left
1388 if (x1
> wx1 || x0
< wx0
) return; // out of screen
1389 stx
= -1; // going left
1394 mixin(swap
!("wx0", "wx1"));
1398 // from top to bottom
1399 if (y0
> wy1 || y1
< wy0
) return; // out of screen
1400 sty
= 1; // going down
1402 // from bottom to top
1403 if (y1
> wy1 || y0
< wy0
) return; // out of screen
1404 sty
= -1; // going up
1409 mixin(swap
!("wy0", "wy1"));
1416 mixin(swap
!("x0", "y0"));
1417 mixin(swap
!("x1", "y1"));
1418 mixin(swap
!("dsx", "dsy"));
1419 mixin(swap
!("wx0", "wy0"));
1420 mixin(swap
!("wx1", "wy1"));
1421 mixin(swap
!("stx", "sty"));
1432 bool xfixed
= false;
1435 int temp
= dx2
*(wy0
-y0
)-dsx
;
1438 if (xd
> wx1
) return; // x is moved out of clipping rect, nothing to do
1442 if (rem
> 0) { ++xd
; e
+= dy2
; }
1446 if (!xfixed
&& x0
< wx0
) {
1448 int temp
= dy2
*(wx0
-x0
);
1451 if (yd
> wy1 || yd
== wy1
&& rem
>= dsx
) return;
1454 if (rem
>= dsx
) { ++yd
; e
-= dx2
; }
1458 int temp
= dx2
*(wy1
-y0
)+dsx
;
1461 if (rem
== 0) --term
;
1463 if (term
> wx1
) term
= wx1
; // clip at right
1464 static if (lastPoint
) {
1468 if (term
== xd
) return; // this is the only point, get out of here
1470 if (sty
== -1) yd
= -yd
;
1471 if (stx
== -1) { xd
= -xd
; term
= -term
; }
1473 // draw it; `putPixel()` can omit checks
1474 while (xd
!= term
) {
1475 // inlined `putPixel(*d0, *d1, col)`
1476 // this can be made even faster by precalculating `da` and making
1477 // separate code branches for mixing and non-mixing drawing, but...
1479 uint* da = mVScr
+(*d1
)*mWidth
+(*d0
);
1481 immutable uint a
= 256-(col
>>24); // to not loose bits
1482 immutable uint dc
= (*da)&0xffffff;
1483 immutable uint srb
= (col
&0xff00ff);
1484 immutable uint sg
= (col
&0x00ff00);
1485 immutable uint drb
= (dc
&0xff00ff);
1486 immutable uint dg
= (dc
&0x00ff00);
1487 immutable uint orb
= (drb
+(((srb
-drb
)*a
+0x800080)>>8))&0xff00ff;
1488 immutable uint og
= (dg
+(((sg
-dg
)*a
+0x008000)>>8))&0x00ff00;
1493 // done drawing, move coords
1504 void line (int x0
, int y0
, int x1
, int y1
, Color col
) @trusted => drawLine
!true(x0
, y0
, x1
, y1
, col
);
1505 void lineNoLast (int x0
, int y0
, int x1
, int y1
, Color col
) @trusted => drawLine
!false(x0
, y0
, x1
, y1
, col
);
1507 void fillRect (int x
, int y
, int w
, int h
, Color col
) @trusted {
1510 if (w
> 0 && h
> 0 && x
+w
> mClipX0
&& y
+h
> mClipY0
&& x
<= mClipX1
&& y
<= mClipY1
) {
1512 sr
.x
= mClipX0
; sr
.y
= mClipY0
; sr
.w
= mClipX1
-mClipX0
+1; sr
.h
= mClipY1
-mClipY0
+1;
1513 r
.x
= x
; r
.y
= y
; r
.w
= w
; r
.h
= h
;
1514 if (SDL_IntersectRect(&sr
, &r
, &dr
)) {
1517 while (dr
.h
-- > 0) hline(x
, y
++, dr
.w
, col
);
1522 void rect (int x
, int y
, int w
, int h
, Color col
) @trusted {
1523 if (w
> 0 && h
> 0) {
1524 hline(x
, y
, w
, col
);
1525 hline(x
, y
+h
-1, w
, col
);
1526 vline(x
, y
+1, h
-2, col
);
1527 vline(x
+w
-1, y
+1, h
-2, col
);
1532 void selectionRect (int phase
, int x0
, int y0
, int wdt
, int hgt
, Color col0
, Color col1
=Transparent
) @trusted {
1533 if (wdt
> 0 && hgt
> 0) {
1535 foreach (immutable f
; x0
..x0
+wdt
) { putPixel(f
, y0
, ((phase
%= 4) < 2 ? col0
: col1
)); ++phase
; }
1537 foreach (immutable f
; y0
+1..y0
+hgt
) { putPixel(x0
+wdt
-1, f
, ((phase
%= 4) < 2 ? col0
: col1
)); ++phase
; }
1539 foreach_reverse (immutable f
; x0
..x0
+wdt
-1) { putPixel(f
, y0
+hgt
-1, ((phase
%= 4) < 2 ? col0
: col1
)); ++phase
; }
1541 foreach_reverse (immutable f
; y0
..y0
+hgt
-1) { putPixel(x0
, f
, ((phase
%= 4) < 2 ? col0
: col1
)); ++phase
; }
1545 private void plot4points() (int cx
, int cy
, int x
, int y
, Color clr
) @trusted {
1546 putPixel(cx
+x
, cy
+y
, clr
);
1547 if (x
!= 0) putPixel(cx
-x
, cy
+y
, clr
);
1548 if (y
!= 0) putPixel(cx
+x
, cy
-y
, clr
);
1549 putPixel(cx
-x
, cy
-y
, clr
);
1552 void circle (int cx
, int cy
, int radius
, Color clr
) @trusted {
1553 if (radius
> 0 && !isTransparent(clr
)) {
1554 int error
= -radius
, x
= radius
, y
= 0;
1555 if (radius
== 1) { putPixel(cx
, cy
, clr
); return; }
1557 plot4points(cx
, cy
, x
, y
, clr
);
1558 plot4points(cx
, cy
, y
, x
, clr
);
1561 if (error
>= 0) { --x
; error
-= x
*2; }
1563 plot4points(cx
, cy
, x
, y
, clr
);
1567 void fillCircle (int cx
, int cy
, int radius
, Color clr
) @trusted {
1568 if (radius
> 0 && !isTransparent(clr
)) {
1569 int error
= -radius
, x
= radius
, y
= 0;
1570 if (radius
== 1) { putPixel(cx
, cy
, clr
); return; }
1576 hline(cx
-x
, cy
+last_y
, 2*x
+1, clr
);
1577 if (x
!= 0 && last_y
!= 0) hline(cx
-x
, cy
-last_y
, 2*x
+1, clr
);
1580 hline(cx
-last_y
, cy
+x
, 2*last_y
+1, clr
);
1581 if (last_y
!= 0 && x
!= 0) hline(cx
-last_y
, cy
-x
, 2*last_y
+1, clr
);
1591 void ellipse (int x0
, int y0
, int x1
, int y1
, Color clr
) @trusted {
1592 import std
.math
: abs
;
1593 int a
= abs(x1
-x0
), b
= abs(y1
-y0
), b1
= b
&1; // values of diameter
1594 long dx
= 4*(1-a
)*b
*b
, dy
= 4*(b1
+1)*a
*a
; // error increment
1595 long err
= dx
+dy
+b1
*a
*a
; // error of 1.step
1596 if (x0
> x1
) { x0
= x1
; x1
+= a
; } // if called with swapped points...
1597 if (y0
> y1
) y0
= y1
; // ...exchange them
1598 y0
+= (b
+1)/2; y1
= y0
-b1
; // starting pixel
1599 a
*= 8*a
; b1
= 8*b
*b
;
1602 putPixel(x1
, y0
, clr
); // I. Quadrant
1603 putPixel(x0
, y0
, clr
); // II. Quadrant
1604 putPixel(x0
, y1
, clr
); // III. Quadrant
1605 putPixel(x1
, y1
, clr
); // IV. Quadrant
1607 if (e2
>= dx
) { ++x0
; --x1
; err
+= dx
+= b1
; } // x step
1608 if (e2
<= dy
) { ++y0
; --y1
; err
+= dy
+= a
; } // y step
1611 // too early stop of flat ellipses a=1
1612 putPixel(x0
-1, ++y0
, clr
); // complete tip of ellipse
1613 putPixel(x0
-1, --y1
, clr
);
1617 void fillEllipse (int x0
, int y0
, int x1
, int y1
, Color clr
) @trusted {
1618 import std
.math
: abs
;
1619 int a
= abs(x1
-x0
), b
= abs(y1
-y0
), b1
= b
&1; // values of diameter
1620 long dx
= 4*(1-a
)*b
*b
, dy
= 4*(b1
+1)*a
*a
; // error increment
1621 long err
= dx
+dy
+b1
*a
*a
; // error of 1.step
1622 int prev_y0
= -1, prev_y1
= -1;
1623 if (x0
> x1
) { x0
= x1
; x1
+= a
; } // if called with swapped points...
1624 if (y0
> y1
) y0
= y1
; // ...exchange them
1625 y0
+= (b
+1)/2; y1
= y0
-b1
; // starting pixel
1626 a
*= 8*a
; b1
= 8*b
*b
;
1629 if (y0
!= prev_y0
) { hline(x0
, y0
, x1
-x0
+1, clr
); prev_y0
= y0
; }
1630 if (y1
!= y0
&& y1
!= prev_y1
) { hline(x0
, y1
, x1
-x0
+1, clr
); prev_y1
= y1
; }
1632 if (e2
>= dx
) { ++x0
; --x1
; err
+= dx
+= b1
; } // x step
1633 if (e2
<= dy
) { ++y0
; --y1
; err
+= dy
+= a
; } // y step
1636 // too early stop of flat ellipses a=1
1637 putPixel(x0
-1, ++y0
, clr
); // complete tip of ellipse
1638 putPixel(x0
-1, --y1
, clr
);
1642 /** blit overlay to main screen */
1643 void blitTpl(string btype
) (VLOverlay destovl
, int xd
, int yd
, ubyte alpha
=0) @trusted {
1644 static if (btype
== "NoSrcAlpha") import core
.stdc
.string
: memcpy
;
1645 if (!valid || destovl
is null ||
!destovl
.valid
) return;
1646 if (xd
> -mWidth
&& yd
> -mHeight
&& xd
< destovl
.mWidth
&& yd
< destovl
.mHeight
&& alpha
< 255) {
1647 int w
= mWidth
, h
= mHeight
;
1648 immutable uint vsPitch
= destovl
.mWidth
;
1649 immutable uint myPitch
= mWidth
;
1652 // vertical clipping
1654 // skip invisible top part
1655 if ((h
+= yd
) < 1) return;
1659 if (yd
+h
> destovl
.mHeight
) {
1660 // don't draw invisible bottom part
1661 if ((h
= destovl
.mHeight
-yd
) < 1) return;
1663 // horizontal clipping
1665 // skip invisible left part
1666 if ((w
+= xd
) < 1) return;
1670 if (xd
+w
> destovl
.mWidth
) {
1671 // don't draw invisible right part
1672 if ((w
= destovl
.mWidth
-xd
) < 1) return;
1675 dest
= destovl
.mVScr
+yd
*vsPitch
+xd
;
1676 static if (btype
== "NoSrcAlpha") {
1679 import core
.stdc
.string
: memcpy
;
1680 memcpy(dest
, my
, w
*destovl
.mVScr
[0].sizeof
);
1689 static if (btype
== "NoSrcAlpha") immutable uint a
= 256-alpha
; // to not loose bits
1691 auto src
= cast(immutable(uint)*)my
;
1693 foreach_reverse (immutable dx
; 0..w
) {
1694 immutable uint s
= *src
++;
1695 static if (btype
== "SrcAlpha") {
1696 if ((s
&AMask
) == Transparent
) { ++dst
; continue; }
1697 immutable uint a
= 256-clampToByte(cast(int)(alpha
+(s
>>AShift
)&0xff)); // to not loose bits
1699 immutable uint dc
= (*dst
)&0xffffff;
1700 immutable uint srb
= (s
&0xff00ff);
1701 immutable uint sg
= (s
&0x00ff00);
1702 immutable uint drb
= (dc
&0xff00ff);
1703 immutable uint dg
= (dc
&0x00ff00);
1704 immutable uint orb
= (drb
+(((srb
-drb
)*a
+0x800080)>>8))&0xff00ff;
1705 immutable uint og
= (dg
+(((sg
-dg
)*a
+0x008000)>>8))&0x00ff00;
1715 alias blit
= blitTpl
!"NoSrcAlpha";
1716 alias blitSrcAlpha
= blitTpl
!"SrcAlpha";
1720 // ////////////////////////////////////////////////////////////////////////// //
1721 shared static this () {
1726 shared static ~this () {
1731 // ////////////////////////////////////////////////////////////////////////// //
1733 private immutable ubyte[0x458-0x401] utf2koiTable = [
1734 0xB3,0x3F,0x3F,0xB4,0x3F,0xB6,0xB7,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0xE1,
1735 0xE2,0xF7,0xE7,0xE4,0xE5,0xF6,0xFA,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF2,
1736 0xF3,0xF4,0xF5,0xE6,0xE8,0xE3,0xFE,0xFB,0xFD,0xFF,0xF9,0xF8,0xFC,0xE0,0xF1,0xC1,
1737 0xC2,0xD7,0xC7,0xC4,0xC5,0xD6,0xDA,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD2,
1738 0xD3,0xD4,0xD5,0xC6,0xC8,0xC3,0xDE,0xDB,0xDD,0xDF,0xD9,0xD8,0xDC,0xC0,0xD1,0x3F,
1739 0xA3,0x3F,0x3F,0xA4,0x3F,0xA6,0xA7
1743 /// Convert utf-8 to koi8-u
1744 /*@gcc_inline*/ ubyte utf2koi() (dchar ch) @safe pure nothrow @nogc {
1745 if (ch < 127) return ch&0xff;
1746 if (ch > 0x400 && ch < 0x458) return utf2koiTable[ch-0x401];
1747 if (ch == 0x490) return 0xBD;
1748 if (ch == 0x491) return 0xAD;
1754 // ////////////////////////////////////////////////////////////////////////// //
1755 public immutable ubyte[256*8] vlFont6
= [
3232 /* 164 '¤' ukr. ie small */
3250 /* 166 '¦' ukr. i small */
3259 /* 167 '§' ukr. ji small */
3376 /* 180 '´' ukr. ie big */
3394 /* 182 '¶' ukr. i big */
3403 /* 183 '·' ukr. ij big */
4064 // bits 4..7: lshift
4065 public immutable ubyte[256] vlFontPropWidth
= () {
4067 foreach (immutable cnum
; 0..256) {
4068 import core
.bitop
: bsf, bsr;
4070 (cnum
>= 32 && cnum
<= 127) ||
4071 (cnum
>= 143 && cnum
<= 144) ||
4072 (cnum
>= 166 && cnum
<= 167) ||
4073 (cnum
>= 192 && cnum
<= 255);
4077 foreach (immutable dy
; 0..8) {
4078 immutable b
= vlFont6
[cnum
*8+dy
];
4080 immutable mn
= 7-bsr(b
);
4081 if (mn
< shift
) shift
= mn
;
4086 foreach (immutable dy
; 0..8) {
4087 immutable b
= (vlFont6
[cnum
*8+dy
]<<shift
);
4088 immutable cwdt
= (b ?
8-bsf(b
) : 0);
4089 if (cwdt
> wdt
) wdt
= cast(ubyte)cwdt
;
4092 case 0: wdt
= 8; break; // 8px space
4093 case 32: wdt
= 5; break; // 5px space
4094 case 17: .. case 27: wdt
= 8; break; // single frames
4095 case 48: .. case 57: wdt
= 5; break; // digits are monospaced
4096 case 127: .. case 142: wdt
= 8; break; // filled frames
4097 case 145: .. case 151: wdt
= 8; break; // filled frames
4098 case 155: .. case 159: wdt
= 8; break; // filled frames
4101 res
[cnum
] = (wdt
&0x0f)|
((shift
<<4)&0xf0);