initial commit
[raymarch.git] / zrm3_adam_trd.d
blob0b97f2d7d3476137037db6bb7ca298d2b6bbbc31
1 // ////////////////////////////////////////////////////////////////////////// //
2 import core.time : MonoTime, Duration;
4 import core.atomic;
5 import core.sync.condition;
6 import core.sync.rwmutex;
7 import core.sync.mutex;
8 import core.thread;
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
19 shared uint frameDur;
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;
30 } else {
31 public import simpledisplay;
34 __gshared SimpleWindow sdwindow;
35 __gshared Image texImage;
37 shared static ~this () {
38 destroy(texImage);
41 enum vlWidth = 160;
42 enum vlHeight = 120;
43 enum scale = 3;
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)));
57 } else {
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?");
63 return cast(ubyte)n;
64 } else {
65 static assert(false, "clampToByte: integer too big");
70 // ////////////////////////////////////////////////////////////////////////// //
71 struct Vec3 {
72 import std.format : FormatSpec;
73 void toString (scope void delegate(const(char)[]) sink, FormatSpec!char fmt) const @trusted {
74 import std.format;
75 sink("Vec3(");
76 sink.formatValue(x, fmt);
77 sink(",");
78 sink.formatValue(y, fmt);
79 sink(",");
80 sink.formatValue(z, fmt);
81 sink(")");
84 nothrow @safe @nogc:
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");
96 final switch (idx) {
97 case 0: x = v; break;
98 case 1: y = v; break;
99 case 2: z = v; break;
101 return v;
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);
108 x *= invlen;
109 y *= invlen;
110 z *= invlen;
111 return this;
114 const pure {
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);
127 float length () {
128 //pragma(inline, true);
129 import std.math : sqrt;
130 return sqrt(x*x+y*y+z*z);
131 //return length(x, y, z);
134 Vec3 normalized () {
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);
141 Vec3 abs () {
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 {
207 fld angle;
208 fsincos;
209 fstp [cosine];
210 fstp [sine];
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 {
219 fld angle;
220 fsincos;
221 fstp [cosine];
222 fstp [sine];
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 {
231 fld angle;
232 fsincos;
233 fstp [cosine];
234 fstp [sine];
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;");
246 return this;
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;
270 return x-floor(x);
274 // is this correct?
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
285 // repetition
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);
317 // intersection
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);
326 if (obj0 < obj1) {
327 dest = obj0;
328 return oid0;
329 } else {
330 dest = obj1;
331 return oid1;
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);
338 b = -b;
339 if (a > b) {
340 dest = a;
341 return oid0;
342 } else {
343 dest = b;
344 return oid1;
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);
351 if (obj0 > obj1) {
352 dest = obj0;
353 return oid0;
354 } else {
355 dest = obj1;
356 return oid1;
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);
369 return d1+d2;
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);
378 return -log(res)/k;
381 // power smooth min (k = 8);
382 float sminPow (float a, float b, float k) {
383 import std.math : pow;
384 a = pow(a, k);
385 b = pow(b, k);
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);
410 // Mat2
412 float[2][2] mm = [
413 [c, -s],
414 [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);
440 enum planeD = 12.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)};
447 // sphere, signed
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)};
461 // box, unsigned
462 static immutable boxuSize = Vec3(2.0f, 0.7f, 2.0f);
463 enum objfuncBoxu = q{((p.abs-boxuSize).minmax!"max"(0.0f).length)};
465 // cylinder, signed
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 // ////////////////////////////////////////////////////////////////////////// //
471 // object ids:
472 enum ObjId : int {
473 floor = 0,
474 roundbox = 1,
475 torus = 2,
476 box = 3,
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;
485 //obj = ObjId.floor;
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);
490 return res;
494 // ////////////////////////////////////////////////////////////////////////// //
495 // floor color (checkerboard)
496 Vec3 floorColor() (in auto ref Vec3 p) @nogc {
497 static if (__VERSION__ > 2067) pragma(inline, true);
498 Vec3 res = void;
499 if (fract(p.x*0.2f) > 0.2f) {
500 if (fract(p.z*0.2f) > 0.2f) {
501 res.x = 0.0f;
502 res.y = 0.1f;
503 res.z = 0.2f;
504 } else {
505 res.x = res.y = res.z = 1.0f;
507 } else {
508 if (fract(p.z*0.2f) > 0.2f) {
509 res.x = res.y = res.z = 1.0f;
510 } else {
511 res.x = 0.3f;
512 res.y = 0.0f;
513 res.z = 0.0f;
516 return res;
520 // ////////////////////////////////////////////////////////////////////////// //
521 // raymarching
522 // L: light
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;
526 float dx = 0.1f;
527 int obj = 0;
529 float f = 1.0f;
530 foreach (immutable _; 0..256) {
531 import std.math : abs;
532 if (abs(dx) < 0.001 || f > maxd) break;
533 f += dx;
534 p = prp+scp*f;
535 dx = mapWorld(p, obj);
538 // did we hit something?
539 if (f < maxd) {
540 // get primitive color
541 switch (obj) {
542 case ObjId.floor:
543 c = floorColor(p);
544 break;
545 case ObjId.roundbox:
546 c = Vec3(0.0f, 0.6f, 0.0f);
547 break;
548 case ObjId.torus:
549 c = Vec3(0.6f, 0.6f, 0.8f);
550 break;
551 default:
552 // RED
553 c.x = 1.0f;
554 c.y = c.z = 0;
555 break;
557 // do lighting
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);
563 if (b < 0) {
564 color.x = color.y = color.z = 0.0f; // background color
565 color.z = 0.1f;
566 } else {
567 // simple phong lighting
568 color = (c*b+pow(b, 16.0f))*(1.0f-f*0.01f);
570 } else {
571 color.x = color.y = color.z = 0.0f; // background color
576 // ////////////////////////////////////////////////////////////////////////// //
577 // various image parameters
578 __gshared int nextLineAdjustment;
579 __gshared int offR;
580 __gshared int offB;
581 __gshared int offG;
582 __gshared int bpp;
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;
594 auto v = vpn%u;
595 auto vcv = prp+vpn;
597 //vertex shader for each vertex
598 immutable float[2][4] vPos = [
599 [-1, 1], //0--1
600 [ 1, 1], //| |
601 [ 1, -1], //3--2
602 [-1, -1],
605 Vec3[4] scp = void;
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;
635 accxv = accy1v;
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) {
644 vbptr[offR] = r;
645 vbptr[offG] = g;
646 vbptr[offB] = b;
647 static if (!scanlines) {
648 auto vbptrv = vbptr;
649 foreach (immutable _1; 1..scale) {
650 vbptrv += nextLineAdjustment;
651 vbptrv[offR] = r;
652 vbptrv[offG] = g;
653 vbptrv[offB] = b;
656 vbptr += bpp;
658 } else {
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));
662 vbptr += bpp;
664 x += x_inc;
665 accxv += gradxv;
668 accy1v += grady1v;
669 accy2v += grady2v;
671 y += y_inc;
672 x = -0.5f;
677 // ////////////////////////////////////////////////////////////////////////// //
678 __gshared MonoTime sttime;
680 void animate () @nogc {
681 __gshared bool firstTime = true;
682 if (firstTime) {
683 sttime = MonoTime.currTime;
684 firstTime = false;
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;
693 // view up vector
694 auto_vuv.x = 0; //sin(worldtime/*timemsecs*/);
695 auto_vuv.y = 1;
696 auto_vuv.z = 0;
698 // view reference point
699 auto_vrp.x = 0; //sin(time*0.7f)*10.0f;
700 auto_vrp.y = 0;
701 auto_vrp.z = 0; //cos(time*0.9f)*10.0f;
703 // camera position
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;
708 vuv = auto_vuv;
709 vrp = auto_vrp;
710 prp = auto_prp;
714 // ////////////////////////////////////////////////////////////////////////// //
715 //private extern (C) void thread_suspendAll() nothrow; // steal that func!
717 void renderThreadFunc () {
718 // detach ourself, so GC will not stop us
719 thread_detachThis();
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();
726 for (;;) {
727 if (cas(&renderDie, 1, 2)) break;
728 synchronized(mutexCondCanRender) waitCondCanRender.wait();
729 auto cft = MonoTime.currTime;
730 animate();
731 renderToTexture();
732 atomicStore(frameDur, cast(uint)(MonoTime.currTime-cft).total!"msecs");
733 atomicStore(renderComplete, true);
738 // ////////////////////////////////////////////////////////////////////////// //
739 __gshared string aftstr; // average frame time
741 void blitScene () {
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();
764 animate();
765 renderToTexture();
767 sdwindow = new SimpleWindow(vlEffectiveWidth, vlEffectiveHeight, "RayMarching demo", OpenGlOptions.no, Resizablity.fixedSize);
768 blitScene();
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,
778 delegate () {
779 if (sdwindow.closed) return;
780 if (atomicLoad(renderComplete)) {
781 // do average frame time
782 uint aft = 0;
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;
792 import std.string;
793 aftstr = "%s FPS".format(1000/aft);
795 //version(show_frame_time) { import core.stdc.stdio; printf("%u msecs per frame\n", frameDur); }
796 blitScene();
797 atomicStore(renderComplete, false);
798 synchronized(mutexCondCanRender) waitCondCanRender.notify();
801 delegate (KeyEvent event) {
802 if (sdwindow.closed) return;
803 if (event.key == Key.Escape) {
804 flushGui();
805 sdwindow.close();
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) {
817 flushGui();
818 sdwindow.close();
820 flushGui();