some constants
[dd2d.git] / render.d
blob2888de662ac5c9bcb5630293a831b15b5600316f
1 /* DooM2D: Midnight on the Firing Line
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, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module render is aliced;
20 //version = dont_use_vsync;
22 private:
23 import core.atomic;
24 import core.thread;
25 import core.time;
27 import std.concurrency;
29 import iv.glbinds;
30 import glutils;
31 import console;
32 import wadarc;
34 import iv.stream;
36 import d2dmap;
37 import d2dadefs;
38 //import d2dactors;
39 import d2dgfx;
40 import d2dfont;
41 import dacs;
43 import d2dunigrid;
45 // `map` is there
46 import dengapi;
48 import d2dparts;
51 // ////////////////////////////////////////////////////////////////////////// //
52 import arsd.color;
53 import arsd.png;
56 // ////////////////////////////////////////////////////////////////////////// //
57 public __gshared bool cheatNoDoors = false;
58 public __gshared bool cheatNoWallClip = false;
61 // ////////////////////////////////////////////////////////////////////////// //
62 public __gshared SimpleWindow sdwindow;
65 public enum vlWidth = 800;
66 public enum vlHeight = 800;
67 __gshared int scale = 2;
69 public int getScale () nothrow @trusted @nogc { pragma(inline, true); return scale; }
72 // ////////////////////////////////////////////////////////////////////////// //
73 __gshared bool levelLoaded = false;
76 // ////////////////////////////////////////////////////////////////////////// //
77 __gshared bool scanlines = false;
78 __gshared bool doLighting = true;
79 __gshared bool gamePaused = false;
82 // ////////////////////////////////////////////////////////////////////////// //
83 // interpolation
84 __gshared ubyte[] prevFrameActorsData;
85 __gshared uint[65536] prevFrameActorOfs; // uint.max-1: dead; uint.max: last
86 __gshared MonoTime lastthink = MonoTime.zero; // for interpolator
87 __gshared MonoTime nextthink = MonoTime.zero;
88 __gshared bool frameInterpolation = true;
91 // ////////////////////////////////////////////////////////////////////////// //
92 // attached lights
93 struct AttachedLightInfo {
94 enum Type {
95 Point,
96 Ambient,
98 Type type;
99 int x, y;
100 int w, h; // for ambient lights
101 float r, g, b;
102 bool uncolored;
103 int radius;
106 __gshared AttachedLightInfo[65536] attachedLights;
107 __gshared uint attachedLightCount = 0;
110 // ////////////////////////////////////////////////////////////////////////// //
111 enum MaxLightRadius = 255;
114 // ////////////////////////////////////////////////////////////////////////// //
115 // for light
116 __gshared FBO[MaxLightRadius+1] fboDistMap;
117 __gshared FBO fboOccluders;
118 __gshared Shader shadToPolar, shadBlur, shadBlurOcc, shadAmbient;
120 __gshared FBO fboLevel, fboLevelLight, fboOrigBack, fboLMaskSmall;
121 __gshared Shader shadScanlines;
122 __gshared Shader shadLiquidDistort;
125 // ////////////////////////////////////////////////////////////////////////// //
126 // call once!
127 public void initOpenGL () {
128 gloStackClear();
130 glEnable(GL_TEXTURE_2D);
131 glDisable(GL_LIGHTING);
132 glDisable(GL_DITHER);
133 glDisable(GL_BLEND);
134 glDisable(GL_DEPTH_TEST);
136 // create shaders
137 shadScanlines = new Shader("scanlines", loadTextFile("shaders/srscanlines.frag"));
139 shadLiquidDistort = new Shader("liquid_distort", loadTextFile("shaders/srliquid_distort.frag"));
140 shadLiquidDistort.exec((Shader shad) {
141 shad["texLqMap"] = 0;
144 // lights
145 shadToPolar = new Shader("light_trace", loadTextFile("shaders/srlight_trace.frag"));
146 shadToPolar.exec((Shader shad) {
147 shad["texOcc"] = 0;
148 shad["texOccFull"] = 2;
149 shad["texOccSmall"] = 3;
152 shadBlur = new Shader("light_blur", loadTextFile("shaders/srlight_blur.frag"));
153 shadBlur.exec((Shader shad) {
154 shad["texDist"] = 0;
155 shad["texBg"] = 1;
156 shad["texOcc"] = 2;
157 shad["texOccSmall"] = 3;
160 shadBlurOcc = new Shader("light_blur_occ", loadTextFile("shaders/srlight_blur_occ.frag"));
161 shadBlurOcc.exec((Shader shad) {
162 shad["texLMap"] = 0;
163 shad["texBg"] = 1;
164 shad["texOcc"] = 2;
165 shad["texOccSmall"] = 3;
168 shadAmbient = new Shader("light_ambient", loadTextFile("shaders/srlight_ambient.frag"));
169 shadAmbient.exec((Shader shad) {
170 shad["texBg"] = 1;
171 shad["texOcc"] = 2;
172 shad["texOccSmall"] = 3;
175 fboOccluders = new FBO(MaxLightRadius*2, MaxLightRadius*2, Texture.Option.Clamp, Texture.Option.Linear);
176 //TODO: this sux!
177 foreach (int sz; 2..MaxLightRadius+1) {
178 fboDistMap[sz] = new FBO(sz*2, 1, Texture.Option.Clamp, Texture.Option.Linear); // create 1d distance map FBO
181 // setup matrices
182 glMatrixMode(GL_MODELVIEW);
183 glLoadIdentity();
185 loadSmFont();
186 loadAllMonsterGraphics();
190 // ////////////////////////////////////////////////////////////////////////// //
191 // should be called when OpenGL is initialized
192 void loadMap (string mapname) {
193 mapscripts.runUnloading(); // "map unloading" script
194 clearMapScripts();
196 if (map !is null) map.clear();
197 map = new LevelMap(mapname);
198 curmapname = mapname;
200 ugInit(map.width*TileSize, map.height*TileSize);
202 map.oglBuildMega();
203 mapTilesChanged = 0;
205 if (fboLevel !is null) fboLevel.clear();
206 if (fboLevelLight !is null) fboLevelLight.clear();
207 if (fboOrigBack !is null) fboOrigBack.clear();
208 if (fboLMaskSmall !is null) fboLMaskSmall.clear();
210 fboLevel = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // final level render will be here
211 fboLevelLight = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // level lights will be rendered here
212 fboOrigBack = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest/*, Texture.Option.Depth*/); // background+foreground
213 fboLMaskSmall = new FBO(map.width, map.height, Texture.Option.Nearest); // small lightmask
215 shadToPolar.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize-1, map.height*TileSize-1); });
216 shadBlur.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
217 shadBlurOcc.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
218 shadAmbient.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
220 glActiveTexture(GL_TEXTURE0+0);
221 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
222 orthoCamera(vlWidth, vlHeight);
224 Actor.resetStorage();
226 setupMapScripts();
227 mapscripts.runInit();
228 loadMapMonsters();
229 dotInit();
230 mapscripts.runLoaded();
232 // save first snapshot
233 if (prevFrameActorsData.length == 0) prevFrameActorsData = new ubyte[](Actor.actorSize*65536); // ~15-20 megabytes
234 prevFrameActorOfs[] = uint.max; // just for fun
235 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
237 levelLoaded = true;
239 { import core.memory : GC; GC.collect(); }
243 // ////////////////////////////////////////////////////////////////////////// //
244 //FIXME: optimize!
245 __gshared uint mapTilesChanged = 0;
248 //WARNING! this can be called only from DACS, so we don't have to sync it!
249 public void mapDirty (uint layermask) { mapTilesChanged |= layermask; }
252 void rebuildMapMegaTextures () {
253 //fbo.replaceTexture
254 //mapTilesChanged = false;
255 //map.clearMegaTextures();
256 map.oglBuildMega(mapTilesChanged);
257 mapTilesChanged = 0;
261 // ////////////////////////////////////////////////////////////////////////// //
262 // messages
263 struct Message {
264 enum Phase { FadeIn, Stay, FadeOut }
265 Phase phase;
266 int alpha;
267 int pauseMsecs;
268 MonoTime removeTime;
269 char[256] text;
270 usize textlen;
273 private import core.sync.mutex : Mutex;
275 __gshared Message[128] messages;
276 __gshared uint messagesUsed = 0;
278 //__gshared Mutex messageLock;
279 //shared static this () { messageLock = new Mutex(); }
282 void addMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
283 //messageLock.lock();
284 //scope(exit) messageLock.unlock();
285 if (msgtext.length == 0) return;
286 conwriteln(msgtext);
287 if (pauseMsecs <= 50) return;
288 if (messagesUsed == messages.length) {
289 // remove top message
290 foreach (immutable cidx; 1..messagesUsed) messages.ptr[cidx-1] = messages.ptr[cidx];
291 messages.ptr[0].alpha = 255;
292 --messagesUsed;
294 // quick replace
295 if (!noreplace && messagesUsed == 1) {
296 switch (messages.ptr[0].phase) {
297 case Message.Phase.FadeIn:
298 messages.ptr[0].phase = Message.Phase.FadeOut;
299 break;
300 case Message.Phase.Stay:
301 messages.ptr[0].phase = Message.Phase.FadeOut;
302 messages.ptr[0].alpha = 255;
303 break;
304 default:
307 auto msg = messages.ptr+messagesUsed;
308 ++messagesUsed;
309 msg.phase = Message.Phase.FadeIn;
310 msg.alpha = 0;
311 msg.pauseMsecs = pauseMsecs;
312 // copy text
313 if (msgtext.length > msg.text.length) {
314 msg.text = msgtext[0..msg.text.length];
315 msg.textlen = msg.text.length;
316 } else {
317 msg.text[0..msgtext.length] = msgtext[];
318 msg.textlen = msgtext.length;
323 void doMessages (MonoTime curtime) {
324 //messageLock.lock();
325 //scope(exit) messageLock.unlock();
327 if (messagesUsed == 0) return;
328 glEnable(GL_BLEND);
329 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
331 Message* msg;
333 again:
334 msg = messages.ptr;
335 final switch (msg.phase) {
336 case Message.Phase.FadeIn:
337 if ((msg.alpha += 10) >= 255) {
338 msg.phase = Message.Phase.Stay;
339 msg.removeTime = curtime+dur!"msecs"(msg.pauseMsecs);
340 goto case; // to stay
342 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
343 break;
344 case Message.Phase.Stay:
345 if (msg.removeTime <= curtime) {
346 msg.alpha = 255;
347 msg.phase = Message.Phase.FadeOut;
348 goto case; // to fade
350 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
351 break;
352 case Message.Phase.FadeOut:
353 if ((msg.alpha -= 10) <= 0) {
354 if (--messagesUsed == 0) return;
355 // remove this message
356 foreach (immutable cidx; 1..messagesUsed+1) messages.ptr[cidx-1] = messages.ptr[cidx];
357 goto again;
359 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
360 break;
363 smDrawText(10, 10, msg.text[0..msg.textlen]);
367 // ////////////////////////////////////////////////////////////////////////// //
368 //mixin(Actor.FieldPropMixin!("0drawlistpos", uint));
370 mixin(Actor.FieldGetMixin!("classtype", StrId)); // fget_classtype
371 mixin(Actor.FieldGetMixin!("classname", StrId)); // fget_classname
372 mixin(Actor.FieldGetMixin!("x", int));
373 mixin(Actor.FieldGetMixin!("y", int));
374 mixin(Actor.FieldGetMixin!("radius", int));
375 mixin(Actor.FieldGetMixin!("height", int));
376 mixin(Actor.FieldGetMixin!("flags", uint));
377 mixin(Actor.FieldGetMixin!("zAnimstate", StrId));
378 mixin(Actor.FieldGetMixin!("zAnimidx", int));
379 mixin(Actor.FieldGetMixin!("dir", uint));
380 mixin(Actor.FieldGetMixin!("attLightXOfs", int));
381 mixin(Actor.FieldGetMixin!("attLightYOfs", int));
382 mixin(Actor.FieldGetMixin!("attLightRGBX", uint));
384 //mixin(Actor.FieldGetPtrMixin!("classtype", StrId)); // fget_classtype
385 //mixin(Actor.FieldGetPtrMixin!("classname", StrId)); // fget_classname
386 mixin(Actor.FieldGetPtrMixin!("x", int));
387 mixin(Actor.FieldGetPtrMixin!("y", int));
388 //mixin(Actor.FieldGetPtrMixin!("flags", uint));
389 //mixin(Actor.FieldGetPtrMixin!("zAnimstate", StrId));
390 //mixin(Actor.FieldGetPtrMixin!("zAnimidx", int));
391 //mixin(Actor.FieldGetPtrMixin!("dir", uint));
392 //mixin(Actor.FieldGetPtrMixin!("attLightXOfs", int));
393 //mixin(Actor.FieldGetPtrMixin!("attLightYOfs", int));
394 //mixin(Actor.FieldGetPtrMixin!("attLightRGBX", uint));
397 // ////////////////////////////////////////////////////////////////////////// //
398 __gshared int vportX0, vportY0, vportX1, vportY1;
401 // ////////////////////////////////////////////////////////////////////////// //
402 void renderLightAmbient() (int lightX, int lightY, int lightW, int lightH, in auto ref SVec4F lcol) {
403 //conwriteln("!!!000: (", lightX, ",", lightY, ")-(", lightW, ",", lightH, "): r=", cast(uint)(255.0f*lcol.r), "; g=", cast(uint)(255.0f*lcol.g), "; b=", cast(uint)(255.0f*lcol.b), "; a=", cast(uint)(255.0f*lcol.a));
404 if (lightW < 1 || lightH < 1) return;
405 int lightX1 = lightX+lightW-1;
406 int lightY1 = lightY+lightH-1;
407 // clip light to viewport
408 if (lightX < vportX0) lightX = vportX0;
409 if (lightY < vportY0) lightY = vportY0;
410 if (lightX1 > vportX1) lightX1 = vportX1;
411 if (lightY1 > vportY1) lightY1 = vportY1;
412 // is this light visible?
413 //conwriteln("!!!001: (", lightX, ",", lightY, ")-(", lightX1, ",", lightY1, "): r=", cast(uint)(255.0f*lcol.r), "; g=", cast(uint)(255.0f*lcol.g), "; b=", cast(uint)(255.0f*lcol.b), "; a=", cast(uint)(255.0f*lcol.a));
414 if (lightX1 < lightX || lightY1 < lightY || lightX > vportX1 || lightY > vportY1 || lightX1 < vportX0 || lightY1 < vportY0) return;
415 //conwriteln("!!!002: (", lightX, ",", lightY, ")-(", lightX1, ",", lightY1, "): r=", cast(uint)(255.0f*lcol.r), "; g=", cast(uint)(255.0f*lcol.g), "; b=", cast(uint)(255.0f*lcol.b), "; a=", cast(uint)(255.0f*lcol.a));
417 fboLevelLight.exec({
418 glEnable(GL_BLEND);
419 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
420 //glDisable(GL_BLEND);
421 orthoCamera(map.width*TileSize, map.height*TileSize);
422 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
423 shadAmbient.exec((Shader shad) {
424 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
425 //shad["lightPos"] = SVec2F(lightX, lightY);
426 glRectf(lightX, lightY, lightX1, lightY1);
432 // ////////////////////////////////////////////////////////////////////////// //
433 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
434 if (lightRadius < 2) return;
435 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
436 int lightSize = lightRadius*2;
437 // is this light visible?
438 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*TileSize || lightY-lightRadius >= map.height*TileSize) return;
440 // out of viewport -- do nothing
441 if (lightX+lightRadius < vportX0 || lightY+lightRadius < vportY0) return;
442 if (lightX-lightRadius > vportX1 || lightY-lightRadius > vportY1) return;
444 if (lightX >= 0 && lightY >= 0 && lightX < map.width*TileSize && lightY < map.height*TileSize &&
445 map.teximgs[map.LightMask].imageData.colors.ptr[lightY*(map.width*TileSize)+lightX].a > 190) return;
447 // common color for all the following
448 glDisable(GL_BLEND);
449 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
451 // build 1d distance map to fboShadowMapId
452 fboDistMap.ptr[lightRadius].exec({
453 // no need to clear it, shader will take care of that
454 shadToPolar.exec((Shader shad) {
455 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
456 shad["lightPos"] = SVec2F(lightX, lightY);
457 orthoCamera(lightSize, 1);
458 // it doesn't matter what we will draw here, so just draw filled rect
459 glRectf(0, 0, lightSize, 1);
463 // build light texture for blending
464 fboOccluders.exec({
465 // no need to clear it, shader will take care of that
466 // debug
467 //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
468 //glClear(GL_COLOR_BUFFER_BIT);
469 shadBlur.exec((Shader shad) {
470 shad["lightTexSize"] = SVec2F(lightSize, MaxLightRadius*2); // x: size of distmap; y: size of this texture
471 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
472 shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
473 orthoCamera(fboOccluders.tex.width, fboOccluders.tex.height);
474 //drawAtXY(fboDistMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
475 glBindTexture(GL_TEXTURE_2D, fboDistMap.ptr[lightRadius].tex.tid);
476 glBegin(GL_QUADS);
477 glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0); // top-left
478 glTexCoord2f(1.0f, 0.0f); glVertex2i(lightSize, 0); // top-right
479 glTexCoord2f(1.0f, 1.0f); glVertex2i(lightSize, lightSize); // bottom-right
480 glTexCoord2f(0.0f, 1.0f); glVertex2i(0, lightSize); // bottom-left
481 glEnd();
485 // blend light texture
486 fboLevelLight.exec({
487 glEnable(GL_BLEND);
488 //glDisable(GL_BLEND);
489 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
490 orthoCamera(fboLevelLight.tex.width, fboLevelLight.tex.height);
491 //drawAtXY(fboOccluders.tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
492 float occe = 1.0f*lightSize/(MaxLightRadius*2);
493 float occs = 1.0f-occe;
494 int x0 = lightX-lightRadius+0;
495 int y1 = lightY-lightRadius+0;
496 int x1 = lightX+lightRadius-1+1;
497 int y0 = lightY+lightRadius-1+1;
498 glBindTexture(GL_TEXTURE_2D, fboOccluders.tex.tid);
499 glBegin(GL_QUADS);
501 glTexCoord2f(0.0f, 0.0f); glVertex2i(x0, y0); // top-left
502 glTexCoord2f(occe, 0.0f); glVertex2i(x1, y0); // top-right
503 glTexCoord2f(occe, occe); glVertex2i(x1, y1); // bottom-right
504 glTexCoord2f(0.0f, occe); glVertex2i(x0, y1); // bottom-left
506 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
507 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
508 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
509 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
510 glEnd();
512 glBindTexture(GL_TEXTURE_2D, 0);
513 glRectf(x0, y0, x1, y1);
515 // and blend it again, with the shader that will touch only occluders
516 shadBlurOcc.exec((Shader shad) {
517 //shad["lightTexSize"] = SVec2F(lightSize, lightSize);
518 shad["lightTexSize"] = SVec2F(lightSize, fboOccluders.tex.height);
519 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
520 shad["lightPos"] = SVec2F(lightX, lightY);
521 //shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
522 glBindTexture(GL_TEXTURE_2D, fboOccluders.tex.tid);
523 glBegin(GL_QUADS);
524 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
525 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
526 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
527 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
528 glEnd();
529 //drawAtXY(fboOccluders.tex.tid, lightX-lightRadius, lightY-lightRadius, lightSize, lightSize, mirrorY:true);
535 // ////////////////////////////////////////////////////////////////////////// //
536 __gshared int testLightX = vlWidth/2, testLightY = vlHeight/2;
537 __gshared bool testLightMoved = false;
538 //__gshared int mapOfsX, mapOfsY;
539 //__gshared bool movement = false;
540 __gshared float iLiquidTime = 0.0;
541 //__gshared bool altMove = false;
544 void renderScene (MonoTime curtime) {
545 //enum BackIntens = 0.05f;
546 enum BackIntens = 0.0f;
548 gloStackClear();
549 float atob = (curtime > lastthink ? cast(float)((curtime-lastthink).total!"msecs")/cast(float)((nextthink-lastthink).total!"msecs") : 1.0f);
550 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
553 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
554 int curfp = cast(int)((curtime-lastthink).total!"msecs");
555 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
559 int mofsx, mofsy; // camera offset, will be set in background layer builder
561 if (mapTilesChanged != 0) rebuildMapMegaTextures();
563 dotDraw(atob);
565 // build background layer
566 fboOrigBack.exec({
567 //glDisable(GL_BLEND);
568 //glClearDepth(1.0f);
569 //glDepthFunc(GL_LESS);
570 //glDepthFunc(GL_NEVER);
571 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
572 glClear(GL_COLOR_BUFFER_BIT/*|GL_DEPTH_BUFFER_BIT*/);
573 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
574 orthoCamera(map.width*TileSize, map.height*TileSize);
576 glEnable(GL_BLEND);
577 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
578 // draw sky
580 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
581 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
582 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
583 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
585 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*TileSize, map.MapSize*TileSize);
586 // draw background
587 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
588 // draw distorted liquid areas
589 shadLiquidDistort.exec((Shader shad) {
590 shad["iDistortTime"] = iLiquidTime;
591 drawAtXY(map.texgl.ptr[map.AllLiquids], 0, 0);
593 // monsters, items; we'll do linear interpolation here
594 glColor3f(1.0f, 1.0f, 1.0f);
595 //glEnable(GL_DEPTH_TEST);
596 attachedLightCount = 0;
598 // who cares about memory?!
599 // draw order: players, items, monsters, other
600 static struct DrawInfo {
601 ActorDef adef;
602 ActorId aid;
603 int actorX, actorY;
604 @disable this (this); // no copies
606 enum { Players, Items, Monsters, Other }
607 __gshared DrawInfo[65536][4] drawlists;
608 __gshared uint[4] dlpos;
609 DrawInfo camchickdi;
611 dlpos[] = 0;
613 Actor.forEach((ActorId me) {
614 //me.fprop_0drawlistpos = 0;
615 if (auto adef = findActorDef(me)) {
616 uint dlnum = Other;
617 switch (adef.classtype.get) {
618 case "monster": dlnum = (adef.classname.get != "Player" ? Monsters : Players); break;
619 case "item": dlnum = Items; break;
620 default: dlnum = Other; break;
622 int actorX, actorY; // current actor position
624 auto ofs = prevFrameActorOfs.ptr[me.id&0xffff];
625 if (frameInterpolation && ofs < uint.max-1 && Actor.isSameSavedActor(me.id, prevFrameActorsData.ptr, ofs)) {
626 import core.stdc.math : roundf;
627 auto xptr = prevFrameActorsData.ptr+ofs;
628 int ox = xptr.fgetp_x;
629 int nx = me.fget_x;
630 int oy = xptr.fgetp_y;
631 int ny = me.fget_y;
632 actorX = cast(int)(ox+roundf((nx-ox)*atob));
633 actorY = cast(int)(oy+roundf((ny-oy)*atob));
634 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
635 } else {
636 actorX = me.fget_x;
637 actorY = me.fget_y;
640 if (me.id == cameraChick.id) {
641 camchickdi.adef = adef;
642 camchickdi.aid = me;
643 camchickdi.actorX = actorX;
644 camchickdi.actorY = actorY;
646 // draw sprite
647 if ((me.fget_flags&AF_NODRAW) == 0) {
648 //auto dl = &drawlists[dlnum][dlpos.ptr[dlnum]];
649 //me.fprop_0drawlistpos = (dlpos.ptr[dlnum]&0xffff)|((dlnum&0xff)<<16);
650 auto dl = drawlists.ptr[dlnum].ptr+dlpos.ptr[dlnum];
651 ++dlpos.ptr[dlnum];
652 dl.adef = adef;
653 dl.aid = me;
654 dl.actorX = actorX;
655 dl.actorY = actorY;
657 // process attached lights
658 if ((me.fget_flags&AF_NOLIGHT) == 0) {
659 uint alr = me.fget_attLightRGBX;
660 bool isambient = (me.fget_classtype.get == "light" && me.fget_classname.get == "Ambient");
661 if ((alr&0xff) >= 4 || (isambient && me.fget_radius >= 1 && me.fget_height >= 1)) {
662 //if (isambient) conwriteln("isambient: ", isambient, "; x=", me.fget_x, "; y=", me.fget_y, "; w=", me.fget_radius, "; h=", me.fget_height);
663 // yep, add it
664 auto li = attachedLights.ptr+attachedLightCount;
665 ++attachedLightCount;
666 li.type = (!isambient ? AttachedLightInfo.Type.Point : AttachedLightInfo.Type.Ambient);
667 li.x = actorX+me.fget_attLightXOfs;
668 li.y = actorY+me.fget_attLightYOfs;
669 li.r = ((alr>>24)&0xff)/255.0f; // red or intensity
670 if ((alr&0x00_ff_ff_00U) == 0x00_00_01_00U) {
671 li.uncolored = true;
672 } else {
673 li.g = ((alr>>16)&0xff)/255.0f;
674 li.b = ((alr>>8)&0xff)/255.0f;
675 li.uncolored = false;
677 li.radius = (alr&0xff);
678 if (li.radius > MaxLightRadius) li.radius = MaxLightRadius;
679 if (isambient) {
680 li.w = me.fget_radius;
681 li.h = me.fget_height;
685 } else {
686 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
690 // draw actor lists
691 foreach_reverse (uint dlnum; 0..drawlists.length) {
692 auto dl = drawlists.ptr[dlnum].ptr;
693 if (dlnum == Players) dl += dlpos.ptr[dlnum]-1;
694 foreach (uint idx; 0..dlpos.ptr[dlnum]) {
695 auto me = dl.aid;
696 if (auto isp = dl.adef.animSpr(me.fget_zAnimstate, me.fget_dir, me.fget_zAnimidx)) {
697 //drawAtXY(isp.tex, dl.actorX-isp.vga.sx, dl.actorY-isp.vga.sy);
698 isp.drawAtXY(dl.actorX, dl.actorY);
700 if (dlnum != Players) ++dl; else --dl;
704 // camera movement
705 if (/*altMove || movement ||*/ scale == 1 || !cameraChick.valid) {
706 mofsx = 0;
707 mofsy = 0;
708 vportX0 = 0;
709 vportY0 = 0;
710 vportX1 = map.width*TileSize;
711 vportY1 = map.height*TileSize;
712 } else {
713 int tiltHeight = /*getMapViewHeight()*/(vlHeight/scale)/4;
714 int vy = cameraChick.looky!int;
715 if (vy < -tiltHeight) vy = -tiltHeight; else if (vy > tiltHeight) vy = tiltHeight;
716 int swdt = vlWidth/scale;
717 int shgt = vlHeight/scale;
718 int x = camchickdi.actorX-swdt/2;
719 int y = (camchickdi.actorY+vy)-shgt/2;
720 if (x < 0) x = 0; else if (x >= map.width*TileSize-swdt) x = map.width*TileSize-swdt-1;
721 if (y < 0) y = 0; else if (y >= map.height*TileSize-shgt) y = map.height*TileSize-shgt-1;
722 mofsx = x*2;
723 mofsy = y*2;
724 vportX0 = mofsx/scale;
725 vportY0 = mofsy/scale;
726 vportX1 = vportX0+vlWidth/scale;
727 vportY1 = vportY0+vlHeight/scale;
730 //glDisable(GL_DEPTH_TEST);
731 // draw dots
732 drawAtXY(texParts, 0, 0);
733 // do liquid coloring
734 drawAtXY(map.texgl.ptr[map.LiquidMask], 0, 0);
735 // foreground -- hide secrets, draw lifts and such
736 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
740 if (doLighting) {
741 glDisable(GL_BLEND);
742 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
744 // make smaller occluder texture, so we can trace faster
745 //assert(fboLMaskSmall.tex.width == map.width);
746 //assert(fboLMaskSmall.tex.height == map.height);
747 fboLMaskSmall.exec({
748 orthoCamera(map.width, map.height);
749 drawAtXY(map.texgl.ptr[map.LightMask].tid, 0, 0, map.width, map.height, mirrorY:true);
752 // clear light layer
753 fboLevelLight.exec({
754 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
755 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
756 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
757 glClear(GL_COLOR_BUFFER_BIT);
760 // texture 1 is background
761 glActiveTexture(GL_TEXTURE0+1);
762 glBindTexture(GL_TEXTURE_2D, fboOrigBack.tex.tid);
763 // texture 2 is occluders
764 glActiveTexture(GL_TEXTURE0+2);
765 glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.LightMask].tid);
766 // texture 3 is small occluder map
767 glActiveTexture(GL_TEXTURE0+3);
768 glBindTexture(GL_TEXTURE_2D, fboLMaskSmall.tex.tid);
769 // done texture assign
770 glActiveTexture(GL_TEXTURE0+0);
773 enum LYOfs = 1;
775 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
776 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
777 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
778 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
779 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
780 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
781 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
782 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
783 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
784 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
786 renderLight(24*TileSize+4, (24+18)*TileSize-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
788 renderLight(280, 330, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
791 // attached lights
792 foreach (ref li; attachedLights[0..attachedLightCount]) {
793 if (li.type == AttachedLightInfo.Type.Ambient) {
794 //conwriteln("ambient: x=", li.x, "; y=", li.y, "; w=", li.w, "; h=", li.h);
795 // ambient light
796 if (li.uncolored) {
797 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(0.0f, 0.0f, 0.0f, li.r));
798 } else {
799 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(li.r, li.g, li.b, 1.0f));
801 } else if (li.type == AttachedLightInfo.Type.Point) {
802 // point light
803 if (li.uncolored) {
804 renderLight(li.x, li.y, SVec4F(0.0f, 0.0f, 0.0f, li.r), li.radius);
805 } else {
806 renderLight(li.x, li.y, SVec4F(li.r, li.g, li.b, 1.0f), li.radius);
812 if (testLightMoved) {
813 testLightX = testLightX/scale+mofsx/scale;
814 testLightY = testLightY/scale+mofsy/scale;
815 testLightMoved = false;
817 foreach (immutable _; 0..1) {
818 renderLight(testLightX, testLightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
822 glActiveTexture(GL_TEXTURE0+1);
823 glBindTexture(GL_TEXTURE_2D, 0);
824 glActiveTexture(GL_TEXTURE0+2);
825 glBindTexture(GL_TEXTURE_2D, 0);
826 glActiveTexture(GL_TEXTURE0+3);
827 glBindTexture(GL_TEXTURE_2D, 0);
828 glActiveTexture(GL_TEXTURE0+0);
831 // draw scaled level
833 shadScanlines.exec((Shader shad) {
834 shad["scanlines"] = scanlines;
835 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
836 glClear(GL_COLOR_BUFFER_BIT);
837 orthoCamera(vlWidth, vlHeight);
838 //orthoCamera(map.width*TileSize*scale, map.height*TileSize*scale);
839 //glMatrixMode(GL_MODELVIEW);
840 //glLoadIdentity();
841 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
842 // somehow, FBO objects are mirrored; wtf?!
843 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
844 //glLoadIdentity();
849 fboLevelLight.exec({
850 smDrawText(map.width*TileSize/2, map.height*TileSize/2, "Testing...");
855 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
856 glDisable(GL_BLEND);
859 fboOrigBack.exec({
860 //auto img = smfont.ptr[0x39];
861 auto img = fftest;
862 if (img !is null) {
863 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
864 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
871 { // http://stackoverflow.com/questions/7207422/setting-up-opengl-multiple-render-targets
872 GLenum[1] buffers = [ GL_BACK_LEFT, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
873 //GLenum[1] buffers = [ GL_NONE, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
874 glDrawBuffers(1, buffers.ptr);
879 orthoCamera(vlWidth, vlHeight);
880 auto tex = (doLighting ? fboLevelLight.tex.tid : fboOrigBack.tex.tid);
881 drawAtXY(tex, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
883 //drawAtXY(map.texgl.ptr[map.LightMask].tid, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
884 //drawAtXY(fboLMaskSmall.tex.tid, 0, 0, map.width*TileSize, map.height*TileSize);
886 doMessages(curtime);
890 // ////////////////////////////////////////////////////////////////////////// //
891 // returns time slept
892 int sleepAtMaxMsecs (int msecs) {
893 if (msecs > 0) {
894 import core.sys.posix.signal : timespec;
895 import core.sys.posix.time : nanosleep;
896 timespec ts = void, tpassed = void;
897 ts.tv_sec = 0;
898 ts.tv_nsec = msecs*1000*1000+(500*1000); // milli to nano
899 nanosleep(&ts, &tpassed);
900 return (ts.tv_nsec-tpassed.tv_nsec)/(1000*1000);
901 } else {
902 return 0;
907 // ////////////////////////////////////////////////////////////////////////// //
908 // rendering thread
909 shared int diedie = 0;
911 enum D2DFrameTime = 55; // milliseconds
912 enum MinFrameTime = 1000/60; // ~60 FPS
914 public void renderThread (Tid starterTid) {
915 enum BoolOptVarMsgMixin(string varname) =
916 "if (msg.toggle) msg.value = !"~varname~";\n"~
917 "if ("~varname~" != msg.value) "~varname~" = msg.value; else msg.showMessage = false;\n";
919 send(starterTid, 42);
920 try {
921 MonoTime curtime = MonoTime.currTime;
923 lastthink = curtime; // for interpolator
924 nextthink = curtime+dur!"msecs"(D2DFrameTime);
925 MonoTime nextvframe = curtime;
927 enum MaxFPSFrames = 16;
928 float frtimes = 0.0f;
929 int framenum = 0;
930 int prevFPS = -1;
931 int hushFrames = 6; // ignore first `hushFrames` frames overtime
932 MonoTime prevFrameStartTime = curtime;
934 bool vframeWasLost = false;
936 void resetFrameTimers () {
937 MonoTime curtime = MonoTime.currTime;
938 lastthink = curtime; // for interpolator
939 nextthink = curtime+dur!"msecs"(D2DFrameTime);
940 nextvframe = curtime;
943 void loadNewLevel (string name) {
945 if (levelLoaded) {
946 conwriteln("ERROR: can't load new levels yet");
947 return;
950 if (name.length == 0) {
951 conwriteln("ERROR: can't load empty level!");
953 conwriteln("loading map '", name, "'");
954 loadMap(name);
955 resetFrameTimers();
958 void receiveMessages () {
959 for (;;) {
960 import core.time : Duration;
961 //conwriteln("rendering thread: waiting for messages...");
962 auto got = receiveTimeout(
963 Duration.zero, // don't wait
964 (TMsgMessage msg) {
965 addMessage(msg.text[0..msg.textlen], msg.pauseMsecs, msg.noreplace);
967 (TMsgLoadLevel msg) {
968 nextmapname = null; // clear "exit" flag
969 loadNewLevel(msg.mapfile[0..msg.textlen].idup);
971 (TMsgSkipLevel msg) {
972 string mn = genNextMapName(0);
973 if (mn.length) {
974 nextmapname = null; // clear "exit" flag
975 loadNewLevel(mn);
978 (TMsgIntOption msg) {
979 final switch (msg.type) with (TMsgIntOption.Type) {
980 case Scale:
981 if (msg.value >= 1 && msg.value <= 2) scale = msg.value;
982 break;
985 (TMsgBoolOption msg) {
986 final switch (msg.type) with (TMsgBoolOption.Type) {
987 case Interpolation: mixin(BoolOptVarMsgMixin!"frameInterpolation"); break;
988 case Lighting: mixin(BoolOptVarMsgMixin!"doLighting"); break;
989 case Pause: mixin(BoolOptVarMsgMixin!"gamePaused"); break;
990 case CheatNoDoors: mixin(BoolOptVarMsgMixin!"cheatNoDoors"); break;
991 case CheatNoWallClip: mixin(BoolOptVarMsgMixin!"cheatNoWallClip"); break;
993 if (msg.showMessage) {
994 char[128] mbuf;
995 uint msgpos;
996 void putStr(T) (T s) if (is(T : const(char)[])) {
997 foreach (char ch; s) {
998 if (msgpos >= mbuf.length) break;
999 mbuf.ptr[msgpos++] = ch;
1002 putStr("Option \"");
1003 { import std.conv : to; putStr(to!string(msg.type)); }
1004 putStr("\": O");
1005 if (msg.value) putStr("N"); else putStr("FF");
1006 addMessage(mbuf[0..msgpos]);
1009 (TMsgTestLightMove msg) {
1010 testLightX = msg.x;
1011 testLightY = msg.y;
1012 testLightMoved = true;
1014 (Variant v) {
1015 conwriteln("WARNING: unknown thread message received and ignored");
1018 if (!got) {
1019 // no more messages
1020 //conwriteln("rendering thread: no more messages");
1021 break;
1024 if (nextmapname.length) {
1025 string mn = nextmapname;
1026 nextmapname = null; // clear "exit" flag
1027 loadNewLevel(mn);
1031 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1032 bool doThinkFrame () {
1033 if (curtime >= nextthink) {
1034 lastthink = curtime;
1035 while (nextthink <= curtime) nextthink += dur!"msecs"(D2DFrameTime);
1036 if (levelLoaded && !gamePaused) {
1037 // save snapshot and other data for interpolator
1038 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
1039 // process actors
1040 doActorsThink();
1041 dotThink();
1043 // some timing
1044 auto tm = MonoTime.currTime;
1045 int thinkTime = cast(int)((tm-curtime).total!"msecs");
1046 if (thinkTime > 9) { import core.stdc.stdio; printf("spent on thinking: %d msecs\n", thinkTime); }
1047 curtime = tm;
1048 return true;
1049 } else {
1050 return false;
1054 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1055 bool doVFrame () {
1056 version(dont_use_vsync) {
1057 // timer
1058 enum doCheckTime = true;
1059 } else {
1060 // vsync
1061 __gshared bool prevLost = false;
1062 bool doCheckTime = vframeWasLost;
1063 if (vframeWasLost) {
1064 if (!prevLost) {
1065 { import core.stdc.stdio; printf("frame was lost!\n"); }
1067 prevLost = true;
1068 } else {
1069 prevLost = false;
1072 if (doCheckTime) {
1073 if (curtime < nextvframe) return false;
1074 version(dont_use_vsync) {
1075 if (curtime > nextvframe) {
1076 auto overtime = cast(int)((curtime-nextvframe).total!"msecs");
1077 if (overtime > 2500) {
1078 if (hushFrames) {
1079 --hushFrames;
1080 } else {
1081 { import core.stdc.stdio; printf(" spent whole %d msecs\n", overtime); }
1087 while (nextvframe <= curtime) nextvframe += dur!"msecs"(MinFrameTime);
1088 bool ctset = false;
1090 sdwindow.mtLock();
1091 scope(exit) sdwindow.mtUnlock();
1092 ctset = sdwindow.setAsCurrentOpenGlContextNT;
1094 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
1095 if (ctset) {
1096 // render scene
1097 iLiquidTime = cast(float)((curtime-MonoTime.zero).total!"msecs"%10000000)/18.0f*0.04f;
1098 receiveMessages(); // here, 'cause we need active OpenGL context for some messages
1099 if (levelLoaded) {
1100 renderScene(curtime);
1101 } else {
1102 //renderLoading(curtime);
1104 sdwindow.mtLock();
1105 scope(exit) sdwindow.mtUnlock();
1106 sdwindow.swapOpenGlBuffers();
1107 glFinish();
1108 sdwindow.releaseCurrentOpenGlContext();
1109 vframeWasLost = false;
1110 } else {
1111 vframeWasLost = true;
1112 { import core.stdc.stdio; printf("xframe was lost!\n"); }
1114 curtime = MonoTime.currTime;
1115 return true;
1118 for (;;) {
1119 if (sdwindow.closed) break;
1120 if (atomicLoad(diedie) > 0) break;
1122 curtime = MonoTime.currTime;
1123 auto fstime = curtime;
1125 doThinkFrame(); // this will fix curtime if necessary
1126 if (doVFrame()) {
1127 if (!vframeWasLost) {
1128 // fps
1129 auto frameTime = cast(float)(curtime-prevFrameStartTime).total!"msecs"/1000.0f;
1130 prevFrameStartTime = curtime;
1131 frtimes += frameTime;
1132 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
1133 import std.string : format;
1134 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
1135 if (newFPS != prevFPS) {
1136 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
1137 prevFPS = newFPS;
1139 framenum = 0;
1140 frtimes = 0.0f;
1145 curtime = MonoTime.currTime;
1147 // now sleep until next "video" or "think" frame
1148 if (nextthink > curtime && nextvframe > curtime) {
1149 // let's decide
1150 immutable nextVideoFrameSleep = cast(int)((nextvframe-curtime).total!"msecs");
1151 immutable nextThinkFrameSleep = cast(int)((nextthink-curtime).total!"msecs");
1152 immutable sleepTime = (nextVideoFrameSleep < nextThinkFrameSleep ? nextVideoFrameSleep : nextThinkFrameSleep);
1153 sleepAtMaxMsecs(sleepTime);
1154 //curtime = MonoTime.currTime;
1157 } catch (Throwable e) {
1158 // here, we are dead and fucked (the exact order doesn't matter)
1159 import core.stdc.stdlib : abort;
1160 import core.stdc.stdio : fprintf, stderr;
1161 import core.memory : GC;
1162 GC.disable(); // yeah
1163 thread_suspendAll(); // stop right here, you criminal scum!
1164 auto s = e.toString();
1165 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
1166 abort(); // die, you bitch!
1168 import core.stdc.stdio;
1169 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
1170 import std.stdio : stderr;
1171 writeln(
1172 for (;;) {
1173 if (sdwindow.closed) break;
1174 if (atomicLoad(diedie) > 0) break;
1175 sleepAtMaxMsecs(100);
1179 atomicStore(diedie, 2);
1183 // ////////////////////////////////////////////////////////////////////////// //
1184 __gshared Tid renderTid;
1185 shared bool renderThreadStarted = false;
1188 public void startRenderThread () {
1189 if (!cas(&renderThreadStarted, false, true)) {
1190 assert(0, "render thread already started!");
1192 renderTid = spawn(&renderThread, thisTid);
1193 setMaxMailboxSize(renderTid, 1024, OnCrowding.throwException); //FIXME
1194 // wait for "i'm ready" signal
1195 receive(
1196 (int ok) {
1197 if (ok != 42) assert(0, "wtf?!");
1200 conwriteln("rendering thread started");
1204 // ////////////////////////////////////////////////////////////////////////// //
1205 public void closeWindow () {
1206 if (atomicLoad(diedie) != 2) {
1207 atomicStore(diedie, 1);
1208 while (atomicLoad(diedie) != 2) {}
1210 if (!sdwindow.closed) {
1211 flushGui();
1212 sdwindow.close();
1217 // ////////////////////////////////////////////////////////////////////////// //
1218 // thread messages
1221 // ////////////////////////////////////////////////////////////////////////// //
1222 struct TMsgTestLightMove {
1223 int x, y;
1226 public void postTestLightMove (int x, int y) {
1227 if (!atomicLoad(renderThreadStarted)) return;
1228 auto msg = TMsgTestLightMove(x, y);
1229 send(renderTid, msg);
1233 // ////////////////////////////////////////////////////////////////////////// //
1234 struct TMsgMessage {
1235 char[256] text;
1236 uint textlen;
1237 int pauseMsecs;
1238 bool noreplace;
1241 public void postAddMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
1242 if (!atomicLoad(renderThreadStarted)) return;
1243 if (msgtext.length > TMsgMessage.text.length) msgtext = msgtext[0..TMsgMessage.text.length];
1244 TMsgMessage msg;
1245 msg.textlen = cast(uint)msgtext.length;
1246 if (msg.textlen) msg.text[0..msg.textlen] = msgtext[0..msg.textlen];
1247 msg.pauseMsecs = pauseMsecs;
1248 msg.noreplace = noreplace;
1249 send(renderTid, msg);
1253 // ////////////////////////////////////////////////////////////////////////// //
1254 struct TMsgLoadLevel {
1255 char[1024] mapfile;
1256 uint textlen;
1259 public void postLoadLevel (const(char)[] mapfile) {
1260 if (!atomicLoad(renderThreadStarted)) return;
1261 if (mapfile.length > TMsgLoadLevel.mapfile.length) mapfile = mapfile[0..TMsgLoadLevel.mapfile.length];
1262 TMsgLoadLevel msg;
1263 msg.textlen = cast(uint)mapfile.length;
1264 if (msg.textlen) msg.mapfile[0..msg.textlen] = mapfile[0..msg.textlen];
1265 send(renderTid, msg);
1269 // ////////////////////////////////////////////////////////////////////////// //
1270 struct TMsgSkipLevel {
1273 public void postSkipLevel () {
1274 if (!atomicLoad(renderThreadStarted)) return;
1275 TMsgSkipLevel msg;
1276 send(renderTid, msg);
1280 // ////////////////////////////////////////////////////////////////////////// //
1281 struct TMsgIntOption {
1282 enum Type {
1283 Scale,
1285 Type type;
1286 int value;
1287 bool showMessage;
1291 struct TMsgBoolOption {
1292 enum Type {
1293 Interpolation,
1294 Lighting,
1295 Pause,
1296 CheatNoDoors,
1297 CheatNoWallClip,
1299 Type type;
1300 bool value;
1301 bool toggle;
1302 bool showMessage;
1306 bool strCaseEqu (const(char)[] s0, const(char)[] s1) {
1307 if (s0.length != s1.length) return false;
1308 foreach (immutable idx, char ch; s0) {
1309 if (ch >= 'A' && ch <= 'Z') ch += 32;
1310 char c1 = s1.ptr[idx];
1311 if (c1 >= 'A' && c1 <= 'Z') c1 += 32;
1312 if (ch != c1) return false;
1314 return true;
1318 public bool postToggleOption (const(char)[] name, bool showMessage=false) { pragma(inline, true); return postSetOption(name, true, showMessage:showMessage, toggle:true); }
1320 public bool postSetOption(T) (const(char)[] name, T value, bool showMessage=false, bool toggle=false) if ((is(T == int) || is(T == bool))) {
1321 if (!atomicLoad(renderThreadStarted)) return false;
1322 if (name.length == 0 || name.length > 127) return false;
1323 static if (is(T == int)) {
1324 TMsgIntOption msg;
1325 foreach (string oname; __traits(allMembers, TMsgIntOption.Type)) {
1326 if (strCaseEqu(oname, name)) {
1327 msg.type = __traits(getMember, TMsgIntOption.Type, oname);
1328 msg.value = value;
1329 msg.showMessage = showMessage;
1330 send(renderTid, msg);
1331 return true;
1334 } else static if (is(T == bool)) {
1335 TMsgBoolOption msg;
1336 foreach (string oname; __traits(allMembers, TMsgBoolOption.Type)) {
1337 if (strCaseEqu(oname, name)) {
1338 msg.type = __traits(getMember, TMsgBoolOption.Type, oname);
1339 msg.value = value;
1340 msg.toggle = toggle;
1341 msg.showMessage = showMessage;
1342 send(renderTid, msg);
1343 return true;
1346 } else {
1347 static assert(0, "invalid option type '"~T.stringof~"'");
1349 return false;