better console font, etc
[dd2d.git] / render.d
blob70548cfc895d2f9de09fe6e6895da763546bb3e7
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 __gshared int rConsoleHeight = 16*3;
81 shared bool editMode = false;
82 shared bool vquitRequested = false;
84 public @property bool inEditMode () nothrow @trusted @nogc { import core.atomic; return atomicLoad(editMode); }
85 @property void inEditMode (bool v) nothrow @trusted @nogc { import core.atomic; atomicStore(editMode, v); }
87 public @property bool quitRequested () nothrow @trusted @nogc { import core.atomic; return atomicLoad(vquitRequested); }
90 // ////////////////////////////////////////////////////////////////////////// //
91 __gshared char[] concmdbuf;
92 __gshared uint concmdbufpos;
93 private import core.sync.mutex : Mutex;
94 __gshared Mutex concmdbufLock;
95 shared static this () { concmdbuf.length = 65536; concmdbufLock = new Mutex(); }
98 void concmdAdd (const(char)[] s) {
99 if (s.length) {
100 if (concmdbuf.length-concmdbufpos < s.length+1) {
101 concmdbuf.assumeSafeAppend.length += s.length-(concmdbuf.length-concmdbufpos)+512;
103 if (concmdbufpos > 0 && concmdbuf[concmdbufpos-1] != '\n') concmdbuf.ptr[concmdbufpos++] = '\n';
104 concmdbuf[concmdbufpos..concmdbufpos+s.length] = s[];
105 concmdbufpos += s.length;
109 // `null`: no more
110 void concmdDoAll () {
111 if (concmdbufpos == 0) return;
112 scope(exit) concmdbufpos = 0;
113 auto ebuf = concmdbufpos;
114 const(char)[] s = concmdbuf[0..concmdbufpos];
115 for (;;) {
116 while (s.length) {
117 auto cmd = conGetCommand(s);
118 if (cmd is null) break;
119 try {
120 conExecute(cmd);
121 } catch (Exception e) {
122 conwriteln("***ERROR: ", e.msg);
125 if (concmdbufpos <= ebuf) break;
126 s = concmdbuf[ebuf..concmdbufpos];
127 ebuf = concmdbufpos;
132 // ////////////////////////////////////////////////////////////////////////// //
133 shared static this () {
134 conRegVar!doLighting("r_lighting", "dynamic lighting");
135 conRegVar!gamePaused("g_pause", "pause game");
136 conRegVar!rConsoleVisible("r_console", "console visibility");
137 conRegVar!rConsoleHeight(16*3, vlHeight, "r_conheight");
138 rConsoleHeight = vlHeight-vlHeight/3;
139 rConsoleHeight = vlHeight/2;
140 conRegVar!frameInterpolation("r_interpolation", "interpolate camera and sprite movement");
141 conRegVar!scale(1, 2, "r_scale");
142 conRegFunc!({
143 cheatNoDoors = !cheatNoDoors;
144 if (cheatNoDoors) conwriteln("player ignores doors"); else conwriteln("player respects doors");
145 })("nodoorclip", "ignore doors");
146 conRegFunc!({
147 cheatNoWallClip = !cheatNoWallClip;
148 if (cheatNoWallClip) conwriteln("player ignores walls"); else conwriteln("player respects walls");
149 })("nowallclip", "ignore walls");
150 conRegFunc!({
151 import core.atomic;
152 atomicStore(vquitRequested, true);
153 })("quit", "quit game");
154 conRegFunc!((const(char)[] msg, int pauseMsecs=3000, bool noreplace=false) {
155 char[256] buf;
156 auto s = buf.conFormatStr(msg);
157 if (s.length) postAddMessage(s, pauseMsecs, noreplace);
158 })("hudmsg", "show hud message; hudmsg msg [pausemsecs [noreplace]]");
162 // ////////////////////////////////////////////////////////////////////////// //
163 // interpolation
164 __gshared ubyte[] prevFrameActorsData;
165 __gshared uint[65536] prevFrameActorOfs; // uint.max-1: dead; uint.max: last
166 __gshared MonoTime lastthink = MonoTime.zero; // for interpolator
167 __gshared MonoTime nextthink = MonoTime.zero;
168 __gshared bool frameInterpolation = true;
171 // ////////////////////////////////////////////////////////////////////////// //
172 // attached lights
173 struct AttachedLightInfo {
174 enum Type {
175 Point,
176 Ambient,
178 Type type;
179 int x, y;
180 int w, h; // for ambient lights
181 float r, g, b;
182 bool uncolored;
183 int radius;
186 __gshared AttachedLightInfo[65536] attachedLights;
187 __gshared uint attachedLightCount = 0;
190 // ////////////////////////////////////////////////////////////////////////// //
191 enum MaxLightRadius = 255;
194 // ////////////////////////////////////////////////////////////////////////// //
195 // for light
196 __gshared FBO[MaxLightRadius+1] fboDistMap;
197 __gshared FBO fboOccluders;
198 __gshared Shader shadToPolar, shadBlur, shadBlurOcc, shadAmbient;
199 __gshared TrueColorImage editorImg;
200 __gshared FBO fboEditor;
201 __gshared FBO fboConsole;
202 __gshared Texture texSigil;
204 __gshared FBO fboLevel, fboLevelLight, fboOrigBack, fboLMaskSmall;
205 __gshared Shader shadScanlines;
206 __gshared Shader shadLiquidDistort;
209 // ////////////////////////////////////////////////////////////////////////// //
210 // call once!
211 public void initOpenGL () {
212 gloStackClear();
214 glEnable(GL_TEXTURE_2D);
215 glDisable(GL_LIGHTING);
216 glDisable(GL_DITHER);
217 glDisable(GL_BLEND);
218 glDisable(GL_DEPTH_TEST);
220 // create shaders
221 shadScanlines = new Shader("scanlines", loadTextFile("shaders/srscanlines.frag"));
223 shadLiquidDistort = new Shader("liquid_distort", loadTextFile("shaders/srliquid_distort.frag"));
224 shadLiquidDistort.exec((Shader shad) {
225 shad["texLqMap"] = 0;
228 // lights
229 shadToPolar = new Shader("light_trace", loadTextFile("shaders/srlight_trace.frag"));
230 shadToPolar.exec((Shader shad) {
231 shad["texOcc"] = 0;
232 shad["texOccFull"] = 2;
233 shad["texOccSmall"] = 3;
236 shadBlur = new Shader("light_blur", loadTextFile("shaders/srlight_blur.frag"));
237 shadBlur.exec((Shader shad) {
238 shad["texDist"] = 0;
239 shad["texBg"] = 1;
240 shad["texOcc"] = 2;
241 shad["texOccSmall"] = 3;
244 shadBlurOcc = new Shader("light_blur_occ", loadTextFile("shaders/srlight_blur_occ.frag"));
245 shadBlurOcc.exec((Shader shad) {
246 shad["texLMap"] = 0;
247 shad["texBg"] = 1;
248 shad["texOcc"] = 2;
249 shad["texOccSmall"] = 3;
252 shadAmbient = new Shader("light_ambient", loadTextFile("shaders/srlight_ambient.frag"));
253 shadAmbient.exec((Shader shad) {
254 shad["texBg"] = 1;
255 shad["texOcc"] = 2;
256 shad["texOccSmall"] = 3;
259 fboOccluders = new FBO(MaxLightRadius*2, MaxLightRadius*2, Texture.Option.Clamp, Texture.Option.Linear);
260 //TODO: this sux!
261 foreach (int sz; 2..MaxLightRadius+1) {
262 fboDistMap[sz] = new FBO(sz*2, 1, Texture.Option.Clamp, Texture.Option.Linear); // create 1d distance map FBO
265 editorImg = new TrueColorImage(vlWidth, vlHeight);
266 editorImg.imageData.colors[] = Color(0, 0, 0, 0);
267 fboEditor = new FBO(vlWidth, vlHeight, Texture.Option.Nearest);
269 texSigil = new Texture("console/sigil_of_baphomet.png", Texture.Option.Nearest);
270 fboConsole = new FBO(vlWidth, vlHeight, Texture.Option.Nearest);
271 loadConFont();
273 // setup matrices
274 glMatrixMode(GL_MODELVIEW);
275 glLoadIdentity();
277 loadSmFont();
278 loadBfFont();
279 loadAllMonsterGraphics();
283 // ////////////////////////////////////////////////////////////////////////// //
284 void renderConsoleFBO () {
285 enum XOfs = 2;
286 __gshared ulong lastChange = 0;
287 if (lastChange == cbufLastChange) return;
288 // rerender console
289 lastChange = cbufLastChange;
290 //foreach (auto s; conbufLinesRev) stdout.writeln(s, "|");
291 fboConsole.exec((FBO me) {
292 bindTexture(0);
293 orthoCamera(me.width, me.height);
294 // clear it
295 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
296 glClear(GL_COLOR_BUFFER_BIT);
298 glDisable(GL_BLEND);
299 glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
300 glRectf(0, 0, me.width-1, me.height-1);
302 // text
303 glEnable(GL_BLEND);
304 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
305 // draw sigil
306 glColor4f(1.0f, 1.0f, 1.0f, 0.2f);
307 drawAtXY(texSigil, (me.width-texSigil.width)/2, (vlHeight-rConsoleHeight)/2+(me.height-texSigil.height)/2);
308 // draw text
309 glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
310 int y = me.height-conCharHeight-2;
311 //glPushAttrib(GL_LIST_BIT/*|GL_TEXTURE_BIT*/);
312 //scope(exit) glPopAttrib();
313 glPushMatrix();
314 scope(exit) glPopMatrix();
315 glTranslatef(XOfs, y, 0);
316 foreach (auto line; conbufLinesRev) {
317 if (line.length == 0) {
318 glPopMatrix();
319 glPushMatrix();
320 y -= conCharHeight;
321 glTranslatef(XOfs, y, 0);
322 } else {
323 usize pos = line.length;
324 for (;;) {
325 int w = 0;
326 usize sp = pos;
327 while (sp > 0) {
328 char ch = line[sp-1];
329 if (w+conCharWidth(ch) > me.width-XOfs*2) break;
330 w += conCharWidth(ch);
331 --sp;
333 foreach (immutable p; sp..pos) conDrawChar(line[p]);
334 glPopMatrix();
335 glPushMatrix();
336 y -= conCharHeight;
337 glTranslatef(XOfs, y, 0);
338 if (sp == 0 || y < 0) break;
339 pos = sp;
342 if (y < 0) break;
348 // ////////////////////////////////////////////////////////////////////////// //
349 // should be called when OpenGL is initialized
350 void loadMap (string mapname) {
351 mapscripts.runUnloading(); // "map unloading" script
352 clearMapScripts();
354 if (map !is null) map.clear();
355 map = new LevelMap(mapname);
356 curmapname = mapname;
358 ugInit(map.width*TileSize, map.height*TileSize);
360 map.oglBuildMega();
361 mapTilesChanged = 0;
363 if (fboLevel !is null) fboLevel.clear();
364 if (fboLevelLight !is null) fboLevelLight.clear();
365 if (fboOrigBack !is null) fboOrigBack.clear();
366 if (fboLMaskSmall !is null) fboLMaskSmall.clear();
368 fboLevel = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // final level render will be here
369 fboLevelLight = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // level lights will be rendered here
370 fboOrigBack = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest/*, Texture.Option.Depth*/); // background+foreground
371 fboLMaskSmall = new FBO(map.width, map.height, Texture.Option.Nearest); // small lightmask
373 shadToPolar.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize-1, map.height*TileSize-1); });
374 shadBlur.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
375 shadBlurOcc.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
376 shadAmbient.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
378 glActiveTexture(GL_TEXTURE0+0);
379 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
380 orthoCamera(vlWidth, vlHeight);
382 Actor.resetStorage();
384 setupMapScripts();
385 mapscripts.runInit();
386 loadMapMonsters();
387 dotInit();
388 mapscripts.runLoaded();
390 // save first snapshot
391 if (prevFrameActorsData.length == 0) prevFrameActorsData = new ubyte[](Actor.actorSize*65536); // ~15-20 megabytes
392 prevFrameActorOfs[] = uint.max; // just for fun
393 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
395 levelLoaded = true;
397 { import core.memory : GC; GC.collect(); }
401 // ////////////////////////////////////////////////////////////////////////// //
402 //FIXME: optimize!
403 __gshared uint mapTilesChanged = 0;
406 //WARNING! this can be called only from DACS, so we don't have to sync it!
407 public void mapDirty (uint layermask) { mapTilesChanged |= layermask; }
410 void rebuildMapMegaTextures () {
411 //fbo.replaceTexture
412 //mapTilesChanged = false;
413 //map.clearMegaTextures();
414 map.oglBuildMega(mapTilesChanged);
415 mapTilesChanged = 0;
416 dotsAwake(); // let dormant dots fall
420 // ////////////////////////////////////////////////////////////////////////// //
421 // messages
422 struct Message {
423 enum Phase { FadeIn, Stay, FadeOut }
424 Phase phase;
425 int alpha;
426 int pauseMsecs;
427 MonoTime removeTime;
428 char[256] text;
429 usize textlen;
432 //private import core.sync.mutex : Mutex;
434 __gshared Message[128] messages;
435 __gshared uint messagesUsed = 0;
437 //__gshared Mutex messageLock;
438 //shared static this () { messageLock = new Mutex(); }
441 void addMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
442 //messageLock.lock();
443 //scope(exit) messageLock.unlock();
444 if (msgtext.length == 0) return;
445 conwriteln(msgtext);
446 if (pauseMsecs <= 50) return;
447 if (messagesUsed == messages.length) {
448 // remove top message
449 foreach (immutable cidx; 1..messagesUsed) messages.ptr[cidx-1] = messages.ptr[cidx];
450 messages.ptr[0].alpha = 255;
451 --messagesUsed;
453 // quick replace
454 if (!noreplace && messagesUsed == 1) {
455 switch (messages.ptr[0].phase) {
456 case Message.Phase.FadeIn:
457 messages.ptr[0].phase = Message.Phase.FadeOut;
458 break;
459 case Message.Phase.Stay:
460 messages.ptr[0].phase = Message.Phase.FadeOut;
461 messages.ptr[0].alpha = 255;
462 break;
463 default:
466 auto msg = messages.ptr+messagesUsed;
467 ++messagesUsed;
468 msg.phase = Message.Phase.FadeIn;
469 msg.alpha = 0;
470 msg.pauseMsecs = pauseMsecs;
471 // copy text
472 if (msgtext.length > msg.text.length) {
473 msg.text = msgtext[0..msg.text.length];
474 msg.textlen = msg.text.length;
475 } else {
476 msg.text[0..msgtext.length] = msgtext[];
477 msg.textlen = msgtext.length;
482 void doMessages (MonoTime curtime) {
483 //messageLock.lock();
484 //scope(exit) messageLock.unlock();
486 if (messagesUsed == 0) return;
487 glEnable(GL_BLEND);
488 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
490 Message* msg;
492 again:
493 msg = messages.ptr;
494 final switch (msg.phase) {
495 case Message.Phase.FadeIn:
496 if ((msg.alpha += 10) >= 255) {
497 msg.phase = Message.Phase.Stay;
498 msg.removeTime = curtime+dur!"msecs"(msg.pauseMsecs);
499 goto case; // to stay
501 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
502 break;
503 case Message.Phase.Stay:
504 if (msg.removeTime <= curtime) {
505 msg.alpha = 255;
506 msg.phase = Message.Phase.FadeOut;
507 goto case; // to fade
509 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
510 break;
511 case Message.Phase.FadeOut:
512 if ((msg.alpha -= 10) <= 0) {
513 if (--messagesUsed == 0) return;
514 // remove this message
515 foreach (immutable cidx; 1..messagesUsed+1) messages.ptr[cidx-1] = messages.ptr[cidx];
516 goto again;
518 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
519 break;
522 smDrawText(10, 10, msg.text[0..msg.textlen]);
526 // ////////////////////////////////////////////////////////////////////////// //
527 //mixin(Actor.FieldPropMixin!("0drawlistpos", uint));
529 mixin(Actor.FieldGetMixin!("classtype", StrId)); // fget_classtype
530 mixin(Actor.FieldGetMixin!("classname", StrId)); // fget_classname
531 mixin(Actor.FieldGetMixin!("x", int));
532 mixin(Actor.FieldGetMixin!("y", int));
533 mixin(Actor.FieldGetMixin!("s", int));
534 mixin(Actor.FieldGetMixin!("radius", int));
535 mixin(Actor.FieldGetMixin!("height", int));
536 mixin(Actor.FieldGetMixin!("flags", uint));
537 mixin(Actor.FieldGetMixin!("zAnimstate", StrId));
538 mixin(Actor.FieldGetMixin!("zAnimidx", int));
539 mixin(Actor.FieldGetMixin!("dir", uint));
540 mixin(Actor.FieldGetMixin!("attLightXOfs", int));
541 mixin(Actor.FieldGetMixin!("attLightYOfs", int));
542 mixin(Actor.FieldGetMixin!("attLightRGBX", uint));
544 //mixin(Actor.FieldGetPtrMixin!("classtype", StrId)); // fget_classtype
545 //mixin(Actor.FieldGetPtrMixin!("classname", StrId)); // fget_classname
546 mixin(Actor.FieldGetPtrMixin!("x", int));
547 mixin(Actor.FieldGetPtrMixin!("y", int));
548 //mixin(Actor.FieldGetPtrMixin!("flags", uint));
549 //mixin(Actor.FieldGetPtrMixin!("zAnimstate", StrId));
550 //mixin(Actor.FieldGetPtrMixin!("zAnimidx", int));
551 //mixin(Actor.FieldGetPtrMixin!("dir", uint));
552 //mixin(Actor.FieldGetPtrMixin!("attLightXOfs", int));
553 //mixin(Actor.FieldGetPtrMixin!("attLightYOfs", int));
554 //mixin(Actor.FieldGetPtrMixin!("attLightRGBX", uint));
557 // ////////////////////////////////////////////////////////////////////////// //
558 __gshared int vportX0, vportY0, vportX1, vportY1;
561 // ////////////////////////////////////////////////////////////////////////// //
562 void renderLightAmbient() (int lightX, int lightY, int lightW, int lightH, in auto ref SVec4F lcol) {
563 //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));
564 if (lightW < 1 || lightH < 1) return;
565 int lightX1 = lightX+lightW-1;
566 int lightY1 = lightY+lightH-1;
567 // clip light to viewport
568 if (lightX < vportX0) lightX = vportX0;
569 if (lightY < vportY0) lightY = vportY0;
570 if (lightX1 > vportX1) lightX1 = vportX1;
571 if (lightY1 > vportY1) lightY1 = vportY1;
572 // is this light visible?
573 //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));
574 if (lightX1 < lightX || lightY1 < lightY || lightX > vportX1 || lightY > vportY1 || lightX1 < vportX0 || lightY1 < vportY0) return;
575 //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));
577 fboLevelLight.exec({
578 bindTexture(0);
579 glEnable(GL_BLEND);
580 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
581 //glDisable(GL_BLEND);
582 orthoCamera(map.width*TileSize, map.height*TileSize);
583 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
584 shadAmbient.exec((Shader shad) {
585 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
586 //shad["lightPos"] = SVec2F(lightX, lightY);
587 glRectf(lightX, lightY, lightX1, lightY1);
593 // ////////////////////////////////////////////////////////////////////////// //
594 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
595 if (lightRadius < 2) return;
596 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
597 int lightSize = lightRadius*2;
598 // is this light visible?
599 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*TileSize || lightY-lightRadius >= map.height*TileSize) return;
601 // out of viewport -- do nothing
602 if (lightX+lightRadius < vportX0 || lightY+lightRadius < vportY0) return;
603 if (lightX-lightRadius > vportX1 || lightY-lightRadius > vportY1) return;
605 if (lightX >= 0 && lightY >= 0 && lightX < map.width*TileSize && lightY < map.height*TileSize &&
606 map.teximgs[map.LightMask].imageData.colors.ptr[lightY*(map.width*TileSize)+lightX].a > 190) return;
608 // common color for all the following
609 glDisable(GL_BLEND);
610 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
612 // build 1d distance map to fboShadowMapId
613 fboDistMap.ptr[lightRadius].exec({
614 // no need to clear it, shader will take care of that
615 shadToPolar.exec((Shader shad) {
616 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
617 shad["lightPos"] = SVec2F(lightX, lightY);
618 orthoCamera(lightSize, 1);
619 // it doesn't matter what we will draw here, so just draw filled rect
620 glRectf(0, 0, lightSize, 1);
624 // build light texture for blending
625 fboOccluders.exec({
626 // no need to clear it, shader will take care of that
627 // debug
628 //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
629 //glClear(GL_COLOR_BUFFER_BIT);
630 shadBlur.exec((Shader shad) {
631 shad["lightTexSize"] = SVec2F(lightSize, MaxLightRadius*2); // x: size of distmap; y: size of this texture
632 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
633 shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
634 orthoCamera(fboOccluders.tex.width, fboOccluders.tex.height);
635 //drawAtXY(fboDistMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
636 bindTexture(fboDistMap.ptr[lightRadius].tex.tid);
637 glBegin(GL_QUADS);
638 glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0); // top-left
639 glTexCoord2f(1.0f, 0.0f); glVertex2i(lightSize, 0); // top-right
640 glTexCoord2f(1.0f, 1.0f); glVertex2i(lightSize, lightSize); // bottom-right
641 glTexCoord2f(0.0f, 1.0f); glVertex2i(0, lightSize); // bottom-left
642 glEnd();
646 // blend light texture
647 fboLevelLight.exec({
648 glEnable(GL_BLEND);
649 //glDisable(GL_BLEND);
650 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
651 orthoCamera(fboLevelLight.tex.width, fboLevelLight.tex.height);
652 //drawAtXY(fboOccluders.tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
653 float occe = 1.0f*lightSize/(MaxLightRadius*2);
654 float occs = 1.0f-occe;
655 int x0 = lightX-lightRadius+0;
656 int y1 = lightY-lightRadius+0;
657 int x1 = lightX+lightRadius-1+1;
658 int y0 = lightY+lightRadius-1+1;
659 bindTexture(fboOccluders.tex.tid);
660 glBegin(GL_QUADS);
662 glTexCoord2f(0.0f, 0.0f); glVertex2i(x0, y0); // top-left
663 glTexCoord2f(occe, 0.0f); glVertex2i(x1, y0); // top-right
664 glTexCoord2f(occe, occe); glVertex2i(x1, y1); // bottom-right
665 glTexCoord2f(0.0f, occe); glVertex2i(x0, y1); // bottom-left
667 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
668 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
669 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
670 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
671 glEnd();
673 bindTexture(0);
674 glRectf(x0, y0, x1, y1);
676 // and blend it again, with the shader that will touch only occluders
677 shadBlurOcc.exec((Shader shad) {
678 //shad["lightTexSize"] = SVec2F(lightSize, lightSize);
679 shad["lightTexSize"] = SVec2F(lightSize, fboOccluders.tex.height);
680 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
681 shad["lightPos"] = SVec2F(lightX, lightY);
682 //shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
683 bindTexture(fboOccluders.tex.tid);
684 glBegin(GL_QUADS);
685 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
686 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
687 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
688 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
689 glEnd();
690 //drawAtXY(fboOccluders.tex.tid, lightX-lightRadius, lightY-lightRadius, lightSize, lightSize, mirrorY:true);
696 // ////////////////////////////////////////////////////////////////////////// //
697 __gshared int testLightX = vlWidth/2, testLightY = vlHeight/2;
698 __gshared bool testLightMoved = false;
699 //__gshared int mapOfsX, mapOfsY;
700 //__gshared bool movement = false;
701 __gshared float iLiquidTime = 0.0;
702 //__gshared bool altMove = false;
705 void renderScene (MonoTime curtime) {
706 //enum BackIntens = 0.05f;
707 enum BackIntens = 0.0f;
709 gloStackClear();
710 float atob = (curtime > lastthink ? cast(float)((curtime-lastthink).total!"msecs")/cast(float)((nextthink-lastthink).total!"msecs") : 1.0f);
711 if (gamePaused || inEditMode) atob = 1.0f;
712 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
715 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
716 int curfp = cast(int)((curtime-lastthink).total!"msecs");
717 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
721 int mofsx, mofsy; // camera offset, will be set in background layer builder
723 if (mapTilesChanged != 0) rebuildMapMegaTextures();
725 // build background layer
726 fboOrigBack.exec({
727 //glDisable(GL_BLEND);
728 //glClearDepth(1.0f);
729 //glDepthFunc(GL_LESS);
730 //glDepthFunc(GL_NEVER);
731 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
732 glClear(GL_COLOR_BUFFER_BIT/*|GL_DEPTH_BUFFER_BIT*/);
733 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
734 orthoCamera(map.width*TileSize, map.height*TileSize);
736 glEnable(GL_BLEND);
737 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
738 // draw sky
740 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
741 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
742 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
743 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
745 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*TileSize, map.MapSize*TileSize);
746 // draw background
747 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
748 // draw distorted liquid areas
749 shadLiquidDistort.exec((Shader shad) {
750 shad["iDistortTime"] = iLiquidTime;
751 drawAtXY(map.texgl.ptr[map.AllLiquids], 0, 0);
753 // monsters, items; we'll do linear interpolation here
754 glColor3f(1.0f, 1.0f, 1.0f);
755 //glEnable(GL_DEPTH_TEST);
756 attachedLightCount = 0;
758 // who cares about memory?!
759 // draw order: players, items, monsters, other
760 static struct DrawInfo {
761 ActorDef adef;
762 ActorId aid;
763 int actorX, actorY;
764 @disable this (this); // no copies
766 enum { Pixels, Players, Items, Monsters, Other }
767 __gshared DrawInfo[65536][4] drawlists;
768 __gshared uint[4] dlpos;
769 DrawInfo camchickdi;
771 dlpos[] = 0;
773 Actor.forEach((ActorId me) {
774 //me.fprop_0drawlistpos = 0;
775 if (auto adef = findActorDef(me)) {
776 uint dlnum = Other;
777 switch (adef.classtype.get) {
778 case "monster": dlnum = (adef.classname.get != "Player" ? Monsters : Players); break;
779 case "item": dlnum = Items; break;
780 default: dlnum = Other; break;
782 if (me.fget_flags&AF_PIXEL) dlnum = Pixels;
783 int actorX, actorY; // current actor position
785 auto ofs = prevFrameActorOfs.ptr[me.id&0xffff];
786 if (frameInterpolation && ofs < uint.max-1 && (me.fget_flags&AF_TELEPORT) == 0 && Actor.isSameSavedActor(me.id, prevFrameActorsData.ptr, ofs)) {
787 import core.stdc.math : roundf;
788 auto xptr = prevFrameActorsData.ptr+ofs;
789 int ox = xptr.fgetp_x;
790 int nx = me.fget_x;
791 int oy = xptr.fgetp_y;
792 int ny = me.fget_y;
793 actorX = cast(int)(ox+roundf((nx-ox)*atob));
794 actorY = cast(int)(oy+roundf((ny-oy)*atob));
795 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
796 } else {
797 actorX = me.fget_x;
798 actorY = me.fget_y;
801 if (me.id == cameraChick.id) {
802 camchickdi.adef = adef;
803 camchickdi.aid = me;
804 camchickdi.actorX = actorX;
805 camchickdi.actorY = actorY;
807 // draw sprite
808 if ((me.fget_flags&AF_NODRAW) == 0) {
809 //auto dl = &drawlists[dlnum][dlpos.ptr[dlnum]];
810 //me.fprop_0drawlistpos = (dlpos.ptr[dlnum]&0xffff)|((dlnum&0xff)<<16);
811 auto dl = drawlists.ptr[dlnum].ptr+dlpos.ptr[dlnum];
812 ++dlpos.ptr[dlnum];
813 dl.adef = adef;
814 dl.aid = me;
815 dl.actorX = actorX;
816 dl.actorY = actorY;
818 // process attached lights
819 if ((me.fget_flags&AF_NOLIGHT) == 0) {
820 uint alr = me.fget_attLightRGBX;
821 bool isambient = (me.fget_classtype.get == "light" && me.fget_classname.get == "Ambient");
822 if ((alr&0xff) >= 4 || (isambient && me.fget_radius >= 1 && me.fget_height >= 1)) {
823 //if (isambient) conwriteln("isambient: ", isambient, "; x=", me.fget_x, "; y=", me.fget_y, "; w=", me.fget_radius, "; h=", me.fget_height);
824 // yep, add it
825 auto li = attachedLights.ptr+attachedLightCount;
826 ++attachedLightCount;
827 li.type = (!isambient ? AttachedLightInfo.Type.Point : AttachedLightInfo.Type.Ambient);
828 li.x = actorX+me.fget_attLightXOfs;
829 li.y = actorY+me.fget_attLightYOfs;
830 li.r = ((alr>>24)&0xff)/255.0f; // red or intensity
831 if ((alr&0x00_ff_ff_00U) == 0x00_00_01_00U) {
832 li.uncolored = true;
833 } else {
834 li.g = ((alr>>16)&0xff)/255.0f;
835 li.b = ((alr>>8)&0xff)/255.0f;
836 li.uncolored = false;
838 li.radius = (alr&0xff);
839 if (li.radius > MaxLightRadius) li.radius = MaxLightRadius;
840 if (isambient) {
841 li.w = me.fget_radius;
842 li.h = me.fget_height;
846 } else {
847 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
851 // draw actor lists
852 foreach_reverse (uint dlnum; 0..drawlists.length) {
853 if (dlnum == Pixels) continue;
854 auto dl = drawlists.ptr[dlnum].ptr;
855 if (dlnum == Players) dl += dlpos.ptr[dlnum]-1;
856 foreach (uint idx; 0..dlpos.ptr[dlnum]) {
857 auto me = dl.aid;
858 if (auto isp = dl.adef.animSpr(me.fget_zAnimstate, me.fget_dir, me.fget_zAnimidx)) {
859 //drawAtXY(isp.tex, dl.actorX-isp.vga.sx, dl.actorY-isp.vga.sy);
860 isp.drawAtXY(dl.actorX, dl.actorY);
862 if (dlnum != Players) ++dl; else --dl;
865 // draw pixels
866 if (dlpos[Pixels]) {
867 bindTexture(0);
868 bool pointsStarted = false;
869 Color lastColor = Color(0, 0, 0, 0);
870 auto dl = drawlists.ptr[Pixels].ptr;
871 foreach (uint idx; 0..dlpos.ptr[Pixels]) {
872 auto me = dl.aid;
873 auto s = me.fget_s;
874 if (s < 0 || s > 255) continue; //FIXME
875 Color clr = d2dpal.ptr[s&0xff];
876 if (clr.a == 0) continue;
877 if (clr != lastColor) {
878 if (pointsStarted) glEnd();
879 glColor4f(clr.r/255.0f, clr.g/255.0f, clr.b/255.0f, clr.a/255.0f);
880 lastColor = clr;
881 pointsStarted = false;
883 if (!pointsStarted) {
884 glBegin(GL_POINTS);
885 pointsStarted = true;
887 glVertex2i(dl.actorX, dl.actorY);
888 ++dl;
890 if (pointsStarted) {
891 glEnd();
892 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
896 // camera movement
897 if (/*altMove || movement ||*/ scale == 1 || !cameraChick.valid) {
898 mofsx = 0;
899 mofsy = 0;
900 vportX0 = 0;
901 vportY0 = 0;
902 vportX1 = map.width*TileSize;
903 vportY1 = map.height*TileSize;
904 } else {
905 int tiltHeight = /*getMapViewHeight()*/(vlHeight/scale)/4;
906 int vy = cameraChick.looky!int;
907 if (vy < -tiltHeight) vy = -tiltHeight; else if (vy > tiltHeight) vy = tiltHeight;
908 int swdt = vlWidth/scale;
909 int shgt = vlHeight/scale;
910 int x = camchickdi.actorX-swdt/2;
911 int y = (camchickdi.actorY+vy)-shgt/2;
912 if (x < 0) x = 0; else if (x >= map.width*TileSize-swdt) x = map.width*TileSize-swdt-1;
913 if (y < 0) y = 0; else if (y >= map.height*TileSize-shgt) y = map.height*TileSize-shgt-1;
914 mofsx = x*2;
915 mofsy = y*2;
916 vportX0 = mofsx/scale;
917 vportY0 = mofsy/scale;
918 vportX1 = vportX0+vlWidth/scale;
919 vportY1 = vportY0+vlHeight/scale;
922 //glDisable(GL_DEPTH_TEST);
923 // draw dots
924 dotDraw(atob);
925 // do liquid coloring
926 drawAtXY(map.texgl.ptr[map.LiquidMask], 0, 0);
927 // foreground -- hide secrets, draw lifts and such
928 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
931 enum r = 255;
932 enum g = 0;
933 enum b = 0;
934 enum a = 255;
935 bindTexture(0);
936 glColor4f(r/255.0f, g/255.0f, b/255.0f, a/255.0f);
937 glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
938 if (cameraChick.valid) {
939 glBegin(GL_POINTS);
940 glVertex2i(camchickdi.actorX, camchickdi.actorY-70);
941 glEnd();
942 //glRectf(camchickdi.actorX, camchickdi.actorY-70, camchickdi.actorX+4, camchickdi.actorY-70+4);
944 //glRectf(0, 0, 300, 300);
945 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
951 if (doLighting) {
952 glDisable(GL_BLEND);
953 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
955 // make smaller occluder texture, so we can trace faster
956 //assert(fboLMaskSmall.tex.width == map.width);
957 //assert(fboLMaskSmall.tex.height == map.height);
958 fboLMaskSmall.exec({
959 orthoCamera(map.width, map.height);
960 drawAtXY(map.texgl.ptr[map.LightMask].tid, 0, 0, map.width, map.height, mirrorY:true);
963 // clear light layer
964 fboLevelLight.exec({
965 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
966 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
967 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
968 glClear(GL_COLOR_BUFFER_BIT);
971 // texture 1 is background
972 glActiveTexture(GL_TEXTURE0+1);
973 glBindTexture(GL_TEXTURE_2D, fboOrigBack.tex.tid);
974 // texture 2 is occluders
975 glActiveTexture(GL_TEXTURE0+2);
976 glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.LightMask].tid);
977 // texture 3 is small occluder map
978 glActiveTexture(GL_TEXTURE0+3);
979 glBindTexture(GL_TEXTURE_2D, fboLMaskSmall.tex.tid);
980 // done texture assign
981 glActiveTexture(GL_TEXTURE0+0);
984 enum LYOfs = 1;
986 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
987 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
988 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
989 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
990 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
991 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
992 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
993 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
994 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
995 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
997 renderLight(24*TileSize+4, (24+18)*TileSize-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
999 renderLight(280, 330, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
1002 foreach (; 0..1) {
1003 // attached lights
1004 foreach (ref li; attachedLights[0..attachedLightCount]) {
1005 if (li.type == AttachedLightInfo.Type.Ambient) {
1006 //conwriteln("ambient: x=", li.x, "; y=", li.y, "; w=", li.w, "; h=", li.h);
1007 // ambient light
1008 if (li.uncolored) {
1009 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(0.0f, 0.0f, 0.0f, li.r));
1010 } else {
1011 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(li.r, li.g, li.b, 1.0f));
1013 } else if (li.type == AttachedLightInfo.Type.Point) {
1014 // point light
1015 if (li.uncolored) {
1016 renderLight(li.x, li.y, SVec4F(0.0f, 0.0f, 0.0f, li.r), li.radius);
1017 } else {
1018 renderLight(li.x, li.y, SVec4F(li.r, li.g, li.b, 1.0f), li.radius);
1025 if (testLightMoved) {
1026 testLightX = testLightX/scale+mofsx/scale;
1027 testLightY = testLightY/scale+mofsy/scale;
1028 testLightMoved = false;
1030 foreach (immutable _; 0..1) {
1031 renderLight(testLightX, testLightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
1035 glActiveTexture(GL_TEXTURE0+1);
1036 glBindTexture(GL_TEXTURE_2D, 0);
1037 glActiveTexture(GL_TEXTURE0+2);
1038 glBindTexture(GL_TEXTURE_2D, 0);
1039 glActiveTexture(GL_TEXTURE0+3);
1040 glBindTexture(GL_TEXTURE_2D, 0);
1041 glActiveTexture(GL_TEXTURE0+0);
1044 // draw scaled level
1046 shadScanlines.exec((Shader shad) {
1047 shad["scanlines"] = scanlines;
1048 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
1049 glClear(GL_COLOR_BUFFER_BIT);
1050 orthoCamera(vlWidth, vlHeight);
1051 //orthoCamera(map.width*TileSize*scale, map.height*TileSize*scale);
1052 //glMatrixMode(GL_MODELVIEW);
1053 //glLoadIdentity();
1054 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
1055 // somehow, FBO objects are mirrored; wtf?!
1056 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1057 //glLoadIdentity();
1062 fboLevelLight.exec({
1063 smDrawText(map.width*TileSize/2, map.height*TileSize/2, "Testing...");
1068 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
1070 glDisable(GL_BLEND);
1073 fboOrigBack.exec({
1074 //auto img = smfont.ptr[0x39];
1075 auto img = fftest;
1076 if (img !is null) {
1077 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
1078 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
1085 { // http://stackoverflow.com/questions/7207422/setting-up-opengl-multiple-render-targets
1086 GLenum[1] buffers = [ GL_BACK_LEFT, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
1087 //GLenum[1] buffers = [ GL_NONE, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
1088 glDrawBuffers(1, buffers.ptr);
1093 orthoCamera(vlWidth, vlHeight);
1094 auto tex = (doLighting ? fboLevelLight.tex.tid : fboOrigBack.tex.tid);
1095 drawAtXY(tex, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1097 if (levelLoaded) {
1098 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1099 //orthoCamera(map.width*TileSize, map.height*TileSize);
1100 glEnable(GL_BLEND);
1101 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1102 hudScripts.runDraw();
1105 //drawAtXY(map.texgl.ptr[map.LightMask].tid, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1106 //drawAtXY(fboLMaskSmall.tex.tid, 0, 0, map.width*TileSize, map.height*TileSize);
1108 if (inEditMode) {
1109 glEnable(GL_BLEND);
1110 //glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1111 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1112 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1113 editorUpdateImage();
1114 fboEditor.tex.setFromImage(editorImg);
1115 drawAtXY(fboEditor.tex, 0, 0);
1116 glDisable(GL_BLEND);
1119 doMessages(curtime);
1121 if (rConsoleVisible) {
1122 renderConsoleFBO();
1123 orthoCamera(vlWidth, vlHeight);
1124 glEnable(GL_BLEND);
1125 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1126 glColor4f(1.0f, 1.0f, 1.0f, 0.7f);
1127 drawAtXY(fboConsole.tex, 0, rConsoleHeight-vlHeight, mirrorY:true);
1132 // ////////////////////////////////////////////////////////////////////////// //
1133 // returns time slept
1134 int sleepAtMaxMsecs (int msecs) {
1135 if (msecs > 0) {
1136 import core.sys.posix.signal : timespec;
1137 import core.sys.posix.time : nanosleep;
1138 timespec ts = void, tpassed = void;
1139 ts.tv_sec = 0;
1140 ts.tv_nsec = msecs*1000*1000+(500*1000); // milli to nano
1141 nanosleep(&ts, &tpassed);
1142 return (ts.tv_nsec-tpassed.tv_nsec)/(1000*1000);
1143 } else {
1144 return 0;
1149 // ////////////////////////////////////////////////////////////////////////// //
1150 mixin(import("editor.d"));
1153 // ////////////////////////////////////////////////////////////////////////// //
1154 // rendering thread
1155 shared int diedie = 0;
1157 enum D2DFrameTime = 55; // milliseconds
1158 enum MinFrameTime = 1000/60; // ~60 FPS
1160 public void renderThread (Tid starterTid) {
1161 enum BoolOptVarMsgMixin(string varname) =
1162 "if (msg.toggle) msg.value = !"~varname~";\n"~
1163 "if ("~varname~" != msg.value) "~varname~" = msg.value; else msg.showMessage = false;\n";
1165 send(starterTid, 42);
1166 try {
1167 MonoTime curtime = MonoTime.currTime;
1169 lastthink = curtime; // for interpolator
1170 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1171 MonoTime nextvframe = curtime;
1173 enum MaxFPSFrames = 16;
1174 float frtimes = 0.0f;
1175 int framenum = 0;
1176 int prevFPS = -1;
1177 int hushFrames = 6; // ignore first `hushFrames` frames overtime
1178 MonoTime prevFrameStartTime = curtime;
1180 bool vframeWasLost = false;
1182 void resetFrameTimers () {
1183 MonoTime curtime = MonoTime.currTime;
1184 lastthink = curtime; // for interpolator
1185 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1186 nextvframe = curtime;
1189 void loadNewLevel (string name) {
1191 if (levelLoaded) {
1192 conwriteln("ERROR: can't load new levels yet");
1193 return;
1196 if (name.length == 0) {
1197 conwriteln("ERROR: can't load empty level!");
1199 conwriteln("loading map '", name, "'");
1200 loadMap(name);
1201 resetFrameTimers();
1204 conRegFunc!({
1205 string mn = genNextMapName(0);
1206 if (mn.length) {
1207 nextmapname = null; // clear "exit" flag
1208 loadNewLevel(mn);
1209 } else {
1210 conwriteln("can't skip level");
1212 })("skiplevel", "skip current level");
1214 conRegFunc!({
1215 inEditMode = !inEditMode;
1216 if (inEditMode) sdwindow.hideCursor(); else sdwindow.showCursor();
1217 })("ed_toggle", "toggle editor");
1219 conRegFunc!({
1220 if (inEditMode) {
1221 inEditMode = false;
1222 sdwindow.showCursor();
1224 })("ed_exit", "exit from editor");
1226 conRegFunc!((string mapname) {
1227 nextmapname = null; // clear "exit" flag
1228 loadNewLevel(mapname);
1229 })("map", "load map");
1231 void receiveMessages () {
1232 for (;;) {
1233 import core.time : Duration;
1234 //conwriteln("rendering thread: waiting for messages...");
1235 auto got = receiveTimeout(
1236 Duration.zero, // don't wait
1237 (TMsgMessage msg) {
1238 addMessage(msg.text[0..msg.textlen], msg.pauseMsecs, msg.noreplace);
1240 (TMsgTestLightMove msg) {
1241 testLightX = msg.x;
1242 testLightY = msg.y;
1243 testLightMoved = true;
1245 (TMsgMouseEvent msg) { editorMouseEvent(msg); },
1246 (TMsgKeyEvent msg) { editorKeyEvent(msg); },
1247 (Variant v) {
1248 conwriteln("WARNING: unknown thread message received and ignored");
1251 if (!got) {
1252 // no more messages
1253 //conwriteln("rendering thread: no more messages");
1254 break;
1257 if (nextmapname.length) {
1258 string mn = nextmapname;
1259 nextmapname = null; // clear "exit" flag
1260 loadNewLevel(mn);
1264 void processConsoleCommands () {
1265 concmdbufLock.lock();
1266 scope(exit) concmdbufLock.unlock();
1267 concmdDoAll();
1270 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1271 bool doThinkFrame () {
1272 if (curtime >= nextthink) {
1273 lastthink = curtime;
1274 while (nextthink <= curtime) nextthink += dur!"msecs"(D2DFrameTime);
1275 if (levelLoaded) {
1276 // save snapshot and other data for interpolator
1277 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
1278 if (!gamePaused && !inEditMode) {
1279 // process actors
1280 doActorsThink();
1281 dotThink();
1284 // some timing
1285 auto tm = MonoTime.currTime;
1286 int thinkTime = cast(int)((tm-curtime).total!"msecs");
1287 if (thinkTime > 9) { import core.stdc.stdio; printf("spent on thinking: %d msecs\n", thinkTime); }
1288 curtime = tm;
1289 return true;
1290 } else {
1291 return false;
1295 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1296 bool doVFrame () {
1297 version(dont_use_vsync) {
1298 // timer
1299 enum doCheckTime = true;
1300 } else {
1301 // vsync
1302 __gshared bool prevLost = false;
1303 bool doCheckTime = vframeWasLost;
1304 if (vframeWasLost) {
1305 if (!prevLost) {
1306 { import core.stdc.stdio; printf("frame was lost!\n"); }
1308 prevLost = true;
1309 } else {
1310 prevLost = false;
1313 if (doCheckTime) {
1314 if (curtime < nextvframe) return false;
1315 version(dont_use_vsync) {
1316 if (curtime > nextvframe) {
1317 auto overtime = cast(int)((curtime-nextvframe).total!"msecs");
1318 if (overtime > 2500) {
1319 if (hushFrames) {
1320 --hushFrames;
1321 } else {
1322 { import core.stdc.stdio; printf(" spent whole %d msecs\n", overtime); }
1328 while (nextvframe <= curtime) nextvframe += dur!"msecs"(MinFrameTime);
1329 bool ctset = false;
1331 sdwindow.mtLock();
1332 scope(exit) sdwindow.mtUnlock();
1333 ctset = sdwindow.setAsCurrentOpenGlContextNT;
1335 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
1336 if (ctset) {
1337 // render scene
1338 iLiquidTime = cast(float)((curtime-MonoTime.zero).total!"msecs"%10000000)/18.0f*0.04f;
1339 receiveMessages(); // here, 'cause we need active OpenGL context for some messages
1340 processConsoleCommands();
1341 if (levelLoaded) {
1342 renderScene(curtime);
1343 } else {
1344 //renderLoading(curtime);
1346 sdwindow.mtLock();
1347 scope(exit) sdwindow.mtUnlock();
1348 sdwindow.swapOpenGlBuffers();
1349 glFinish();
1350 sdwindow.releaseCurrentOpenGlContext();
1351 vframeWasLost = false;
1352 } else {
1353 vframeWasLost = true;
1354 { import core.stdc.stdio; printf("xframe was lost!\n"); }
1356 curtime = MonoTime.currTime;
1357 return true;
1360 for (;;) {
1361 if (sdwindow.closed) break;
1362 if (atomicLoad(diedie) > 0) break;
1364 curtime = MonoTime.currTime;
1365 auto fstime = curtime;
1367 doThinkFrame(); // this will fix curtime if necessary
1368 if (doVFrame()) {
1369 if (!vframeWasLost) {
1370 // fps
1371 auto frameTime = cast(float)(curtime-prevFrameStartTime).total!"msecs"/1000.0f;
1372 prevFrameStartTime = curtime;
1373 frtimes += frameTime;
1374 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
1375 import std.string : format;
1376 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
1377 if (newFPS != prevFPS) {
1378 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
1379 prevFPS = newFPS;
1381 framenum = 0;
1382 frtimes = 0.0f;
1387 curtime = MonoTime.currTime;
1389 // now sleep until next "video" or "think" frame
1390 if (nextthink > curtime && nextvframe > curtime) {
1391 // let's decide
1392 immutable nextVideoFrameSleep = cast(int)((nextvframe-curtime).total!"msecs");
1393 immutable nextThinkFrameSleep = cast(int)((nextthink-curtime).total!"msecs");
1394 immutable sleepTime = (nextVideoFrameSleep < nextThinkFrameSleep ? nextVideoFrameSleep : nextThinkFrameSleep);
1395 sleepAtMaxMsecs(sleepTime);
1396 //curtime = MonoTime.currTime;
1399 } catch (Throwable e) {
1400 // here, we are dead and fucked (the exact order doesn't matter)
1401 import core.stdc.stdlib : abort;
1402 import core.stdc.stdio : fprintf, stderr;
1403 import core.memory : GC;
1404 GC.disable(); // yeah
1405 thread_suspendAll(); // stop right here, you criminal scum!
1406 auto s = e.toString();
1407 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
1408 abort(); // die, you bitch!
1410 import core.stdc.stdio;
1411 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
1412 import std.stdio : stderr;
1413 writeln(
1414 for (;;) {
1415 if (sdwindow.closed) break;
1416 if (atomicLoad(diedie) > 0) break;
1417 sleepAtMaxMsecs(100);
1421 atomicStore(diedie, 2);
1425 // ////////////////////////////////////////////////////////////////////////// //
1426 __gshared Tid renderTid;
1427 shared bool renderThreadStarted = false;
1430 public void startRenderThread () {
1431 if (!cas(&renderThreadStarted, false, true)) {
1432 assert(0, "render thread already started!");
1434 renderTid = spawn(&renderThread, thisTid);
1435 setMaxMailboxSize(renderTid, 1024, OnCrowding.throwException); //FIXME
1436 // wait for "i'm ready" signal
1437 receive(
1438 (int ok) {
1439 if (ok != 42) assert(0, "wtf?!");
1442 conwriteln("rendering thread started");
1446 // ////////////////////////////////////////////////////////////////////////// //
1447 public void closeWindow () {
1448 if (atomicLoad(diedie) != 2) {
1449 atomicStore(diedie, 1);
1450 while (atomicLoad(diedie) != 2) {}
1452 if (!sdwindow.closed) {
1453 flushGui();
1454 sdwindow.close();
1459 // ////////////////////////////////////////////////////////////////////////// //
1460 // thread messages
1463 // ////////////////////////////////////////////////////////////////////////// //
1464 struct TMsgMouseEvent {
1465 MouseEventType type;
1466 int x, y;
1467 int dx, dy;
1468 MouseButton button; /// See $(LREF MouseButton)
1469 int modifierState; /// See $(LREF ModifierState)
1472 public void postMouseEvent() (in auto ref MouseEvent evt) {
1473 if (!atomicLoad(renderThreadStarted)) return;
1474 TMsgMouseEvent msg;
1475 msg.type = evt.type;
1476 msg.x = evt.x;
1477 msg.y = evt.y;
1478 msg.dx = evt.dx;
1479 msg.dy = evt.dy;
1480 msg.button = evt.button;
1481 msg.modifierState = evt.modifierState;
1482 send(renderTid, msg);
1486 // ////////////////////////////////////////////////////////////////////////// //
1487 struct TMsgKeyEvent {
1488 Key key;
1489 uint hardwareCode;
1490 bool pressed;
1491 dchar character;
1492 uint modifierState;
1495 public void postKeyEvent() (in auto ref KeyEvent evt) {
1496 if (!atomicLoad(renderThreadStarted)) return;
1497 TMsgKeyEvent msg;
1498 msg.key = evt.key;
1499 msg.pressed = evt.pressed;
1500 msg.character = evt.character;
1501 msg.modifierState = evt.modifierState;
1502 send(renderTid, msg);
1506 // ////////////////////////////////////////////////////////////////////////// //
1507 struct TMsgTestLightMove {
1508 int x, y;
1511 public void postTestLightMove (int x, int y) {
1512 if (!atomicLoad(renderThreadStarted)) return;
1513 auto msg = TMsgTestLightMove(x, y);
1514 send(renderTid, msg);
1518 // ////////////////////////////////////////////////////////////////////////// //
1519 struct TMsgMessage {
1520 char[256] text;
1521 uint textlen;
1522 int pauseMsecs;
1523 bool noreplace;
1526 public void postAddMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
1527 if (!atomicLoad(renderThreadStarted)) return;
1528 if (msgtext.length > TMsgMessage.text.length) msgtext = msgtext[0..TMsgMessage.text.length];
1529 TMsgMessage msg;
1530 msg.textlen = cast(uint)msgtext.length;
1531 if (msg.textlen) msg.text[0..msg.textlen] = msgtext[0..msg.textlen];
1532 msg.pauseMsecs = pauseMsecs;
1533 msg.noreplace = noreplace;
1534 send(renderTid, msg);
1538 // ////////////////////////////////////////////////////////////////////////// //
1539 public void concmd (const(char)[] cmd) {
1540 //if (!atomicLoad(renderThreadStarted)) return;
1541 concmdbufLock.lock();
1542 scope(exit) concmdbufLock.unlock();
1543 concmdAdd(cmd);