3 //version = render_debug;
7 import gfxcore
: texImage
, vlWidth
, vlHeight
, scale
, scanlines
;
11 import core
.time
: MonoTime
, Duration
;
14 import core
.sync
.condition
;
15 import core
.sync
.rwmutex
;
16 import core
.sync
.mutex
;
19 import std
.concurrency
;
22 __gshared Mutex mutexCondCanRender
;
23 __gshared Condition waitCondCanRender
;
24 shared bool renderComplete
= true;
25 shared int renderDie
= 0; // 1: waiting for death; 2: dead
29 public @property bool mtrenderComplete () {
30 return atomicLoad(renderComplete
);
34 public @property uint mtrenderFrameTime () {
35 return atomicLoad(frameDur
);
39 public void mtrenderStartSignal () {
40 atomicStore(renderComplete
, false);
41 synchronized(mutexCondCanRender
) waitCondCanRender
.notify();
45 public void mtrenderInit () {
46 mutexCondCanRender
= new Mutex();
47 waitCondCanRender
= new Condition(mutexCondCanRender
);
49 nextLineAdjustment
= texImage
.adjustmentForNextLine();
50 offR
= texImage
.redByteOffset();
51 offB
= texImage
.blueByteOffset();
52 offG
= texImage
.greenByteOffset();
53 bpp
= texImage
.bytesPerPixel();
54 imgdata
= texImage
.getDataPointer();
55 fpxoffset
= texImage
.offsetForTopLeftPixel();
57 { import core
.stdc
.stdio
; printf("%u tiles per image\n", tilesPerTexture()); }
61 foreach (uint tile
; 0..tilesPerTexture
) renderToTexture(tile
);
63 renderThread
= new Thread(&renderThreadFunc
);
68 public void mtrenderShutdown () {
69 synchronized(mutexCondCanRender
) waitCondCanRender
.notify(); // just in case
70 atomicStore(renderDie
, 1); // die
71 while (atomicLoad(renderDie
) != 2) {}
75 // ////////////////////////////////////////////////////////////////////////// //
76 enum TileSize
= 32; // tile is 32x32 pixels
78 // various image parameters
79 __gshared
int nextLineAdjustment
;
84 __gshared
ubyte* imgdata
;
85 __gshared
int fpxoffset
;
87 __gshared Vec3
[4] scp
;
90 __gshared Vec3 grady1v
;
91 __gshared Vec3 grady2v
;
93 __gshared Thread renderThread
;
96 // ////////////////////////////////////////////////////////////////////////// //
97 void prepareRenderer () @nogc {
98 import std
.math
: sin
, cos
;
100 // vertex shader common part
101 auto vpn
= (vrp
-prp
).normalize
;
102 auto u
= (vuv
%vpn
).normalize
;
106 //vertex shader for each vertex
107 immutable float[2][4] vPos
= [
114 Vec3 scrCoord
= void; // temp
115 immutable float cxy
= cast(float)vlWidth
/cast(float)vlHeight
;
116 foreach (immutable i
; 0..vPos
.length
) {
117 scrCoord
.x
= vcv
.x
+vPos
.ptr
[i
].ptr
[0]*u
.x
*cxy
+vPos
.ptr
[i
].ptr
[1]*v
.x
;
118 scrCoord
.y
= vcv
.y
+vPos
.ptr
[i
].ptr
[0]*u
.y
*cxy
+vPos
.ptr
[i
].ptr
[1]*v
.y
;
119 scrCoord
.z
= vcv
.z
+vPos
.ptr
[i
].ptr
[0]*u
.z
*cxy
+vPos
.ptr
[i
].ptr
[1]*v
.z
;
120 scp
.ptr
[i
] = (scrCoord
-prp
).normalize
;
123 float y_inc
= 1.0f/vlHeight
;
125 grady1v
= (scp
.ptr
[3]-scp
.ptr
[0])*y_inc
;
126 grady2v
= (scp
.ptr
[2]-scp
.ptr
[1])*y_inc
;
130 uint tilesPerTexture () @nogc {
131 return ((vlWidth
+TileSize
-1)/TileSize
)*((vlHeight
+TileSize
-1)/TileSize
);
135 void tileOfs (uint tile
, out int x
, out int y
) @nogc {
136 immutable tpx
= (vlWidth
+TileSize
-1)/TileSize
;
137 x
= (tile
%tpx
)*TileSize
;
138 y
= (tile
/tpx
)*TileSize
;
142 // Adam don't care about @nogc, but image functions actually are
143 void renderToTexture (uint tile
) @nogc {
144 float x
= -0.5f, y
= -0.5f, x_inc
= 1.0f/vlWidth
, y_inc
= 1.0f/vlHeight
;
147 tileOfs(tile
, tofsx
, tofsy
);
148 //{ import core.stdc.stdio; printf("tile=%u; x=%u; y=%u\n", tile, tofsx, tofsy); }
149 int ex
= tofsx
+TileSize
;
150 int ey
= tofsy
+TileSize
;
151 if (ex
> vlWidth
) ex
= vlWidth
;
152 if (ey
> vlHeight
) ey
= vlHeight
;
157 auto accy1v
= scp
.ptr
[0]+grady1v
*tofsy
;
158 auto accy2v
= scp
.ptr
[1]+grady2v
*tofsy
;
160 Vec3 colorout
= void;
161 Vec3 gradxv
= void, gradxvdown
= void, accxvdown
= void, accxv
= void, rdx
= void;
163 auto offset
= fpxoffset
+tofsy
*scale
*nextLineAdjustment
+tofsx
*scale
*bpp
;
164 auto startOfLine
= imgdata
+offset
; // get our pointer lined up on the first pixel
167 foreach (int iy
; tofsy
..ey
) {
168 auto vbptr
= startOfLine
; // we keep the start of line separately so moving to the next line is simple and portable
169 startOfLine
+= nextLineAdjustment
*scale
;
170 gradxv
= (accy2v
-accy1v
)*x_inc
;
171 accxv
= accy1v
+gradxv
*tofsx
;
176 accxvdown
= accy1v
+gradxv
*tofsx
;
177 gradxvdown
= (accy2v
-accy1v
)*x_inc
;
179 foreach (int ix
; tofsx
..ex
) {
181 raymarch(/*x, y,*/ accxv
, L
, colorout
, rdx
, accxvdown
);
182 ubyte r
= void, g
= void, b
= void;
183 if (colorout
.x
< 0 || colorout
.y
< 0 || colorout
.z
< 0) {
186 r
= clampToByte(cast(int)(colorout
.x
*255.0f));
187 g
= clampToByte(cast(int)(colorout
.y
*255.0f));
188 b
= clampToByte(cast(int)(colorout
.z
*255.0f));
190 static if (scale
> 1) {
191 foreach (immutable _
; 0..scale
) {
195 static if (!scanlines
) {
197 foreach (immutable _1
; 1..scale
) {
198 vbptrv
+= nextLineAdjustment
;
215 accxvdown
+= gradxvdown
;
224 // ////////////////////////////////////////////////////////////////////////// //
225 __gshared MonoTime sttime
, lasttime
;
226 shared bool paused
= false;
227 __gshared wasPaused
= false;
230 public void mtrenderTogglePause () {
231 auto ps
= atomicLoad(paused
);
232 atomicStore(paused
, !paused
);
236 public void mtrenderPaused (bool v
) {
237 atomicStore(paused
, v
);
241 void animate () @nogc {
242 __gshared
bool firstTime
= true;
244 sttime
= lasttime
= MonoTime
.currTime
;
248 import std
.math
: sin
, cos
;
250 if (atomicLoad(paused
)) {
254 lasttime
= MonoTime
.currTime
;
257 //worldtime = cast(float)(MonoTime.currTime-sttime).total!"msecs"/1000.0f;
258 auto time
= MonoTime
.currTime
;
259 worldtime
+= cast(float)(time
-lasttime
).total
!"msecs"/1000.0f;
263 Vec3 auto_vuv
= void, auto_vrp
= void, auto_prp
= void;
266 auto_vuv
.x
= 0; //sin(worldtime/*timemsecs*/);
270 // view reference point
271 auto_vrp
.x
= 0; //sin(time*0.7f)*10.0f;
273 auto_vrp
.z
= 0; //cos(time*0.9f)*10.0f;
276 auto_prp
.x
= 3.0f; //sin(time*0.7f)*20.0f+auto_vrp.x+20.0f;
277 auto_prp
.y
= 3.0f; //sin(time)*4.0f+4.0f+auto_vrp.y+3.0f;
278 auto_prp
.z
= 3.0f; //cos(time*0.6f)*20.0f+auto_vrp.z+14.0f;
284 L
= Vec3(sin(worldtime
)*20.0f, 20.0f/*+sin(worldtime)*20.0f*/, cos(worldtime
)*20.0f);
285 //L = Vec3(sin(0.0f)*20.0f, 10.0f+sin(0.0f)*20.0f, cos(0.0f)*20.0f);
289 // ////////////////////////////////////////////////////////////////////////// //
290 void tileRenderFunc (Tid ownerTid
, uint wkid
) {
294 version(render_debug
) { import core
.stdc
.stdio
; printf(" worker %u idle...\n", wkid
); }
298 if (tile
== uint.max
) exit
= true;
302 version(render_debug
) { import core
.stdc
.stdio
; printf(" worker %u got tile %u\n", wkid
, tnum
); }
303 renderToTexture(tnum
);
304 version(render_debug
) { import core
.stdc
.stdio
; printf(" worker %u completed tile %u\n", wkid
, tnum
); }
305 ownerTid
.send(wkid
, 1);
307 ownerTid
.send(wkid
, 666);
311 // ////////////////////////////////////////////////////////////////////////// //
312 enum ThreadCount
= 4;
313 __gshared Tid
[ThreadCount
] wkTids
;
314 __gshared
bool[ThreadCount
] wkFree
;
315 __gshared
uint wkFreeCount
= 0;
318 void spawnWorkers () {
319 foreach (uint idx
; 0..ThreadCount
) {
320 wkTids
[idx
] = spawn(&tileRenderFunc
, thisTid
, idx
);
327 void renderThreadFunc () {
328 static void waitForFreeWorker () {
330 (uint wkid
, int id
) {
333 version(render_debug
) { import core
.stdc
.stdio
; printf(" worker %u added to free pool (%u free workers now)\n", wkid
, wkFreeCount
); }
336 { import core
.stdc
.stdio
; printf(" FUUUUUUUUUUUUUUU\n"); }
343 if (atomicLoad(renderDie
) == 1) break;
344 synchronized(mutexCondCanRender
) waitCondCanRender
.wait();
345 version(render_debug
) { import core
.stdc
.stdio
; printf("render job started (%u free workers, %u tiles)...\n", wkFreeCount
, tilesPerTexture
); }
346 auto cft
= MonoTime
.currTime
;
349 // pass tiles to threads
350 uint tile
= 0, tileDone
= 0, tcount
= tilesPerTexture
;
351 while (tileDone
< tcount
) {
352 // collect free threads
353 if (tile
>= tcount || wkFreeCount
== 0) {
354 // nothing to do, just wait
355 version(render_debug
) { import core
.stdc
.stdio
; printf(" %u of %u tiles done; %u workers still busy\n", tileDone
, tcount
, ThreadCount
-wkFreeCount
); }
360 // dispatch next tile
361 foreach (uint idx
; 0..ThreadCount
) {
363 version(render_debug
) { import core
.stdc
.stdio
; printf(" dispatching tile %u to worker %u\n", tile
, idx
); }
366 wkTids
[idx
].send(tile
);
373 version(render_debug
) { import core
.stdc
.stdio
; printf("render job complete\n"); }
374 atomicStore(frameDur
, cast(uint)(MonoTime
.currTime
-cft
).total
!"msecs");
375 atomicStore(renderComplete
, true);
378 foreach (uint idx
; 0..ThreadCount
) wkTids
[idx
].send(uint.max
);
379 foreach (uint idx
; 0..ThreadCount
) {
381 (uint wkid
, int id
) {}
384 atomicStore(renderDie
, 2);