simple, slow and stupid console rendering
[dd2d.git] / render.d
blobb76c375fa227364adffff50a52664a5799567d3c
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 __gshared bool rConsoleVisible = false;
80 shared bool editMode = false;
81 shared bool vquitRequested = false;
83 public @property bool inEditMode () nothrow @trusted @nogc { import core.atomic; return atomicLoad(editMode); }
84 @property void inEditMode (bool v) nothrow @trusted @nogc { import core.atomic; atomicStore(editMode, v); }
86 public @property bool quitRequested () nothrow @trusted @nogc { import core.atomic; return atomicLoad(vquitRequested); }
89 // ////////////////////////////////////////////////////////////////////////// //
90 __gshared char[] concmdbuf;
91 __gshared uint concmdbufpos;
92 private import core.sync.mutex : Mutex;
93 __gshared Mutex concmdbufLock;
94 shared static this () { concmdbuf.length = 65536; concmdbufLock = new Mutex(); }
97 void concmdAdd (const(char)[] s) {
98 if (s.length) {
99 if (concmdbuf.length-concmdbufpos < s.length+1) {
100 concmdbuf.assumeSafeAppend.length += s.length-(concmdbuf.length-concmdbufpos)+512;
102 if (concmdbufpos > 0 && concmdbuf[concmdbufpos-1] != '\n') concmdbuf.ptr[concmdbufpos++] = '\n';
103 concmdbuf[concmdbufpos..concmdbufpos+s.length] = s[];
104 concmdbufpos += s.length;
108 // `null`: no more
109 void concmdDoAll () {
110 if (concmdbufpos == 0) return;
111 scope(exit) concmdbufpos = 0;
112 auto ebuf = concmdbufpos;
113 const(char)[] s = concmdbuf[0..concmdbufpos];
114 for (;;) {
115 while (s.length) {
116 auto cmd = conGetCommand(s);
117 if (cmd is null) break;
118 try {
119 conExecute(cmd);
120 } catch (Exception e) {
121 conwriteln("***ERROR: ", e.msg);
124 if (concmdbufpos <= ebuf) break;
125 s = concmdbuf[ebuf..concmdbufpos];
126 ebuf = concmdbufpos;
131 // ////////////////////////////////////////////////////////////////////////// //
132 shared static this () {
133 conRegVar!doLighting("r_lighting", "dynamic lighting");
134 conRegVar!gamePaused("g_pause", "pause game");
135 conRegVar!rConsoleVisible("r_console", "console visibility");
136 conRegVar!frameInterpolation("r_interpolation", "interpolate camera and sprite movement");
137 conRegVar!scale(1, 2, "r_scale");
138 conRegFunc!({
139 cheatNoDoors = !cheatNoDoors;
140 if (cheatNoDoors) conwriteln("player ignores doors"); else conwriteln("player respects doors");
141 })("nodoorclip", "ignore doors");
142 conRegFunc!({
143 cheatNoWallClip = !cheatNoWallClip;
144 if (cheatNoWallClip) conwriteln("player ignores walls"); else conwriteln("player respects walls");
145 })("nowallclip", "ignore walls");
146 conRegFunc!({
147 import core.atomic;
148 atomicStore(vquitRequested, true);
149 })("quit", "quit game");
150 conRegFunc!((const(char)[] msg, int pauseMsecs=3000, bool noreplace=false) {
151 char[256] buf;
152 auto s = buf.conFormatStr(msg);
153 if (s.length) postAddMessage(s, pauseMsecs, noreplace);
154 })("hudmsg", "show hud message; hudmsg msg [pausemsecs [noreplace]]");
158 // ////////////////////////////////////////////////////////////////////////// //
159 // interpolation
160 __gshared ubyte[] prevFrameActorsData;
161 __gshared uint[65536] prevFrameActorOfs; // uint.max-1: dead; uint.max: last
162 __gshared MonoTime lastthink = MonoTime.zero; // for interpolator
163 __gshared MonoTime nextthink = MonoTime.zero;
164 __gshared bool frameInterpolation = true;
167 // ////////////////////////////////////////////////////////////////////////// //
168 // attached lights
169 struct AttachedLightInfo {
170 enum Type {
171 Point,
172 Ambient,
174 Type type;
175 int x, y;
176 int w, h; // for ambient lights
177 float r, g, b;
178 bool uncolored;
179 int radius;
182 __gshared AttachedLightInfo[65536] attachedLights;
183 __gshared uint attachedLightCount = 0;
186 // ////////////////////////////////////////////////////////////////////////// //
187 enum MaxLightRadius = 255;
190 // ////////////////////////////////////////////////////////////////////////// //
191 // for light
192 __gshared FBO[MaxLightRadius+1] fboDistMap;
193 __gshared FBO fboOccluders;
194 __gshared Shader shadToPolar, shadBlur, shadBlurOcc, shadAmbient;
195 __gshared TrueColorImage editorImg;
196 __gshared FBO fboEditor;
197 __gshared FBO fboConsole;
198 __gshared Texture texSigil;
200 __gshared FBO fboLevel, fboLevelLight, fboOrigBack, fboLMaskSmall;
201 __gshared Shader shadScanlines;
202 __gshared Shader shadLiquidDistort;
205 // ////////////////////////////////////////////////////////////////////////// //
206 // call once!
207 public void initOpenGL () {
208 gloStackClear();
210 glEnable(GL_TEXTURE_2D);
211 glDisable(GL_LIGHTING);
212 glDisable(GL_DITHER);
213 glDisable(GL_BLEND);
214 glDisable(GL_DEPTH_TEST);
216 // create shaders
217 shadScanlines = new Shader("scanlines", loadTextFile("shaders/srscanlines.frag"));
219 shadLiquidDistort = new Shader("liquid_distort", loadTextFile("shaders/srliquid_distort.frag"));
220 shadLiquidDistort.exec((Shader shad) {
221 shad["texLqMap"] = 0;
224 // lights
225 shadToPolar = new Shader("light_trace", loadTextFile("shaders/srlight_trace.frag"));
226 shadToPolar.exec((Shader shad) {
227 shad["texOcc"] = 0;
228 shad["texOccFull"] = 2;
229 shad["texOccSmall"] = 3;
232 shadBlur = new Shader("light_blur", loadTextFile("shaders/srlight_blur.frag"));
233 shadBlur.exec((Shader shad) {
234 shad["texDist"] = 0;
235 shad["texBg"] = 1;
236 shad["texOcc"] = 2;
237 shad["texOccSmall"] = 3;
240 shadBlurOcc = new Shader("light_blur_occ", loadTextFile("shaders/srlight_blur_occ.frag"));
241 shadBlurOcc.exec((Shader shad) {
242 shad["texLMap"] = 0;
243 shad["texBg"] = 1;
244 shad["texOcc"] = 2;
245 shad["texOccSmall"] = 3;
248 shadAmbient = new Shader("light_ambient", loadTextFile("shaders/srlight_ambient.frag"));
249 shadAmbient.exec((Shader shad) {
250 shad["texBg"] = 1;
251 shad["texOcc"] = 2;
252 shad["texOccSmall"] = 3;
255 fboOccluders = new FBO(MaxLightRadius*2, MaxLightRadius*2, Texture.Option.Clamp, Texture.Option.Linear);
256 //TODO: this sux!
257 foreach (int sz; 2..MaxLightRadius+1) {
258 fboDistMap[sz] = new FBO(sz*2, 1, Texture.Option.Clamp, Texture.Option.Linear); // create 1d distance map FBO
261 editorImg = new TrueColorImage(vlWidth, vlHeight);
262 editorImg.imageData.colors[] = Color(0, 0, 0, 0);
263 fboEditor = new FBO(vlWidth, vlHeight, Texture.Option.Nearest);
265 texSigil = new Texture("sigil_of_baphomet.png", Texture.Option.Nearest);
266 fboConsole = new FBO(vlWidth, vlHeight, Texture.Option.Nearest);
268 // setup matrices
269 glMatrixMode(GL_MODELVIEW);
270 glLoadIdentity();
272 loadSmFont();
273 loadBfFont();
274 loadAllMonsterGraphics();
278 // ////////////////////////////////////////////////////////////////////////// //
279 void renderConsoleFBO () {
280 __gshared ulong lastChange = 0;
281 if (lastChange == cbufLastChange) return;
282 // rerender console
283 lastChange = cbufLastChange;
284 //foreach (auto s; conbufLinesRev) stdout.writeln(s, "|");
285 fboConsole.exec((FBO me) {
286 bindTexture(0);
287 orthoCamera(me.width, me.height);
288 // clear it
289 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
290 glClear(GL_COLOR_BUFFER_BIT);
292 glDisable(GL_BLEND);
293 glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
294 glRectf(0, 0, me.width-1, me.height-1);
296 // text
297 glEnable(GL_BLEND);
298 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
299 // draw sigil
300 glColor4f(1.0f, 1.0f, 1.0f, 0.2f);
301 drawAtXY(texSigil, (me.width-texSigil.width)/2, (me.height-texSigil.height)/2);
302 // draw text
303 glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
304 int y = me.height-conCharHeight-2;
305 //glPushAttrib(GL_LIST_BIT/*|GL_TEXTURE_BIT*/);
306 //scope(exit) glPopAttrib();
307 glPushMatrix();
308 scope(exit) glPopMatrix();
309 glTranslatef(0, y, 0);
310 foreach (auto line; conbufLinesRev) {
311 if (line.length == 0) {
312 glPopMatrix();
313 glPushMatrix();
314 y -= conCharHeight;
315 glTranslatef(2, y, 0);
316 } else {
317 usize pos = line.length;
318 for (;;) {
319 int w = 0;
320 usize sp = pos;
321 while (sp > 0) {
322 char ch = line[sp-1];
323 if (w+conCharWidth(ch) > me.width-4) break;
324 w += conCharWidth(ch);
325 --sp;
327 foreach (immutable p; sp..pos) conDrawChar(line[p]);
328 glPopMatrix();
329 glPushMatrix();
330 y -= conCharHeight;
331 glTranslatef(2, y, 0);
332 if (sp == 0 || y < 0) break;
333 pos = sp;
336 if (y < 0) break;
342 // ////////////////////////////////////////////////////////////////////////// //
343 // should be called when OpenGL is initialized
344 void loadMap (string mapname) {
345 mapscripts.runUnloading(); // "map unloading" script
346 clearMapScripts();
348 if (map !is null) map.clear();
349 map = new LevelMap(mapname);
350 curmapname = mapname;
352 ugInit(map.width*TileSize, map.height*TileSize);
354 map.oglBuildMega();
355 mapTilesChanged = 0;
357 if (fboLevel !is null) fboLevel.clear();
358 if (fboLevelLight !is null) fboLevelLight.clear();
359 if (fboOrigBack !is null) fboOrigBack.clear();
360 if (fboLMaskSmall !is null) fboLMaskSmall.clear();
362 fboLevel = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // final level render will be here
363 fboLevelLight = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // level lights will be rendered here
364 fboOrigBack = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest/*, Texture.Option.Depth*/); // background+foreground
365 fboLMaskSmall = new FBO(map.width, map.height, Texture.Option.Nearest); // small lightmask
367 shadToPolar.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize-1, map.height*TileSize-1); });
368 shadBlur.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
369 shadBlurOcc.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
370 shadAmbient.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
372 glActiveTexture(GL_TEXTURE0+0);
373 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
374 orthoCamera(vlWidth, vlHeight);
376 Actor.resetStorage();
378 setupMapScripts();
379 mapscripts.runInit();
380 loadMapMonsters();
381 dotInit();
382 mapscripts.runLoaded();
384 // save first snapshot
385 if (prevFrameActorsData.length == 0) prevFrameActorsData = new ubyte[](Actor.actorSize*65536); // ~15-20 megabytes
386 prevFrameActorOfs[] = uint.max; // just for fun
387 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
389 levelLoaded = true;
391 { import core.memory : GC; GC.collect(); }
395 // ////////////////////////////////////////////////////////////////////////// //
396 //FIXME: optimize!
397 __gshared uint mapTilesChanged = 0;
400 //WARNING! this can be called only from DACS, so we don't have to sync it!
401 public void mapDirty (uint layermask) { mapTilesChanged |= layermask; }
404 void rebuildMapMegaTextures () {
405 //fbo.replaceTexture
406 //mapTilesChanged = false;
407 //map.clearMegaTextures();
408 map.oglBuildMega(mapTilesChanged);
409 mapTilesChanged = 0;
410 dotsAwake(); // let dormant dots fall
414 // ////////////////////////////////////////////////////////////////////////// //
415 // messages
416 struct Message {
417 enum Phase { FadeIn, Stay, FadeOut }
418 Phase phase;
419 int alpha;
420 int pauseMsecs;
421 MonoTime removeTime;
422 char[256] text;
423 usize textlen;
426 //private import core.sync.mutex : Mutex;
428 __gshared Message[128] messages;
429 __gshared uint messagesUsed = 0;
431 //__gshared Mutex messageLock;
432 //shared static this () { messageLock = new Mutex(); }
435 void addMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
436 //messageLock.lock();
437 //scope(exit) messageLock.unlock();
438 if (msgtext.length == 0) return;
439 conwriteln(msgtext);
440 if (pauseMsecs <= 50) return;
441 if (messagesUsed == messages.length) {
442 // remove top message
443 foreach (immutable cidx; 1..messagesUsed) messages.ptr[cidx-1] = messages.ptr[cidx];
444 messages.ptr[0].alpha = 255;
445 --messagesUsed;
447 // quick replace
448 if (!noreplace && messagesUsed == 1) {
449 switch (messages.ptr[0].phase) {
450 case Message.Phase.FadeIn:
451 messages.ptr[0].phase = Message.Phase.FadeOut;
452 break;
453 case Message.Phase.Stay:
454 messages.ptr[0].phase = Message.Phase.FadeOut;
455 messages.ptr[0].alpha = 255;
456 break;
457 default:
460 auto msg = messages.ptr+messagesUsed;
461 ++messagesUsed;
462 msg.phase = Message.Phase.FadeIn;
463 msg.alpha = 0;
464 msg.pauseMsecs = pauseMsecs;
465 // copy text
466 if (msgtext.length > msg.text.length) {
467 msg.text = msgtext[0..msg.text.length];
468 msg.textlen = msg.text.length;
469 } else {
470 msg.text[0..msgtext.length] = msgtext[];
471 msg.textlen = msgtext.length;
476 void doMessages (MonoTime curtime) {
477 //messageLock.lock();
478 //scope(exit) messageLock.unlock();
480 if (messagesUsed == 0) return;
481 glEnable(GL_BLEND);
482 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
484 Message* msg;
486 again:
487 msg = messages.ptr;
488 final switch (msg.phase) {
489 case Message.Phase.FadeIn:
490 if ((msg.alpha += 10) >= 255) {
491 msg.phase = Message.Phase.Stay;
492 msg.removeTime = curtime+dur!"msecs"(msg.pauseMsecs);
493 goto case; // to stay
495 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
496 break;
497 case Message.Phase.Stay:
498 if (msg.removeTime <= curtime) {
499 msg.alpha = 255;
500 msg.phase = Message.Phase.FadeOut;
501 goto case; // to fade
503 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
504 break;
505 case Message.Phase.FadeOut:
506 if ((msg.alpha -= 10) <= 0) {
507 if (--messagesUsed == 0) return;
508 // remove this message
509 foreach (immutable cidx; 1..messagesUsed+1) messages.ptr[cidx-1] = messages.ptr[cidx];
510 goto again;
512 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
513 break;
516 smDrawText(10, 10, msg.text[0..msg.textlen]);
520 // ////////////////////////////////////////////////////////////////////////// //
521 //mixin(Actor.FieldPropMixin!("0drawlistpos", uint));
523 mixin(Actor.FieldGetMixin!("classtype", StrId)); // fget_classtype
524 mixin(Actor.FieldGetMixin!("classname", StrId)); // fget_classname
525 mixin(Actor.FieldGetMixin!("x", int));
526 mixin(Actor.FieldGetMixin!("y", int));
527 mixin(Actor.FieldGetMixin!("s", int));
528 mixin(Actor.FieldGetMixin!("radius", int));
529 mixin(Actor.FieldGetMixin!("height", int));
530 mixin(Actor.FieldGetMixin!("flags", uint));
531 mixin(Actor.FieldGetMixin!("zAnimstate", StrId));
532 mixin(Actor.FieldGetMixin!("zAnimidx", int));
533 mixin(Actor.FieldGetMixin!("dir", uint));
534 mixin(Actor.FieldGetMixin!("attLightXOfs", int));
535 mixin(Actor.FieldGetMixin!("attLightYOfs", int));
536 mixin(Actor.FieldGetMixin!("attLightRGBX", uint));
538 //mixin(Actor.FieldGetPtrMixin!("classtype", StrId)); // fget_classtype
539 //mixin(Actor.FieldGetPtrMixin!("classname", StrId)); // fget_classname
540 mixin(Actor.FieldGetPtrMixin!("x", int));
541 mixin(Actor.FieldGetPtrMixin!("y", int));
542 //mixin(Actor.FieldGetPtrMixin!("flags", uint));
543 //mixin(Actor.FieldGetPtrMixin!("zAnimstate", StrId));
544 //mixin(Actor.FieldGetPtrMixin!("zAnimidx", int));
545 //mixin(Actor.FieldGetPtrMixin!("dir", uint));
546 //mixin(Actor.FieldGetPtrMixin!("attLightXOfs", int));
547 //mixin(Actor.FieldGetPtrMixin!("attLightYOfs", int));
548 //mixin(Actor.FieldGetPtrMixin!("attLightRGBX", uint));
551 // ////////////////////////////////////////////////////////////////////////// //
552 __gshared int vportX0, vportY0, vportX1, vportY1;
555 // ////////////////////////////////////////////////////////////////////////// //
556 void renderLightAmbient() (int lightX, int lightY, int lightW, int lightH, in auto ref SVec4F lcol) {
557 //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));
558 if (lightW < 1 || lightH < 1) return;
559 int lightX1 = lightX+lightW-1;
560 int lightY1 = lightY+lightH-1;
561 // clip light to viewport
562 if (lightX < vportX0) lightX = vportX0;
563 if (lightY < vportY0) lightY = vportY0;
564 if (lightX1 > vportX1) lightX1 = vportX1;
565 if (lightY1 > vportY1) lightY1 = vportY1;
566 // is this light visible?
567 //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));
568 if (lightX1 < lightX || lightY1 < lightY || lightX > vportX1 || lightY > vportY1 || lightX1 < vportX0 || lightY1 < vportY0) return;
569 //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));
571 fboLevelLight.exec({
572 bindTexture(0);
573 glEnable(GL_BLEND);
574 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
575 //glDisable(GL_BLEND);
576 orthoCamera(map.width*TileSize, map.height*TileSize);
577 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
578 shadAmbient.exec((Shader shad) {
579 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
580 //shad["lightPos"] = SVec2F(lightX, lightY);
581 glRectf(lightX, lightY, lightX1, lightY1);
587 // ////////////////////////////////////////////////////////////////////////// //
588 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
589 if (lightRadius < 2) return;
590 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
591 int lightSize = lightRadius*2;
592 // is this light visible?
593 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*TileSize || lightY-lightRadius >= map.height*TileSize) return;
595 // out of viewport -- do nothing
596 if (lightX+lightRadius < vportX0 || lightY+lightRadius < vportY0) return;
597 if (lightX-lightRadius > vportX1 || lightY-lightRadius > vportY1) return;
599 if (lightX >= 0 && lightY >= 0 && lightX < map.width*TileSize && lightY < map.height*TileSize &&
600 map.teximgs[map.LightMask].imageData.colors.ptr[lightY*(map.width*TileSize)+lightX].a > 190) return;
602 // common color for all the following
603 glDisable(GL_BLEND);
604 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
606 // build 1d distance map to fboShadowMapId
607 fboDistMap.ptr[lightRadius].exec({
608 // no need to clear it, shader will take care of that
609 shadToPolar.exec((Shader shad) {
610 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
611 shad["lightPos"] = SVec2F(lightX, lightY);
612 orthoCamera(lightSize, 1);
613 // it doesn't matter what we will draw here, so just draw filled rect
614 glRectf(0, 0, lightSize, 1);
618 // build light texture for blending
619 fboOccluders.exec({
620 // no need to clear it, shader will take care of that
621 // debug
622 //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
623 //glClear(GL_COLOR_BUFFER_BIT);
624 shadBlur.exec((Shader shad) {
625 shad["lightTexSize"] = SVec2F(lightSize, MaxLightRadius*2); // x: size of distmap; y: size of this texture
626 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
627 shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
628 orthoCamera(fboOccluders.tex.width, fboOccluders.tex.height);
629 //drawAtXY(fboDistMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
630 bindTexture(fboDistMap.ptr[lightRadius].tex.tid);
631 glBegin(GL_QUADS);
632 glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0); // top-left
633 glTexCoord2f(1.0f, 0.0f); glVertex2i(lightSize, 0); // top-right
634 glTexCoord2f(1.0f, 1.0f); glVertex2i(lightSize, lightSize); // bottom-right
635 glTexCoord2f(0.0f, 1.0f); glVertex2i(0, lightSize); // bottom-left
636 glEnd();
640 // blend light texture
641 fboLevelLight.exec({
642 glEnable(GL_BLEND);
643 //glDisable(GL_BLEND);
644 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
645 orthoCamera(fboLevelLight.tex.width, fboLevelLight.tex.height);
646 //drawAtXY(fboOccluders.tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
647 float occe = 1.0f*lightSize/(MaxLightRadius*2);
648 float occs = 1.0f-occe;
649 int x0 = lightX-lightRadius+0;
650 int y1 = lightY-lightRadius+0;
651 int x1 = lightX+lightRadius-1+1;
652 int y0 = lightY+lightRadius-1+1;
653 bindTexture(fboOccluders.tex.tid);
654 glBegin(GL_QUADS);
656 glTexCoord2f(0.0f, 0.0f); glVertex2i(x0, y0); // top-left
657 glTexCoord2f(occe, 0.0f); glVertex2i(x1, y0); // top-right
658 glTexCoord2f(occe, occe); glVertex2i(x1, y1); // bottom-right
659 glTexCoord2f(0.0f, occe); glVertex2i(x0, y1); // bottom-left
661 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
662 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
663 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
664 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
665 glEnd();
667 bindTexture(0);
668 glRectf(x0, y0, x1, y1);
670 // and blend it again, with the shader that will touch only occluders
671 shadBlurOcc.exec((Shader shad) {
672 //shad["lightTexSize"] = SVec2F(lightSize, lightSize);
673 shad["lightTexSize"] = SVec2F(lightSize, fboOccluders.tex.height);
674 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
675 shad["lightPos"] = SVec2F(lightX, lightY);
676 //shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
677 bindTexture(fboOccluders.tex.tid);
678 glBegin(GL_QUADS);
679 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
680 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
681 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
682 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
683 glEnd();
684 //drawAtXY(fboOccluders.tex.tid, lightX-lightRadius, lightY-lightRadius, lightSize, lightSize, mirrorY:true);
690 // ////////////////////////////////////////////////////////////////////////// //
691 __gshared int testLightX = vlWidth/2, testLightY = vlHeight/2;
692 __gshared bool testLightMoved = false;
693 //__gshared int mapOfsX, mapOfsY;
694 //__gshared bool movement = false;
695 __gshared float iLiquidTime = 0.0;
696 //__gshared bool altMove = false;
699 void renderScene (MonoTime curtime) {
700 //enum BackIntens = 0.05f;
701 enum BackIntens = 0.0f;
703 gloStackClear();
704 float atob = (curtime > lastthink ? cast(float)((curtime-lastthink).total!"msecs")/cast(float)((nextthink-lastthink).total!"msecs") : 1.0f);
705 if (gamePaused || inEditMode) atob = 1.0f;
706 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
709 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
710 int curfp = cast(int)((curtime-lastthink).total!"msecs");
711 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
715 int mofsx, mofsy; // camera offset, will be set in background layer builder
717 if (mapTilesChanged != 0) rebuildMapMegaTextures();
719 // build background layer
720 fboOrigBack.exec({
721 //glDisable(GL_BLEND);
722 //glClearDepth(1.0f);
723 //glDepthFunc(GL_LESS);
724 //glDepthFunc(GL_NEVER);
725 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
726 glClear(GL_COLOR_BUFFER_BIT/*|GL_DEPTH_BUFFER_BIT*/);
727 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
728 orthoCamera(map.width*TileSize, map.height*TileSize);
730 glEnable(GL_BLEND);
731 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
732 // draw sky
734 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
735 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
736 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
737 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
739 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*TileSize, map.MapSize*TileSize);
740 // draw background
741 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
742 // draw distorted liquid areas
743 shadLiquidDistort.exec((Shader shad) {
744 shad["iDistortTime"] = iLiquidTime;
745 drawAtXY(map.texgl.ptr[map.AllLiquids], 0, 0);
747 // monsters, items; we'll do linear interpolation here
748 glColor3f(1.0f, 1.0f, 1.0f);
749 //glEnable(GL_DEPTH_TEST);
750 attachedLightCount = 0;
752 // who cares about memory?!
753 // draw order: players, items, monsters, other
754 static struct DrawInfo {
755 ActorDef adef;
756 ActorId aid;
757 int actorX, actorY;
758 @disable this (this); // no copies
760 enum { Pixels, Players, Items, Monsters, Other }
761 __gshared DrawInfo[65536][4] drawlists;
762 __gshared uint[4] dlpos;
763 DrawInfo camchickdi;
765 dlpos[] = 0;
767 Actor.forEach((ActorId me) {
768 //me.fprop_0drawlistpos = 0;
769 if (auto adef = findActorDef(me)) {
770 uint dlnum = Other;
771 switch (adef.classtype.get) {
772 case "monster": dlnum = (adef.classname.get != "Player" ? Monsters : Players); break;
773 case "item": dlnum = Items; break;
774 default: dlnum = Other; break;
776 if (me.fget_flags&AF_PIXEL) dlnum = Pixels;
777 int actorX, actorY; // current actor position
779 auto ofs = prevFrameActorOfs.ptr[me.id&0xffff];
780 if (frameInterpolation && ofs < uint.max-1 && (me.fget_flags&AF_TELEPORT) == 0 && Actor.isSameSavedActor(me.id, prevFrameActorsData.ptr, ofs)) {
781 import core.stdc.math : roundf;
782 auto xptr = prevFrameActorsData.ptr+ofs;
783 int ox = xptr.fgetp_x;
784 int nx = me.fget_x;
785 int oy = xptr.fgetp_y;
786 int ny = me.fget_y;
787 actorX = cast(int)(ox+roundf((nx-ox)*atob));
788 actorY = cast(int)(oy+roundf((ny-oy)*atob));
789 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
790 } else {
791 actorX = me.fget_x;
792 actorY = me.fget_y;
795 if (me.id == cameraChick.id) {
796 camchickdi.adef = adef;
797 camchickdi.aid = me;
798 camchickdi.actorX = actorX;
799 camchickdi.actorY = actorY;
801 // draw sprite
802 if ((me.fget_flags&AF_NODRAW) == 0) {
803 //auto dl = &drawlists[dlnum][dlpos.ptr[dlnum]];
804 //me.fprop_0drawlistpos = (dlpos.ptr[dlnum]&0xffff)|((dlnum&0xff)<<16);
805 auto dl = drawlists.ptr[dlnum].ptr+dlpos.ptr[dlnum];
806 ++dlpos.ptr[dlnum];
807 dl.adef = adef;
808 dl.aid = me;
809 dl.actorX = actorX;
810 dl.actorY = actorY;
812 // process attached lights
813 if ((me.fget_flags&AF_NOLIGHT) == 0) {
814 uint alr = me.fget_attLightRGBX;
815 bool isambient = (me.fget_classtype.get == "light" && me.fget_classname.get == "Ambient");
816 if ((alr&0xff) >= 4 || (isambient && me.fget_radius >= 1 && me.fget_height >= 1)) {
817 //if (isambient) conwriteln("isambient: ", isambient, "; x=", me.fget_x, "; y=", me.fget_y, "; w=", me.fget_radius, "; h=", me.fget_height);
818 // yep, add it
819 auto li = attachedLights.ptr+attachedLightCount;
820 ++attachedLightCount;
821 li.type = (!isambient ? AttachedLightInfo.Type.Point : AttachedLightInfo.Type.Ambient);
822 li.x = actorX+me.fget_attLightXOfs;
823 li.y = actorY+me.fget_attLightYOfs;
824 li.r = ((alr>>24)&0xff)/255.0f; // red or intensity
825 if ((alr&0x00_ff_ff_00U) == 0x00_00_01_00U) {
826 li.uncolored = true;
827 } else {
828 li.g = ((alr>>16)&0xff)/255.0f;
829 li.b = ((alr>>8)&0xff)/255.0f;
830 li.uncolored = false;
832 li.radius = (alr&0xff);
833 if (li.radius > MaxLightRadius) li.radius = MaxLightRadius;
834 if (isambient) {
835 li.w = me.fget_radius;
836 li.h = me.fget_height;
840 } else {
841 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
845 // draw actor lists
846 foreach_reverse (uint dlnum; 0..drawlists.length) {
847 if (dlnum == Pixels) continue;
848 auto dl = drawlists.ptr[dlnum].ptr;
849 if (dlnum == Players) dl += dlpos.ptr[dlnum]-1;
850 foreach (uint idx; 0..dlpos.ptr[dlnum]) {
851 auto me = dl.aid;
852 if (auto isp = dl.adef.animSpr(me.fget_zAnimstate, me.fget_dir, me.fget_zAnimidx)) {
853 //drawAtXY(isp.tex, dl.actorX-isp.vga.sx, dl.actorY-isp.vga.sy);
854 isp.drawAtXY(dl.actorX, dl.actorY);
856 if (dlnum != Players) ++dl; else --dl;
859 // draw pixels
860 if (dlpos[Pixels]) {
861 bindTexture(0);
862 bool pointsStarted = false;
863 Color lastColor = Color(0, 0, 0, 0);
864 auto dl = drawlists.ptr[Pixels].ptr;
865 foreach (uint idx; 0..dlpos.ptr[Pixels]) {
866 auto me = dl.aid;
867 auto s = me.fget_s;
868 if (s < 0 || s > 255) continue; //FIXME
869 Color clr = d2dpal.ptr[s&0xff];
870 if (clr.a == 0) continue;
871 if (clr != lastColor) {
872 if (pointsStarted) glEnd();
873 glColor4f(clr.r/255.0f, clr.g/255.0f, clr.b/255.0f, clr.a/255.0f);
874 lastColor = clr;
875 pointsStarted = false;
877 if (!pointsStarted) {
878 glBegin(GL_POINTS);
879 pointsStarted = true;
881 glVertex2i(dl.actorX, dl.actorY);
882 ++dl;
884 if (pointsStarted) {
885 glEnd();
886 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
890 // camera movement
891 if (/*altMove || movement ||*/ scale == 1 || !cameraChick.valid) {
892 mofsx = 0;
893 mofsy = 0;
894 vportX0 = 0;
895 vportY0 = 0;
896 vportX1 = map.width*TileSize;
897 vportY1 = map.height*TileSize;
898 } else {
899 int tiltHeight = /*getMapViewHeight()*/(vlHeight/scale)/4;
900 int vy = cameraChick.looky!int;
901 if (vy < -tiltHeight) vy = -tiltHeight; else if (vy > tiltHeight) vy = tiltHeight;
902 int swdt = vlWidth/scale;
903 int shgt = vlHeight/scale;
904 int x = camchickdi.actorX-swdt/2;
905 int y = (camchickdi.actorY+vy)-shgt/2;
906 if (x < 0) x = 0; else if (x >= map.width*TileSize-swdt) x = map.width*TileSize-swdt-1;
907 if (y < 0) y = 0; else if (y >= map.height*TileSize-shgt) y = map.height*TileSize-shgt-1;
908 mofsx = x*2;
909 mofsy = y*2;
910 vportX0 = mofsx/scale;
911 vportY0 = mofsy/scale;
912 vportX1 = vportX0+vlWidth/scale;
913 vportY1 = vportY0+vlHeight/scale;
916 //glDisable(GL_DEPTH_TEST);
917 // draw dots
918 dotDraw(atob);
919 // do liquid coloring
920 drawAtXY(map.texgl.ptr[map.LiquidMask], 0, 0);
921 // foreground -- hide secrets, draw lifts and such
922 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
925 enum r = 255;
926 enum g = 0;
927 enum b = 0;
928 enum a = 255;
929 bindTexture(0);
930 glColor4f(r/255.0f, g/255.0f, b/255.0f, a/255.0f);
931 glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
932 if (cameraChick.valid) {
933 glBegin(GL_POINTS);
934 glVertex2i(camchickdi.actorX, camchickdi.actorY-70);
935 glEnd();
936 //glRectf(camchickdi.actorX, camchickdi.actorY-70, camchickdi.actorX+4, camchickdi.actorY-70+4);
938 //glRectf(0, 0, 300, 300);
939 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
945 if (doLighting) {
946 glDisable(GL_BLEND);
947 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
949 // make smaller occluder texture, so we can trace faster
950 //assert(fboLMaskSmall.tex.width == map.width);
951 //assert(fboLMaskSmall.tex.height == map.height);
952 fboLMaskSmall.exec({
953 orthoCamera(map.width, map.height);
954 drawAtXY(map.texgl.ptr[map.LightMask].tid, 0, 0, map.width, map.height, mirrorY:true);
957 // clear light layer
958 fboLevelLight.exec({
959 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
960 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
961 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
962 glClear(GL_COLOR_BUFFER_BIT);
965 // texture 1 is background
966 glActiveTexture(GL_TEXTURE0+1);
967 glBindTexture(GL_TEXTURE_2D, fboOrigBack.tex.tid);
968 // texture 2 is occluders
969 glActiveTexture(GL_TEXTURE0+2);
970 glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.LightMask].tid);
971 // texture 3 is small occluder map
972 glActiveTexture(GL_TEXTURE0+3);
973 glBindTexture(GL_TEXTURE_2D, fboLMaskSmall.tex.tid);
974 // done texture assign
975 glActiveTexture(GL_TEXTURE0+0);
978 enum LYOfs = 1;
980 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
981 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
982 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
983 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
984 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
985 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
986 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
987 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
988 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
989 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
991 renderLight(24*TileSize+4, (24+18)*TileSize-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
993 renderLight(280, 330, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
996 foreach (; 0..1) {
997 // attached lights
998 foreach (ref li; attachedLights[0..attachedLightCount]) {
999 if (li.type == AttachedLightInfo.Type.Ambient) {
1000 //conwriteln("ambient: x=", li.x, "; y=", li.y, "; w=", li.w, "; h=", li.h);
1001 // ambient light
1002 if (li.uncolored) {
1003 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(0.0f, 0.0f, 0.0f, li.r));
1004 } else {
1005 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(li.r, li.g, li.b, 1.0f));
1007 } else if (li.type == AttachedLightInfo.Type.Point) {
1008 // point light
1009 if (li.uncolored) {
1010 renderLight(li.x, li.y, SVec4F(0.0f, 0.0f, 0.0f, li.r), li.radius);
1011 } else {
1012 renderLight(li.x, li.y, SVec4F(li.r, li.g, li.b, 1.0f), li.radius);
1019 if (testLightMoved) {
1020 testLightX = testLightX/scale+mofsx/scale;
1021 testLightY = testLightY/scale+mofsy/scale;
1022 testLightMoved = false;
1024 foreach (immutable _; 0..1) {
1025 renderLight(testLightX, testLightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
1029 glActiveTexture(GL_TEXTURE0+1);
1030 glBindTexture(GL_TEXTURE_2D, 0);
1031 glActiveTexture(GL_TEXTURE0+2);
1032 glBindTexture(GL_TEXTURE_2D, 0);
1033 glActiveTexture(GL_TEXTURE0+3);
1034 glBindTexture(GL_TEXTURE_2D, 0);
1035 glActiveTexture(GL_TEXTURE0+0);
1038 // draw scaled level
1040 shadScanlines.exec((Shader shad) {
1041 shad["scanlines"] = scanlines;
1042 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
1043 glClear(GL_COLOR_BUFFER_BIT);
1044 orthoCamera(vlWidth, vlHeight);
1045 //orthoCamera(map.width*TileSize*scale, map.height*TileSize*scale);
1046 //glMatrixMode(GL_MODELVIEW);
1047 //glLoadIdentity();
1048 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
1049 // somehow, FBO objects are mirrored; wtf?!
1050 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1051 //glLoadIdentity();
1056 fboLevelLight.exec({
1057 smDrawText(map.width*TileSize/2, map.height*TileSize/2, "Testing...");
1062 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
1064 glDisable(GL_BLEND);
1067 fboOrigBack.exec({
1068 //auto img = smfont.ptr[0x39];
1069 auto img = fftest;
1070 if (img !is null) {
1071 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
1072 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
1079 { // http://stackoverflow.com/questions/7207422/setting-up-opengl-multiple-render-targets
1080 GLenum[1] buffers = [ GL_BACK_LEFT, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
1081 //GLenum[1] buffers = [ GL_NONE, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
1082 glDrawBuffers(1, buffers.ptr);
1087 orthoCamera(vlWidth, vlHeight);
1088 auto tex = (doLighting ? fboLevelLight.tex.tid : fboOrigBack.tex.tid);
1089 drawAtXY(tex, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1091 if (levelLoaded) {
1092 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1093 //orthoCamera(map.width*TileSize, map.height*TileSize);
1094 glEnable(GL_BLEND);
1095 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1096 hudScripts.runDraw();
1099 //drawAtXY(map.texgl.ptr[map.LightMask].tid, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1100 //drawAtXY(fboLMaskSmall.tex.tid, 0, 0, map.width*TileSize, map.height*TileSize);
1102 if (inEditMode) {
1103 glEnable(GL_BLEND);
1104 //glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1105 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1106 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1107 editorUpdateImage();
1108 fboEditor.tex.setFromImage(editorImg);
1109 drawAtXY(fboEditor.tex, 0, 0);
1110 glDisable(GL_BLEND);
1113 doMessages(curtime);
1115 if (rConsoleVisible) {
1116 renderConsoleFBO();
1117 orthoCamera(vlWidth, vlHeight);
1118 glEnable(GL_BLEND);
1119 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1120 glColor4f(1.0f, 1.0f, 1.0f, 0.7f);
1121 drawAtXY(fboConsole.tex, 0, 0-vlHeight/3, mirrorY:true);
1126 // ////////////////////////////////////////////////////////////////////////// //
1127 // returns time slept
1128 int sleepAtMaxMsecs (int msecs) {
1129 if (msecs > 0) {
1130 import core.sys.posix.signal : timespec;
1131 import core.sys.posix.time : nanosleep;
1132 timespec ts = void, tpassed = void;
1133 ts.tv_sec = 0;
1134 ts.tv_nsec = msecs*1000*1000+(500*1000); // milli to nano
1135 nanosleep(&ts, &tpassed);
1136 return (ts.tv_nsec-tpassed.tv_nsec)/(1000*1000);
1137 } else {
1138 return 0;
1143 // ////////////////////////////////////////////////////////////////////////// //
1144 mixin(import("editor.d"));
1147 // ////////////////////////////////////////////////////////////////////////// //
1148 // rendering thread
1149 shared int diedie = 0;
1151 enum D2DFrameTime = 55; // milliseconds
1152 enum MinFrameTime = 1000/60; // ~60 FPS
1154 public void renderThread (Tid starterTid) {
1155 enum BoolOptVarMsgMixin(string varname) =
1156 "if (msg.toggle) msg.value = !"~varname~";\n"~
1157 "if ("~varname~" != msg.value) "~varname~" = msg.value; else msg.showMessage = false;\n";
1159 send(starterTid, 42);
1160 try {
1161 MonoTime curtime = MonoTime.currTime;
1163 lastthink = curtime; // for interpolator
1164 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1165 MonoTime nextvframe = curtime;
1167 enum MaxFPSFrames = 16;
1168 float frtimes = 0.0f;
1169 int framenum = 0;
1170 int prevFPS = -1;
1171 int hushFrames = 6; // ignore first `hushFrames` frames overtime
1172 MonoTime prevFrameStartTime = curtime;
1174 bool vframeWasLost = false;
1176 void resetFrameTimers () {
1177 MonoTime curtime = MonoTime.currTime;
1178 lastthink = curtime; // for interpolator
1179 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1180 nextvframe = curtime;
1183 void loadNewLevel (string name) {
1185 if (levelLoaded) {
1186 conwriteln("ERROR: can't load new levels yet");
1187 return;
1190 if (name.length == 0) {
1191 conwriteln("ERROR: can't load empty level!");
1193 conwriteln("loading map '", name, "'");
1194 loadMap(name);
1195 resetFrameTimers();
1198 conRegFunc!({
1199 string mn = genNextMapName(0);
1200 if (mn.length) {
1201 nextmapname = null; // clear "exit" flag
1202 loadNewLevel(mn);
1203 } else {
1204 conwriteln("can't skip level");
1206 })("skiplevel", "skip current level");
1208 conRegFunc!({
1209 inEditMode = !inEditMode;
1210 if (inEditMode) sdwindow.hideCursor(); else sdwindow.showCursor();
1211 })("ed_toggle", "toggle editor");
1213 conRegFunc!({
1214 if (inEditMode) {
1215 inEditMode = false;
1216 sdwindow.showCursor();
1218 })("ed_exit", "exit from editor");
1220 conRegFunc!((string mapname) {
1221 nextmapname = null; // clear "exit" flag
1222 loadNewLevel(mapname);
1223 })("map", "load map");
1225 void receiveMessages () {
1226 for (;;) {
1227 import core.time : Duration;
1228 //conwriteln("rendering thread: waiting for messages...");
1229 auto got = receiveTimeout(
1230 Duration.zero, // don't wait
1231 (TMsgMessage msg) {
1232 addMessage(msg.text[0..msg.textlen], msg.pauseMsecs, msg.noreplace);
1234 (TMsgTestLightMove msg) {
1235 testLightX = msg.x;
1236 testLightY = msg.y;
1237 testLightMoved = true;
1239 (TMsgMouseEvent msg) { editorMouseEvent(msg); },
1240 (TMsgKeyEvent msg) { editorKeyEvent(msg); },
1241 (Variant v) {
1242 conwriteln("WARNING: unknown thread message received and ignored");
1245 if (!got) {
1246 // no more messages
1247 //conwriteln("rendering thread: no more messages");
1248 break;
1251 if (nextmapname.length) {
1252 string mn = nextmapname;
1253 nextmapname = null; // clear "exit" flag
1254 loadNewLevel(mn);
1258 void processConsoleCommands () {
1259 concmdbufLock.lock();
1260 scope(exit) concmdbufLock.unlock();
1261 concmdDoAll();
1264 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1265 bool doThinkFrame () {
1266 if (curtime >= nextthink) {
1267 lastthink = curtime;
1268 while (nextthink <= curtime) nextthink += dur!"msecs"(D2DFrameTime);
1269 if (levelLoaded) {
1270 // save snapshot and other data for interpolator
1271 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
1272 if (!gamePaused && !inEditMode) {
1273 // process actors
1274 doActorsThink();
1275 dotThink();
1278 // some timing
1279 auto tm = MonoTime.currTime;
1280 int thinkTime = cast(int)((tm-curtime).total!"msecs");
1281 if (thinkTime > 9) { import core.stdc.stdio; printf("spent on thinking: %d msecs\n", thinkTime); }
1282 curtime = tm;
1283 return true;
1284 } else {
1285 return false;
1289 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1290 bool doVFrame () {
1291 version(dont_use_vsync) {
1292 // timer
1293 enum doCheckTime = true;
1294 } else {
1295 // vsync
1296 __gshared bool prevLost = false;
1297 bool doCheckTime = vframeWasLost;
1298 if (vframeWasLost) {
1299 if (!prevLost) {
1300 { import core.stdc.stdio; printf("frame was lost!\n"); }
1302 prevLost = true;
1303 } else {
1304 prevLost = false;
1307 if (doCheckTime) {
1308 if (curtime < nextvframe) return false;
1309 version(dont_use_vsync) {
1310 if (curtime > nextvframe) {
1311 auto overtime = cast(int)((curtime-nextvframe).total!"msecs");
1312 if (overtime > 2500) {
1313 if (hushFrames) {
1314 --hushFrames;
1315 } else {
1316 { import core.stdc.stdio; printf(" spent whole %d msecs\n", overtime); }
1322 while (nextvframe <= curtime) nextvframe += dur!"msecs"(MinFrameTime);
1323 bool ctset = false;
1325 sdwindow.mtLock();
1326 scope(exit) sdwindow.mtUnlock();
1327 ctset = sdwindow.setAsCurrentOpenGlContextNT;
1329 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
1330 if (ctset) {
1331 // render scene
1332 iLiquidTime = cast(float)((curtime-MonoTime.zero).total!"msecs"%10000000)/18.0f*0.04f;
1333 receiveMessages(); // here, 'cause we need active OpenGL context for some messages
1334 processConsoleCommands();
1335 if (levelLoaded) {
1336 renderScene(curtime);
1337 } else {
1338 //renderLoading(curtime);
1340 sdwindow.mtLock();
1341 scope(exit) sdwindow.mtUnlock();
1342 sdwindow.swapOpenGlBuffers();
1343 glFinish();
1344 sdwindow.releaseCurrentOpenGlContext();
1345 vframeWasLost = false;
1346 } else {
1347 vframeWasLost = true;
1348 { import core.stdc.stdio; printf("xframe was lost!\n"); }
1350 curtime = MonoTime.currTime;
1351 return true;
1354 for (;;) {
1355 if (sdwindow.closed) break;
1356 if (atomicLoad(diedie) > 0) break;
1358 curtime = MonoTime.currTime;
1359 auto fstime = curtime;
1361 doThinkFrame(); // this will fix curtime if necessary
1362 if (doVFrame()) {
1363 if (!vframeWasLost) {
1364 // fps
1365 auto frameTime = cast(float)(curtime-prevFrameStartTime).total!"msecs"/1000.0f;
1366 prevFrameStartTime = curtime;
1367 frtimes += frameTime;
1368 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
1369 import std.string : format;
1370 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
1371 if (newFPS != prevFPS) {
1372 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
1373 prevFPS = newFPS;
1375 framenum = 0;
1376 frtimes = 0.0f;
1381 curtime = MonoTime.currTime;
1383 // now sleep until next "video" or "think" frame
1384 if (nextthink > curtime && nextvframe > curtime) {
1385 // let's decide
1386 immutable nextVideoFrameSleep = cast(int)((nextvframe-curtime).total!"msecs");
1387 immutable nextThinkFrameSleep = cast(int)((nextthink-curtime).total!"msecs");
1388 immutable sleepTime = (nextVideoFrameSleep < nextThinkFrameSleep ? nextVideoFrameSleep : nextThinkFrameSleep);
1389 sleepAtMaxMsecs(sleepTime);
1390 //curtime = MonoTime.currTime;
1393 } catch (Throwable e) {
1394 // here, we are dead and fucked (the exact order doesn't matter)
1395 import core.stdc.stdlib : abort;
1396 import core.stdc.stdio : fprintf, stderr;
1397 import core.memory : GC;
1398 GC.disable(); // yeah
1399 thread_suspendAll(); // stop right here, you criminal scum!
1400 auto s = e.toString();
1401 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
1402 abort(); // die, you bitch!
1404 import core.stdc.stdio;
1405 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
1406 import std.stdio : stderr;
1407 writeln(
1408 for (;;) {
1409 if (sdwindow.closed) break;
1410 if (atomicLoad(diedie) > 0) break;
1411 sleepAtMaxMsecs(100);
1415 atomicStore(diedie, 2);
1419 // ////////////////////////////////////////////////////////////////////////// //
1420 __gshared Tid renderTid;
1421 shared bool renderThreadStarted = false;
1424 public void startRenderThread () {
1425 if (!cas(&renderThreadStarted, false, true)) {
1426 assert(0, "render thread already started!");
1428 renderTid = spawn(&renderThread, thisTid);
1429 setMaxMailboxSize(renderTid, 1024, OnCrowding.throwException); //FIXME
1430 // wait for "i'm ready" signal
1431 receive(
1432 (int ok) {
1433 if (ok != 42) assert(0, "wtf?!");
1436 conwriteln("rendering thread started");
1440 // ////////////////////////////////////////////////////////////////////////// //
1441 public void closeWindow () {
1442 if (atomicLoad(diedie) != 2) {
1443 atomicStore(diedie, 1);
1444 while (atomicLoad(diedie) != 2) {}
1446 if (!sdwindow.closed) {
1447 flushGui();
1448 sdwindow.close();
1453 // ////////////////////////////////////////////////////////////////////////// //
1454 // thread messages
1457 // ////////////////////////////////////////////////////////////////////////// //
1458 struct TMsgMouseEvent {
1459 MouseEventType type;
1460 int x, y;
1461 int dx, dy;
1462 MouseButton button; /// See $(LREF MouseButton)
1463 int modifierState; /// See $(LREF ModifierState)
1466 public void postMouseEvent() (in auto ref MouseEvent evt) {
1467 if (!atomicLoad(renderThreadStarted)) return;
1468 TMsgMouseEvent msg;
1469 msg.type = evt.type;
1470 msg.x = evt.x;
1471 msg.y = evt.y;
1472 msg.dx = evt.dx;
1473 msg.dy = evt.dy;
1474 msg.button = evt.button;
1475 msg.modifierState = evt.modifierState;
1476 send(renderTid, msg);
1480 // ////////////////////////////////////////////////////////////////////////// //
1481 struct TMsgKeyEvent {
1482 Key key;
1483 uint hardwareCode;
1484 bool pressed;
1485 dchar character;
1486 uint modifierState;
1489 public void postKeyEvent() (in auto ref KeyEvent evt) {
1490 if (!atomicLoad(renderThreadStarted)) return;
1491 TMsgKeyEvent msg;
1492 msg.key = evt.key;
1493 msg.pressed = evt.pressed;
1494 msg.character = evt.character;
1495 msg.modifierState = evt.modifierState;
1496 send(renderTid, msg);
1500 // ////////////////////////////////////////////////////////////////////////// //
1501 struct TMsgTestLightMove {
1502 int x, y;
1505 public void postTestLightMove (int x, int y) {
1506 if (!atomicLoad(renderThreadStarted)) return;
1507 auto msg = TMsgTestLightMove(x, y);
1508 send(renderTid, msg);
1512 // ////////////////////////////////////////////////////////////////////////// //
1513 struct TMsgMessage {
1514 char[256] text;
1515 uint textlen;
1516 int pauseMsecs;
1517 bool noreplace;
1520 public void postAddMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
1521 if (!atomicLoad(renderThreadStarted)) return;
1522 if (msgtext.length > TMsgMessage.text.length) msgtext = msgtext[0..TMsgMessage.text.length];
1523 TMsgMessage msg;
1524 msg.textlen = cast(uint)msgtext.length;
1525 if (msg.textlen) msg.text[0..msg.textlen] = msgtext[0..msg.textlen];
1526 msg.pauseMsecs = pauseMsecs;
1527 msg.noreplace = noreplace;
1528 send(renderTid, msg);
1532 // ////////////////////////////////////////////////////////////////////////// //
1533 public void concmd (const(char)[] cmd) {
1534 //if (!atomicLoad(renderThreadStarted)) return;
1535 concmdbufLock.lock();
1536 scope(exit) concmdbufLock.unlock();
1537 concmdAdd(cmd);