more HUD API and air indicator
[dd2d.git] / render.d
bloba212f521d720efc46be20c33247a2a72ea7aae6b
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.vfs.augs;
36 import d2dmap;
37 import d2dadefs;
38 import d2dimage;
39 import d2dfont;
40 import dacs;
42 import d2dunigrid;
44 // `map` is there
45 import dengapi;
47 import d2dparts;
50 // ////////////////////////////////////////////////////////////////////////// //
51 import arsd.color;
52 import arsd.png;
55 // ////////////////////////////////////////////////////////////////////////// //
56 public __gshared bool cheatNoDoors = false;
57 public __gshared bool cheatNoWallClip = false;
60 // ////////////////////////////////////////////////////////////////////////// //
61 public __gshared SimpleWindow sdwindow;
64 public enum vlWidth = 800;
65 public enum vlHeight = 800;
66 __gshared int scale = 2;
68 public int getScale () nothrow @trusted @nogc { pragma(inline, true); return scale; }
71 // ////////////////////////////////////////////////////////////////////////// //
72 __gshared bool levelLoaded = false;
75 // ////////////////////////////////////////////////////////////////////////// //
76 __gshared bool scanlines = false;
77 __gshared bool doLighting = true;
78 __gshared bool gamePaused = false;
79 shared bool editMode = false;
81 public @property bool inEditMode () nothrow @trusted @nogc { import core.atomic; return atomicLoad(editMode); }
82 @property void inEditMode (bool v) nothrow @trusted @nogc { import core.atomic; atomicStore(editMode, v); }
85 // ////////////////////////////////////////////////////////////////////////// //
86 // interpolation
87 __gshared ubyte[] prevFrameActorsData;
88 __gshared uint[65536] prevFrameActorOfs; // uint.max-1: dead; uint.max: last
89 __gshared MonoTime lastthink = MonoTime.zero; // for interpolator
90 __gshared MonoTime nextthink = MonoTime.zero;
91 __gshared bool frameInterpolation = true;
94 // ////////////////////////////////////////////////////////////////////////// //
95 // attached lights
96 struct AttachedLightInfo {
97 enum Type {
98 Point,
99 Ambient,
101 Type type;
102 int x, y;
103 int w, h; // for ambient lights
104 float r, g, b;
105 bool uncolored;
106 int radius;
109 __gshared AttachedLightInfo[65536] attachedLights;
110 __gshared uint attachedLightCount = 0;
113 // ////////////////////////////////////////////////////////////////////////// //
114 enum MaxLightRadius = 255;
117 // ////////////////////////////////////////////////////////////////////////// //
118 // for light
119 __gshared FBO[MaxLightRadius+1] fboDistMap;
120 __gshared FBO fboOccluders;
121 __gshared Shader shadToPolar, shadBlur, shadBlurOcc, shadAmbient;
122 __gshared TrueColorImage editorImg;
123 __gshared FBO fboEditor;
125 __gshared FBO fboLevel, fboLevelLight, fboOrigBack, fboLMaskSmall;
126 __gshared Shader shadScanlines;
127 __gshared Shader shadLiquidDistort;
130 // ////////////////////////////////////////////////////////////////////////// //
131 // call once!
132 public void initOpenGL () {
133 gloStackClear();
135 glEnable(GL_TEXTURE_2D);
136 glDisable(GL_LIGHTING);
137 glDisable(GL_DITHER);
138 glDisable(GL_BLEND);
139 glDisable(GL_DEPTH_TEST);
141 // create shaders
142 shadScanlines = new Shader("scanlines", loadTextFile("shaders/srscanlines.frag"));
144 shadLiquidDistort = new Shader("liquid_distort", loadTextFile("shaders/srliquid_distort.frag"));
145 shadLiquidDistort.exec((Shader shad) {
146 shad["texLqMap"] = 0;
149 // lights
150 shadToPolar = new Shader("light_trace", loadTextFile("shaders/srlight_trace.frag"));
151 shadToPolar.exec((Shader shad) {
152 shad["texOcc"] = 0;
153 shad["texOccFull"] = 2;
154 shad["texOccSmall"] = 3;
157 shadBlur = new Shader("light_blur", loadTextFile("shaders/srlight_blur.frag"));
158 shadBlur.exec((Shader shad) {
159 shad["texDist"] = 0;
160 shad["texBg"] = 1;
161 shad["texOcc"] = 2;
162 shad["texOccSmall"] = 3;
165 shadBlurOcc = new Shader("light_blur_occ", loadTextFile("shaders/srlight_blur_occ.frag"));
166 shadBlurOcc.exec((Shader shad) {
167 shad["texLMap"] = 0;
168 shad["texBg"] = 1;
169 shad["texOcc"] = 2;
170 shad["texOccSmall"] = 3;
173 shadAmbient = new Shader("light_ambient", loadTextFile("shaders/srlight_ambient.frag"));
174 shadAmbient.exec((Shader shad) {
175 shad["texBg"] = 1;
176 shad["texOcc"] = 2;
177 shad["texOccSmall"] = 3;
180 fboOccluders = new FBO(MaxLightRadius*2, MaxLightRadius*2, Texture.Option.Clamp, Texture.Option.Linear);
181 //TODO: this sux!
182 foreach (int sz; 2..MaxLightRadius+1) {
183 fboDistMap[sz] = new FBO(sz*2, 1, Texture.Option.Clamp, Texture.Option.Linear); // create 1d distance map FBO
186 editorImg = new TrueColorImage(vlWidth, vlHeight);
187 editorImg.imageData.colors[] = Color(0, 0, 0, 0);
188 fboEditor = new FBO(vlWidth, vlHeight, Texture.Option.Nearest);
190 // setup matrices
191 glMatrixMode(GL_MODELVIEW);
192 glLoadIdentity();
194 loadSmFont();
195 loadBfFont();
196 loadAllMonsterGraphics();
200 // ////////////////////////////////////////////////////////////////////////// //
201 // should be called when OpenGL is initialized
202 void loadMap (string mapname) {
203 mapscripts.runUnloading(); // "map unloading" script
204 clearMapScripts();
206 if (map !is null) map.clear();
207 map = new LevelMap(mapname);
208 curmapname = mapname;
210 ugInit(map.width*TileSize, map.height*TileSize);
212 map.oglBuildMega();
213 mapTilesChanged = 0;
215 if (fboLevel !is null) fboLevel.clear();
216 if (fboLevelLight !is null) fboLevelLight.clear();
217 if (fboOrigBack !is null) fboOrigBack.clear();
218 if (fboLMaskSmall !is null) fboLMaskSmall.clear();
220 fboLevel = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // final level render will be here
221 fboLevelLight = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // level lights will be rendered here
222 fboOrigBack = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest/*, Texture.Option.Depth*/); // background+foreground
223 fboLMaskSmall = new FBO(map.width, map.height, Texture.Option.Nearest); // small lightmask
225 shadToPolar.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize-1, map.height*TileSize-1); });
226 shadBlur.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
227 shadBlurOcc.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
228 shadAmbient.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
230 glActiveTexture(GL_TEXTURE0+0);
231 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
232 orthoCamera(vlWidth, vlHeight);
234 Actor.resetStorage();
236 setupMapScripts();
237 mapscripts.runInit();
238 loadMapMonsters();
239 dotInit();
240 mapscripts.runLoaded();
242 // save first snapshot
243 if (prevFrameActorsData.length == 0) prevFrameActorsData = new ubyte[](Actor.actorSize*65536); // ~15-20 megabytes
244 prevFrameActorOfs[] = uint.max; // just for fun
245 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
247 levelLoaded = true;
249 { import core.memory : GC; GC.collect(); }
253 // ////////////////////////////////////////////////////////////////////////// //
254 //FIXME: optimize!
255 __gshared uint mapTilesChanged = 0;
258 //WARNING! this can be called only from DACS, so we don't have to sync it!
259 public void mapDirty (uint layermask) { mapTilesChanged |= layermask; }
262 void rebuildMapMegaTextures () {
263 //fbo.replaceTexture
264 //mapTilesChanged = false;
265 //map.clearMegaTextures();
266 map.oglBuildMega(mapTilesChanged);
267 mapTilesChanged = 0;
268 dotsAwake(); // let dormant dots fall
272 // ////////////////////////////////////////////////////////////////////////// //
273 // messages
274 struct Message {
275 enum Phase { FadeIn, Stay, FadeOut }
276 Phase phase;
277 int alpha;
278 int pauseMsecs;
279 MonoTime removeTime;
280 char[256] text;
281 usize textlen;
284 private import core.sync.mutex : Mutex;
286 __gshared Message[128] messages;
287 __gshared uint messagesUsed = 0;
289 //__gshared Mutex messageLock;
290 //shared static this () { messageLock = new Mutex(); }
293 void addMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
294 //messageLock.lock();
295 //scope(exit) messageLock.unlock();
296 if (msgtext.length == 0) return;
297 conwriteln(msgtext);
298 if (pauseMsecs <= 50) return;
299 if (messagesUsed == messages.length) {
300 // remove top message
301 foreach (immutable cidx; 1..messagesUsed) messages.ptr[cidx-1] = messages.ptr[cidx];
302 messages.ptr[0].alpha = 255;
303 --messagesUsed;
305 // quick replace
306 if (!noreplace && messagesUsed == 1) {
307 switch (messages.ptr[0].phase) {
308 case Message.Phase.FadeIn:
309 messages.ptr[0].phase = Message.Phase.FadeOut;
310 break;
311 case Message.Phase.Stay:
312 messages.ptr[0].phase = Message.Phase.FadeOut;
313 messages.ptr[0].alpha = 255;
314 break;
315 default:
318 auto msg = messages.ptr+messagesUsed;
319 ++messagesUsed;
320 msg.phase = Message.Phase.FadeIn;
321 msg.alpha = 0;
322 msg.pauseMsecs = pauseMsecs;
323 // copy text
324 if (msgtext.length > msg.text.length) {
325 msg.text = msgtext[0..msg.text.length];
326 msg.textlen = msg.text.length;
327 } else {
328 msg.text[0..msgtext.length] = msgtext[];
329 msg.textlen = msgtext.length;
334 void doMessages (MonoTime curtime) {
335 //messageLock.lock();
336 //scope(exit) messageLock.unlock();
338 if (messagesUsed == 0) return;
339 glEnable(GL_BLEND);
340 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
342 Message* msg;
344 again:
345 msg = messages.ptr;
346 final switch (msg.phase) {
347 case Message.Phase.FadeIn:
348 if ((msg.alpha += 10) >= 255) {
349 msg.phase = Message.Phase.Stay;
350 msg.removeTime = curtime+dur!"msecs"(msg.pauseMsecs);
351 goto case; // to stay
353 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
354 break;
355 case Message.Phase.Stay:
356 if (msg.removeTime <= curtime) {
357 msg.alpha = 255;
358 msg.phase = Message.Phase.FadeOut;
359 goto case; // to fade
361 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
362 break;
363 case Message.Phase.FadeOut:
364 if ((msg.alpha -= 10) <= 0) {
365 if (--messagesUsed == 0) return;
366 // remove this message
367 foreach (immutable cidx; 1..messagesUsed+1) messages.ptr[cidx-1] = messages.ptr[cidx];
368 goto again;
370 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
371 break;
374 smDrawText(10, 10, msg.text[0..msg.textlen]);
378 // ////////////////////////////////////////////////////////////////////////// //
379 //mixin(Actor.FieldPropMixin!("0drawlistpos", uint));
381 mixin(Actor.FieldGetMixin!("classtype", StrId)); // fget_classtype
382 mixin(Actor.FieldGetMixin!("classname", StrId)); // fget_classname
383 mixin(Actor.FieldGetMixin!("x", int));
384 mixin(Actor.FieldGetMixin!("y", int));
385 mixin(Actor.FieldGetMixin!("s", int));
386 mixin(Actor.FieldGetMixin!("radius", int));
387 mixin(Actor.FieldGetMixin!("height", int));
388 mixin(Actor.FieldGetMixin!("flags", uint));
389 mixin(Actor.FieldGetMixin!("zAnimstate", StrId));
390 mixin(Actor.FieldGetMixin!("zAnimidx", int));
391 mixin(Actor.FieldGetMixin!("dir", uint));
392 mixin(Actor.FieldGetMixin!("attLightXOfs", int));
393 mixin(Actor.FieldGetMixin!("attLightYOfs", int));
394 mixin(Actor.FieldGetMixin!("attLightRGBX", uint));
396 //mixin(Actor.FieldGetPtrMixin!("classtype", StrId)); // fget_classtype
397 //mixin(Actor.FieldGetPtrMixin!("classname", StrId)); // fget_classname
398 mixin(Actor.FieldGetPtrMixin!("x", int));
399 mixin(Actor.FieldGetPtrMixin!("y", int));
400 //mixin(Actor.FieldGetPtrMixin!("flags", uint));
401 //mixin(Actor.FieldGetPtrMixin!("zAnimstate", StrId));
402 //mixin(Actor.FieldGetPtrMixin!("zAnimidx", int));
403 //mixin(Actor.FieldGetPtrMixin!("dir", uint));
404 //mixin(Actor.FieldGetPtrMixin!("attLightXOfs", int));
405 //mixin(Actor.FieldGetPtrMixin!("attLightYOfs", int));
406 //mixin(Actor.FieldGetPtrMixin!("attLightRGBX", uint));
409 // ////////////////////////////////////////////////////////////////////////// //
410 __gshared int vportX0, vportY0, vportX1, vportY1;
413 // ////////////////////////////////////////////////////////////////////////// //
414 void renderLightAmbient() (int lightX, int lightY, int lightW, int lightH, in auto ref SVec4F lcol) {
415 //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));
416 if (lightW < 1 || lightH < 1) return;
417 int lightX1 = lightX+lightW-1;
418 int lightY1 = lightY+lightH-1;
419 // clip light to viewport
420 if (lightX < vportX0) lightX = vportX0;
421 if (lightY < vportY0) lightY = vportY0;
422 if (lightX1 > vportX1) lightX1 = vportX1;
423 if (lightY1 > vportY1) lightY1 = vportY1;
424 // is this light visible?
425 //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));
426 if (lightX1 < lightX || lightY1 < lightY || lightX > vportX1 || lightY > vportY1 || lightX1 < vportX0 || lightY1 < vportY0) return;
427 //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));
429 fboLevelLight.exec({
430 glEnable(GL_BLEND);
431 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
432 //glDisable(GL_BLEND);
433 orthoCamera(map.width*TileSize, map.height*TileSize);
434 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
435 shadAmbient.exec((Shader shad) {
436 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
437 //shad["lightPos"] = SVec2F(lightX, lightY);
438 glRectf(lightX, lightY, lightX1, lightY1);
444 // ////////////////////////////////////////////////////////////////////////// //
445 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
446 if (lightRadius < 2) return;
447 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
448 int lightSize = lightRadius*2;
449 // is this light visible?
450 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*TileSize || lightY-lightRadius >= map.height*TileSize) return;
452 // out of viewport -- do nothing
453 if (lightX+lightRadius < vportX0 || lightY+lightRadius < vportY0) return;
454 if (lightX-lightRadius > vportX1 || lightY-lightRadius > vportY1) return;
456 if (lightX >= 0 && lightY >= 0 && lightX < map.width*TileSize && lightY < map.height*TileSize &&
457 map.teximgs[map.LightMask].imageData.colors.ptr[lightY*(map.width*TileSize)+lightX].a > 190) return;
459 // common color for all the following
460 glDisable(GL_BLEND);
461 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
463 // build 1d distance map to fboShadowMapId
464 fboDistMap.ptr[lightRadius].exec({
465 // no need to clear it, shader will take care of that
466 shadToPolar.exec((Shader shad) {
467 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
468 shad["lightPos"] = SVec2F(lightX, lightY);
469 orthoCamera(lightSize, 1);
470 // it doesn't matter what we will draw here, so just draw filled rect
471 glRectf(0, 0, lightSize, 1);
475 // build light texture for blending
476 fboOccluders.exec({
477 // no need to clear it, shader will take care of that
478 // debug
479 //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
480 //glClear(GL_COLOR_BUFFER_BIT);
481 shadBlur.exec((Shader shad) {
482 shad["lightTexSize"] = SVec2F(lightSize, MaxLightRadius*2); // x: size of distmap; y: size of this texture
483 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
484 shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
485 orthoCamera(fboOccluders.tex.width, fboOccluders.tex.height);
486 //drawAtXY(fboDistMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
487 bindTexture(fboDistMap.ptr[lightRadius].tex.tid);
488 glBegin(GL_QUADS);
489 glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0); // top-left
490 glTexCoord2f(1.0f, 0.0f); glVertex2i(lightSize, 0); // top-right
491 glTexCoord2f(1.0f, 1.0f); glVertex2i(lightSize, lightSize); // bottom-right
492 glTexCoord2f(0.0f, 1.0f); glVertex2i(0, lightSize); // bottom-left
493 glEnd();
497 // blend light texture
498 fboLevelLight.exec({
499 glEnable(GL_BLEND);
500 //glDisable(GL_BLEND);
501 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
502 orthoCamera(fboLevelLight.tex.width, fboLevelLight.tex.height);
503 //drawAtXY(fboOccluders.tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
504 float occe = 1.0f*lightSize/(MaxLightRadius*2);
505 float occs = 1.0f-occe;
506 int x0 = lightX-lightRadius+0;
507 int y1 = lightY-lightRadius+0;
508 int x1 = lightX+lightRadius-1+1;
509 int y0 = lightY+lightRadius-1+1;
510 bindTexture(fboOccluders.tex.tid);
511 glBegin(GL_QUADS);
513 glTexCoord2f(0.0f, 0.0f); glVertex2i(x0, y0); // top-left
514 glTexCoord2f(occe, 0.0f); glVertex2i(x1, y0); // top-right
515 glTexCoord2f(occe, occe); glVertex2i(x1, y1); // bottom-right
516 glTexCoord2f(0.0f, occe); glVertex2i(x0, y1); // bottom-left
518 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
519 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
520 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
521 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
522 glEnd();
524 bindTexture(0);
525 glRectf(x0, y0, x1, y1);
527 // and blend it again, with the shader that will touch only occluders
528 shadBlurOcc.exec((Shader shad) {
529 //shad["lightTexSize"] = SVec2F(lightSize, lightSize);
530 shad["lightTexSize"] = SVec2F(lightSize, fboOccluders.tex.height);
531 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
532 shad["lightPos"] = SVec2F(lightX, lightY);
533 //shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
534 bindTexture(fboOccluders.tex.tid);
535 glBegin(GL_QUADS);
536 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
537 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
538 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
539 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
540 glEnd();
541 //drawAtXY(fboOccluders.tex.tid, lightX-lightRadius, lightY-lightRadius, lightSize, lightSize, mirrorY:true);
547 // ////////////////////////////////////////////////////////////////////////// //
548 __gshared int testLightX = vlWidth/2, testLightY = vlHeight/2;
549 __gshared bool testLightMoved = false;
550 //__gshared int mapOfsX, mapOfsY;
551 //__gshared bool movement = false;
552 __gshared float iLiquidTime = 0.0;
553 //__gshared bool altMove = false;
556 void renderScene (MonoTime curtime) {
557 //enum BackIntens = 0.05f;
558 enum BackIntens = 0.0f;
560 gloStackClear();
561 float atob = (curtime > lastthink ? cast(float)((curtime-lastthink).total!"msecs")/cast(float)((nextthink-lastthink).total!"msecs") : 1.0f);
562 if (gamePaused || inEditMode) atob = 1.0f;
563 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
566 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
567 int curfp = cast(int)((curtime-lastthink).total!"msecs");
568 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
572 int mofsx, mofsy; // camera offset, will be set in background layer builder
574 if (mapTilesChanged != 0) rebuildMapMegaTextures();
576 // build background layer
577 fboOrigBack.exec({
578 //glDisable(GL_BLEND);
579 //glClearDepth(1.0f);
580 //glDepthFunc(GL_LESS);
581 //glDepthFunc(GL_NEVER);
582 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
583 glClear(GL_COLOR_BUFFER_BIT/*|GL_DEPTH_BUFFER_BIT*/);
584 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
585 orthoCamera(map.width*TileSize, map.height*TileSize);
587 glEnable(GL_BLEND);
588 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
589 // draw sky
591 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
592 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
593 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
594 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
596 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*TileSize, map.MapSize*TileSize);
597 // draw background
598 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
599 // draw distorted liquid areas
600 shadLiquidDistort.exec((Shader shad) {
601 shad["iDistortTime"] = iLiquidTime;
602 drawAtXY(map.texgl.ptr[map.AllLiquids], 0, 0);
604 // monsters, items; we'll do linear interpolation here
605 glColor3f(1.0f, 1.0f, 1.0f);
606 //glEnable(GL_DEPTH_TEST);
607 attachedLightCount = 0;
609 // who cares about memory?!
610 // draw order: players, items, monsters, other
611 static struct DrawInfo {
612 ActorDef adef;
613 ActorId aid;
614 int actorX, actorY;
615 @disable this (this); // no copies
617 enum { Pixels, Players, Items, Monsters, Other }
618 __gshared DrawInfo[65536][4] drawlists;
619 __gshared uint[4] dlpos;
620 DrawInfo camchickdi;
622 dlpos[] = 0;
624 Actor.forEach((ActorId me) {
625 //me.fprop_0drawlistpos = 0;
626 if (auto adef = findActorDef(me)) {
627 uint dlnum = Other;
628 switch (adef.classtype.get) {
629 case "monster": dlnum = (adef.classname.get != "Player" ? Monsters : Players); break;
630 case "item": dlnum = Items; break;
631 default: dlnum = Other; break;
633 if (me.fget_flags&AF_PIXEL) dlnum = Pixels;
634 int actorX, actorY; // current actor position
636 auto ofs = prevFrameActorOfs.ptr[me.id&0xffff];
637 if (frameInterpolation && ofs < uint.max-1 && Actor.isSameSavedActor(me.id, prevFrameActorsData.ptr, ofs)) {
638 import core.stdc.math : roundf;
639 auto xptr = prevFrameActorsData.ptr+ofs;
640 int ox = xptr.fgetp_x;
641 int nx = me.fget_x;
642 int oy = xptr.fgetp_y;
643 int ny = me.fget_y;
644 actorX = cast(int)(ox+roundf((nx-ox)*atob));
645 actorY = cast(int)(oy+roundf((ny-oy)*atob));
646 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
647 } else {
648 actorX = me.fget_x;
649 actorY = me.fget_y;
652 if (me.id == cameraChick.id) {
653 camchickdi.adef = adef;
654 camchickdi.aid = me;
655 camchickdi.actorX = actorX;
656 camchickdi.actorY = actorY;
658 // draw sprite
659 if ((me.fget_flags&AF_NODRAW) == 0) {
660 //auto dl = &drawlists[dlnum][dlpos.ptr[dlnum]];
661 //me.fprop_0drawlistpos = (dlpos.ptr[dlnum]&0xffff)|((dlnum&0xff)<<16);
662 auto dl = drawlists.ptr[dlnum].ptr+dlpos.ptr[dlnum];
663 ++dlpos.ptr[dlnum];
664 dl.adef = adef;
665 dl.aid = me;
666 dl.actorX = actorX;
667 dl.actorY = actorY;
669 // process attached lights
670 if ((me.fget_flags&AF_NOLIGHT) == 0) {
671 uint alr = me.fget_attLightRGBX;
672 bool isambient = (me.fget_classtype.get == "light" && me.fget_classname.get == "Ambient");
673 if ((alr&0xff) >= 4 || (isambient && me.fget_radius >= 1 && me.fget_height >= 1)) {
674 //if (isambient) conwriteln("isambient: ", isambient, "; x=", me.fget_x, "; y=", me.fget_y, "; w=", me.fget_radius, "; h=", me.fget_height);
675 // yep, add it
676 auto li = attachedLights.ptr+attachedLightCount;
677 ++attachedLightCount;
678 li.type = (!isambient ? AttachedLightInfo.Type.Point : AttachedLightInfo.Type.Ambient);
679 li.x = actorX+me.fget_attLightXOfs;
680 li.y = actorY+me.fget_attLightYOfs;
681 li.r = ((alr>>24)&0xff)/255.0f; // red or intensity
682 if ((alr&0x00_ff_ff_00U) == 0x00_00_01_00U) {
683 li.uncolored = true;
684 } else {
685 li.g = ((alr>>16)&0xff)/255.0f;
686 li.b = ((alr>>8)&0xff)/255.0f;
687 li.uncolored = false;
689 li.radius = (alr&0xff);
690 if (li.radius > MaxLightRadius) li.radius = MaxLightRadius;
691 if (isambient) {
692 li.w = me.fget_radius;
693 li.h = me.fget_height;
697 } else {
698 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
702 // draw actor lists
703 foreach_reverse (uint dlnum; 0..drawlists.length) {
704 if (dlnum == Pixels) continue;
705 auto dl = drawlists.ptr[dlnum].ptr;
706 if (dlnum == Players) dl += dlpos.ptr[dlnum]-1;
707 foreach (uint idx; 0..dlpos.ptr[dlnum]) {
708 auto me = dl.aid;
709 if (auto isp = dl.adef.animSpr(me.fget_zAnimstate, me.fget_dir, me.fget_zAnimidx)) {
710 //drawAtXY(isp.tex, dl.actorX-isp.vga.sx, dl.actorY-isp.vga.sy);
711 isp.drawAtXY(dl.actorX, dl.actorY);
713 if (dlnum != Players) ++dl; else --dl;
716 // draw pixels
717 if (dlpos[Pixels]) {
718 bindTexture(0);
719 bool pointsStarted = false;
720 Color lastColor = Color(0, 0, 0, 0);
721 auto dl = drawlists.ptr[Pixels].ptr;
722 foreach (uint idx; 0..dlpos.ptr[Pixels]) {
723 auto me = dl.aid;
724 auto s = me.fget_s;
725 if (s < 0 || s > 255) continue; //FIXME
726 Color clr = d2dpal.ptr[s&0xff];
727 if (clr.a == 0) continue;
728 if (clr != lastColor) {
729 if (pointsStarted) glEnd();
730 glColor4f(clr.r/255.0f, clr.g/255.0f, clr.b/255.0f, clr.a/255.0f);
731 lastColor = clr;
732 pointsStarted = false;
734 if (!pointsStarted) {
735 glBegin(GL_POINTS);
736 pointsStarted = true;
738 glVertex2i(dl.actorX, dl.actorY);
739 ++dl;
741 if (pointsStarted) {
742 glEnd();
743 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
747 // camera movement
748 if (/*altMove || movement ||*/ scale == 1 || !cameraChick.valid) {
749 mofsx = 0;
750 mofsy = 0;
751 vportX0 = 0;
752 vportY0 = 0;
753 vportX1 = map.width*TileSize;
754 vportY1 = map.height*TileSize;
755 } else {
756 int tiltHeight = /*getMapViewHeight()*/(vlHeight/scale)/4;
757 int vy = cameraChick.looky!int;
758 if (vy < -tiltHeight) vy = -tiltHeight; else if (vy > tiltHeight) vy = tiltHeight;
759 int swdt = vlWidth/scale;
760 int shgt = vlHeight/scale;
761 int x = camchickdi.actorX-swdt/2;
762 int y = (camchickdi.actorY+vy)-shgt/2;
763 if (x < 0) x = 0; else if (x >= map.width*TileSize-swdt) x = map.width*TileSize-swdt-1;
764 if (y < 0) y = 0; else if (y >= map.height*TileSize-shgt) y = map.height*TileSize-shgt-1;
765 mofsx = x*2;
766 mofsy = y*2;
767 vportX0 = mofsx/scale;
768 vportY0 = mofsy/scale;
769 vportX1 = vportX0+vlWidth/scale;
770 vportY1 = vportY0+vlHeight/scale;
773 //glDisable(GL_DEPTH_TEST);
774 // draw dots
775 dotDraw(atob);
776 // do liquid coloring
777 drawAtXY(map.texgl.ptr[map.LiquidMask], 0, 0);
778 // foreground -- hide secrets, draw lifts and such
779 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
782 enum r = 255;
783 enum g = 0;
784 enum b = 0;
785 enum a = 255;
786 bindTexture(0);
787 glColor4f(r/255.0f, g/255.0f, b/255.0f, a/255.0f);
788 glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
789 if (cameraChick.valid) {
790 glBegin(GL_POINTS);
791 glVertex2i(camchickdi.actorX, camchickdi.actorY-70);
792 glEnd();
793 //glRectf(camchickdi.actorX, camchickdi.actorY-70, camchickdi.actorX+4, camchickdi.actorY-70+4);
795 //glRectf(0, 0, 300, 300);
796 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
802 if (doLighting) {
803 glDisable(GL_BLEND);
804 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
806 // make smaller occluder texture, so we can trace faster
807 //assert(fboLMaskSmall.tex.width == map.width);
808 //assert(fboLMaskSmall.tex.height == map.height);
809 fboLMaskSmall.exec({
810 orthoCamera(map.width, map.height);
811 drawAtXY(map.texgl.ptr[map.LightMask].tid, 0, 0, map.width, map.height, mirrorY:true);
814 // clear light layer
815 fboLevelLight.exec({
816 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
817 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
818 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
819 glClear(GL_COLOR_BUFFER_BIT);
822 // texture 1 is background
823 glActiveTexture(GL_TEXTURE0+1);
824 glBindTexture(GL_TEXTURE_2D, fboOrigBack.tex.tid);
825 // texture 2 is occluders
826 glActiveTexture(GL_TEXTURE0+2);
827 glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.LightMask].tid);
828 // texture 3 is small occluder map
829 glActiveTexture(GL_TEXTURE0+3);
830 glBindTexture(GL_TEXTURE_2D, fboLMaskSmall.tex.tid);
831 // done texture assign
832 glActiveTexture(GL_TEXTURE0+0);
835 enum LYOfs = 1;
837 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
838 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
839 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
840 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
841 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
842 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
843 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
844 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
845 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
846 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
848 renderLight(24*TileSize+4, (24+18)*TileSize-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
850 renderLight(280, 330, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
853 foreach (; 0..1) {
854 // attached lights
855 foreach (ref li; attachedLights[0..attachedLightCount]) {
856 if (li.type == AttachedLightInfo.Type.Ambient) {
857 //conwriteln("ambient: x=", li.x, "; y=", li.y, "; w=", li.w, "; h=", li.h);
858 // ambient light
859 if (li.uncolored) {
860 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(0.0f, 0.0f, 0.0f, li.r));
861 } else {
862 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(li.r, li.g, li.b, 1.0f));
864 } else if (li.type == AttachedLightInfo.Type.Point) {
865 // point light
866 if (li.uncolored) {
867 renderLight(li.x, li.y, SVec4F(0.0f, 0.0f, 0.0f, li.r), li.radius);
868 } else {
869 renderLight(li.x, li.y, SVec4F(li.r, li.g, li.b, 1.0f), li.radius);
876 if (testLightMoved) {
877 testLightX = testLightX/scale+mofsx/scale;
878 testLightY = testLightY/scale+mofsy/scale;
879 testLightMoved = false;
881 foreach (immutable _; 0..1) {
882 renderLight(testLightX, testLightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
886 glActiveTexture(GL_TEXTURE0+1);
887 glBindTexture(GL_TEXTURE_2D, 0);
888 glActiveTexture(GL_TEXTURE0+2);
889 glBindTexture(GL_TEXTURE_2D, 0);
890 glActiveTexture(GL_TEXTURE0+3);
891 glBindTexture(GL_TEXTURE_2D, 0);
892 glActiveTexture(GL_TEXTURE0+0);
895 // draw scaled level
897 shadScanlines.exec((Shader shad) {
898 shad["scanlines"] = scanlines;
899 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
900 glClear(GL_COLOR_BUFFER_BIT);
901 orthoCamera(vlWidth, vlHeight);
902 //orthoCamera(map.width*TileSize*scale, map.height*TileSize*scale);
903 //glMatrixMode(GL_MODELVIEW);
904 //glLoadIdentity();
905 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
906 // somehow, FBO objects are mirrored; wtf?!
907 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
908 //glLoadIdentity();
913 fboLevelLight.exec({
914 smDrawText(map.width*TileSize/2, map.height*TileSize/2, "Testing...");
919 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
921 glDisable(GL_BLEND);
924 fboOrigBack.exec({
925 //auto img = smfont.ptr[0x39];
926 auto img = fftest;
927 if (img !is null) {
928 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
929 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
936 { // http://stackoverflow.com/questions/7207422/setting-up-opengl-multiple-render-targets
937 GLenum[1] buffers = [ GL_BACK_LEFT, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
938 //GLenum[1] buffers = [ GL_NONE, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
939 glDrawBuffers(1, buffers.ptr);
944 orthoCamera(vlWidth, vlHeight);
945 auto tex = (doLighting ? fboLevelLight.tex.tid : fboOrigBack.tex.tid);
946 drawAtXY(tex, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
948 if (levelLoaded) {
949 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
950 //orthoCamera(map.width*TileSize, map.height*TileSize);
951 glEnable(GL_BLEND);
952 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
953 hudScripts.runDraw();
956 //drawAtXY(map.texgl.ptr[map.LightMask].tid, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
957 //drawAtXY(fboLMaskSmall.tex.tid, 0, 0, map.width*TileSize, map.height*TileSize);
959 if (inEditMode) {
960 glEnable(GL_BLEND);
961 //glBlendFunc(GL_SRC_ALPHA, GL_ONE);
962 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
963 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
964 editorUpdateImage();
965 fboEditor.tex.setFromImage(editorImg);
966 drawAtXY(fboEditor.tex, 0, 0);
967 glDisable(GL_BLEND);
970 doMessages(curtime);
974 // ////////////////////////////////////////////////////////////////////////// //
975 // returns time slept
976 int sleepAtMaxMsecs (int msecs) {
977 if (msecs > 0) {
978 import core.sys.posix.signal : timespec;
979 import core.sys.posix.time : nanosleep;
980 timespec ts = void, tpassed = void;
981 ts.tv_sec = 0;
982 ts.tv_nsec = msecs*1000*1000+(500*1000); // milli to nano
983 nanosleep(&ts, &tpassed);
984 return (ts.tv_nsec-tpassed.tv_nsec)/(1000*1000);
985 } else {
986 return 0;
991 // ////////////////////////////////////////////////////////////////////////// //
992 mixin(import("editor.d"));
995 // ////////////////////////////////////////////////////////////////////////// //
996 // rendering thread
997 shared int diedie = 0;
999 enum D2DFrameTime = 55; // milliseconds
1000 enum MinFrameTime = 1000/60; // ~60 FPS
1002 public void renderThread (Tid starterTid) {
1003 enum BoolOptVarMsgMixin(string varname) =
1004 "if (msg.toggle) msg.value = !"~varname~";\n"~
1005 "if ("~varname~" != msg.value) "~varname~" = msg.value; else msg.showMessage = false;\n";
1007 send(starterTid, 42);
1008 try {
1009 MonoTime curtime = MonoTime.currTime;
1011 lastthink = curtime; // for interpolator
1012 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1013 MonoTime nextvframe = curtime;
1015 enum MaxFPSFrames = 16;
1016 float frtimes = 0.0f;
1017 int framenum = 0;
1018 int prevFPS = -1;
1019 int hushFrames = 6; // ignore first `hushFrames` frames overtime
1020 MonoTime prevFrameStartTime = curtime;
1022 bool vframeWasLost = false;
1024 void resetFrameTimers () {
1025 MonoTime curtime = MonoTime.currTime;
1026 lastthink = curtime; // for interpolator
1027 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1028 nextvframe = curtime;
1031 void loadNewLevel (string name) {
1033 if (levelLoaded) {
1034 conwriteln("ERROR: can't load new levels yet");
1035 return;
1038 if (name.length == 0) {
1039 conwriteln("ERROR: can't load empty level!");
1041 conwriteln("loading map '", name, "'");
1042 loadMap(name);
1043 resetFrameTimers();
1046 void receiveMessages () {
1047 for (;;) {
1048 import core.time : Duration;
1049 //conwriteln("rendering thread: waiting for messages...");
1050 auto got = receiveTimeout(
1051 Duration.zero, // don't wait
1052 (TMsgMessage msg) {
1053 addMessage(msg.text[0..msg.textlen], msg.pauseMsecs, msg.noreplace);
1055 (TMsgLoadLevel msg) {
1056 nextmapname = null; // clear "exit" flag
1057 loadNewLevel(msg.mapfile[0..msg.textlen].idup);
1059 (TMsgSkipLevel msg) {
1060 string mn = genNextMapName(0);
1061 if (mn.length) {
1062 nextmapname = null; // clear "exit" flag
1063 loadNewLevel(mn);
1066 (TMsgIntOption msg) {
1067 final switch (msg.type) with (TMsgIntOption.Type) {
1068 case Scale:
1069 if (msg.value >= 1 && msg.value <= 2) scale = msg.value;
1070 break;
1073 (TMsgBoolOption msg) {
1074 final switch (msg.type) with (TMsgBoolOption.Type) {
1075 case Interpolation: mixin(BoolOptVarMsgMixin!"frameInterpolation"); break;
1076 case Lighting: mixin(BoolOptVarMsgMixin!"doLighting"); break;
1077 case Pause: mixin(BoolOptVarMsgMixin!"gamePaused"); break;
1078 case EditMode:
1079 if (msg.toggle) msg.value = !inEditMode;
1080 if (inEditMode != msg.value) {
1081 inEditMode = msg.value;
1082 if (msg.value) sdwindow.hideCursor(); else sdwindow.showCursor();
1083 } else {
1084 msg.showMessage = false;
1086 break;
1087 mixin(BoolOptVarMsgMixin!"inEditMode"); break;
1088 case CheatNoDoors: mixin(BoolOptVarMsgMixin!"cheatNoDoors"); break;
1089 case CheatNoWallClip: mixin(BoolOptVarMsgMixin!"cheatNoWallClip"); break;
1091 if (msg.showMessage) {
1092 char[128] mbuf;
1093 uint msgpos;
1094 void putStr(T) (T s) if (is(T : const(char)[])) {
1095 foreach (char ch; s) {
1096 if (msgpos >= mbuf.length) break;
1097 mbuf.ptr[msgpos++] = ch;
1100 putStr("Option \"");
1101 { import std.conv : to; putStr(to!string(msg.type)); }
1102 putStr("\": O");
1103 if (msg.value) putStr("N"); else putStr("FF");
1104 addMessage(mbuf[0..msgpos]);
1107 (TMsgTestLightMove msg) {
1108 testLightX = msg.x;
1109 testLightY = msg.y;
1110 testLightMoved = true;
1112 (TMsgMouseEvent msg) { editorMouseEvent(msg); },
1113 (TMsgKeyEvent msg) { editorKeyEvent(msg); },
1114 (Variant v) {
1115 conwriteln("WARNING: unknown thread message received and ignored");
1118 if (!got) {
1119 // no more messages
1120 //conwriteln("rendering thread: no more messages");
1121 break;
1124 if (nextmapname.length) {
1125 string mn = nextmapname;
1126 nextmapname = null; // clear "exit" flag
1127 loadNewLevel(mn);
1131 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1132 bool doThinkFrame () {
1133 if (curtime >= nextthink) {
1134 lastthink = curtime;
1135 while (nextthink <= curtime) nextthink += dur!"msecs"(D2DFrameTime);
1136 if (levelLoaded) {
1137 // save snapshot and other data for interpolator
1138 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
1139 if (!gamePaused && !inEditMode) {
1140 // process actors
1141 doActorsThink();
1142 dotThink();
1145 // some timing
1146 auto tm = MonoTime.currTime;
1147 int thinkTime = cast(int)((tm-curtime).total!"msecs");
1148 if (thinkTime > 9) { import core.stdc.stdio; printf("spent on thinking: %d msecs\n", thinkTime); }
1149 curtime = tm;
1150 return true;
1151 } else {
1152 return false;
1156 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1157 bool doVFrame () {
1158 version(dont_use_vsync) {
1159 // timer
1160 enum doCheckTime = true;
1161 } else {
1162 // vsync
1163 __gshared bool prevLost = false;
1164 bool doCheckTime = vframeWasLost;
1165 if (vframeWasLost) {
1166 if (!prevLost) {
1167 { import core.stdc.stdio; printf("frame was lost!\n"); }
1169 prevLost = true;
1170 } else {
1171 prevLost = false;
1174 if (doCheckTime) {
1175 if (curtime < nextvframe) return false;
1176 version(dont_use_vsync) {
1177 if (curtime > nextvframe) {
1178 auto overtime = cast(int)((curtime-nextvframe).total!"msecs");
1179 if (overtime > 2500) {
1180 if (hushFrames) {
1181 --hushFrames;
1182 } else {
1183 { import core.stdc.stdio; printf(" spent whole %d msecs\n", overtime); }
1189 while (nextvframe <= curtime) nextvframe += dur!"msecs"(MinFrameTime);
1190 bool ctset = false;
1192 sdwindow.mtLock();
1193 scope(exit) sdwindow.mtUnlock();
1194 ctset = sdwindow.setAsCurrentOpenGlContextNT;
1196 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
1197 if (ctset) {
1198 // render scene
1199 iLiquidTime = cast(float)((curtime-MonoTime.zero).total!"msecs"%10000000)/18.0f*0.04f;
1200 receiveMessages(); // here, 'cause we need active OpenGL context for some messages
1201 if (levelLoaded) {
1202 renderScene(curtime);
1203 } else {
1204 //renderLoading(curtime);
1206 sdwindow.mtLock();
1207 scope(exit) sdwindow.mtUnlock();
1208 sdwindow.swapOpenGlBuffers();
1209 glFinish();
1210 sdwindow.releaseCurrentOpenGlContext();
1211 vframeWasLost = false;
1212 } else {
1213 vframeWasLost = true;
1214 { import core.stdc.stdio; printf("xframe was lost!\n"); }
1216 curtime = MonoTime.currTime;
1217 return true;
1220 for (;;) {
1221 if (sdwindow.closed) break;
1222 if (atomicLoad(diedie) > 0) break;
1224 curtime = MonoTime.currTime;
1225 auto fstime = curtime;
1227 doThinkFrame(); // this will fix curtime if necessary
1228 if (doVFrame()) {
1229 if (!vframeWasLost) {
1230 // fps
1231 auto frameTime = cast(float)(curtime-prevFrameStartTime).total!"msecs"/1000.0f;
1232 prevFrameStartTime = curtime;
1233 frtimes += frameTime;
1234 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
1235 import std.string : format;
1236 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
1237 if (newFPS != prevFPS) {
1238 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
1239 prevFPS = newFPS;
1241 framenum = 0;
1242 frtimes = 0.0f;
1247 curtime = MonoTime.currTime;
1249 // now sleep until next "video" or "think" frame
1250 if (nextthink > curtime && nextvframe > curtime) {
1251 // let's decide
1252 immutable nextVideoFrameSleep = cast(int)((nextvframe-curtime).total!"msecs");
1253 immutable nextThinkFrameSleep = cast(int)((nextthink-curtime).total!"msecs");
1254 immutable sleepTime = (nextVideoFrameSleep < nextThinkFrameSleep ? nextVideoFrameSleep : nextThinkFrameSleep);
1255 sleepAtMaxMsecs(sleepTime);
1256 //curtime = MonoTime.currTime;
1259 } catch (Throwable e) {
1260 // here, we are dead and fucked (the exact order doesn't matter)
1261 import core.stdc.stdlib : abort;
1262 import core.stdc.stdio : fprintf, stderr;
1263 import core.memory : GC;
1264 GC.disable(); // yeah
1265 thread_suspendAll(); // stop right here, you criminal scum!
1266 auto s = e.toString();
1267 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
1268 abort(); // die, you bitch!
1270 import core.stdc.stdio;
1271 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
1272 import std.stdio : stderr;
1273 writeln(
1274 for (;;) {
1275 if (sdwindow.closed) break;
1276 if (atomicLoad(diedie) > 0) break;
1277 sleepAtMaxMsecs(100);
1281 atomicStore(diedie, 2);
1285 // ////////////////////////////////////////////////////////////////////////// //
1286 __gshared Tid renderTid;
1287 shared bool renderThreadStarted = false;
1290 public void startRenderThread () {
1291 if (!cas(&renderThreadStarted, false, true)) {
1292 assert(0, "render thread already started!");
1294 renderTid = spawn(&renderThread, thisTid);
1295 setMaxMailboxSize(renderTid, 1024, OnCrowding.throwException); //FIXME
1296 // wait for "i'm ready" signal
1297 receive(
1298 (int ok) {
1299 if (ok != 42) assert(0, "wtf?!");
1302 conwriteln("rendering thread started");
1306 // ////////////////////////////////////////////////////////////////////////// //
1307 public void closeWindow () {
1308 if (atomicLoad(diedie) != 2) {
1309 atomicStore(diedie, 1);
1310 while (atomicLoad(diedie) != 2) {}
1312 if (!sdwindow.closed) {
1313 flushGui();
1314 sdwindow.close();
1319 // ////////////////////////////////////////////////////////////////////////// //
1320 // thread messages
1323 // ////////////////////////////////////////////////////////////////////////// //
1324 struct TMsgMouseEvent {
1325 MouseEventType type;
1326 int x, y;
1327 int dx, dy;
1328 MouseButton button; /// See $(LREF MouseButton)
1329 int modifierState; /// See $(LREF ModifierState)
1332 public void postMouseEvent() (in auto ref MouseEvent evt) {
1333 if (!atomicLoad(renderThreadStarted)) return;
1334 TMsgMouseEvent msg;
1335 msg.type = evt.type;
1336 msg.x = evt.x;
1337 msg.y = evt.y;
1338 msg.dx = evt.dx;
1339 msg.dy = evt.dy;
1340 msg.button = evt.button;
1341 msg.modifierState = evt.modifierState;
1342 send(renderTid, msg);
1346 // ////////////////////////////////////////////////////////////////////////// //
1347 struct TMsgKeyEvent {
1348 Key key;
1349 uint hardwareCode;
1350 bool pressed;
1351 dchar character;
1352 uint modifierState;
1355 public void postKeyEvent() (in auto ref KeyEvent evt) {
1356 if (!atomicLoad(renderThreadStarted)) return;
1357 TMsgKeyEvent msg;
1358 msg.key = evt.key;
1359 msg.pressed = evt.pressed;
1360 msg.character = evt.character;
1361 msg.modifierState = evt.modifierState;
1362 send(renderTid, msg);
1366 // ////////////////////////////////////////////////////////////////////////// //
1367 struct TMsgTestLightMove {
1368 int x, y;
1371 public void postTestLightMove (int x, int y) {
1372 if (!atomicLoad(renderThreadStarted)) return;
1373 auto msg = TMsgTestLightMove(x, y);
1374 send(renderTid, msg);
1378 // ////////////////////////////////////////////////////////////////////////// //
1379 struct TMsgMessage {
1380 char[256] text;
1381 uint textlen;
1382 int pauseMsecs;
1383 bool noreplace;
1386 public void postAddMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
1387 if (!atomicLoad(renderThreadStarted)) return;
1388 if (msgtext.length > TMsgMessage.text.length) msgtext = msgtext[0..TMsgMessage.text.length];
1389 TMsgMessage msg;
1390 msg.textlen = cast(uint)msgtext.length;
1391 if (msg.textlen) msg.text[0..msg.textlen] = msgtext[0..msg.textlen];
1392 msg.pauseMsecs = pauseMsecs;
1393 msg.noreplace = noreplace;
1394 send(renderTid, msg);
1398 // ////////////////////////////////////////////////////////////////////////// //
1399 struct TMsgLoadLevel {
1400 char[1024] mapfile;
1401 uint textlen;
1404 public void postLoadLevel (const(char)[] mapfile) {
1405 if (!atomicLoad(renderThreadStarted)) return;
1406 if (mapfile.length > TMsgLoadLevel.mapfile.length) mapfile = mapfile[0..TMsgLoadLevel.mapfile.length];
1407 TMsgLoadLevel msg;
1408 msg.textlen = cast(uint)mapfile.length;
1409 if (msg.textlen) msg.mapfile[0..msg.textlen] = mapfile[0..msg.textlen];
1410 send(renderTid, msg);
1414 // ////////////////////////////////////////////////////////////////////////// //
1415 struct TMsgSkipLevel {
1418 public void postSkipLevel () {
1419 if (!atomicLoad(renderThreadStarted)) return;
1420 TMsgSkipLevel msg;
1421 send(renderTid, msg);
1425 // ////////////////////////////////////////////////////////////////////////// //
1426 struct TMsgIntOption {
1427 enum Type {
1428 Scale,
1430 Type type;
1431 int value;
1432 bool showMessage;
1436 struct TMsgBoolOption {
1437 enum Type {
1438 Interpolation,
1439 Lighting,
1440 Pause,
1441 EditMode,
1442 CheatNoDoors,
1443 CheatNoWallClip,
1445 Type type;
1446 bool value;
1447 bool toggle;
1448 bool showMessage;
1452 bool strCaseEqu (const(char)[] s0, const(char)[] s1) {
1453 if (s0.length != s1.length) return false;
1454 foreach (immutable idx, char ch; s0) {
1455 if (ch >= 'A' && ch <= 'Z') ch += 32;
1456 char c1 = s1.ptr[idx];
1457 if (c1 >= 'A' && c1 <= 'Z') c1 += 32;
1458 if (ch != c1) return false;
1460 return true;
1464 public bool postToggleOption (const(char)[] name, bool showMessage=false) { /*pragma(inline, true);*/ return postSetOption(name, true, showMessage:showMessage, toggle:true); }
1466 public bool postSetOption(T) (const(char)[] name, T value, bool showMessage=false, bool toggle=false) if ((is(T == int) || is(T == bool))) {
1467 if (!atomicLoad(renderThreadStarted)) return false;
1468 if (name.length == 0 || name.length > 127) return false;
1469 static if (is(T == int)) {
1470 TMsgIntOption msg;
1471 foreach (string oname; __traits(allMembers, TMsgIntOption.Type)) {
1472 if (strCaseEqu(oname, name)) {
1473 msg.type = __traits(getMember, TMsgIntOption.Type, oname);
1474 msg.value = value;
1475 msg.showMessage = showMessage;
1476 send(renderTid, msg);
1477 return true;
1480 } else static if (is(T == bool)) {
1481 TMsgBoolOption msg;
1482 foreach (string oname; __traits(allMembers, TMsgBoolOption.Type)) {
1483 if (strCaseEqu(oname, name)) {
1484 msg.type = __traits(getMember, TMsgBoolOption.Type, oname);
1485 msg.value = value;
1486 msg.toggle = toggle;
1487 msg.showMessage = showMessage;
1488 send(renderTid, msg);
1489 return true;
1492 } else {
1493 static assert(0, "invalid option type '"~T.stringof~"'");
1495 return false;