added simple command console implementation
[dd2d.git] / render.d
blob742724eb841d505c6388b94dda2aab73eab02d3c
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 __gshared char[32768] concmdbuf;
87 __gshared uint concmdbufpos;
88 private import core.sync.mutex : Mutex;
89 __gshared Mutex concmdbufLock;
90 shared static this () { concmdbufLock = new Mutex(); }
93 void concmdAdd (const(char)[] s) {
94 for (;;) {
95 auto cmd = conGetCommand(s);
96 if (cmd is null) break;
97 if (concmdbuf.length-concmdbufpos < cmd.length+1) return; // alas
98 concmdbuf[concmdbufpos..concmdbufpos+cmd.length] = cmd[];
99 concmdbufpos += cmd.length;
100 concmdbuf.ptr[concmdbufpos++] = '\x00';
104 // `null`: no more
105 const(char)[] concmdGet (ref uint pos) {
106 if (pos >= concmdbufpos) return null;
107 auto st = pos;
108 while (pos < concmdbufpos && concmdbuf.ptr[pos]) ++pos;
109 auto res = concmdbuf[st..pos];
110 ++pos;
111 return res;
115 // ////////////////////////////////////////////////////////////////////////// //
116 shared static this () {
117 conRegVar!doLighting("r_lighting", "dynamic lighting");
118 conRegVar!gamePaused("g_pause", "pause game");
119 conRegVar!frameInterpolation("r_interpolation", "interpolate camera and sprite movement");
120 conRegVar!scale(1, 2, "r_scale");
121 conRegFunc!({
122 cheatNoDoors = !cheatNoDoors;
123 if (cheatNoDoors) conwriteln("player ignores doors"); else conwriteln("player respects doors");
124 })("nodoorclip", "ignore doors");
125 conRegFunc!({
126 cheatNoWallClip = !cheatNoWallClip;
127 if (cheatNoWallClip) conwriteln("player ignores walls"); else conwriteln("player respects walls");
128 })("nowallclip", "ignore walls");
132 // ////////////////////////////////////////////////////////////////////////// //
133 // interpolation
134 __gshared ubyte[] prevFrameActorsData;
135 __gshared uint[65536] prevFrameActorOfs; // uint.max-1: dead; uint.max: last
136 __gshared MonoTime lastthink = MonoTime.zero; // for interpolator
137 __gshared MonoTime nextthink = MonoTime.zero;
138 __gshared bool frameInterpolation = true;
141 // ////////////////////////////////////////////////////////////////////////// //
142 // attached lights
143 struct AttachedLightInfo {
144 enum Type {
145 Point,
146 Ambient,
148 Type type;
149 int x, y;
150 int w, h; // for ambient lights
151 float r, g, b;
152 bool uncolored;
153 int radius;
156 __gshared AttachedLightInfo[65536] attachedLights;
157 __gshared uint attachedLightCount = 0;
160 // ////////////////////////////////////////////////////////////////////////// //
161 enum MaxLightRadius = 255;
164 // ////////////////////////////////////////////////////////////////////////// //
165 // for light
166 __gshared FBO[MaxLightRadius+1] fboDistMap;
167 __gshared FBO fboOccluders;
168 __gshared Shader shadToPolar, shadBlur, shadBlurOcc, shadAmbient;
169 __gshared TrueColorImage editorImg;
170 __gshared FBO fboEditor;
172 __gshared FBO fboLevel, fboLevelLight, fboOrigBack, fboLMaskSmall;
173 __gshared Shader shadScanlines;
174 __gshared Shader shadLiquidDistort;
177 // ////////////////////////////////////////////////////////////////////////// //
178 // call once!
179 public void initOpenGL () {
180 gloStackClear();
182 glEnable(GL_TEXTURE_2D);
183 glDisable(GL_LIGHTING);
184 glDisable(GL_DITHER);
185 glDisable(GL_BLEND);
186 glDisable(GL_DEPTH_TEST);
188 // create shaders
189 shadScanlines = new Shader("scanlines", loadTextFile("shaders/srscanlines.frag"));
191 shadLiquidDistort = new Shader("liquid_distort", loadTextFile("shaders/srliquid_distort.frag"));
192 shadLiquidDistort.exec((Shader shad) {
193 shad["texLqMap"] = 0;
196 // lights
197 shadToPolar = new Shader("light_trace", loadTextFile("shaders/srlight_trace.frag"));
198 shadToPolar.exec((Shader shad) {
199 shad["texOcc"] = 0;
200 shad["texOccFull"] = 2;
201 shad["texOccSmall"] = 3;
204 shadBlur = new Shader("light_blur", loadTextFile("shaders/srlight_blur.frag"));
205 shadBlur.exec((Shader shad) {
206 shad["texDist"] = 0;
207 shad["texBg"] = 1;
208 shad["texOcc"] = 2;
209 shad["texOccSmall"] = 3;
212 shadBlurOcc = new Shader("light_blur_occ", loadTextFile("shaders/srlight_blur_occ.frag"));
213 shadBlurOcc.exec((Shader shad) {
214 shad["texLMap"] = 0;
215 shad["texBg"] = 1;
216 shad["texOcc"] = 2;
217 shad["texOccSmall"] = 3;
220 shadAmbient = new Shader("light_ambient", loadTextFile("shaders/srlight_ambient.frag"));
221 shadAmbient.exec((Shader shad) {
222 shad["texBg"] = 1;
223 shad["texOcc"] = 2;
224 shad["texOccSmall"] = 3;
227 fboOccluders = new FBO(MaxLightRadius*2, MaxLightRadius*2, Texture.Option.Clamp, Texture.Option.Linear);
228 //TODO: this sux!
229 foreach (int sz; 2..MaxLightRadius+1) {
230 fboDistMap[sz] = new FBO(sz*2, 1, Texture.Option.Clamp, Texture.Option.Linear); // create 1d distance map FBO
233 editorImg = new TrueColorImage(vlWidth, vlHeight);
234 editorImg.imageData.colors[] = Color(0, 0, 0, 0);
235 fboEditor = new FBO(vlWidth, vlHeight, Texture.Option.Nearest);
237 // setup matrices
238 glMatrixMode(GL_MODELVIEW);
239 glLoadIdentity();
241 loadSmFont();
242 loadBfFont();
243 loadAllMonsterGraphics();
247 // ////////////////////////////////////////////////////////////////////////// //
248 // should be called when OpenGL is initialized
249 void loadMap (string mapname) {
250 mapscripts.runUnloading(); // "map unloading" script
251 clearMapScripts();
253 if (map !is null) map.clear();
254 map = new LevelMap(mapname);
255 curmapname = mapname;
257 ugInit(map.width*TileSize, map.height*TileSize);
259 map.oglBuildMega();
260 mapTilesChanged = 0;
262 if (fboLevel !is null) fboLevel.clear();
263 if (fboLevelLight !is null) fboLevelLight.clear();
264 if (fboOrigBack !is null) fboOrigBack.clear();
265 if (fboLMaskSmall !is null) fboLMaskSmall.clear();
267 fboLevel = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // final level render will be here
268 fboLevelLight = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // level lights will be rendered here
269 fboOrigBack = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest/*, Texture.Option.Depth*/); // background+foreground
270 fboLMaskSmall = new FBO(map.width, map.height, Texture.Option.Nearest); // small lightmask
272 shadToPolar.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize-1, map.height*TileSize-1); });
273 shadBlur.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
274 shadBlurOcc.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
275 shadAmbient.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
277 glActiveTexture(GL_TEXTURE0+0);
278 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
279 orthoCamera(vlWidth, vlHeight);
281 Actor.resetStorage();
283 setupMapScripts();
284 mapscripts.runInit();
285 loadMapMonsters();
286 dotInit();
287 mapscripts.runLoaded();
289 // save first snapshot
290 if (prevFrameActorsData.length == 0) prevFrameActorsData = new ubyte[](Actor.actorSize*65536); // ~15-20 megabytes
291 prevFrameActorOfs[] = uint.max; // just for fun
292 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
294 levelLoaded = true;
296 { import core.memory : GC; GC.collect(); }
300 // ////////////////////////////////////////////////////////////////////////// //
301 //FIXME: optimize!
302 __gshared uint mapTilesChanged = 0;
305 //WARNING! this can be called only from DACS, so we don't have to sync it!
306 public void mapDirty (uint layermask) { mapTilesChanged |= layermask; }
309 void rebuildMapMegaTextures () {
310 //fbo.replaceTexture
311 //mapTilesChanged = false;
312 //map.clearMegaTextures();
313 map.oglBuildMega(mapTilesChanged);
314 mapTilesChanged = 0;
315 dotsAwake(); // let dormant dots fall
319 // ////////////////////////////////////////////////////////////////////////// //
320 // messages
321 struct Message {
322 enum Phase { FadeIn, Stay, FadeOut }
323 Phase phase;
324 int alpha;
325 int pauseMsecs;
326 MonoTime removeTime;
327 char[256] text;
328 usize textlen;
331 //private import core.sync.mutex : Mutex;
333 __gshared Message[128] messages;
334 __gshared uint messagesUsed = 0;
336 //__gshared Mutex messageLock;
337 //shared static this () { messageLock = new Mutex(); }
340 void addMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
341 //messageLock.lock();
342 //scope(exit) messageLock.unlock();
343 if (msgtext.length == 0) return;
344 conwriteln(msgtext);
345 if (pauseMsecs <= 50) return;
346 if (messagesUsed == messages.length) {
347 // remove top message
348 foreach (immutable cidx; 1..messagesUsed) messages.ptr[cidx-1] = messages.ptr[cidx];
349 messages.ptr[0].alpha = 255;
350 --messagesUsed;
352 // quick replace
353 if (!noreplace && messagesUsed == 1) {
354 switch (messages.ptr[0].phase) {
355 case Message.Phase.FadeIn:
356 messages.ptr[0].phase = Message.Phase.FadeOut;
357 break;
358 case Message.Phase.Stay:
359 messages.ptr[0].phase = Message.Phase.FadeOut;
360 messages.ptr[0].alpha = 255;
361 break;
362 default:
365 auto msg = messages.ptr+messagesUsed;
366 ++messagesUsed;
367 msg.phase = Message.Phase.FadeIn;
368 msg.alpha = 0;
369 msg.pauseMsecs = pauseMsecs;
370 // copy text
371 if (msgtext.length > msg.text.length) {
372 msg.text = msgtext[0..msg.text.length];
373 msg.textlen = msg.text.length;
374 } else {
375 msg.text[0..msgtext.length] = msgtext[];
376 msg.textlen = msgtext.length;
381 void doMessages (MonoTime curtime) {
382 //messageLock.lock();
383 //scope(exit) messageLock.unlock();
385 if (messagesUsed == 0) return;
386 glEnable(GL_BLEND);
387 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
389 Message* msg;
391 again:
392 msg = messages.ptr;
393 final switch (msg.phase) {
394 case Message.Phase.FadeIn:
395 if ((msg.alpha += 10) >= 255) {
396 msg.phase = Message.Phase.Stay;
397 msg.removeTime = curtime+dur!"msecs"(msg.pauseMsecs);
398 goto case; // to stay
400 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
401 break;
402 case Message.Phase.Stay:
403 if (msg.removeTime <= curtime) {
404 msg.alpha = 255;
405 msg.phase = Message.Phase.FadeOut;
406 goto case; // to fade
408 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
409 break;
410 case Message.Phase.FadeOut:
411 if ((msg.alpha -= 10) <= 0) {
412 if (--messagesUsed == 0) return;
413 // remove this message
414 foreach (immutable cidx; 1..messagesUsed+1) messages.ptr[cidx-1] = messages.ptr[cidx];
415 goto again;
417 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
418 break;
421 smDrawText(10, 10, msg.text[0..msg.textlen]);
425 // ////////////////////////////////////////////////////////////////////////// //
426 //mixin(Actor.FieldPropMixin!("0drawlistpos", uint));
428 mixin(Actor.FieldGetMixin!("classtype", StrId)); // fget_classtype
429 mixin(Actor.FieldGetMixin!("classname", StrId)); // fget_classname
430 mixin(Actor.FieldGetMixin!("x", int));
431 mixin(Actor.FieldGetMixin!("y", int));
432 mixin(Actor.FieldGetMixin!("s", int));
433 mixin(Actor.FieldGetMixin!("radius", int));
434 mixin(Actor.FieldGetMixin!("height", int));
435 mixin(Actor.FieldGetMixin!("flags", uint));
436 mixin(Actor.FieldGetMixin!("zAnimstate", StrId));
437 mixin(Actor.FieldGetMixin!("zAnimidx", int));
438 mixin(Actor.FieldGetMixin!("dir", uint));
439 mixin(Actor.FieldGetMixin!("attLightXOfs", int));
440 mixin(Actor.FieldGetMixin!("attLightYOfs", int));
441 mixin(Actor.FieldGetMixin!("attLightRGBX", uint));
443 //mixin(Actor.FieldGetPtrMixin!("classtype", StrId)); // fget_classtype
444 //mixin(Actor.FieldGetPtrMixin!("classname", StrId)); // fget_classname
445 mixin(Actor.FieldGetPtrMixin!("x", int));
446 mixin(Actor.FieldGetPtrMixin!("y", int));
447 //mixin(Actor.FieldGetPtrMixin!("flags", uint));
448 //mixin(Actor.FieldGetPtrMixin!("zAnimstate", StrId));
449 //mixin(Actor.FieldGetPtrMixin!("zAnimidx", int));
450 //mixin(Actor.FieldGetPtrMixin!("dir", uint));
451 //mixin(Actor.FieldGetPtrMixin!("attLightXOfs", int));
452 //mixin(Actor.FieldGetPtrMixin!("attLightYOfs", int));
453 //mixin(Actor.FieldGetPtrMixin!("attLightRGBX", uint));
456 // ////////////////////////////////////////////////////////////////////////// //
457 __gshared int vportX0, vportY0, vportX1, vportY1;
460 // ////////////////////////////////////////////////////////////////////////// //
461 void renderLightAmbient() (int lightX, int lightY, int lightW, int lightH, in auto ref SVec4F lcol) {
462 //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));
463 if (lightW < 1 || lightH < 1) return;
464 int lightX1 = lightX+lightW-1;
465 int lightY1 = lightY+lightH-1;
466 // clip light to viewport
467 if (lightX < vportX0) lightX = vportX0;
468 if (lightY < vportY0) lightY = vportY0;
469 if (lightX1 > vportX1) lightX1 = vportX1;
470 if (lightY1 > vportY1) lightY1 = vportY1;
471 // is this light visible?
472 //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));
473 if (lightX1 < lightX || lightY1 < lightY || lightX > vportX1 || lightY > vportY1 || lightX1 < vportX0 || lightY1 < vportY0) return;
474 //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));
476 fboLevelLight.exec({
477 glEnable(GL_BLEND);
478 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
479 //glDisable(GL_BLEND);
480 orthoCamera(map.width*TileSize, map.height*TileSize);
481 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
482 shadAmbient.exec((Shader shad) {
483 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
484 //shad["lightPos"] = SVec2F(lightX, lightY);
485 glRectf(lightX, lightY, lightX1, lightY1);
491 // ////////////////////////////////////////////////////////////////////////// //
492 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
493 if (lightRadius < 2) return;
494 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
495 int lightSize = lightRadius*2;
496 // is this light visible?
497 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*TileSize || lightY-lightRadius >= map.height*TileSize) return;
499 // out of viewport -- do nothing
500 if (lightX+lightRadius < vportX0 || lightY+lightRadius < vportY0) return;
501 if (lightX-lightRadius > vportX1 || lightY-lightRadius > vportY1) return;
503 if (lightX >= 0 && lightY >= 0 && lightX < map.width*TileSize && lightY < map.height*TileSize &&
504 map.teximgs[map.LightMask].imageData.colors.ptr[lightY*(map.width*TileSize)+lightX].a > 190) return;
506 // common color for all the following
507 glDisable(GL_BLEND);
508 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
510 // build 1d distance map to fboShadowMapId
511 fboDistMap.ptr[lightRadius].exec({
512 // no need to clear it, shader will take care of that
513 shadToPolar.exec((Shader shad) {
514 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
515 shad["lightPos"] = SVec2F(lightX, lightY);
516 orthoCamera(lightSize, 1);
517 // it doesn't matter what we will draw here, so just draw filled rect
518 glRectf(0, 0, lightSize, 1);
522 // build light texture for blending
523 fboOccluders.exec({
524 // no need to clear it, shader will take care of that
525 // debug
526 //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
527 //glClear(GL_COLOR_BUFFER_BIT);
528 shadBlur.exec((Shader shad) {
529 shad["lightTexSize"] = SVec2F(lightSize, MaxLightRadius*2); // x: size of distmap; y: size of this texture
530 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
531 shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
532 orthoCamera(fboOccluders.tex.width, fboOccluders.tex.height);
533 //drawAtXY(fboDistMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
534 bindTexture(fboDistMap.ptr[lightRadius].tex.tid);
535 glBegin(GL_QUADS);
536 glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0); // top-left
537 glTexCoord2f(1.0f, 0.0f); glVertex2i(lightSize, 0); // top-right
538 glTexCoord2f(1.0f, 1.0f); glVertex2i(lightSize, lightSize); // bottom-right
539 glTexCoord2f(0.0f, 1.0f); glVertex2i(0, lightSize); // bottom-left
540 glEnd();
544 // blend light texture
545 fboLevelLight.exec({
546 glEnable(GL_BLEND);
547 //glDisable(GL_BLEND);
548 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
549 orthoCamera(fboLevelLight.tex.width, fboLevelLight.tex.height);
550 //drawAtXY(fboOccluders.tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
551 float occe = 1.0f*lightSize/(MaxLightRadius*2);
552 float occs = 1.0f-occe;
553 int x0 = lightX-lightRadius+0;
554 int y1 = lightY-lightRadius+0;
555 int x1 = lightX+lightRadius-1+1;
556 int y0 = lightY+lightRadius-1+1;
557 bindTexture(fboOccluders.tex.tid);
558 glBegin(GL_QUADS);
560 glTexCoord2f(0.0f, 0.0f); glVertex2i(x0, y0); // top-left
561 glTexCoord2f(occe, 0.0f); glVertex2i(x1, y0); // top-right
562 glTexCoord2f(occe, occe); glVertex2i(x1, y1); // bottom-right
563 glTexCoord2f(0.0f, occe); glVertex2i(x0, y1); // bottom-left
565 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
566 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
567 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
568 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
569 glEnd();
571 bindTexture(0);
572 glRectf(x0, y0, x1, y1);
574 // and blend it again, with the shader that will touch only occluders
575 shadBlurOcc.exec((Shader shad) {
576 //shad["lightTexSize"] = SVec2F(lightSize, lightSize);
577 shad["lightTexSize"] = SVec2F(lightSize, fboOccluders.tex.height);
578 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
579 shad["lightPos"] = SVec2F(lightX, lightY);
580 //shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
581 bindTexture(fboOccluders.tex.tid);
582 glBegin(GL_QUADS);
583 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
584 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
585 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
586 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
587 glEnd();
588 //drawAtXY(fboOccluders.tex.tid, lightX-lightRadius, lightY-lightRadius, lightSize, lightSize, mirrorY:true);
594 // ////////////////////////////////////////////////////////////////////////// //
595 __gshared int testLightX = vlWidth/2, testLightY = vlHeight/2;
596 __gshared bool testLightMoved = false;
597 //__gshared int mapOfsX, mapOfsY;
598 //__gshared bool movement = false;
599 __gshared float iLiquidTime = 0.0;
600 //__gshared bool altMove = false;
603 void renderScene (MonoTime curtime) {
604 //enum BackIntens = 0.05f;
605 enum BackIntens = 0.0f;
607 gloStackClear();
608 float atob = (curtime > lastthink ? cast(float)((curtime-lastthink).total!"msecs")/cast(float)((nextthink-lastthink).total!"msecs") : 1.0f);
609 if (gamePaused || inEditMode) atob = 1.0f;
610 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
613 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
614 int curfp = cast(int)((curtime-lastthink).total!"msecs");
615 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
619 int mofsx, mofsy; // camera offset, will be set in background layer builder
621 if (mapTilesChanged != 0) rebuildMapMegaTextures();
623 // build background layer
624 fboOrigBack.exec({
625 //glDisable(GL_BLEND);
626 //glClearDepth(1.0f);
627 //glDepthFunc(GL_LESS);
628 //glDepthFunc(GL_NEVER);
629 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
630 glClear(GL_COLOR_BUFFER_BIT/*|GL_DEPTH_BUFFER_BIT*/);
631 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
632 orthoCamera(map.width*TileSize, map.height*TileSize);
634 glEnable(GL_BLEND);
635 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
636 // draw sky
638 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
639 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
640 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
641 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
643 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*TileSize, map.MapSize*TileSize);
644 // draw background
645 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
646 // draw distorted liquid areas
647 shadLiquidDistort.exec((Shader shad) {
648 shad["iDistortTime"] = iLiquidTime;
649 drawAtXY(map.texgl.ptr[map.AllLiquids], 0, 0);
651 // monsters, items; we'll do linear interpolation here
652 glColor3f(1.0f, 1.0f, 1.0f);
653 //glEnable(GL_DEPTH_TEST);
654 attachedLightCount = 0;
656 // who cares about memory?!
657 // draw order: players, items, monsters, other
658 static struct DrawInfo {
659 ActorDef adef;
660 ActorId aid;
661 int actorX, actorY;
662 @disable this (this); // no copies
664 enum { Pixels, Players, Items, Monsters, Other }
665 __gshared DrawInfo[65536][4] drawlists;
666 __gshared uint[4] dlpos;
667 DrawInfo camchickdi;
669 dlpos[] = 0;
671 Actor.forEach((ActorId me) {
672 //me.fprop_0drawlistpos = 0;
673 if (auto adef = findActorDef(me)) {
674 uint dlnum = Other;
675 switch (adef.classtype.get) {
676 case "monster": dlnum = (adef.classname.get != "Player" ? Monsters : Players); break;
677 case "item": dlnum = Items; break;
678 default: dlnum = Other; break;
680 if (me.fget_flags&AF_PIXEL) dlnum = Pixels;
681 int actorX, actorY; // current actor position
683 auto ofs = prevFrameActorOfs.ptr[me.id&0xffff];
684 if (frameInterpolation && ofs < uint.max-1 && (me.fget_flags&AF_TELEPORT) == 0 && Actor.isSameSavedActor(me.id, prevFrameActorsData.ptr, ofs)) {
685 import core.stdc.math : roundf;
686 auto xptr = prevFrameActorsData.ptr+ofs;
687 int ox = xptr.fgetp_x;
688 int nx = me.fget_x;
689 int oy = xptr.fgetp_y;
690 int ny = me.fget_y;
691 actorX = cast(int)(ox+roundf((nx-ox)*atob));
692 actorY = cast(int)(oy+roundf((ny-oy)*atob));
693 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
694 } else {
695 actorX = me.fget_x;
696 actorY = me.fget_y;
699 if (me.id == cameraChick.id) {
700 camchickdi.adef = adef;
701 camchickdi.aid = me;
702 camchickdi.actorX = actorX;
703 camchickdi.actorY = actorY;
705 // draw sprite
706 if ((me.fget_flags&AF_NODRAW) == 0) {
707 //auto dl = &drawlists[dlnum][dlpos.ptr[dlnum]];
708 //me.fprop_0drawlistpos = (dlpos.ptr[dlnum]&0xffff)|((dlnum&0xff)<<16);
709 auto dl = drawlists.ptr[dlnum].ptr+dlpos.ptr[dlnum];
710 ++dlpos.ptr[dlnum];
711 dl.adef = adef;
712 dl.aid = me;
713 dl.actorX = actorX;
714 dl.actorY = actorY;
716 // process attached lights
717 if ((me.fget_flags&AF_NOLIGHT) == 0) {
718 uint alr = me.fget_attLightRGBX;
719 bool isambient = (me.fget_classtype.get == "light" && me.fget_classname.get == "Ambient");
720 if ((alr&0xff) >= 4 || (isambient && me.fget_radius >= 1 && me.fget_height >= 1)) {
721 //if (isambient) conwriteln("isambient: ", isambient, "; x=", me.fget_x, "; y=", me.fget_y, "; w=", me.fget_radius, "; h=", me.fget_height);
722 // yep, add it
723 auto li = attachedLights.ptr+attachedLightCount;
724 ++attachedLightCount;
725 li.type = (!isambient ? AttachedLightInfo.Type.Point : AttachedLightInfo.Type.Ambient);
726 li.x = actorX+me.fget_attLightXOfs;
727 li.y = actorY+me.fget_attLightYOfs;
728 li.r = ((alr>>24)&0xff)/255.0f; // red or intensity
729 if ((alr&0x00_ff_ff_00U) == 0x00_00_01_00U) {
730 li.uncolored = true;
731 } else {
732 li.g = ((alr>>16)&0xff)/255.0f;
733 li.b = ((alr>>8)&0xff)/255.0f;
734 li.uncolored = false;
736 li.radius = (alr&0xff);
737 if (li.radius > MaxLightRadius) li.radius = MaxLightRadius;
738 if (isambient) {
739 li.w = me.fget_radius;
740 li.h = me.fget_height;
744 } else {
745 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
749 // draw actor lists
750 foreach_reverse (uint dlnum; 0..drawlists.length) {
751 if (dlnum == Pixels) continue;
752 auto dl = drawlists.ptr[dlnum].ptr;
753 if (dlnum == Players) dl += dlpos.ptr[dlnum]-1;
754 foreach (uint idx; 0..dlpos.ptr[dlnum]) {
755 auto me = dl.aid;
756 if (auto isp = dl.adef.animSpr(me.fget_zAnimstate, me.fget_dir, me.fget_zAnimidx)) {
757 //drawAtXY(isp.tex, dl.actorX-isp.vga.sx, dl.actorY-isp.vga.sy);
758 isp.drawAtXY(dl.actorX, dl.actorY);
760 if (dlnum != Players) ++dl; else --dl;
763 // draw pixels
764 if (dlpos[Pixels]) {
765 bindTexture(0);
766 bool pointsStarted = false;
767 Color lastColor = Color(0, 0, 0, 0);
768 auto dl = drawlists.ptr[Pixels].ptr;
769 foreach (uint idx; 0..dlpos.ptr[Pixels]) {
770 auto me = dl.aid;
771 auto s = me.fget_s;
772 if (s < 0 || s > 255) continue; //FIXME
773 Color clr = d2dpal.ptr[s&0xff];
774 if (clr.a == 0) continue;
775 if (clr != lastColor) {
776 if (pointsStarted) glEnd();
777 glColor4f(clr.r/255.0f, clr.g/255.0f, clr.b/255.0f, clr.a/255.0f);
778 lastColor = clr;
779 pointsStarted = false;
781 if (!pointsStarted) {
782 glBegin(GL_POINTS);
783 pointsStarted = true;
785 glVertex2i(dl.actorX, dl.actorY);
786 ++dl;
788 if (pointsStarted) {
789 glEnd();
790 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
794 // camera movement
795 if (/*altMove || movement ||*/ scale == 1 || !cameraChick.valid) {
796 mofsx = 0;
797 mofsy = 0;
798 vportX0 = 0;
799 vportY0 = 0;
800 vportX1 = map.width*TileSize;
801 vportY1 = map.height*TileSize;
802 } else {
803 int tiltHeight = /*getMapViewHeight()*/(vlHeight/scale)/4;
804 int vy = cameraChick.looky!int;
805 if (vy < -tiltHeight) vy = -tiltHeight; else if (vy > tiltHeight) vy = tiltHeight;
806 int swdt = vlWidth/scale;
807 int shgt = vlHeight/scale;
808 int x = camchickdi.actorX-swdt/2;
809 int y = (camchickdi.actorY+vy)-shgt/2;
810 if (x < 0) x = 0; else if (x >= map.width*TileSize-swdt) x = map.width*TileSize-swdt-1;
811 if (y < 0) y = 0; else if (y >= map.height*TileSize-shgt) y = map.height*TileSize-shgt-1;
812 mofsx = x*2;
813 mofsy = y*2;
814 vportX0 = mofsx/scale;
815 vportY0 = mofsy/scale;
816 vportX1 = vportX0+vlWidth/scale;
817 vportY1 = vportY0+vlHeight/scale;
820 //glDisable(GL_DEPTH_TEST);
821 // draw dots
822 dotDraw(atob);
823 // do liquid coloring
824 drawAtXY(map.texgl.ptr[map.LiquidMask], 0, 0);
825 // foreground -- hide secrets, draw lifts and such
826 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
829 enum r = 255;
830 enum g = 0;
831 enum b = 0;
832 enum a = 255;
833 bindTexture(0);
834 glColor4f(r/255.0f, g/255.0f, b/255.0f, a/255.0f);
835 glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
836 if (cameraChick.valid) {
837 glBegin(GL_POINTS);
838 glVertex2i(camchickdi.actorX, camchickdi.actorY-70);
839 glEnd();
840 //glRectf(camchickdi.actorX, camchickdi.actorY-70, camchickdi.actorX+4, camchickdi.actorY-70+4);
842 //glRectf(0, 0, 300, 300);
843 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
849 if (doLighting) {
850 glDisable(GL_BLEND);
851 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
853 // make smaller occluder texture, so we can trace faster
854 //assert(fboLMaskSmall.tex.width == map.width);
855 //assert(fboLMaskSmall.tex.height == map.height);
856 fboLMaskSmall.exec({
857 orthoCamera(map.width, map.height);
858 drawAtXY(map.texgl.ptr[map.LightMask].tid, 0, 0, map.width, map.height, mirrorY:true);
861 // clear light layer
862 fboLevelLight.exec({
863 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
864 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
865 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
866 glClear(GL_COLOR_BUFFER_BIT);
869 // texture 1 is background
870 glActiveTexture(GL_TEXTURE0+1);
871 glBindTexture(GL_TEXTURE_2D, fboOrigBack.tex.tid);
872 // texture 2 is occluders
873 glActiveTexture(GL_TEXTURE0+2);
874 glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.LightMask].tid);
875 // texture 3 is small occluder map
876 glActiveTexture(GL_TEXTURE0+3);
877 glBindTexture(GL_TEXTURE_2D, fboLMaskSmall.tex.tid);
878 // done texture assign
879 glActiveTexture(GL_TEXTURE0+0);
882 enum LYOfs = 1;
884 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
885 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
886 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
887 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
888 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
889 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
890 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
891 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
892 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
893 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
895 renderLight(24*TileSize+4, (24+18)*TileSize-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
897 renderLight(280, 330, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
900 foreach (; 0..1) {
901 // attached lights
902 foreach (ref li; attachedLights[0..attachedLightCount]) {
903 if (li.type == AttachedLightInfo.Type.Ambient) {
904 //conwriteln("ambient: x=", li.x, "; y=", li.y, "; w=", li.w, "; h=", li.h);
905 // ambient light
906 if (li.uncolored) {
907 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(0.0f, 0.0f, 0.0f, li.r));
908 } else {
909 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(li.r, li.g, li.b, 1.0f));
911 } else if (li.type == AttachedLightInfo.Type.Point) {
912 // point light
913 if (li.uncolored) {
914 renderLight(li.x, li.y, SVec4F(0.0f, 0.0f, 0.0f, li.r), li.radius);
915 } else {
916 renderLight(li.x, li.y, SVec4F(li.r, li.g, li.b, 1.0f), li.radius);
923 if (testLightMoved) {
924 testLightX = testLightX/scale+mofsx/scale;
925 testLightY = testLightY/scale+mofsy/scale;
926 testLightMoved = false;
928 foreach (immutable _; 0..1) {
929 renderLight(testLightX, testLightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
933 glActiveTexture(GL_TEXTURE0+1);
934 glBindTexture(GL_TEXTURE_2D, 0);
935 glActiveTexture(GL_TEXTURE0+2);
936 glBindTexture(GL_TEXTURE_2D, 0);
937 glActiveTexture(GL_TEXTURE0+3);
938 glBindTexture(GL_TEXTURE_2D, 0);
939 glActiveTexture(GL_TEXTURE0+0);
942 // draw scaled level
944 shadScanlines.exec((Shader shad) {
945 shad["scanlines"] = scanlines;
946 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
947 glClear(GL_COLOR_BUFFER_BIT);
948 orthoCamera(vlWidth, vlHeight);
949 //orthoCamera(map.width*TileSize*scale, map.height*TileSize*scale);
950 //glMatrixMode(GL_MODELVIEW);
951 //glLoadIdentity();
952 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
953 // somehow, FBO objects are mirrored; wtf?!
954 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
955 //glLoadIdentity();
960 fboLevelLight.exec({
961 smDrawText(map.width*TileSize/2, map.height*TileSize/2, "Testing...");
966 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
968 glDisable(GL_BLEND);
971 fboOrigBack.exec({
972 //auto img = smfont.ptr[0x39];
973 auto img = fftest;
974 if (img !is null) {
975 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
976 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
983 { // http://stackoverflow.com/questions/7207422/setting-up-opengl-multiple-render-targets
984 GLenum[1] buffers = [ GL_BACK_LEFT, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
985 //GLenum[1] buffers = [ GL_NONE, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
986 glDrawBuffers(1, buffers.ptr);
991 orthoCamera(vlWidth, vlHeight);
992 auto tex = (doLighting ? fboLevelLight.tex.tid : fboOrigBack.tex.tid);
993 drawAtXY(tex, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
995 if (levelLoaded) {
996 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
997 //orthoCamera(map.width*TileSize, map.height*TileSize);
998 glEnable(GL_BLEND);
999 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1000 hudScripts.runDraw();
1003 //drawAtXY(map.texgl.ptr[map.LightMask].tid, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1004 //drawAtXY(fboLMaskSmall.tex.tid, 0, 0, map.width*TileSize, map.height*TileSize);
1006 if (inEditMode) {
1007 glEnable(GL_BLEND);
1008 //glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1009 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1010 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1011 editorUpdateImage();
1012 fboEditor.tex.setFromImage(editorImg);
1013 drawAtXY(fboEditor.tex, 0, 0);
1014 glDisable(GL_BLEND);
1017 doMessages(curtime);
1021 // ////////////////////////////////////////////////////////////////////////// //
1022 // returns time slept
1023 int sleepAtMaxMsecs (int msecs) {
1024 if (msecs > 0) {
1025 import core.sys.posix.signal : timespec;
1026 import core.sys.posix.time : nanosleep;
1027 timespec ts = void, tpassed = void;
1028 ts.tv_sec = 0;
1029 ts.tv_nsec = msecs*1000*1000+(500*1000); // milli to nano
1030 nanosleep(&ts, &tpassed);
1031 return (ts.tv_nsec-tpassed.tv_nsec)/(1000*1000);
1032 } else {
1033 return 0;
1038 // ////////////////////////////////////////////////////////////////////////// //
1039 mixin(import("editor.d"));
1042 // ////////////////////////////////////////////////////////////////////////// //
1043 // rendering thread
1044 shared int diedie = 0;
1046 enum D2DFrameTime = 55; // milliseconds
1047 enum MinFrameTime = 1000/60; // ~60 FPS
1049 public void renderThread (Tid starterTid) {
1050 enum BoolOptVarMsgMixin(string varname) =
1051 "if (msg.toggle) msg.value = !"~varname~";\n"~
1052 "if ("~varname~" != msg.value) "~varname~" = msg.value; else msg.showMessage = false;\n";
1054 send(starterTid, 42);
1055 try {
1056 MonoTime curtime = MonoTime.currTime;
1058 lastthink = curtime; // for interpolator
1059 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1060 MonoTime nextvframe = curtime;
1062 enum MaxFPSFrames = 16;
1063 float frtimes = 0.0f;
1064 int framenum = 0;
1065 int prevFPS = -1;
1066 int hushFrames = 6; // ignore first `hushFrames` frames overtime
1067 MonoTime prevFrameStartTime = curtime;
1069 bool vframeWasLost = false;
1071 void resetFrameTimers () {
1072 MonoTime curtime = MonoTime.currTime;
1073 lastthink = curtime; // for interpolator
1074 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1075 nextvframe = curtime;
1078 void loadNewLevel (string name) {
1080 if (levelLoaded) {
1081 conwriteln("ERROR: can't load new levels yet");
1082 return;
1085 if (name.length == 0) {
1086 conwriteln("ERROR: can't load empty level!");
1088 conwriteln("loading map '", name, "'");
1089 loadMap(name);
1090 resetFrameTimers();
1093 void receiveMessages () {
1094 for (;;) {
1095 import core.time : Duration;
1096 //conwriteln("rendering thread: waiting for messages...");
1097 auto got = receiveTimeout(
1098 Duration.zero, // don't wait
1099 (TMsgMessage msg) {
1100 addMessage(msg.text[0..msg.textlen], msg.pauseMsecs, msg.noreplace);
1102 (TMsgLoadLevel msg) {
1103 nextmapname = null; // clear "exit" flag
1104 loadNewLevel(msg.mapfile[0..msg.textlen].idup);
1106 (TMsgSkipLevel msg) {
1107 string mn = genNextMapName(0);
1108 if (mn.length) {
1109 nextmapname = null; // clear "exit" flag
1110 loadNewLevel(mn);
1113 (TMsgIntOption msg) {
1114 final switch (msg.type) with (TMsgIntOption.Type) {
1115 case Scale:
1116 if (msg.value >= 1 && msg.value <= 2) scale = msg.value;
1117 break;
1120 (TMsgBoolOption msg) {
1121 final switch (msg.type) with (TMsgBoolOption.Type) {
1122 case Interpolation: mixin(BoolOptVarMsgMixin!"frameInterpolation"); break;
1123 case Lighting: mixin(BoolOptVarMsgMixin!"doLighting"); break;
1124 case Pause: mixin(BoolOptVarMsgMixin!"gamePaused"); break;
1125 case EditMode:
1126 if (msg.toggle) msg.value = !inEditMode;
1127 if (inEditMode != msg.value) {
1128 inEditMode = msg.value;
1129 if (msg.value) sdwindow.hideCursor(); else sdwindow.showCursor();
1130 } else {
1131 msg.showMessage = false;
1133 break;
1134 mixin(BoolOptVarMsgMixin!"inEditMode"); break;
1135 case CheatNoDoors: mixin(BoolOptVarMsgMixin!"cheatNoDoors"); break;
1136 case CheatNoWallClip: mixin(BoolOptVarMsgMixin!"cheatNoWallClip"); break;
1138 if (msg.showMessage) {
1139 char[128] mbuf;
1140 uint msgpos;
1141 void putStr(T) (T s) if (is(T : const(char)[])) {
1142 foreach (char ch; s) {
1143 if (msgpos >= mbuf.length) break;
1144 mbuf.ptr[msgpos++] = ch;
1147 putStr("Option \"");
1148 { import std.conv : to; putStr(to!string(msg.type)); }
1149 putStr("\": O");
1150 if (msg.value) putStr("N"); else putStr("FF");
1151 addMessage(mbuf[0..msgpos]);
1154 (TMsgTestLightMove msg) {
1155 testLightX = msg.x;
1156 testLightY = msg.y;
1157 testLightMoved = true;
1159 (TMsgMouseEvent msg) { editorMouseEvent(msg); },
1160 (TMsgKeyEvent msg) { editorKeyEvent(msg); },
1161 (Variant v) {
1162 conwriteln("WARNING: unknown thread message received and ignored");
1165 if (!got) {
1166 // no more messages
1167 //conwriteln("rendering thread: no more messages");
1168 break;
1171 if (nextmapname.length) {
1172 string mn = nextmapname;
1173 nextmapname = null; // clear "exit" flag
1174 loadNewLevel(mn);
1178 void processConsoleCommands () {
1179 concmdbufLock.lock();
1180 scope(exit) { concmdbufpos = 0; concmdbufLock.unlock(); }
1181 uint pos = 0;
1182 for (;;) {
1183 auto cmd = concmdGet(pos);
1184 if (cmd is null) break;
1185 conExec(cmd);
1189 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1190 bool doThinkFrame () {
1191 if (curtime >= nextthink) {
1192 lastthink = curtime;
1193 while (nextthink <= curtime) nextthink += dur!"msecs"(D2DFrameTime);
1194 if (levelLoaded) {
1195 // save snapshot and other data for interpolator
1196 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
1197 if (!gamePaused && !inEditMode) {
1198 // process actors
1199 doActorsThink();
1200 dotThink();
1203 // some timing
1204 auto tm = MonoTime.currTime;
1205 int thinkTime = cast(int)((tm-curtime).total!"msecs");
1206 if (thinkTime > 9) { import core.stdc.stdio; printf("spent on thinking: %d msecs\n", thinkTime); }
1207 curtime = tm;
1208 return true;
1209 } else {
1210 return false;
1214 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1215 bool doVFrame () {
1216 version(dont_use_vsync) {
1217 // timer
1218 enum doCheckTime = true;
1219 } else {
1220 // vsync
1221 __gshared bool prevLost = false;
1222 bool doCheckTime = vframeWasLost;
1223 if (vframeWasLost) {
1224 if (!prevLost) {
1225 { import core.stdc.stdio; printf("frame was lost!\n"); }
1227 prevLost = true;
1228 } else {
1229 prevLost = false;
1232 if (doCheckTime) {
1233 if (curtime < nextvframe) return false;
1234 version(dont_use_vsync) {
1235 if (curtime > nextvframe) {
1236 auto overtime = cast(int)((curtime-nextvframe).total!"msecs");
1237 if (overtime > 2500) {
1238 if (hushFrames) {
1239 --hushFrames;
1240 } else {
1241 { import core.stdc.stdio; printf(" spent whole %d msecs\n", overtime); }
1247 while (nextvframe <= curtime) nextvframe += dur!"msecs"(MinFrameTime);
1248 bool ctset = false;
1250 sdwindow.mtLock();
1251 scope(exit) sdwindow.mtUnlock();
1252 ctset = sdwindow.setAsCurrentOpenGlContextNT;
1254 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
1255 if (ctset) {
1256 // render scene
1257 iLiquidTime = cast(float)((curtime-MonoTime.zero).total!"msecs"%10000000)/18.0f*0.04f;
1258 receiveMessages(); // here, 'cause we need active OpenGL context for some messages
1259 processConsoleCommands();
1260 if (levelLoaded) {
1261 renderScene(curtime);
1262 } else {
1263 //renderLoading(curtime);
1265 sdwindow.mtLock();
1266 scope(exit) sdwindow.mtUnlock();
1267 sdwindow.swapOpenGlBuffers();
1268 glFinish();
1269 sdwindow.releaseCurrentOpenGlContext();
1270 vframeWasLost = false;
1271 } else {
1272 vframeWasLost = true;
1273 { import core.stdc.stdio; printf("xframe was lost!\n"); }
1275 curtime = MonoTime.currTime;
1276 return true;
1279 for (;;) {
1280 if (sdwindow.closed) break;
1281 if (atomicLoad(diedie) > 0) break;
1283 curtime = MonoTime.currTime;
1284 auto fstime = curtime;
1286 doThinkFrame(); // this will fix curtime if necessary
1287 if (doVFrame()) {
1288 if (!vframeWasLost) {
1289 // fps
1290 auto frameTime = cast(float)(curtime-prevFrameStartTime).total!"msecs"/1000.0f;
1291 prevFrameStartTime = curtime;
1292 frtimes += frameTime;
1293 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
1294 import std.string : format;
1295 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
1296 if (newFPS != prevFPS) {
1297 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
1298 prevFPS = newFPS;
1300 framenum = 0;
1301 frtimes = 0.0f;
1306 curtime = MonoTime.currTime;
1308 // now sleep until next "video" or "think" frame
1309 if (nextthink > curtime && nextvframe > curtime) {
1310 // let's decide
1311 immutable nextVideoFrameSleep = cast(int)((nextvframe-curtime).total!"msecs");
1312 immutable nextThinkFrameSleep = cast(int)((nextthink-curtime).total!"msecs");
1313 immutable sleepTime = (nextVideoFrameSleep < nextThinkFrameSleep ? nextVideoFrameSleep : nextThinkFrameSleep);
1314 sleepAtMaxMsecs(sleepTime);
1315 //curtime = MonoTime.currTime;
1318 } catch (Throwable e) {
1319 // here, we are dead and fucked (the exact order doesn't matter)
1320 import core.stdc.stdlib : abort;
1321 import core.stdc.stdio : fprintf, stderr;
1322 import core.memory : GC;
1323 GC.disable(); // yeah
1324 thread_suspendAll(); // stop right here, you criminal scum!
1325 auto s = e.toString();
1326 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
1327 abort(); // die, you bitch!
1329 import core.stdc.stdio;
1330 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
1331 import std.stdio : stderr;
1332 writeln(
1333 for (;;) {
1334 if (sdwindow.closed) break;
1335 if (atomicLoad(diedie) > 0) break;
1336 sleepAtMaxMsecs(100);
1340 atomicStore(diedie, 2);
1344 // ////////////////////////////////////////////////////////////////////////// //
1345 __gshared Tid renderTid;
1346 shared bool renderThreadStarted = false;
1349 public void startRenderThread () {
1350 if (!cas(&renderThreadStarted, false, true)) {
1351 assert(0, "render thread already started!");
1353 renderTid = spawn(&renderThread, thisTid);
1354 setMaxMailboxSize(renderTid, 1024, OnCrowding.throwException); //FIXME
1355 // wait for "i'm ready" signal
1356 receive(
1357 (int ok) {
1358 if (ok != 42) assert(0, "wtf?!");
1361 conwriteln("rendering thread started");
1365 // ////////////////////////////////////////////////////////////////////////// //
1366 public void closeWindow () {
1367 if (atomicLoad(diedie) != 2) {
1368 atomicStore(diedie, 1);
1369 while (atomicLoad(diedie) != 2) {}
1371 if (!sdwindow.closed) {
1372 flushGui();
1373 sdwindow.close();
1378 // ////////////////////////////////////////////////////////////////////////// //
1379 // thread messages
1382 // ////////////////////////////////////////////////////////////////////////// //
1383 struct TMsgMouseEvent {
1384 MouseEventType type;
1385 int x, y;
1386 int dx, dy;
1387 MouseButton button; /// See $(LREF MouseButton)
1388 int modifierState; /// See $(LREF ModifierState)
1391 public void postMouseEvent() (in auto ref MouseEvent evt) {
1392 if (!atomicLoad(renderThreadStarted)) return;
1393 TMsgMouseEvent msg;
1394 msg.type = evt.type;
1395 msg.x = evt.x;
1396 msg.y = evt.y;
1397 msg.dx = evt.dx;
1398 msg.dy = evt.dy;
1399 msg.button = evt.button;
1400 msg.modifierState = evt.modifierState;
1401 send(renderTid, msg);
1405 // ////////////////////////////////////////////////////////////////////////// //
1406 struct TMsgKeyEvent {
1407 Key key;
1408 uint hardwareCode;
1409 bool pressed;
1410 dchar character;
1411 uint modifierState;
1414 public void postKeyEvent() (in auto ref KeyEvent evt) {
1415 if (!atomicLoad(renderThreadStarted)) return;
1416 TMsgKeyEvent msg;
1417 msg.key = evt.key;
1418 msg.pressed = evt.pressed;
1419 msg.character = evt.character;
1420 msg.modifierState = evt.modifierState;
1421 send(renderTid, msg);
1425 // ////////////////////////////////////////////////////////////////////////// //
1426 struct TMsgTestLightMove {
1427 int x, y;
1430 public void postTestLightMove (int x, int y) {
1431 if (!atomicLoad(renderThreadStarted)) return;
1432 auto msg = TMsgTestLightMove(x, y);
1433 send(renderTid, msg);
1437 // ////////////////////////////////////////////////////////////////////////// //
1438 struct TMsgMessage {
1439 char[256] text;
1440 uint textlen;
1441 int pauseMsecs;
1442 bool noreplace;
1445 public void postAddMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
1446 if (!atomicLoad(renderThreadStarted)) return;
1447 if (msgtext.length > TMsgMessage.text.length) msgtext = msgtext[0..TMsgMessage.text.length];
1448 TMsgMessage msg;
1449 msg.textlen = cast(uint)msgtext.length;
1450 if (msg.textlen) msg.text[0..msg.textlen] = msgtext[0..msg.textlen];
1451 msg.pauseMsecs = pauseMsecs;
1452 msg.noreplace = noreplace;
1453 send(renderTid, msg);
1457 // ////////////////////////////////////////////////////////////////////////// //
1458 struct TMsgLoadLevel {
1459 char[1024] mapfile;
1460 uint textlen;
1463 public void postLoadLevel (const(char)[] mapfile) {
1464 if (!atomicLoad(renderThreadStarted)) return;
1465 if (mapfile.length > TMsgLoadLevel.mapfile.length) mapfile = mapfile[0..TMsgLoadLevel.mapfile.length];
1466 TMsgLoadLevel msg;
1467 msg.textlen = cast(uint)mapfile.length;
1468 if (msg.textlen) msg.mapfile[0..msg.textlen] = mapfile[0..msg.textlen];
1469 send(renderTid, msg);
1473 // ////////////////////////////////////////////////////////////////////////// //
1474 struct TMsgSkipLevel {
1477 public void postSkipLevel () {
1478 if (!atomicLoad(renderThreadStarted)) return;
1479 TMsgSkipLevel msg;
1480 send(renderTid, msg);
1484 // ////////////////////////////////////////////////////////////////////////// //
1485 struct TMsgIntOption {
1486 enum Type {
1487 Scale,
1489 Type type;
1490 int value;
1491 bool showMessage;
1495 struct TMsgBoolOption {
1496 enum Type {
1497 Interpolation,
1498 Lighting,
1499 Pause,
1500 EditMode,
1501 CheatNoDoors,
1502 CheatNoWallClip,
1504 Type type;
1505 bool value;
1506 bool toggle;
1507 bool showMessage;
1511 bool strCaseEqu (const(char)[] s0, const(char)[] s1) {
1512 if (s0.length != s1.length) return false;
1513 foreach (immutable idx, char ch; s0) {
1514 if (ch >= 'A' && ch <= 'Z') ch += 32;
1515 char c1 = s1.ptr[idx];
1516 if (c1 >= 'A' && c1 <= 'Z') c1 += 32;
1517 if (ch != c1) return false;
1519 return true;
1523 public bool postToggleOption (const(char)[] name, bool showMessage=false) { /*pragma(inline, true);*/ return postSetOption(name, true, showMessage:showMessage, toggle:true); }
1525 public bool postSetOption(T) (const(char)[] name, T value, bool showMessage=false, bool toggle=false) if ((is(T == int) || is(T == bool))) {
1526 if (!atomicLoad(renderThreadStarted)) return false;
1527 if (name.length == 0 || name.length > 127) return false;
1528 static if (is(T == int)) {
1529 TMsgIntOption msg;
1530 foreach (string oname; __traits(allMembers, TMsgIntOption.Type)) {
1531 if (strCaseEqu(oname, name)) {
1532 msg.type = __traits(getMember, TMsgIntOption.Type, oname);
1533 msg.value = value;
1534 msg.showMessage = showMessage;
1535 send(renderTid, msg);
1536 return true;
1539 } else static if (is(T == bool)) {
1540 TMsgBoolOption msg;
1541 foreach (string oname; __traits(allMembers, TMsgBoolOption.Type)) {
1542 if (strCaseEqu(oname, name)) {
1543 msg.type = __traits(getMember, TMsgBoolOption.Type, oname);
1544 msg.value = value;
1545 msg.toggle = toggle;
1546 msg.showMessage = showMessage;
1547 send(renderTid, msg);
1548 return true;
1551 } else {
1552 static assert(0, "invalid option type '"~T.stringof~"'");
1554 return false;
1558 // ////////////////////////////////////////////////////////////////////////// //
1559 public void postConExec (const(char)[] cmd) {
1560 //if (!atomicLoad(renderThreadStarted)) return;
1561 concmdbufLock.lock();
1562 scope(exit) concmdbufLock.unlock();
1563 concmdAdd(cmd);