d2dimage: better png writer
[dd2d.git] / render.d
blob9172da223f0e13b8758fcc938d97eabeb946f0b5
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); }
88 public @property bool conVisible () nothrow @trusted @nogc { return rConsoleVisible; }
91 // ////////////////////////////////////////////////////////////////////////// //
92 __gshared char[] concmdbuf;
93 __gshared uint concmdbufpos;
94 private import core.sync.mutex : Mutex;
95 __gshared Mutex concmdbufLock;
96 shared static this () { concmdbuf.length = 65536; concmdbufLock = new Mutex(); }
98 __gshared char[4096] concli = 0;
99 __gshared uint conclilen = 0;
101 __gshared char[4096][128] concmdhistory = void;
102 __gshared int conhisidx = -1;
103 shared static this () { foreach (ref hb; concmdhistory) hb[] = 0; }
105 __gshared int conskiplines = 0;
108 const(char)[] conhisAt (int idx) {
109 if (idx < 0 || idx >= concmdhistory.length) return null;
110 const(char)[] res = concmdhistory.ptr[idx][];
111 usize pos = 0;
112 while (pos < res.length && res.ptr[pos]) ++pos;
113 return res[0..pos];
117 int conhisFind (const(char)[] cmd) {
118 while (cmd.length && cmd[$-1] <= 32) cmd = cmd[0..$-1];
119 if (cmd.length > concmdhistory.ptr[0].length) cmd = cmd[0..concmdhistory.ptr[0].length];
120 if (cmd.length == 0) return -1;
121 foreach (int idx; 0..cast(int)concmdhistory.length) {
122 auto c = conhisAt(idx);
123 while (c.length > 0 && c[$-1] <= 32) c = c[0..$-1];
124 if (c == cmd) return idx;
126 return -1;
130 void conhisAdd (const(char)[] cmd) {
131 while (cmd.length && cmd[$-1] <= 32) cmd = cmd[0..$-1];
132 if (cmd.length > concmdhistory.ptr[0].length) cmd = cmd[0..concmdhistory.ptr[0].length];
133 if (cmd.length == 0) return;
134 auto idx = conhisFind(cmd);
135 if (idx >= 0) {
136 // remove command
137 foreach (immutable c; idx+1..concmdhistory.length) concmdhistory.ptr[c-1][] = concmdhistory.ptr[c][];
139 // make room
140 foreach (immutable c; 1..concmdhistory.length; reverse) concmdhistory.ptr[c][] = concmdhistory.ptr[c-1][];
141 concmdhistory.ptr[0][] = 0;
142 concmdhistory.ptr[0][0..cmd.length] = cmd[];
146 void concmdAdd (const(char)[] s) {
147 if (s.length) {
148 if (concmdbuf.length-concmdbufpos < s.length+1) {
149 concmdbuf.assumeSafeAppend.length += s.length-(concmdbuf.length-concmdbufpos)+512;
151 if (concmdbufpos > 0 && concmdbuf[concmdbufpos-1] != '\n') concmdbuf.ptr[concmdbufpos++] = '\n';
152 concmdbuf[concmdbufpos..concmdbufpos+s.length] = s[];
153 concmdbufpos += s.length;
157 // `null`: no more
158 void concmdDoAll () {
159 if (concmdbufpos == 0) return;
160 scope(exit) concmdbufpos = 0;
161 auto ebuf = concmdbufpos;
162 const(char)[] s = concmdbuf[0..concmdbufpos];
163 for (;;) {
164 while (s.length) {
165 auto cmd = conGetCommand(s);
166 if (cmd is null) break;
167 try {
168 conExecute(cmd);
169 } catch (Exception e) {
170 conwriteln("***ERROR: ", e.msg);
173 if (concmdbufpos <= ebuf) break;
174 s = concmdbuf[ebuf..concmdbufpos];
175 ebuf = concmdbufpos;
180 void concliChar (char ch) {
181 __gshared int prevWasEmptyAndTab = 0;
183 concmdbufLock.lock();
184 scope(exit) concmdbufLock.unlock();
185 //conLastChange = 0;
187 // autocomplete
188 if (ch == 9) {
189 if (conclilen == 0) {
190 if (++prevWasEmptyAndTab < 2) return;
191 } else {
192 prevWasEmptyAndTab = 0;
194 if (conclilen > 0) {
195 string minPfx = null;
196 // find longest command
197 foreach (auto name; conByCommand) {
198 if (name.length >= conclilen && name.length > minPfx.length && name[0..conclilen] == concli[0..conclilen]) minPfx = name;
200 //conwriteln("longest command: [", minPfx, "]");
201 // find longest prefix
202 foreach (auto name; conByCommand) {
203 if (name.length < conclilen) continue;
204 if (name[0..conclilen] != concli[0..conclilen]) continue;
205 usize pos = 0;
206 while (pos < name.length && pos < minPfx.length && minPfx.ptr[pos] == name.ptr[pos]) ++pos;
207 if (pos < minPfx.length) minPfx = minPfx[0..pos];
209 if (minPfx.length > concli.length) minPfx = minPfx[0..concli.length];
210 //conwriteln("longest prefix : [", minPfx, "]");
211 if (minPfx.length >= conclilen) {
212 // wow!
213 bool doRet = (minPfx.length > conclilen);
214 conLastChange = 0;
215 concli[0..minPfx.length] = minPfx[];
216 conclilen = cast(uint)minPfx.length;
217 if (conclilen < concli.length && conHasCommand(minPfx)) {
218 concli.ptr[conclilen++] = ' ';
219 doRet = true;
221 if (doRet) return;
224 // nope, print all available commands
225 bool needDelimiter = true;
226 foreach (auto name; conByCommand) {
227 if (conclilen > 0) {
228 if (name.length < conclilen) continue;
229 if (name[0..conclilen] != concli[0..conclilen]) continue;
231 if (needDelimiter) { conwriteln("----------------"); needDelimiter = false; }
232 conwriteln(name);
234 return;
236 // process other keys
237 prevWasEmptyAndTab = 0;
238 // remove last char
239 if (ch == 8) {
240 if (conclilen > 0) { conLastChange = 0; --conclilen; }
241 return;
243 // execute command
244 if (ch == 13) {
245 if (conskiplines) { conskiplines = 0; conLastChange = 0; }
246 if (conclilen > 0) {
247 conLastChange = 0;
248 conhisidx = -1;
249 conhisAdd(concli[0..conclilen]);
250 concmdAdd(concli[0..conclilen]);
251 conclilen = 0;
253 return;
255 // ^Y
256 if (ch == 25) {
257 if (conclilen > 0) { conLastChange = 0; conclilen = 0; }
258 return;
260 // up
261 if (ch == '\x01') {
262 ++conhisidx;
263 auto cmd = conhisAt(conhisidx);
264 if (cmd.length == 0) {
265 --conhisidx;
266 } else {
267 concli[0..cmd.length] = cmd[];
268 conclilen = cast(uint)cmd.length;
269 conLastChange = 0;
271 return;
273 // down
274 if (ch == '\x02') {
275 --conhisidx;
276 auto cmd = conhisAt(conhisidx);
277 if (cmd.length == 0 && conhisidx < -1) {
278 ++conhisidx;
279 } else {
280 concli[0..cmd.length] = cmd[];
281 conclilen = cast(uint)cmd.length;
282 conLastChange = 0;
284 return;
286 // page up
287 if (ch == '\x03') {
288 int lnx = (rConsoleHeight-4)/conCharHeight-2;
289 if (lnx < 1) lnx = 1;
290 conskiplines += lnx;
291 conLastChange = 0;
292 return;
294 // page down
295 if (ch == '\x04') {
296 if (conskiplines > 0) {
297 int lnx = (rConsoleHeight-4)/conCharHeight-2;
298 if (lnx < 1) lnx = 1;
299 if ((conskiplines -= lnx) < 0) conskiplines = 0;
300 conLastChange = 0;
302 return;
304 // other
305 if (ch < ' ' || ch > 127) return;
306 if (conclilen >= concli.length) return;
307 concli.ptr[conclilen++] = ch;
308 conLastChange = 0;
312 // ////////////////////////////////////////////////////////////////////////// //
313 shared static this () {
314 conRegVar!doLighting("r_lighting", "dynamic lighting");
315 conRegVar!gamePaused("g_pause", "pause game");
316 conRegVar!rConsoleVisible("r_console", "console visibility");
317 conRegVar!rConsoleHeight(16*3, vlHeight, "r_conheight");
318 rConsoleHeight = vlHeight-vlHeight/3;
319 rConsoleHeight = vlHeight/2;
320 conRegVar!frameInterpolation("r_interpolation", "interpolate camera and sprite movement");
321 conRegVar!scale(1, 2, "r_scale");
322 conRegFunc!({
323 cheatNoDoors = !cheatNoDoors;
324 if (cheatNoDoors) conwriteln("player ignores doors"); else conwriteln("player respects doors");
325 })("nodoorclip", "ignore doors");
326 conRegFunc!({
327 cheatNoWallClip = !cheatNoWallClip;
328 if (cheatNoWallClip) conwriteln("player ignores walls"); else conwriteln("player respects walls");
329 })("nowallclip", "ignore walls");
330 conRegFunc!({
331 import core.atomic;
332 atomicStore(vquitRequested, true);
333 })("quit", "quit game");
334 conRegFunc!((const(char)[] msg, int pauseMsecs=3000, bool noreplace=false) {
335 char[256] buf;
336 auto s = buf.conFormatStr(msg);
337 if (s.length) postAddMessage(s, pauseMsecs, noreplace);
338 })("hudmsg", "show hud message; hudmsg msg [pausemsecs [noreplace]]");
342 // ////////////////////////////////////////////////////////////////////////// //
343 // interpolation
344 __gshared ubyte[] prevFrameActorsData;
345 __gshared uint[65536] prevFrameActorOfs; // uint.max-1: dead; uint.max: last
346 __gshared MonoTime lastthink = MonoTime.zero; // for interpolator
347 __gshared MonoTime nextthink = MonoTime.zero;
348 __gshared bool frameInterpolation = true;
351 // ////////////////////////////////////////////////////////////////////////// //
352 // attached lights
353 struct AttachedLightInfo {
354 enum Type {
355 Point,
356 Ambient,
358 Type type;
359 int x, y;
360 int w, h; // for ambient lights
361 float r, g, b;
362 bool uncolored;
363 int radius;
366 __gshared AttachedLightInfo[65536] attachedLights;
367 __gshared uint attachedLightCount = 0;
370 // ////////////////////////////////////////////////////////////////////////// //
371 enum MaxLightRadius = 255;
374 // ////////////////////////////////////////////////////////////////////////// //
375 // for light
376 __gshared FBO[MaxLightRadius+1] fboDistMap;
377 __gshared FBO fboOccluders;
378 __gshared Shader shadToPolar, shadBlur, shadBlurOcc, shadAmbient;
379 __gshared TrueColorImage editorImg;
380 __gshared FBO fboEditor;
381 __gshared FBO fboConsole;
382 __gshared Texture texSigil;
384 __gshared FBO fboLevel, fboLevelLight, fboOrigBack, fboLMaskSmall;
385 __gshared Shader shadScanlines;
386 __gshared Shader shadLiquidDistort;
389 // ////////////////////////////////////////////////////////////////////////// //
390 // call once!
391 public void initOpenGL () {
392 gloStackClear();
394 glEnable(GL_TEXTURE_2D);
395 glDisable(GL_LIGHTING);
396 glDisable(GL_DITHER);
397 glDisable(GL_BLEND);
398 glDisable(GL_DEPTH_TEST);
400 // create shaders
401 shadScanlines = new Shader("scanlines", loadTextFile("shaders/srscanlines.frag"));
403 shadLiquidDistort = new Shader("liquid_distort", loadTextFile("shaders/srliquid_distort.frag"));
404 shadLiquidDistort.exec((Shader shad) {
405 shad["texLqMap"] = 0;
408 // lights
409 shadToPolar = new Shader("light_trace", loadTextFile("shaders/srlight_trace.frag"));
410 shadToPolar.exec((Shader shad) {
411 shad["texOcc"] = 0;
412 shad["texOccFull"] = 2;
413 shad["texOccSmall"] = 3;
416 shadBlur = new Shader("light_blur", loadTextFile("shaders/srlight_blur.frag"));
417 shadBlur.exec((Shader shad) {
418 shad["texDist"] = 0;
419 shad["texBg"] = 1;
420 shad["texOcc"] = 2;
421 shad["texOccSmall"] = 3;
424 shadBlurOcc = new Shader("light_blur_occ", loadTextFile("shaders/srlight_blur_occ.frag"));
425 shadBlurOcc.exec((Shader shad) {
426 shad["texLMap"] = 0;
427 shad["texBg"] = 1;
428 shad["texOcc"] = 2;
429 shad["texOccSmall"] = 3;
432 shadAmbient = new Shader("light_ambient", loadTextFile("shaders/srlight_ambient.frag"));
433 shadAmbient.exec((Shader shad) {
434 shad["texBg"] = 1;
435 shad["texOcc"] = 2;
436 shad["texOccSmall"] = 3;
439 fboOccluders = new FBO(MaxLightRadius*2, MaxLightRadius*2, Texture.Option.Clamp, Texture.Option.Linear);
440 //TODO: this sux!
441 foreach (int sz; 2..MaxLightRadius+1) {
442 fboDistMap[sz] = new FBO(sz*2, 1, Texture.Option.Clamp, Texture.Option.Linear); // create 1d distance map FBO
445 editorImg = new TrueColorImage(vlWidth, vlHeight);
446 editorImg.imageData.colors[] = Color(0, 0, 0, 0);
447 fboEditor = new FBO(vlWidth, vlHeight, Texture.Option.Nearest);
449 texSigil = new Texture("console/sigil_of_baphomet.png", Texture.Option.Nearest);
450 fboConsole = new FBO(vlWidth, vlHeight, Texture.Option.Nearest);
451 loadConFont();
453 // setup matrices
454 glMatrixMode(GL_MODELVIEW);
455 glLoadIdentity();
457 loadSmFont();
458 loadBfFont();
459 loadAllMonsterGraphics();
463 // ////////////////////////////////////////////////////////////////////////// //
464 __gshared ulong conLastChange = 0;
466 void renderConsoleFBO () {
467 enum XOfs = 2;
468 if (conLastChange == cbufLastChange) return;
469 concmdbufLock.lock();
470 scope(exit) concmdbufLock.unlock();
471 // rerender console
472 conLastChange = cbufLastChange;
473 //foreach (auto s; conbufLinesRev) stdout.writeln(s, "|");
474 int skipLines = conskiplines;
475 fboConsole.exec((FBO me) {
476 bindTexture(0);
477 orthoCamera(me.width, me.height);
478 // clear it
479 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
480 glClear(GL_COLOR_BUFFER_BIT);
482 glDisable(GL_BLEND);
483 glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
484 glRectf(0, 0, me.width-1, me.height-1);
486 // text
487 glEnable(GL_BLEND);
488 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
489 // draw sigil
490 glColor4f(1.0f, 1.0f, 1.0f, 0.2f);
491 drawAtXY(texSigil, (me.width-texSigil.width)/2, (vlHeight-rConsoleHeight)/2+(me.height-texSigil.height)/2);
492 // draw command line
493 int y = me.height-conCharHeight-2;
495 glPushMatrix();
496 scope(exit) glPopMatrix();
497 glTranslatef(XOfs, y, 0);
498 int w = conCharWidth('>');
499 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
500 conDrawChar('>');
501 uint spos = conclilen;
502 while (spos > 0) {
503 char ch = concli.ptr[spos-1];
504 if (w+conCharWidth(ch) > me.width-XOfs*2-12) break;
505 w += conCharWidth(ch);
506 --spos;
508 glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
509 foreach (char ch; concli[spos..conclilen]) conDrawChar(ch);
510 // cursor
511 bindTexture(0);
512 glColor4f(1.0f, 0.5f, 0.0f, 1.0f);
513 glRectf(0, 0, 12, 16);
514 y -= conCharHeight;
516 // draw console text
517 glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
518 glPushMatrix();
519 scope(exit) glPopMatrix();
520 glTranslatef(XOfs, y, 0);
521 foreach (auto line; conbufLinesRev) {
522 if (line.length == 0) {
523 if (skipLines-- <= 0) {
524 glPopMatrix();
525 glPushMatrix();
526 y -= conCharHeight;
527 glTranslatef(XOfs, y, 0);
529 } else {
530 usize pos = line.length;
531 for (;;) {
532 int w = 0;
533 usize sp = pos;
534 while (sp > 0) {
535 char ch = line[sp-1];
536 if (w+conCharWidth(ch) > me.width-XOfs*2) break;
537 w += conCharWidth(ch);
538 --sp;
540 if (skipLines-- <= 0) {
541 foreach (immutable p; sp..pos) conDrawChar(line[p]);
542 glPopMatrix();
543 glPushMatrix();
544 y -= conCharHeight;
545 glTranslatef(XOfs, y, 0);
547 if (sp == 0 || y < 0) break;
548 pos = sp;
551 if (y < 0) break;
557 // ////////////////////////////////////////////////////////////////////////// //
558 // should be called when OpenGL is initialized
559 void loadMap (string mapname) {
560 mapscripts.runUnloading(); // "map unloading" script
561 clearMapScripts();
563 if (map !is null) map.clear();
564 map = new LevelMap(mapname);
565 curmapname = mapname;
567 ugInit(map.width*TileSize, map.height*TileSize);
569 map.oglBuildMega();
570 mapTilesChanged = 0;
572 if (fboLevel !is null) fboLevel.clear();
573 if (fboLevelLight !is null) fboLevelLight.clear();
574 if (fboOrigBack !is null) fboOrigBack.clear();
575 if (fboLMaskSmall !is null) fboLMaskSmall.clear();
577 fboLevel = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // final level render will be here
578 fboLevelLight = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // level lights will be rendered here
579 fboOrigBack = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest/*, Texture.Option.Depth*/); // background+foreground
580 fboLMaskSmall = new FBO(map.width, map.height, Texture.Option.Nearest); // small lightmask
582 shadToPolar.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize-1, map.height*TileSize-1); });
583 shadBlur.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
584 shadBlurOcc.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
585 shadAmbient.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
587 glActiveTexture(GL_TEXTURE0+0);
588 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
589 orthoCamera(vlWidth, vlHeight);
591 Actor.resetStorage();
593 setupMapScripts();
594 mapscripts.runInit();
595 loadMapMonsters();
596 dotInit();
597 mapscripts.runLoaded();
599 // save first snapshot
600 if (prevFrameActorsData.length == 0) prevFrameActorsData = new ubyte[](Actor.actorSize*65536); // ~15-20 megabytes
601 prevFrameActorOfs[] = uint.max; // just for fun
602 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
604 levelLoaded = true;
606 { import core.memory : GC; GC.collect(); }
610 // ////////////////////////////////////////////////////////////////////////// //
611 //FIXME: optimize!
612 __gshared uint mapTilesChanged = 0;
615 //WARNING! this can be called only from DACS, so we don't have to sync it!
616 public void mapDirty (uint layermask) { mapTilesChanged |= layermask; }
619 void rebuildMapMegaTextures () {
620 //fbo.replaceTexture
621 //mapTilesChanged = false;
622 //map.clearMegaTextures();
623 map.oglBuildMega(mapTilesChanged);
624 mapTilesChanged = 0;
625 dotsAwake(); // let dormant dots fall
629 // ////////////////////////////////////////////////////////////////////////// //
630 // messages
631 struct Message {
632 enum Phase { FadeIn, Stay, FadeOut }
633 Phase phase;
634 int alpha;
635 int pauseMsecs;
636 MonoTime removeTime;
637 char[256] text;
638 usize textlen;
641 //private import core.sync.mutex : Mutex;
643 __gshared Message[128] messages;
644 __gshared uint messagesUsed = 0;
646 //__gshared Mutex messageLock;
647 //shared static this () { messageLock = new Mutex(); }
650 void addMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
651 //messageLock.lock();
652 //scope(exit) messageLock.unlock();
653 if (msgtext.length == 0) return;
654 conwriteln(msgtext);
655 if (pauseMsecs <= 50) return;
656 if (messagesUsed == messages.length) {
657 // remove top message
658 foreach (immutable cidx; 1..messagesUsed) messages.ptr[cidx-1] = messages.ptr[cidx];
659 messages.ptr[0].alpha = 255;
660 --messagesUsed;
662 // quick replace
663 if (!noreplace && messagesUsed == 1) {
664 switch (messages.ptr[0].phase) {
665 case Message.Phase.FadeIn:
666 messages.ptr[0].phase = Message.Phase.FadeOut;
667 break;
668 case Message.Phase.Stay:
669 messages.ptr[0].phase = Message.Phase.FadeOut;
670 messages.ptr[0].alpha = 255;
671 break;
672 default:
675 auto msg = messages.ptr+messagesUsed;
676 ++messagesUsed;
677 msg.phase = Message.Phase.FadeIn;
678 msg.alpha = 0;
679 msg.pauseMsecs = pauseMsecs;
680 // copy text
681 if (msgtext.length > msg.text.length) {
682 msg.text = msgtext[0..msg.text.length];
683 msg.textlen = msg.text.length;
684 } else {
685 msg.text[0..msgtext.length] = msgtext[];
686 msg.textlen = msgtext.length;
691 void doMessages (MonoTime curtime) {
692 //messageLock.lock();
693 //scope(exit) messageLock.unlock();
695 if (messagesUsed == 0) return;
696 glEnable(GL_BLEND);
697 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
699 Message* msg;
701 again:
702 msg = messages.ptr;
703 final switch (msg.phase) {
704 case Message.Phase.FadeIn:
705 if ((msg.alpha += 10) >= 255) {
706 msg.phase = Message.Phase.Stay;
707 msg.removeTime = curtime+dur!"msecs"(msg.pauseMsecs);
708 goto case; // to stay
710 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
711 break;
712 case Message.Phase.Stay:
713 if (msg.removeTime <= curtime) {
714 msg.alpha = 255;
715 msg.phase = Message.Phase.FadeOut;
716 goto case; // to fade
718 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
719 break;
720 case Message.Phase.FadeOut:
721 if ((msg.alpha -= 10) <= 0) {
722 if (--messagesUsed == 0) return;
723 // remove this message
724 foreach (immutable cidx; 1..messagesUsed+1) messages.ptr[cidx-1] = messages.ptr[cidx];
725 goto again;
727 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
728 break;
731 smDrawText(10, 10, msg.text[0..msg.textlen]);
735 // ////////////////////////////////////////////////////////////////////////// //
736 //mixin(Actor.FieldPropMixin!("0drawlistpos", uint));
738 mixin(Actor.FieldGetMixin!("classtype", StrId)); // fget_classtype
739 mixin(Actor.FieldGetMixin!("classname", StrId)); // fget_classname
740 mixin(Actor.FieldGetMixin!("x", int));
741 mixin(Actor.FieldGetMixin!("y", int));
742 mixin(Actor.FieldGetMixin!("s", int));
743 mixin(Actor.FieldGetMixin!("radius", int));
744 mixin(Actor.FieldGetMixin!("height", int));
745 mixin(Actor.FieldGetMixin!("flags", uint));
746 mixin(Actor.FieldGetMixin!("zAnimstate", StrId));
747 mixin(Actor.FieldGetMixin!("zAnimidx", int));
748 mixin(Actor.FieldGetMixin!("dir", uint));
749 mixin(Actor.FieldGetMixin!("attLightXOfs", int));
750 mixin(Actor.FieldGetMixin!("attLightYOfs", int));
751 mixin(Actor.FieldGetMixin!("attLightRGBX", uint));
753 //mixin(Actor.FieldGetPtrMixin!("classtype", StrId)); // fget_classtype
754 //mixin(Actor.FieldGetPtrMixin!("classname", StrId)); // fget_classname
755 mixin(Actor.FieldGetPtrMixin!("x", int));
756 mixin(Actor.FieldGetPtrMixin!("y", int));
757 //mixin(Actor.FieldGetPtrMixin!("flags", uint));
758 //mixin(Actor.FieldGetPtrMixin!("zAnimstate", StrId));
759 //mixin(Actor.FieldGetPtrMixin!("zAnimidx", int));
760 //mixin(Actor.FieldGetPtrMixin!("dir", uint));
761 //mixin(Actor.FieldGetPtrMixin!("attLightXOfs", int));
762 //mixin(Actor.FieldGetPtrMixin!("attLightYOfs", int));
763 //mixin(Actor.FieldGetPtrMixin!("attLightRGBX", uint));
766 // ////////////////////////////////////////////////////////////////////////// //
767 __gshared int vportX0, vportY0, vportX1, vportY1;
770 // ////////////////////////////////////////////////////////////////////////// //
771 void renderLightAmbient() (int lightX, int lightY, int lightW, int lightH, in auto ref SVec4F lcol) {
772 //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));
773 if (lightW < 1 || lightH < 1) return;
774 int lightX1 = lightX+lightW-1;
775 int lightY1 = lightY+lightH-1;
776 // clip light to viewport
777 if (lightX < vportX0) lightX = vportX0;
778 if (lightY < vportY0) lightY = vportY0;
779 if (lightX1 > vportX1) lightX1 = vportX1;
780 if (lightY1 > vportY1) lightY1 = vportY1;
781 // is this light visible?
782 //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));
783 if (lightX1 < lightX || lightY1 < lightY || lightX > vportX1 || lightY > vportY1 || lightX1 < vportX0 || lightY1 < vportY0) return;
784 //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));
786 fboLevelLight.exec({
787 bindTexture(0);
788 glEnable(GL_BLEND);
789 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
790 //glDisable(GL_BLEND);
791 orthoCamera(map.width*TileSize, map.height*TileSize);
792 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
793 shadAmbient.exec((Shader shad) {
794 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
795 //shad["lightPos"] = SVec2F(lightX, lightY);
796 glRectf(lightX, lightY, lightX1, lightY1);
802 // ////////////////////////////////////////////////////////////////////////// //
803 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
804 if (lightRadius < 2) return;
805 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
806 int lightSize = lightRadius*2;
807 // is this light visible?
808 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*TileSize || lightY-lightRadius >= map.height*TileSize) return;
810 // out of viewport -- do nothing
811 if (lightX+lightRadius < vportX0 || lightY+lightRadius < vportY0) return;
812 if (lightX-lightRadius > vportX1 || lightY-lightRadius > vportY1) return;
814 if (lightX >= 0 && lightY >= 0 && lightX < map.width*TileSize && lightY < map.height*TileSize &&
815 map.teximgs[map.LightMask].imageData.colors.ptr[lightY*(map.width*TileSize)+lightX].a > 190) return;
817 // common color for all the following
818 glDisable(GL_BLEND);
819 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
821 // build 1d distance map to fboShadowMapId
822 fboDistMap.ptr[lightRadius].exec({
823 // no need to clear it, shader will take care of that
824 shadToPolar.exec((Shader shad) {
825 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
826 shad["lightPos"] = SVec2F(lightX, lightY);
827 orthoCamera(lightSize, 1);
828 // it doesn't matter what we will draw here, so just draw filled rect
829 glRectf(0, 0, lightSize, 1);
833 // build light texture for blending
834 fboOccluders.exec({
835 // no need to clear it, shader will take care of that
836 // debug
837 //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
838 //glClear(GL_COLOR_BUFFER_BIT);
839 shadBlur.exec((Shader shad) {
840 shad["lightTexSize"] = SVec2F(lightSize, MaxLightRadius*2); // x: size of distmap; y: size of this texture
841 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
842 shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
843 orthoCamera(fboOccluders.tex.width, fboOccluders.tex.height);
844 //drawAtXY(fboDistMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
845 bindTexture(fboDistMap.ptr[lightRadius].tex.tid);
846 glBegin(GL_QUADS);
847 glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0); // top-left
848 glTexCoord2f(1.0f, 0.0f); glVertex2i(lightSize, 0); // top-right
849 glTexCoord2f(1.0f, 1.0f); glVertex2i(lightSize, lightSize); // bottom-right
850 glTexCoord2f(0.0f, 1.0f); glVertex2i(0, lightSize); // bottom-left
851 glEnd();
855 // blend light texture
856 fboLevelLight.exec({
857 glEnable(GL_BLEND);
858 //glDisable(GL_BLEND);
859 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
860 orthoCamera(fboLevelLight.tex.width, fboLevelLight.tex.height);
861 //drawAtXY(fboOccluders.tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
862 float occe = 1.0f*lightSize/(MaxLightRadius*2);
863 float occs = 1.0f-occe;
864 int x0 = lightX-lightRadius+0;
865 int y1 = lightY-lightRadius+0;
866 int x1 = lightX+lightRadius-1+1;
867 int y0 = lightY+lightRadius-1+1;
868 bindTexture(fboOccluders.tex.tid);
869 glBegin(GL_QUADS);
871 glTexCoord2f(0.0f, 0.0f); glVertex2i(x0, y0); // top-left
872 glTexCoord2f(occe, 0.0f); glVertex2i(x1, y0); // top-right
873 glTexCoord2f(occe, occe); glVertex2i(x1, y1); // bottom-right
874 glTexCoord2f(0.0f, occe); glVertex2i(x0, y1); // bottom-left
876 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
877 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
878 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
879 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
880 glEnd();
882 bindTexture(0);
883 glRectf(x0, y0, x1, y1);
885 // and blend it again, with the shader that will touch only occluders
886 shadBlurOcc.exec((Shader shad) {
887 //shad["lightTexSize"] = SVec2F(lightSize, lightSize);
888 shad["lightTexSize"] = SVec2F(lightSize, fboOccluders.tex.height);
889 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
890 shad["lightPos"] = SVec2F(lightX, lightY);
891 //shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
892 bindTexture(fboOccluders.tex.tid);
893 glBegin(GL_QUADS);
894 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
895 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
896 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
897 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
898 glEnd();
899 //drawAtXY(fboOccluders.tex.tid, lightX-lightRadius, lightY-lightRadius, lightSize, lightSize, mirrorY:true);
905 // ////////////////////////////////////////////////////////////////////////// //
906 __gshared int testLightX = vlWidth/2, testLightY = vlHeight/2;
907 __gshared bool testLightMoved = false;
908 //__gshared int mapOfsX, mapOfsY;
909 //__gshared bool movement = false;
910 __gshared float iLiquidTime = 0.0;
911 //__gshared bool altMove = false;
914 void renderScene (MonoTime curtime) {
915 //enum BackIntens = 0.05f;
916 enum BackIntens = 0.0f;
918 gloStackClear();
919 float atob = (curtime > lastthink ? cast(float)((curtime-lastthink).total!"msecs")/cast(float)((nextthink-lastthink).total!"msecs") : 1.0f);
920 if (gamePaused || inEditMode) atob = 1.0f;
921 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
924 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
925 int curfp = cast(int)((curtime-lastthink).total!"msecs");
926 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
930 int mofsx, mofsy; // camera offset, will be set in background layer builder
932 if (mapTilesChanged != 0) rebuildMapMegaTextures();
934 // build background layer
935 fboOrigBack.exec({
936 //glDisable(GL_BLEND);
937 //glClearDepth(1.0f);
938 //glDepthFunc(GL_LESS);
939 //glDepthFunc(GL_NEVER);
940 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
941 glClear(GL_COLOR_BUFFER_BIT/*|GL_DEPTH_BUFFER_BIT*/);
942 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
943 orthoCamera(map.width*TileSize, map.height*TileSize);
945 glEnable(GL_BLEND);
946 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
947 // draw sky
949 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
950 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
951 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
952 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
954 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*TileSize, map.MapSize*TileSize);
955 // draw background
956 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
957 // draw distorted liquid areas
958 shadLiquidDistort.exec((Shader shad) {
959 shad["iDistortTime"] = iLiquidTime;
960 drawAtXY(map.texgl.ptr[map.AllLiquids], 0, 0);
962 // monsters, items; we'll do linear interpolation here
963 glColor3f(1.0f, 1.0f, 1.0f);
964 //glEnable(GL_DEPTH_TEST);
965 attachedLightCount = 0;
967 // who cares about memory?!
968 // draw order: players, items, monsters, other
969 static struct DrawInfo {
970 ActorDef adef;
971 ActorId aid;
972 int actorX, actorY;
973 @disable this (this); // no copies
975 enum { Pixels, Players, Items, Monsters, Other }
976 __gshared DrawInfo[65536][4] drawlists;
977 __gshared uint[4] dlpos;
978 DrawInfo camchickdi;
980 dlpos[] = 0;
982 Actor.forEach((ActorId me) {
983 //me.fprop_0drawlistpos = 0;
984 if (auto adef = findActorDef(me)) {
985 uint dlnum = Other;
986 switch (adef.classtype.get) {
987 case "monster": dlnum = (adef.classname.get != "Player" ? Monsters : Players); break;
988 case "item": dlnum = Items; break;
989 default: dlnum = Other; break;
991 if (me.fget_flags&AF_PIXEL) dlnum = Pixels;
992 int actorX, actorY; // current actor position
994 auto ofs = prevFrameActorOfs.ptr[me.id&0xffff];
995 if (frameInterpolation && ofs < uint.max-1 && (me.fget_flags&AF_TELEPORT) == 0 && Actor.isSameSavedActor(me.id, prevFrameActorsData.ptr, ofs)) {
996 import core.stdc.math : roundf;
997 auto xptr = prevFrameActorsData.ptr+ofs;
998 int ox = xptr.fgetp_x;
999 int nx = me.fget_x;
1000 int oy = xptr.fgetp_y;
1001 int ny = me.fget_y;
1002 actorX = cast(int)(ox+roundf((nx-ox)*atob));
1003 actorY = cast(int)(oy+roundf((ny-oy)*atob));
1004 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
1005 } else {
1006 actorX = me.fget_x;
1007 actorY = me.fget_y;
1010 if (me.id == cameraChick.id) {
1011 camchickdi.adef = adef;
1012 camchickdi.aid = me;
1013 camchickdi.actorX = actorX;
1014 camchickdi.actorY = actorY;
1016 // draw sprite
1017 if ((me.fget_flags&AF_NODRAW) == 0) {
1018 //auto dl = &drawlists[dlnum][dlpos.ptr[dlnum]];
1019 //me.fprop_0drawlistpos = (dlpos.ptr[dlnum]&0xffff)|((dlnum&0xff)<<16);
1020 auto dl = drawlists.ptr[dlnum].ptr+dlpos.ptr[dlnum];
1021 ++dlpos.ptr[dlnum];
1022 dl.adef = adef;
1023 dl.aid = me;
1024 dl.actorX = actorX;
1025 dl.actorY = actorY;
1027 // process attached lights
1028 if ((me.fget_flags&AF_NOLIGHT) == 0) {
1029 uint alr = me.fget_attLightRGBX;
1030 bool isambient = (me.fget_classtype.get == "light" && me.fget_classname.get == "Ambient");
1031 if ((alr&0xff) >= 4 || (isambient && me.fget_radius >= 1 && me.fget_height >= 1)) {
1032 //if (isambient) conwriteln("isambient: ", isambient, "; x=", me.fget_x, "; y=", me.fget_y, "; w=", me.fget_radius, "; h=", me.fget_height);
1033 // yep, add it
1034 auto li = attachedLights.ptr+attachedLightCount;
1035 ++attachedLightCount;
1036 li.type = (!isambient ? AttachedLightInfo.Type.Point : AttachedLightInfo.Type.Ambient);
1037 li.x = actorX+me.fget_attLightXOfs;
1038 li.y = actorY+me.fget_attLightYOfs;
1039 li.r = ((alr>>24)&0xff)/255.0f; // red or intensity
1040 if ((alr&0x00_ff_ff_00U) == 0x00_00_01_00U) {
1041 li.uncolored = true;
1042 } else {
1043 li.g = ((alr>>16)&0xff)/255.0f;
1044 li.b = ((alr>>8)&0xff)/255.0f;
1045 li.uncolored = false;
1047 li.radius = (alr&0xff);
1048 if (li.radius > MaxLightRadius) li.radius = MaxLightRadius;
1049 if (isambient) {
1050 li.w = me.fget_radius;
1051 li.h = me.fget_height;
1055 } else {
1056 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
1060 // draw actor lists
1061 foreach_reverse (uint dlnum; 0..drawlists.length) {
1062 if (dlnum == Pixels) continue;
1063 auto dl = drawlists.ptr[dlnum].ptr;
1064 if (dlnum == Players) dl += dlpos.ptr[dlnum]-1;
1065 foreach (uint idx; 0..dlpos.ptr[dlnum]) {
1066 auto me = dl.aid;
1067 if (auto isp = dl.adef.animSpr(me.fget_zAnimstate, me.fget_dir, me.fget_zAnimidx)) {
1068 //drawAtXY(isp.tex, dl.actorX-isp.vga.sx, dl.actorY-isp.vga.sy);
1069 isp.drawAtXY(dl.actorX, dl.actorY);
1071 if (dlnum != Players) ++dl; else --dl;
1074 // draw pixels
1075 if (dlpos[Pixels]) {
1076 bindTexture(0);
1077 bool pointsStarted = false;
1078 Color lastColor = Color(0, 0, 0, 0);
1079 auto dl = drawlists.ptr[Pixels].ptr;
1080 foreach (uint idx; 0..dlpos.ptr[Pixels]) {
1081 auto me = dl.aid;
1082 auto s = me.fget_s;
1083 if (s < 0 || s > 255) continue; //FIXME
1084 Color clr = d2dpal.ptr[s&0xff];
1085 if (clr.a == 0) continue;
1086 if (clr != lastColor) {
1087 if (pointsStarted) glEnd();
1088 glColor4f(clr.r/255.0f, clr.g/255.0f, clr.b/255.0f, clr.a/255.0f);
1089 lastColor = clr;
1090 pointsStarted = false;
1092 if (!pointsStarted) {
1093 glBegin(GL_POINTS);
1094 pointsStarted = true;
1096 glVertex2i(dl.actorX, dl.actorY);
1097 ++dl;
1099 if (pointsStarted) {
1100 glEnd();
1101 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1105 // camera movement
1106 if (/*altMove || movement ||*/ scale == 1 || !cameraChick.valid) {
1107 mofsx = 0;
1108 mofsy = 0;
1109 vportX0 = 0;
1110 vportY0 = 0;
1111 vportX1 = map.width*TileSize;
1112 vportY1 = map.height*TileSize;
1113 } else {
1114 int tiltHeight = /*getMapViewHeight()*/(vlHeight/scale)/4;
1115 int vy = cameraChick.looky!int;
1116 if (vy < -tiltHeight) vy = -tiltHeight; else if (vy > tiltHeight) vy = tiltHeight;
1117 int swdt = vlWidth/scale;
1118 int shgt = vlHeight/scale;
1119 int x = camchickdi.actorX-swdt/2;
1120 int y = (camchickdi.actorY+vy)-shgt/2;
1121 if (x < 0) x = 0; else if (x >= map.width*TileSize-swdt) x = map.width*TileSize-swdt-1;
1122 if (y < 0) y = 0; else if (y >= map.height*TileSize-shgt) y = map.height*TileSize-shgt-1;
1123 mofsx = x*2;
1124 mofsy = y*2;
1125 vportX0 = mofsx/scale;
1126 vportY0 = mofsy/scale;
1127 vportX1 = vportX0+vlWidth/scale;
1128 vportY1 = vportY0+vlHeight/scale;
1131 //glDisable(GL_DEPTH_TEST);
1132 // draw dots
1133 dotDraw(atob);
1134 // do liquid coloring
1135 drawAtXY(map.texgl.ptr[map.LiquidMask], 0, 0);
1136 // foreground -- hide secrets, draw lifts and such
1137 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
1140 enum r = 255;
1141 enum g = 0;
1142 enum b = 0;
1143 enum a = 255;
1144 bindTexture(0);
1145 glColor4f(r/255.0f, g/255.0f, b/255.0f, a/255.0f);
1146 glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
1147 if (cameraChick.valid) {
1148 glBegin(GL_POINTS);
1149 glVertex2i(camchickdi.actorX, camchickdi.actorY-70);
1150 glEnd();
1151 //glRectf(camchickdi.actorX, camchickdi.actorY-70, camchickdi.actorX+4, camchickdi.actorY-70+4);
1153 //glRectf(0, 0, 300, 300);
1154 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1160 if (doLighting) {
1161 glDisable(GL_BLEND);
1162 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1164 // make smaller occluder texture, so we can trace faster
1165 //assert(fboLMaskSmall.tex.width == map.width);
1166 //assert(fboLMaskSmall.tex.height == map.height);
1167 fboLMaskSmall.exec({
1168 orthoCamera(map.width, map.height);
1169 drawAtXY(map.texgl.ptr[map.LightMask].tid, 0, 0, map.width, map.height, mirrorY:true);
1172 // clear light layer
1173 fboLevelLight.exec({
1174 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
1175 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
1176 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
1177 glClear(GL_COLOR_BUFFER_BIT);
1180 // texture 1 is background
1181 glActiveTexture(GL_TEXTURE0+1);
1182 glBindTexture(GL_TEXTURE_2D, fboOrigBack.tex.tid);
1183 // texture 2 is occluders
1184 glActiveTexture(GL_TEXTURE0+2);
1185 glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.LightMask].tid);
1186 // texture 3 is small occluder map
1187 glActiveTexture(GL_TEXTURE0+3);
1188 glBindTexture(GL_TEXTURE_2D, fboLMaskSmall.tex.tid);
1189 // done texture assign
1190 glActiveTexture(GL_TEXTURE0+0);
1193 enum LYOfs = 1;
1195 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
1196 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
1197 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
1198 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
1199 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
1200 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
1201 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
1202 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
1203 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
1204 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
1206 renderLight(24*TileSize+4, (24+18)*TileSize-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
1208 renderLight(280, 330, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
1211 foreach (; 0..1) {
1212 // attached lights
1213 foreach (ref li; attachedLights[0..attachedLightCount]) {
1214 if (li.type == AttachedLightInfo.Type.Ambient) {
1215 //conwriteln("ambient: x=", li.x, "; y=", li.y, "; w=", li.w, "; h=", li.h);
1216 // ambient light
1217 if (li.uncolored) {
1218 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(0.0f, 0.0f, 0.0f, li.r));
1219 } else {
1220 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(li.r, li.g, li.b, 1.0f));
1222 } else if (li.type == AttachedLightInfo.Type.Point) {
1223 // point light
1224 if (li.uncolored) {
1225 renderLight(li.x, li.y, SVec4F(0.0f, 0.0f, 0.0f, li.r), li.radius);
1226 } else {
1227 renderLight(li.x, li.y, SVec4F(li.r, li.g, li.b, 1.0f), li.radius);
1234 if (testLightMoved) {
1235 testLightX = testLightX/scale+mofsx/scale;
1236 testLightY = testLightY/scale+mofsy/scale;
1237 testLightMoved = false;
1239 foreach (immutable _; 0..1) {
1240 renderLight(testLightX, testLightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
1244 glActiveTexture(GL_TEXTURE0+1);
1245 glBindTexture(GL_TEXTURE_2D, 0);
1246 glActiveTexture(GL_TEXTURE0+2);
1247 glBindTexture(GL_TEXTURE_2D, 0);
1248 glActiveTexture(GL_TEXTURE0+3);
1249 glBindTexture(GL_TEXTURE_2D, 0);
1250 glActiveTexture(GL_TEXTURE0+0);
1253 // draw scaled level
1255 shadScanlines.exec((Shader shad) {
1256 shad["scanlines"] = scanlines;
1257 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
1258 glClear(GL_COLOR_BUFFER_BIT);
1259 orthoCamera(vlWidth, vlHeight);
1260 //orthoCamera(map.width*TileSize*scale, map.height*TileSize*scale);
1261 //glMatrixMode(GL_MODELVIEW);
1262 //glLoadIdentity();
1263 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
1264 // somehow, FBO objects are mirrored; wtf?!
1265 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1266 //glLoadIdentity();
1271 fboLevelLight.exec({
1272 smDrawText(map.width*TileSize/2, map.height*TileSize/2, "Testing...");
1277 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
1279 glDisable(GL_BLEND);
1282 fboOrigBack.exec({
1283 //auto img = smfont.ptr[0x39];
1284 auto img = fftest;
1285 if (img !is null) {
1286 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
1287 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
1294 { // http://stackoverflow.com/questions/7207422/setting-up-opengl-multiple-render-targets
1295 GLenum[1] buffers = [ GL_BACK_LEFT, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
1296 //GLenum[1] buffers = [ GL_NONE, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
1297 glDrawBuffers(1, buffers.ptr);
1302 orthoCamera(vlWidth, vlHeight);
1303 auto tex = (doLighting ? fboLevelLight.tex.tid : fboOrigBack.tex.tid);
1304 drawAtXY(tex, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1306 if (levelLoaded) {
1307 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1308 //orthoCamera(map.width*TileSize, map.height*TileSize);
1309 glEnable(GL_BLEND);
1310 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1311 hudScripts.runDraw();
1314 //drawAtXY(map.texgl.ptr[map.LightMask].tid, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1315 //drawAtXY(fboLMaskSmall.tex.tid, 0, 0, map.width*TileSize, map.height*TileSize);
1317 if (inEditMode) {
1318 glEnable(GL_BLEND);
1319 //glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1320 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1321 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1322 editorUpdateImage();
1323 fboEditor.tex.setFromImage(editorImg);
1324 drawAtXY(fboEditor.tex, 0, 0);
1325 glDisable(GL_BLEND);
1328 doMessages(curtime);
1330 if (rConsoleVisible) {
1331 renderConsoleFBO();
1332 orthoCamera(vlWidth, vlHeight);
1333 glEnable(GL_BLEND);
1334 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1335 glColor4f(1.0f, 1.0f, 1.0f, 0.7f);
1336 drawAtXY(fboConsole.tex, 0, rConsoleHeight-vlHeight, mirrorY:true);
1341 // ////////////////////////////////////////////////////////////////////////// //
1342 // returns time slept
1343 int sleepAtMaxMsecs (int msecs) {
1344 if (msecs > 0) {
1345 import core.sys.posix.signal : timespec;
1346 import core.sys.posix.time : nanosleep;
1347 timespec ts = void, tpassed = void;
1348 ts.tv_sec = 0;
1349 ts.tv_nsec = msecs*1000*1000+(500*1000); // milli to nano
1350 nanosleep(&ts, &tpassed);
1351 return (ts.tv_nsec-tpassed.tv_nsec)/(1000*1000);
1352 } else {
1353 return 0;
1358 // ////////////////////////////////////////////////////////////////////////// //
1359 mixin(import("editor.d"));
1362 // ////////////////////////////////////////////////////////////////////////// //
1363 // rendering thread
1364 shared int diedie = 0;
1366 enum D2DFrameTime = 55; // milliseconds
1367 enum MinFrameTime = 1000/60; // ~60 FPS
1369 public void renderThread (Tid starterTid) {
1370 enum BoolOptVarMsgMixin(string varname) =
1371 "if (msg.toggle) msg.value = !"~varname~";\n"~
1372 "if ("~varname~" != msg.value) "~varname~" = msg.value; else msg.showMessage = false;\n";
1374 send(starterTid, 42);
1375 try {
1376 MonoTime curtime = MonoTime.currTime;
1378 lastthink = curtime; // for interpolator
1379 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1380 MonoTime nextvframe = curtime;
1382 enum MaxFPSFrames = 16;
1383 float frtimes = 0.0f;
1384 int framenum = 0;
1385 int prevFPS = -1;
1386 int hushFrames = 6; // ignore first `hushFrames` frames overtime
1387 MonoTime prevFrameStartTime = curtime;
1389 bool vframeWasLost = false;
1391 void resetFrameTimers () {
1392 MonoTime curtime = MonoTime.currTime;
1393 lastthink = curtime; // for interpolator
1394 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1395 nextvframe = curtime;
1398 void loadNewLevel (string name) {
1400 if (levelLoaded) {
1401 conwriteln("ERROR: can't load new levels yet");
1402 return;
1405 if (name.length == 0) {
1406 conwriteln("ERROR: can't load empty level!");
1408 conwriteln("loading map '", name, "'");
1409 loadMap(name);
1410 resetFrameTimers();
1413 conRegFunc!({
1414 string mn = genNextMapName(0);
1415 if (mn.length) {
1416 nextmapname = null; // clear "exit" flag
1417 loadNewLevel(mn);
1418 } else {
1419 conwriteln("can't skip level");
1421 })("skiplevel", "skip current level");
1423 conRegFunc!({
1424 inEditMode = !inEditMode;
1425 if (inEditMode) sdwindow.hideCursor(); else sdwindow.showCursor();
1426 })("ed_toggle", "toggle editor");
1428 conRegFunc!({
1429 if (inEditMode) {
1430 inEditMode = false;
1431 sdwindow.showCursor();
1433 })("ed_exit", "exit from editor");
1435 conRegFunc!((string mapname) {
1436 nextmapname = null; // clear "exit" flag
1437 loadNewLevel(mapname);
1438 })("map", "load map");
1440 void receiveMessages () {
1441 for (;;) {
1442 import core.time : Duration;
1443 //conwriteln("rendering thread: waiting for messages...");
1444 auto got = receiveTimeout(
1445 Duration.zero, // don't wait
1446 (TMsgMessage msg) {
1447 addMessage(msg.text[0..msg.textlen], msg.pauseMsecs, msg.noreplace);
1449 (TMsgTestLightMove msg) {
1450 testLightX = msg.x;
1451 testLightY = msg.y;
1452 testLightMoved = true;
1454 (TMsgMouseEvent msg) { editorMouseEvent(msg); },
1455 (TMsgKeyEvent msg) { editorKeyEvent(msg); },
1456 (TMsgChar msg) { concliChar(msg.ch); },
1457 (Variant v) {
1458 conwriteln("WARNING: unknown thread message received and ignored");
1461 if (!got) {
1462 // no more messages
1463 //conwriteln("rendering thread: no more messages");
1464 break;
1467 if (nextmapname.length) {
1468 string mn = nextmapname;
1469 nextmapname = null; // clear "exit" flag
1470 loadNewLevel(mn);
1474 void processConsoleCommands () {
1475 concmdbufLock.lock();
1476 scope(exit) concmdbufLock.unlock();
1477 concmdDoAll();
1480 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1481 bool doThinkFrame () {
1482 if (curtime >= nextthink) {
1483 lastthink = curtime;
1484 while (nextthink <= curtime) nextthink += dur!"msecs"(D2DFrameTime);
1485 if (levelLoaded) {
1486 // save snapshot and other data for interpolator
1487 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
1488 if (!gamePaused && !inEditMode) {
1489 // process actors
1490 doActorsThink();
1491 dotThink();
1494 // some timing
1495 auto tm = MonoTime.currTime;
1496 int thinkTime = cast(int)((tm-curtime).total!"msecs");
1497 if (thinkTime > 9) { import core.stdc.stdio; printf("spent on thinking: %d msecs\n", thinkTime); }
1498 curtime = tm;
1499 return true;
1500 } else {
1501 return false;
1505 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1506 bool doVFrame () {
1507 version(dont_use_vsync) {
1508 // timer
1509 enum doCheckTime = true;
1510 } else {
1511 // vsync
1512 __gshared bool prevLost = false;
1513 bool doCheckTime = vframeWasLost;
1514 if (vframeWasLost) {
1515 if (!prevLost) {
1516 { import core.stdc.stdio; printf("frame was lost!\n"); }
1518 prevLost = true;
1519 } else {
1520 prevLost = false;
1523 if (doCheckTime) {
1524 if (curtime < nextvframe) return false;
1525 version(dont_use_vsync) {
1526 if (curtime > nextvframe) {
1527 auto overtime = cast(int)((curtime-nextvframe).total!"msecs");
1528 if (overtime > 2500) {
1529 if (hushFrames) {
1530 --hushFrames;
1531 } else {
1532 { import core.stdc.stdio; printf(" spent whole %d msecs\n", overtime); }
1538 while (nextvframe <= curtime) nextvframe += dur!"msecs"(MinFrameTime);
1539 bool ctset = false;
1541 sdwindow.mtLock();
1542 scope(exit) sdwindow.mtUnlock();
1543 ctset = sdwindow.setAsCurrentOpenGlContextNT;
1545 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
1546 if (ctset) {
1547 // render scene
1548 iLiquidTime = cast(float)((curtime-MonoTime.zero).total!"msecs"%10000000)/18.0f*0.04f;
1549 receiveMessages(); // here, 'cause we need active OpenGL context for some messages
1550 processConsoleCommands();
1551 if (levelLoaded) {
1552 renderScene(curtime);
1553 } else {
1554 //renderLoading(curtime);
1556 sdwindow.mtLock();
1557 scope(exit) sdwindow.mtUnlock();
1558 sdwindow.swapOpenGlBuffers();
1559 glFinish();
1560 sdwindow.releaseCurrentOpenGlContext();
1561 vframeWasLost = false;
1562 } else {
1563 vframeWasLost = true;
1564 { import core.stdc.stdio; printf("xframe was lost!\n"); }
1566 curtime = MonoTime.currTime;
1567 return true;
1570 for (;;) {
1571 if (sdwindow.closed) break;
1572 if (atomicLoad(diedie) > 0) break;
1574 curtime = MonoTime.currTime;
1575 auto fstime = curtime;
1577 doThinkFrame(); // this will fix curtime if necessary
1578 if (doVFrame()) {
1579 if (!vframeWasLost) {
1580 // fps
1581 auto frameTime = cast(float)(curtime-prevFrameStartTime).total!"msecs"/1000.0f;
1582 prevFrameStartTime = curtime;
1583 frtimes += frameTime;
1584 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
1585 import std.string : format;
1586 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
1587 if (newFPS != prevFPS) {
1588 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
1589 prevFPS = newFPS;
1591 framenum = 0;
1592 frtimes = 0.0f;
1597 curtime = MonoTime.currTime;
1599 // now sleep until next "video" or "think" frame
1600 if (nextthink > curtime && nextvframe > curtime) {
1601 // let's decide
1602 immutable nextVideoFrameSleep = cast(int)((nextvframe-curtime).total!"msecs");
1603 immutable nextThinkFrameSleep = cast(int)((nextthink-curtime).total!"msecs");
1604 immutable sleepTime = (nextVideoFrameSleep < nextThinkFrameSleep ? nextVideoFrameSleep : nextThinkFrameSleep);
1605 sleepAtMaxMsecs(sleepTime);
1606 //curtime = MonoTime.currTime;
1609 } catch (Throwable e) {
1610 // here, we are dead and fucked (the exact order doesn't matter)
1611 import core.stdc.stdlib : abort;
1612 import core.stdc.stdio : fprintf, stderr;
1613 import core.memory : GC;
1614 GC.disable(); // yeah
1615 thread_suspendAll(); // stop right here, you criminal scum!
1616 auto s = e.toString();
1617 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
1618 abort(); // die, you bitch!
1620 import core.stdc.stdio;
1621 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
1622 import std.stdio : stderr;
1623 writeln(
1624 for (;;) {
1625 if (sdwindow.closed) break;
1626 if (atomicLoad(diedie) > 0) break;
1627 sleepAtMaxMsecs(100);
1631 atomicStore(diedie, 2);
1635 // ////////////////////////////////////////////////////////////////////////// //
1636 __gshared Tid renderTid;
1637 shared bool renderThreadStarted = false;
1640 public void startRenderThread () {
1641 if (!cas(&renderThreadStarted, false, true)) {
1642 assert(0, "render thread already started!");
1644 renderTid = spawn(&renderThread, thisTid);
1645 setMaxMailboxSize(renderTid, 1024, OnCrowding.throwException); //FIXME
1646 // wait for "i'm ready" signal
1647 receive(
1648 (int ok) {
1649 if (ok != 42) assert(0, "wtf?!");
1652 conwriteln("rendering thread started");
1656 // ////////////////////////////////////////////////////////////////////////// //
1657 public void closeWindow () {
1658 if (atomicLoad(diedie) != 2) {
1659 atomicStore(diedie, 1);
1660 while (atomicLoad(diedie) != 2) {}
1662 if (!sdwindow.closed) {
1663 flushGui();
1664 sdwindow.close();
1669 // ////////////////////////////////////////////////////////////////////////// //
1670 // thread messages
1673 // ////////////////////////////////////////////////////////////////////////// //
1674 struct TMsgMouseEvent {
1675 MouseEventType type;
1676 int x, y;
1677 int dx, dy;
1678 MouseButton button; /// See $(LREF MouseButton)
1679 int modifierState; /// See $(LREF ModifierState)
1682 public void postMouseEvent() (in auto ref MouseEvent evt) {
1683 if (!atomicLoad(renderThreadStarted)) return;
1684 TMsgMouseEvent msg;
1685 msg.type = evt.type;
1686 msg.x = evt.x;
1687 msg.y = evt.y;
1688 msg.dx = evt.dx;
1689 msg.dy = evt.dy;
1690 msg.button = evt.button;
1691 msg.modifierState = evt.modifierState;
1692 send(renderTid, msg);
1696 // ////////////////////////////////////////////////////////////////////////// //
1697 struct TMsgKeyEvent {
1698 Key key;
1699 uint hardwareCode;
1700 bool pressed;
1701 dchar character;
1702 uint modifierState;
1705 public void postKeyEvent() (in auto ref KeyEvent evt) {
1706 if (!atomicLoad(renderThreadStarted)) return;
1707 TMsgKeyEvent msg;
1708 msg.key = evt.key;
1709 msg.pressed = evt.pressed;
1710 msg.character = evt.character;
1711 msg.modifierState = evt.modifierState;
1712 send(renderTid, msg);
1716 // ////////////////////////////////////////////////////////////////////////// //
1717 struct TMsgTestLightMove {
1718 int x, y;
1721 public void postTestLightMove (int x, int y) {
1722 if (!atomicLoad(renderThreadStarted)) return;
1723 auto msg = TMsgTestLightMove(x, y);
1724 send(renderTid, msg);
1728 // ////////////////////////////////////////////////////////////////////////// //
1729 struct TMsgMessage {
1730 char[256] text;
1731 uint textlen;
1732 int pauseMsecs;
1733 bool noreplace;
1736 public void postAddMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
1737 if (!atomicLoad(renderThreadStarted)) return;
1738 if (msgtext.length > TMsgMessage.text.length) msgtext = msgtext[0..TMsgMessage.text.length];
1739 TMsgMessage msg;
1740 msg.textlen = cast(uint)msgtext.length;
1741 if (msg.textlen) msg.text[0..msg.textlen] = msgtext[0..msg.textlen];
1742 msg.pauseMsecs = pauseMsecs;
1743 msg.noreplace = noreplace;
1744 send(renderTid, msg);
1748 // ////////////////////////////////////////////////////////////////////////// //
1749 struct TMsgChar {
1750 char ch;
1753 public void postChar (char ch) {
1754 if (!atomicLoad(renderThreadStarted)) return;
1755 TMsgChar msg;
1756 msg.ch = ch;
1757 send(renderTid, msg);
1761 // ////////////////////////////////////////////////////////////////////////// //
1762 public void concmd (const(char)[] cmd) {
1763 //if (!atomicLoad(renderThreadStarted)) return;
1764 concmdbufLock.lock();
1765 scope(exit) concmdbufLock.unlock();
1766 concmdAdd(cmd);