1 // ////////////////////////////////////////////////////////////////////////// //
2 import core
.time
: MonoTime
, Duration
;
5 import core
.sync
.condition
;
6 import core
.sync
.rwmutex
;
7 import core
.sync
.mutex
;
11 version = show_frame_time
;
14 // ////////////////////////////////////////////////////////////////////////// //
15 __gshared Mutex mutexCondCanRender
;
16 __gshared Condition waitCondCanRender
;
17 shared bool renderComplete
= true;
18 shared int renderDie
= 0; // 1: waiting for death; 2: dead
21 shared static this () {
22 mutexCondCanRender
= new Mutex();
23 waitCondCanRender
= new Condition(mutexCondCanRender
);
27 // ////////////////////////////////////////////////////////////////////////// //
28 static if (__traits(compiles
, () { import arsd
.simpledisplay
; })) {
29 public import arsd
.simpledisplay
;
31 public import simpledisplay
;
34 __gshared SimpleWindow sdwindow
;
35 __gshared Image texImage
;
37 shared static ~this () {
44 enum scanlines
= false;
46 enum vlEffectiveWidth
= vlWidth
*scale
;
47 enum vlEffectiveHeight
= vlHeight
*scale
;
50 // ////////////////////////////////////////////////////////////////////////// //
51 ubyte clampToByte(T
) (T n
) pure nothrow @safe @nogc if (__traits(isIntegral
, T
)) {
52 //static assert(!__traits(isUnsigned, T), "clampToByte can't process unsigned types");
53 static if (__VERSION__
> 2067) pragma(inline
, true);
54 static if (T
.sizeof
== 2 || T
.sizeof
== 4) {
55 static if (__traits(isUnsigned
, T
)) {
56 return cast(ubyte)(n
&0xff|
(255-((-cast(int)(n
< 256))>>24)));
58 n
&= -cast(int)(n
>= 0);
59 return cast(ubyte)(n|
((255-cast(int)n
)>>31));
61 } else static if (T
.sizeof
== 1) {
62 static assert(__traits(isUnsigned
, T
), "clampToByte: signed byte? no, really?");
65 static assert(false, "clampToByte: integer too big");
70 // ////////////////////////////////////////////////////////////////////////// //
72 import std
.format
: FormatSpec
;
73 void toString (scope void delegate(const(char)[]) sink
, FormatSpec
!char fmt
) const @trusted {
76 sink
.formatValue(x
, fmt
);
78 sink
.formatValue(y
, fmt
);
80 sink
.formatValue(z
, fmt
);
85 float x
= 0, y
= 0, z
= 0;
87 @property float opIndex (size_t idx
) const pure {
88 static if (__VERSION__
> 2067) pragma(inline
, true);
89 if (idx
> 2) assert(0, "invalid index in Vec3 opindex");
90 return (idx
== 0 ? x
: (idx
== 1 ? y
: z
));
93 @property float opIndexAssign (float v
, size_t idx
) {
94 static if (__VERSION__
> 2067) pragma(inline
, true);
95 if (idx
> 2) assert(0, "invalid index in Vec3 opindex");
104 ref Vec3
normalize () {
105 //pragma(inline, true);
106 import std
.math
: sqrt
;
107 immutable invlen
= 1.0f/sqrt(x
*x
+y
*y
+z
*z
);
115 static float length (float x
, float y
) {
116 //pragma(inline, true);
117 import std
.math
: sqrt
;
118 return sqrt(x
*x
+y
*y
);
121 static float length (float x
, float y
, float z
) {
122 //pragma(inline, true);
123 import std
.math
: sqrt
;
124 return sqrt(x
*x
+y
*y
+z
*z
);
128 //pragma(inline, true);
129 import std
.math
: sqrt
;
130 return sqrt(x
*x
+y
*y
+z
*z
);
131 //return length(x, y, z);
135 //pragma(inline, true);
136 import std
.math
: sqrt
;
137 immutable invlen
= 1.0f/sqrt(x
*x
+y
*y
+z
*z
);
138 return Vec3(x
*invlen
, y
*invlen
, z
*invlen
);
142 //pragma(inline, true);
143 import std
.math
: abs
;
144 return Vec3(abs(x
), abs(y
), abs(z
));
147 Vec3
minmax(string op
) (in auto ref Vec3 b
) if (op
== "min" || op
== "max") {
148 //pragma(inline, true);
149 mixin("import std.algorithm : "~op
~";");
150 return mixin("Vec3("~op
~"(x, b.x), "~op
~"(y, b.y), "~op
~"(z, b.z))");
153 Vec3
minmax(string op
) (float v
) if (op
== "min" || op
== "max") {
154 //pragma(inline, true);
155 mixin("import std.algorithm : "~op
~";");
156 return mixin("Vec3("~op
~"(x, v), "~op
~"(y, v), "~op
~"(z, v))");
160 alias min = minmax!"min";
161 alias max = minmax!"max";
164 Vec3
opBinary(string op
) (in auto ref Vec3 b
) if (op
== "+" || op
== "-" || op
== "*" || op
== "/") {
165 static if (__VERSION__
> 2067) pragma(inline
, true);
166 return mixin("Vec3(x"~op
~"b.x, y"~op
~"b.y, z"~op
~"b.z)");
169 Vec3
opBinary(string op
) (in float b
) if (op
== "+" || op
== "-" || op
== "*" || op
== "/") {
170 static if (__VERSION__
> 2067) pragma(inline
, true);
171 return mixin("Vec3(x"~op
~"b, y"~op
~"b, z"~op
~"b)");
174 // cross product (normal to plane containing a and b)
175 Vec3
opBinary(string op
: "%") (in auto ref Vec3 b
) {
176 static if (__VERSION__
> 2067) pragma(inline
, true);
177 return Vec3(y
*b
.z
-z
*b
.y
, z
*b
.x
-x
*b
.z
, x
*b
.y
-y
*b
.x
);
180 float dot() (in auto ref Vec3 b
) {
181 static if (__VERSION__
> 2067) pragma(inline
, true);
182 return x
*b
.x
+y
*b
.y
+z
*b
.z
;
185 float anglev() (in auto ref Vec3 b
) {
186 static if (__VERSION__
> 2067) pragma(inline
, true);
187 import std
.math
: acos
;
188 return acos(dot(b
)/(length
*b
.length
));
191 // things like `.xyy`
192 Vec3
opDispatch(string type
) ()
193 if (type
.length
== 3 &&
194 (type
[0] == 'x' || type
[0] == 'y' || type
[0] == 'z') &&
195 (type
[1] == 'x' || type
[1] == 'y' || type
[1] == 'z') &&
196 (type
[2] == 'x' || type
[2] == 'y' || type
[2] == 'z'))
198 static if (__VERSION__
> 2067) pragma(inline
, true);
199 return mixin("Vec3("~type
[0]~","~type
[1]~","~type
[2]~")");
203 auto rotx() (float angle) {
204 //pragma(inline, true);
205 float sine = void, cosine = void;
206 asm pure nothrow @trusted @nogc {
212 return Vec3(x, y*cosine-z*sine, y*sine+z*cosine);
215 auto roty() (float angle) {
216 //pragma(inline, true);
217 float sine = void, cosine = void;
218 asm pure nothrow @trusted @nogc {
224 return Vec3(x*cosine-z*sine, y, x*sine+z*cosine);
227 auto rotz() (float angle) {
228 //pragma(inline, true);
229 float sine = void, cosine = void;
230 asm pure nothrow @trusted @nogc {
236 return Vec3(x*cosine-y*sine, x*sine+y*cosine, z);
241 ref Vec3
opOpAssign(string op
) (in auto ref Vec3 b
) if (op
== "+" || op
== "-" || op
== "*" || op
== "/") {
242 static if (__VERSION__
> 2067) pragma(inline
, true);
243 mixin("x"~op
~"=b.x;");
244 mixin("y"~op
~"=b.y;");
245 mixin("z"~op
~"=b.z;");
249 //static float smoothstep (float x) pure { static if (__VERSION__ > 2067) pragma(inline, true); return x*x*(3-2*x); }
253 // ////////////////////////////////////////////////////////////////////////// //
254 // raymarch global vars
255 __gshared
float worldtime
= 0; // seconds
256 __gshared Vec3 vrp
; // view reference point
257 __gshared Vec3 vuv
; // view up vector
258 __gshared Vec3 prp
; // camera position
261 immutable Vec3 e
= Vec3(0.02f, 0.0f, 0.0f);
262 immutable Vec3 exyy
= e
.xyy
;
263 immutable Vec3 eyxy
= e
.yxy
;
264 immutable Vec3 eyyx
= e
.yyx
;
267 float fract() (float x
) {
268 //pragma(inline, true);
269 import std
.math
: floor
;
275 float mod() (float x
, float y
) {
276 //pragma(inline, true);
277 import std
.math
: floor
;
278 // x minus the product of y and floor(x/y)
279 return x
-y
*floor(x
/y
);
283 // ////////////////////////////////////////////////////////////////////////// //
284 // raymarch composition functions
286 float objopRep(string objfunc
) (in auto ref Vec3 pp
, in auto ref Vec3 c
) {
287 static if (__VERSION__
> 2067) pragma(inline
, true);
288 // the following MUST be `p`
289 immutable auto p
= Vec3(mod(p
.x
, c
.x
), mod(p
.y
, c
.y
), mod(p
.z
, c
.z
))-c
*0.5f;
290 return mixin(objfunc
);
293 float objopScale(string objfunc
) (in auto ref Vec3 pp
, float s
) {
294 static if (__VERSION__
> 2067) pragma(inline
, true);
295 immutable auto p
= pp
/s
;
296 return mixin(objfunc
)*s
;
299 // rotation/translation
301 Vec3 objopTx(string objfunc) (in auto ref Vec3 pp, in auto ref Mat4 mt) {
302 immutable auto p = m.invert*pp;
303 return mixin(objfunc);
307 float objopUnion() (float obj0
, float obj1
) {
308 static if (__VERSION__
> 2067) pragma(inline
, true);
309 return (obj0
< obj1 ? obj0
: obj1
); // min
312 float objopSub() (float a
, float b
) {
313 static if (__VERSION__
> 2067) pragma(inline
, true);
314 return (a
> -b ? a
: -b
);
318 float objopInter() (float a
, float b
) {
319 static if (__VERSION__
> 2067) pragma(inline
, true);
320 return (obj0
> obj1 ? obj0
: obj1
); // max
323 // returns object number
324 int objopUnion() (ref float dest
, float obj0
, float obj1
, int oid0
=0, int oid1
=1) {
325 static if (__VERSION__
> 2067) pragma(inline
, true);
335 // returns object number
336 int objopSub() (ref float dest
, float a
, float b
, int oid0
=0, int oid1
=1) {
337 static if (__VERSION__
> 2067) pragma(inline
, true);
348 // returns object number
349 int objopInter() (ref float dest
, float obj0
, float obj1
, int oid0
=0, int oid1
=1) {
350 static if (__VERSION__
> 2067) pragma(inline
, true);
361 // ////////////////////////////////////////////////////////////////////////// //
362 // raymarch composition functions that doesn't preserve distance
363 // you will probably need to decrease your step size, if you are using a raymarcher to sample this
364 // the displacement example below is using sin(20*p.x)*sin(20*p.y)*sin(20*p.z) as displacement pattern
366 float objopDisplace(string objfunc) (in auto ref Vec3 p) {
367 float d1 = mixin(objfunc);
368 float d2 = displacement(p);
374 // exponential smooth min (k = 32);
375 float sminExp (float a
, float b
, float k
) {
376 import std
.math
: exp
, log
;
377 float res
= exp(-k
*a
)+exp(-k
*b
);
381 // power smooth min (k = 8);
382 float sminPow (float a
, float b
, float k
) {
383 import std
.math
: pow
;
386 return pow((a
*b
)/(a
+b
), 1.0f/k
);
389 // polynomial smooth min (k = 0.1);
391 float sminPoly (float a, float b, float k) {
392 import std.math : clamp, exp, log;
393 float h = clamp(0.5f+0.5f*(b-a)/k, 0.0f, 1.0f);
394 return mix(b, a, h)-k*h*(1.0f-h);
398 float objopBlend(alias sminfn
, string objfunc0
, string objfunc1
) (in auto ref Vec3 p
) {
399 float d1
= mixin(objfunc0
);
400 float d2
= mixin(objfunc1
);
401 return sminfn(d1
, d2
);
405 float objopTwist (string objfunc
) (in auto ref Vec3 pp
) {
406 import std
.math
: cos
, sin
;
407 immutable float c
= cos(10.0f*pp
.z
+10.0f);
408 immutable float s
= sin(10.0f*pp
.z
+10.0f);
409 //immutable auto m = Mat2(c, -s, s, c);
417 // multiple matrix2 by vector2
418 //immutable auto p = Vec3(m*p.xz, p.y);
420 float v2x = mm.ptr[0].ptr[0]*pp.x+mm.ptr[0].ptr[1]*pp.z;
421 float v2y = mm.ptr[1].ptr[0]*pp.x+mm.ptr[1].ptr[1]*pp.z;
423 float v2x
= c
*pp
.x
-s
*pp
.y
;
424 float v2y
= s
*pp
.x
+c
*pp
.y
;
425 immutable auto p
= Vec3(v2x
, v2y
, pp
.z
);
426 return mixin(objfunc
);
430 // ////////////////////////////////////////////////////////////////////////// //
431 import std
.algorithm
: maxf
= max
, minf
= min
;
433 // raymarch object functions
434 // as our functions are very simple, use strings and `mixin` to simulate inlining ;-)
435 enum floorHeight
= 12.0f;
436 enum objfuncFloor
= q
{(p
.y
+floorHeight
)};
438 // n must be normalized (invalid formula, broken by k8)
439 static immutable planeN
= Vec3(0.0f, 0.0f, 0.0f);
441 enum objfuncPlane
= q
{(p
.dot(planeN
)+planeD
)};
443 enum torusOuterRadius
= 2.4f;
444 enum torusRadius
= 0.6f;
445 enum objfuncTorus
= q
{(Vec3
.length(p
.length
-torusOuterRadius
, p
.z
)-torusRadius
)};
448 enum shpereRadius
= 1.9f;
449 enum objfuncSphere
= q
{(p
.length
-shpereRadius
)};
451 // round box, unsigned
452 static immutable roundboxSize
= Vec3(2.0f, 0.7f, 2.0f);
453 enum roundboxRadius
= 0.2f;
454 enum objfuncRoundbox
= q
{((p
.abs
-roundboxSize
).minmax
!"max"(0.0f).length
-roundboxRadius
)};
456 // box, signed (interestingly, this is more complex than round box ;-)
457 static immutable boxSize
= Vec3(2.0f, 0.7f, 2.0f);
458 __gshared Vec3 boxTemp
;
459 enum objfuncBox
= q
{(boxTemp
= p
.abs
-boxSize
, minf(maxf(boxTemp
.x
, maxf(boxTemp
.y
, boxTemp
.z
)), 0.0f)+boxTemp
.minmax
!"max"(0.0f).length
)};
462 static immutable boxuSize
= Vec3(2.0f, 0.7f, 2.0f);
463 enum objfuncBoxu
= q
{((p
.abs
-boxuSize
).minmax
!"max"(0.0f).length
)};
466 static immutable cylParams
= Vec3(0.0f, 0.0f, 0.6f); // x, y, width
467 enum objfuncCyl
= q
{(Vec3
.length(p
.x
-cylParams
.x
, p
.z
-cylParams
.y
)-cylParams
.z
)};
470 // ////////////////////////////////////////////////////////////////////////// //
479 float mapWorld() (in auto ref Vec3 p
, ref int obj
) {
480 //static if (__VERSION__ > 2068) pragma(inline, true); // yep, 2.068
481 // we need object number to distinguish floor from other objects
482 // we can paint objects too by getting their numbers ;-)
483 // subtraction always gives us roundbox
484 obj
= ObjId
.roundbox
;
486 float res
= objopSub(mixin(objfuncRoundbox
), mixin(objfuncSphere
));
487 obj
= objopUnion(res
, mixin(objfuncTorus
), res
, ObjId
.torus
, obj
);
488 obj
= objopUnion(res
, objopTwist
!objfuncTorus(p
), res
, ObjId
.torus
, obj
);
489 obj
= objopUnion(res
, mixin(objfuncFloor
), res
, ObjId
.floor
, obj
);
494 // ////////////////////////////////////////////////////////////////////////// //
495 // floor color (checkerboard)
496 Vec3
floorColor() (in auto ref Vec3 p
) @nogc {
497 static if (__VERSION__
> 2067) pragma(inline
, true);
499 if (fract(p
.x
*0.2f) > 0.2f) {
500 if (fract(p
.z
*0.2f) > 0.2f) {
505 res
.x
= res
.y
= res
.z
= 1.0f;
508 if (fract(p
.z
*0.2f) > 0.2f) {
509 res
.x
= res
.y
= res
.z
= 1.0f;
520 // ////////////////////////////////////////////////////////////////////////// //
523 void raymarch() (float x
, float y
, in auto ref Vec3 scp
, in auto ref Vec3 L
, ref Vec3 color
) @nogc {
524 enum maxd
= 100.0f; // max depth
525 Vec3 c
= void, p
= void, N
= void;
530 foreach (immutable _
; 0..256) {
531 import std
.math
: abs
;
532 if (abs(dx
) < 0.001 || f
> maxd
) break;
535 dx
= mapWorld(p
, obj
);
538 // did we hit something?
540 // get primitive color
546 c
= Vec3(0.0f, 0.6f, 0.0f);
549 c
= Vec3(0.6f, 0.6f, 0.8f);
558 import std
.math
: pow
;
559 N
= Vec3(dx
-mapWorld(p
-exyy
, obj
),
560 dx
-mapWorld(p
-eyxy
, obj
),
561 dx
-mapWorld(p
-eyyx
, obj
)).normalize
;
562 float b
= N
.dot((prp
-p
+L
).normalize
);
564 color
.x
= color
.y
= color
.z
= 0.0f; // background color
567 // simple phong lighting
568 color
= (c
*b
+pow(b
, 16.0f))*(1.0f-f
*0.01f);
571 color
.x
= color
.y
= color
.z
= 0.0f; // background color
576 // ////////////////////////////////////////////////////////////////////////// //
577 // various image parameters
578 __gshared
int nextLineAdjustment
;
583 __gshared
ubyte* imgdata
;
584 __gshared
int fpxoffset
;
587 // Adam don't care about @nogc, but image functions actually are
588 void renderToTexture () @nogc {
589 import std
.math
: sin
, cos
;
591 // vertex shader common part
592 auto vpn
= (vrp
-prp
).normalize
;
593 auto u
= (vuv
%vpn
).normalize
;
597 //vertex shader for each vertex
598 immutable float[2][4] vPos
= [
606 Vec3 scrCoord
= void; // temp
607 immutable float cxy
= cast(float)vlWidth
/cast(float)vlHeight
;
608 foreach (immutable i
; 0..vPos
.length
) {
609 scrCoord
.x
= vcv
.x
+vPos
.ptr
[i
].ptr
[0]*u
.x
*cxy
+vPos
.ptr
[i
].ptr
[1]*v
.x
;
610 scrCoord
.y
= vcv
.y
+vPos
.ptr
[i
].ptr
[0]*u
.y
*cxy
+vPos
.ptr
[i
].ptr
[1]*v
.y
;
611 scrCoord
.z
= vcv
.z
+vPos
.ptr
[i
].ptr
[0]*u
.z
*cxy
+vPos
.ptr
[i
].ptr
[1]*v
.z
;
612 scp
.ptr
[i
] = (scrCoord
-prp
).normalize
;
615 float x
= -0.5f, y
= -0.5f, x_inc
= 1.0f/vlWidth
, y_inc
= 1.0f/vlHeight
;
617 auto grady1v
= (scp
.ptr
[3]-scp
.ptr
[0])*y_inc
;
618 auto accy1v
= scp
.ptr
[0];
620 auto grady2v
= (scp
.ptr
[2]-scp
.ptr
[1])*y_inc
;
621 auto accy2v
= scp
.ptr
[1];
623 Vec3 colorout
= void;
624 Vec3 gradxv
= void, accxv
= void;
625 Vec3 L
= Vec3(sin(worldtime
)*20.0f, 10.0f, cos(worldtime
)*20.0f);
627 auto offset
= fpxoffset
;
628 auto startOfLine
= imgdata
+offset
; // get our pointer lined up on the first pixel
630 foreach (int iy
; 0..vlHeight
) {
631 auto vbptr
= startOfLine
; // we keep the start of line separately so moving to the next line is simple and portable
632 startOfLine
+= nextLineAdjustment
*scale
;
634 gradxv
= (accy2v
-accy1v
)*x_inc
;
637 foreach (int ix
; 0..vlWidth
) {
638 raymarch(x
, y
, accxv
, L
, colorout
);
639 static if (scale
> 1) {
640 ubyte r
= clampToByte(cast(int)(colorout
.x
*255.0f));
641 ubyte g
= clampToByte(cast(int)(colorout
.y
*255.0f));
642 ubyte b
= clampToByte(cast(int)(colorout
.z
*255.0f));
643 foreach (immutable _
; 0..scale
) {
647 static if (!scanlines
) {
649 foreach (immutable _1
; 1..scale
) {
650 vbptrv
+= nextLineAdjustment
;
659 vbptr
[offR
] = clampToByte(cast(int)(colorout
.x
*255.0f));
660 vbptr
[offG
] = clampToByte(cast(int)(colorout
.y
*255.0f));
661 vbptr
[offB
] = clampToByte(cast(int)(colorout
.z
*255.0f));
677 // ////////////////////////////////////////////////////////////////////////// //
678 __gshared MonoTime sttime
;
680 void animate () @nogc {
681 __gshared
bool firstTime
= true;
683 sttime
= MonoTime
.currTime
;
687 import std
.math
: sin
, cos
;
689 worldtime
= cast(float)(MonoTime
.currTime
-sttime
).total
!"msecs"/1000.0f;
691 Vec3 auto_vuv
= void, auto_vrp
= void, auto_prp
= void;
694 auto_vuv
.x
= 0; //sin(worldtime/*timemsecs*/);
698 // view reference point
699 auto_vrp
.x
= 0; //sin(time*0.7f)*10.0f;
701 auto_vrp
.z
= 0; //cos(time*0.9f)*10.0f;
704 auto_prp
.x
= 3.0f; //sin(time*0.7f)*20.0f+auto_vrp.x+20.0f;
705 auto_prp
.y
= 3.0f; //sin(time)*4.0f+4.0f+auto_vrp.y+3.0f;
706 auto_prp
.z
= 3.0f; //cos(time*0.6f)*20.0f+auto_vrp.z+14.0f;
714 // ////////////////////////////////////////////////////////////////////////// //
715 //private extern (C) void thread_suspendAll() nothrow; // steal that func!
717 void renderThreadFunc () {
718 // detach ourself, so GC will not stop us
720 // while documentation says that we have to call this, in reality
721 // i see one more module tls dtor called with it. this is due to
722 // the fact that `thread_entryPoint()` will call `rt_moduleTlsDtor()`
723 // on exiting. let's hope that modules won't rely on GC suspending
724 // all threads on collecting (they shouldn't)
725 //rt_moduleTlsDtor();
727 if (cas(&renderDie
, 1, 2)) break;
728 synchronized(mutexCondCanRender
) waitCondCanRender
.wait();
729 auto cft
= MonoTime
.currTime
;
732 atomicStore(frameDur
, cast(uint)(MonoTime
.currTime
-cft
).total
!"msecs");
733 atomicStore(renderComplete
, true);
738 // ////////////////////////////////////////////////////////////////////////// //
739 __gshared string aftstr
; // average frame time
742 if (sdwindow
.closed
) return;
743 auto painter
= sdwindow
.draw();
744 painter
.drawImage(Point(0, 0), texImage
);
746 painter
.outlineColor
= Color
.white
;
747 painter
.drawText(Point(10, 10), aftstr
);
751 __gshared Thread renderThread
;
753 void main (string
[] args
) {
754 texImage
= new Image(vlEffectiveWidth
, vlEffectiveHeight
);
756 nextLineAdjustment
= texImage
.adjustmentForNextLine();
757 offR
= texImage
.redByteOffset();
758 offB
= texImage
.blueByteOffset();
759 offG
= texImage
.greenByteOffset();
760 bpp
= texImage
.bytesPerPixel();
761 imgdata
= texImage
.getDataPointer();
762 fpxoffset
= texImage
.offsetForTopLeftPixel();
767 sdwindow
= new SimpleWindow(vlEffectiveWidth
, vlEffectiveHeight
, "RayMarching demo", OpenGlOptions
.no
, Resizablity
.fixedSize
);
770 enum MSecsPerFrame
= 1000/60; /* 60 is FPS */
772 renderThread
= new Thread(&renderThreadFunc
);
773 renderThread
.start();
775 uint[6] frameTimes
= 0;
777 sdwindow
.eventLoop(MSecsPerFrame
,
779 if (sdwindow
.closed
) return;
780 if (atomicLoad(renderComplete
)) {
781 // do average frame time
783 foreach (immutable idx
; 1..frameTimes
.length
) {
784 aft
+= frameTimes
.ptr
[idx
];
785 frameTimes
.ptr
[idx
-1] = frameTimes
.ptr
[idx
];
787 frameTimes
[$-1] = atomicLoad(frameDur
);
788 aft
+= frameTimes
[$-1];
789 aft
/= cast(uint)frameTimes
.length
;
790 if (aft
== 0) aft
= 1000;
793 aftstr
= "%s FPS".format(1000/aft
);
795 //version(show_frame_time) { import core.stdc.stdio; printf("%u msecs per frame\n", frameDur); }
797 atomicStore(renderComplete
, false);
798 synchronized(mutexCondCanRender
) waitCondCanRender
.notify();
801 delegate (KeyEvent event
) {
802 if (sdwindow
.closed
) return;
803 if (event
.key
== Key
.Escape
) {
808 delegate (MouseEvent event
) {
810 delegate (dchar ch
) {
813 synchronized(mutexCondCanRender
) waitCondCanRender
.notify(); // just in case
814 atomicStore(renderDie
, 1); // die
815 while (atomicLoad(renderDie
) != 2) {}
816 if (!sdwindow
.closed
) {