"r_vsync" console var; slightly faster light tracer
[dd2d.git] / render.d
blob5f8758d5bbef4483b6a0b435bd4da2bb31041aef
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 private:
21 import core.atomic;
22 import core.thread;
23 import core.time;
25 import std.concurrency;
27 import iv.glbinds;
28 import glutils;
29 import console;
30 import wadarc;
32 import iv.vfs.augs;
34 import d2dmap;
35 import d2dadefs;
36 import d2dimage;
37 import d2dfont;
38 import dacs;
40 import d2dunigrid;
42 // `map` is there
43 import dengapi;
45 import d2dparts;
48 // ////////////////////////////////////////////////////////////////////////// //
49 import arsd.color;
50 import arsd.png;
53 // ////////////////////////////////////////////////////////////////////////// //
54 public __gshared bool cheatNoDoors = false;
55 public __gshared bool cheatNoWallClip = false;
58 // ////////////////////////////////////////////////////////////////////////// //
59 public __gshared SimpleWindow sdwindow;
62 public enum vlWidth = 800;
63 public enum vlHeight = 800;
64 __gshared int scale = 2;
66 public int getScale () nothrow @trusted @nogc { pragma(inline, true); return scale; }
69 // ////////////////////////////////////////////////////////////////////////// //
70 __gshared bool levelLoaded = false;
73 // ////////////////////////////////////////////////////////////////////////// //
74 __gshared bool scanlines = false;
75 __gshared bool doLighting = true;
76 __gshared bool gamePaused = false;
77 __gshared bool rConsoleVisible = false;
78 __gshared int rConsoleHeight = 16*3;
79 __gshared bool renderVBL = true;
80 __gshared bool oldRenderVBL = false;
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 shared static this () { concmdbuf.length = 65536; }
97 __gshared char[4096] concli = 0;
98 __gshared uint conclilen = 0;
100 __gshared char[4096][128] concmdhistory = void;
101 __gshared int conhisidx = -1;
102 shared static this () { foreach (ref hb; concmdhistory) hb[] = 0; }
104 __gshared int conskiplines = 0;
107 const(char)[] conhisAt (int idx) {
108 if (idx < 0 || idx >= concmdhistory.length) return null;
109 const(char)[] res = concmdhistory.ptr[idx][];
110 usize pos = 0;
111 while (pos < res.length && res.ptr[pos]) ++pos;
112 return res[0..pos];
116 int conhisFind (const(char)[] cmd) {
117 while (cmd.length && cmd[$-1] <= 32) cmd = cmd[0..$-1];
118 if (cmd.length > concmdhistory.ptr[0].length) cmd = cmd[0..concmdhistory.ptr[0].length];
119 if (cmd.length == 0) return -1;
120 foreach (int idx; 0..cast(int)concmdhistory.length) {
121 auto c = conhisAt(idx);
122 while (c.length > 0 && c[$-1] <= 32) c = c[0..$-1];
123 if (c == cmd) return idx;
125 return -1;
129 void conhisAdd (const(char)[] cmd) {
130 while (cmd.length && cmd[$-1] <= 32) cmd = cmd[0..$-1];
131 if (cmd.length > concmdhistory.ptr[0].length) cmd = cmd[0..concmdhistory.ptr[0].length];
132 if (cmd.length == 0) return;
133 auto idx = conhisFind(cmd);
134 if (idx >= 0) {
135 // remove command
136 foreach (immutable c; idx+1..concmdhistory.length) concmdhistory.ptr[c-1][] = concmdhistory.ptr[c][];
138 // make room
139 foreach (immutable c; 1..concmdhistory.length; reverse) concmdhistory.ptr[c][] = concmdhistory.ptr[c-1][];
140 concmdhistory.ptr[0][] = 0;
141 concmdhistory.ptr[0][0..cmd.length] = cmd[];
145 void concmdAdd (const(char)[] s) {
146 if (s.length) {
147 if (concmdbuf.length-concmdbufpos < s.length+1) {
148 concmdbuf.assumeSafeAppend.length += s.length-(concmdbuf.length-concmdbufpos)+512;
150 if (concmdbufpos > 0 && concmdbuf[concmdbufpos-1] != '\n') concmdbuf.ptr[concmdbufpos++] = '\n';
151 concmdbuf[concmdbufpos..concmdbufpos+s.length] = s[];
152 concmdbufpos += s.length;
156 // `null`: no more
157 void concmdDoAll () {
158 if (concmdbufpos == 0) return;
159 scope(exit) concmdbufpos = 0;
160 auto ebuf = concmdbufpos;
161 const(char)[] s = concmdbuf[0..concmdbufpos];
162 for (;;) {
163 while (s.length) {
164 auto cmd = conGetCommand(s);
165 if (cmd is null) break;
166 try {
167 conExecute(cmd);
168 } catch (Exception e) {
169 conwriteln("***ERROR: ", e.msg);
172 if (concmdbufpos <= ebuf) break;
173 s = concmdbuf[ebuf..concmdbufpos];
174 ebuf = concmdbufpos;
179 void concliChar (char ch) {
180 __gshared int prevWasEmptyAndTab = 0;
182 cbufLock();
183 scope(exit) cbufUnlock();
184 //conLastChange = 0;
186 // autocomplete
187 if (ch == 9) {
188 if (conclilen == 0) {
189 if (++prevWasEmptyAndTab < 2) return;
190 } else {
191 prevWasEmptyAndTab = 0;
193 if (conclilen > 0) {
194 string minPfx = null;
195 // find longest command
196 foreach (auto name; conByCommand) {
197 if (name.length >= conclilen && name.length > minPfx.length && name[0..conclilen] == concli[0..conclilen]) minPfx = name;
199 //conwriteln("longest command: [", minPfx, "]");
200 // find longest prefix
201 foreach (auto name; conByCommand) {
202 if (name.length < conclilen) continue;
203 if (name[0..conclilen] != concli[0..conclilen]) continue;
204 usize pos = 0;
205 while (pos < name.length && pos < minPfx.length && minPfx.ptr[pos] == name.ptr[pos]) ++pos;
206 if (pos < minPfx.length) minPfx = minPfx[0..pos];
208 if (minPfx.length > concli.length) minPfx = minPfx[0..concli.length];
209 //conwriteln("longest prefix : [", minPfx, "]");
210 if (minPfx.length >= conclilen) {
211 // wow!
212 bool doRet = (minPfx.length > conclilen);
213 conLastChange = 0;
214 concli[0..minPfx.length] = minPfx[];
215 conclilen = cast(uint)minPfx.length;
216 if (conclilen < concli.length && conHasCommand(minPfx)) {
217 concli.ptr[conclilen++] = ' ';
218 doRet = true;
220 if (doRet) return;
223 // nope, print all available commands
224 bool needDelimiter = true;
225 foreach (auto name; conByCommand) {
226 if (conclilen > 0) {
227 if (name.length < conclilen) continue;
228 if (name[0..conclilen] != concli[0..conclilen]) continue;
230 if (needDelimiter) { conwriteln("----------------"); needDelimiter = false; }
231 conwriteln(name);
233 return;
235 // process other keys
236 prevWasEmptyAndTab = 0;
237 // remove last char
238 if (ch == 8) {
239 if (conclilen > 0) { conLastChange = 0; --conclilen; }
240 return;
242 // execute command
243 if (ch == 13) {
244 if (conskiplines) { conskiplines = 0; conLastChange = 0; }
245 if (conclilen > 0) {
246 conLastChange = 0;
247 conhisidx = -1;
248 conhisAdd(concli[0..conclilen]);
249 concmdAdd(concli[0..conclilen]);
250 conclilen = 0;
252 return;
254 // ^Y
255 if (ch == 25) {
256 if (conclilen > 0) { conLastChange = 0; conclilen = 0; }
257 return;
259 // up
260 if (ch == '\x01') {
261 ++conhisidx;
262 auto cmd = conhisAt(conhisidx);
263 if (cmd.length == 0) {
264 --conhisidx;
265 } else {
266 concli[0..cmd.length] = cmd[];
267 conclilen = cast(uint)cmd.length;
268 conLastChange = 0;
270 return;
272 // down
273 if (ch == '\x02') {
274 --conhisidx;
275 auto cmd = conhisAt(conhisidx);
276 if (cmd.length == 0 && conhisidx < -1) {
277 ++conhisidx;
278 } else {
279 concli[0..cmd.length] = cmd[];
280 conclilen = cast(uint)cmd.length;
281 conLastChange = 0;
283 return;
285 // page up
286 if (ch == '\x03') {
287 int lnx = (rConsoleHeight-4)/conCharHeight-2;
288 if (lnx < 1) lnx = 1;
289 conskiplines += lnx;
290 conLastChange = 0;
291 return;
293 // page down
294 if (ch == '\x04') {
295 if (conskiplines > 0) {
296 int lnx = (rConsoleHeight-4)/conCharHeight-2;
297 if (lnx < 1) lnx = 1;
298 if ((conskiplines -= lnx) < 0) conskiplines = 0;
299 conLastChange = 0;
301 return;
303 // other
304 if (ch < ' ' || ch > 127) return;
305 if (ch == '`' && conclilen == 0) { concmd("r_console ona"); return; }
306 if (conclilen >= concli.length) return;
307 concli.ptr[conclilen++] = ch;
308 conLastChange = 0;
312 // ////////////////////////////////////////////////////////////////////////// //
313 shared static this () {
314 conRegFunc!((const(char)[] fname) {
315 try {
316 auto s = loadTextFile(fname);
317 concmd(s);
318 } catch (Exception e) {
319 conwriteln("ERROR loading script \"", fname, "\"");
321 })("exec", "execute console script");
322 conRegVar!doLighting("r_lighting", "dynamic lighting");
323 conRegVar!renderVBL("r_vsync", "sync to vblank");
324 conRegVar!gamePaused("g_pause", "pause game");
325 conRegVar!rConsoleVisible("r_console", "console visibility");
326 conRegVar!rConsoleHeight(16*3, vlHeight, "r_conheight");
327 rConsoleHeight = vlHeight-vlHeight/3;
328 rConsoleHeight = vlHeight/2;
329 conRegVar!frameInterpolation("r_interpolation", "interpolate camera and sprite movement");
330 conRegVar!scale(1, 2, "r_scale");
331 conRegFunc!({
332 cheatNoDoors = !cheatNoDoors;
333 if (cheatNoDoors) conwriteln("player ignores doors"); else conwriteln("player respects doors");
334 })("nodoorclip", "ignore doors");
335 conRegFunc!({
336 cheatNoWallClip = !cheatNoWallClip;
337 if (cheatNoWallClip) conwriteln("player ignores walls"); else conwriteln("player respects walls");
338 })("nowallclip", "ignore walls");
339 conRegFunc!({
340 import core.atomic;
341 atomicStore(vquitRequested, true);
342 })("quit", "quit game");
343 conRegFunc!((const(char)[] msg, int pauseMsecs=3000, bool noreplace=false) {
344 char[256] buf;
345 auto s = buf.conFormatStr(msg);
346 if (s.length) postAddMessage(s, pauseMsecs, noreplace);
347 })("hudmsg", "show hud message; hudmsg msg [pausemsecs [noreplace]]");
351 // ////////////////////////////////////////////////////////////////////////// //
352 // interpolation
353 __gshared ubyte[] prevFrameActorsData;
354 __gshared uint[65536] prevFrameActorOfs; // uint.max-1: dead; uint.max: last
355 __gshared MonoTime lastthink = MonoTime.zero; // for interpolator
356 __gshared MonoTime nextthink = MonoTime.zero;
357 __gshared bool frameInterpolation = true;
360 // ////////////////////////////////////////////////////////////////////////// //
361 // attached lights
362 struct AttachedLightInfo {
363 enum Type {
364 Point,
365 Ambient,
367 Type type;
368 int x, y;
369 int w, h; // for ambient lights
370 float r, g, b;
371 bool uncolored;
372 int radius;
375 __gshared AttachedLightInfo[65536] attachedLights;
376 __gshared uint attachedLightCount = 0;
379 // ////////////////////////////////////////////////////////////////////////// //
380 enum MaxLightRadius = 255;
383 // ////////////////////////////////////////////////////////////////////////// //
384 // for light
385 __gshared FBO[MaxLightRadius+1] fboDistMap;
386 __gshared FBO fboOccluders;
387 __gshared Shader shadToPolar, shadBlur, shadBlurOcc, shadAmbient;
388 __gshared TrueColorImage editorImg;
389 __gshared FBO fboEditor;
390 __gshared FBO fboConsole;
391 __gshared Texture texSigil;
393 __gshared FBO fboLevel, fboLevelLight, fboOrigBack, fboLMaskSmall;
394 __gshared Shader shadScanlines;
395 __gshared Shader shadLiquidDistort;
398 // ////////////////////////////////////////////////////////////////////////// //
399 // call once!
400 public void initOpenGL () {
401 gloStackClear();
403 glEnable(GL_TEXTURE_2D);
404 glDisable(GL_LIGHTING);
405 glDisable(GL_DITHER);
406 glDisable(GL_BLEND);
407 glDisable(GL_DEPTH_TEST);
409 // create shaders
410 shadScanlines = new Shader("scanlines", loadTextFile("shaders/srscanlines.frag"));
412 shadLiquidDistort = new Shader("liquid_distort", loadTextFile("shaders/srliquid_distort.frag"));
413 shadLiquidDistort.exec((Shader shad) {
414 shad["texLqMap"] = 0;
417 // lights
418 shadToPolar = new Shader("light_trace", loadTextFile("shaders/srlight_trace.frag"));
419 shadToPolar.exec((Shader shad) {
420 shad["texOcc"] = 0;
421 shad["texOccFull"] = 2;
422 shad["texOccSmall"] = 3;
425 shadBlur = new Shader("light_blur", loadTextFile("shaders/srlight_blur.frag"));
426 shadBlur.exec((Shader shad) {
427 shad["texDist"] = 0;
428 shad["texBg"] = 1;
429 shad["texOcc"] = 2;
430 shad["texOccSmall"] = 3;
433 shadBlurOcc = new Shader("light_blur_occ", loadTextFile("shaders/srlight_blur_occ.frag"));
434 shadBlurOcc.exec((Shader shad) {
435 shad["texLMap"] = 0;
436 shad["texBg"] = 1;
437 shad["texOcc"] = 2;
438 shad["texOccSmall"] = 3;
441 shadAmbient = new Shader("light_ambient", loadTextFile("shaders/srlight_ambient.frag"));
442 shadAmbient.exec((Shader shad) {
443 shad["texBg"] = 1;
444 shad["texOcc"] = 2;
445 shad["texOccSmall"] = 3;
448 fboOccluders = new FBO(MaxLightRadius*2, MaxLightRadius*2, Texture.Option.Clamp, Texture.Option.Linear);
449 //TODO: this sux!
450 foreach (int sz; 2..MaxLightRadius+1) {
451 fboDistMap[sz] = new FBO(sz*2, 1, Texture.Option.Clamp, Texture.Option.Linear); // create 1d distance map FBO
454 editorImg = new TrueColorImage(vlWidth, vlHeight);
455 editorImg.imageData.colors[] = Color(0, 0, 0, 0);
456 fboEditor = new FBO(vlWidth, vlHeight, Texture.Option.Nearest);
458 texSigil = new Texture("console/sigil_of_baphomet.png", Texture.Option.Nearest);
459 fboConsole = new FBO(vlWidth, vlHeight, Texture.Option.Nearest);
460 loadConFont();
462 // setup matrices
463 glMatrixMode(GL_MODELVIEW);
464 glLoadIdentity();
466 loadSmFont();
467 loadBfFont();
468 loadAllMonsterGraphics();
472 // ////////////////////////////////////////////////////////////////////////// //
473 __gshared ulong conLastChange = 0;
475 void renderConsoleFBO () {
476 enum XOfs = 2;
477 if (conLastChange == cbufLastChange) return;
478 cbufLock();
479 scope(exit) cbufUnlock();
480 // rerender console
481 conLastChange = cbufLastChange;
482 //foreach (auto s; conbufLinesRev) stdout.writeln(s, "|");
483 int skipLines = conskiplines;
484 fboConsole.exec((FBO me) {
485 bindTexture(0);
486 orthoCamera(me.width, me.height);
487 // clear it
488 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
489 glClear(GL_COLOR_BUFFER_BIT);
491 glDisable(GL_BLEND);
492 glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
493 glRectf(0, 0, me.width-1, me.height-1);
495 // text
496 glEnable(GL_BLEND);
497 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
498 // draw sigil
499 glColor4f(1.0f, 1.0f, 1.0f, 0.2f);
500 drawAtXY(texSigil, (me.width-texSigil.width)/2, (vlHeight-rConsoleHeight)/2+(me.height-texSigil.height)/2);
501 // draw command line
502 int y = me.height-conCharHeight-2;
504 glPushMatrix();
505 scope(exit) glPopMatrix();
506 glTranslatef(XOfs, y, 0);
507 int w = conCharWidth('>');
508 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
509 conDrawChar('>');
510 uint spos = conclilen;
511 while (spos > 0) {
512 char ch = concli.ptr[spos-1];
513 if (w+conCharWidth(ch) > me.width-XOfs*2-12) break;
514 w += conCharWidth(ch);
515 --spos;
517 glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
518 foreach (char ch; concli[spos..conclilen]) conDrawChar(ch);
519 // cursor
520 bindTexture(0);
521 glColor4f(1.0f, 0.5f, 0.0f, 1.0f);
522 glRectf(0, 0, 12, 16);
523 y -= conCharHeight;
525 // draw console text
526 glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
527 glPushMatrix();
528 scope(exit) glPopMatrix();
529 glTranslatef(XOfs, y, 0);
531 void putLine(T) (auto ref T line, usize pos=0) {
532 if (y+conCharHeight <= 0) return;
533 int w = XOfs;
534 usize sp = pos;
535 while (sp < line.length) {
536 char ch = line[sp++];
537 int cw = conCharWidth(ch);
538 if ((w += cw) > me.width-XOfs) { w -= cw; --sp; break; }
540 if (sp < line.length) putLine(line, sp); // recursive put tail
541 // draw line
542 if (skipLines-- <= 0) {
543 while (pos < sp) conDrawChar(line[pos++]);
544 glPopMatrix();
545 glPushMatrix();
546 y -= conCharHeight;
547 glTranslatef(XOfs, y, 0);
551 foreach (auto line; conbufLinesRev) {
552 putLine(line);
553 if (y+conCharHeight <= 0) break;
559 // ////////////////////////////////////////////////////////////////////////// //
560 // should be called when OpenGL is initialized
561 void loadMap (string mapname) {
562 mapscripts.runUnloading(); // "map unloading" script
563 clearMapScripts();
565 if (map !is null) map.clear();
566 map = new LevelMap(mapname);
567 curmapname = mapname;
569 ugInit(map.width*TileSize, map.height*TileSize);
571 map.oglBuildMega();
572 mapTilesChanged = 0;
574 if (fboLevel !is null) fboLevel.clear();
575 if (fboLevelLight !is null) fboLevelLight.clear();
576 if (fboOrigBack !is null) fboOrigBack.clear();
577 if (fboLMaskSmall !is null) fboLMaskSmall.clear();
579 fboLevel = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // final level render will be here
580 fboLevelLight = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // level lights will be rendered here
581 fboOrigBack = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest/*, Texture.Option.Depth*/); // background+foreground
582 fboLMaskSmall = new FBO(map.width, map.height, Texture.Option.Nearest); // small lightmask
584 shadToPolar.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize-1, map.height*TileSize-1); });
585 shadBlur.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
586 shadBlurOcc.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
587 shadAmbient.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
589 glActiveTexture(GL_TEXTURE0+0);
590 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
591 orthoCamera(vlWidth, vlHeight);
593 Actor.resetStorage();
595 setupMapScripts();
596 mapscripts.runInit();
597 loadMapMonsters();
598 dotInit();
599 mapscripts.runLoaded();
601 // save first snapshot
602 if (prevFrameActorsData.length == 0) prevFrameActorsData = new ubyte[](Actor.actorSize*65536); // ~15-20 megabytes
603 prevFrameActorOfs[] = uint.max; // just for fun
604 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
606 levelLoaded = true;
607 rebuildMapMegaTextures();
609 { import core.memory : GC; GC.collect(); }
613 // ////////////////////////////////////////////////////////////////////////// //
614 //FIXME: optimize!
615 __gshared uint mapTilesChanged = 0;
618 //WARNING! this can be called only from DACS, so we don't have to sync it!
619 public void mapDirty (uint layermask) { mapTilesChanged |= layermask; }
622 void rebuildMapMegaTextures () {
623 //fbo.replaceTexture
624 //mapTilesChanged = false;
625 //map.clearMegaTextures();
626 map.oglBuildMega(mapTilesChanged);
627 mapTilesChanged = 0;
628 dotsAwake(); // let dormant dots fall
629 // rebuild small occluders texture
630 glDisable(GL_BLEND);
631 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
632 fboLMaskSmall.exec({
633 orthoCamera(map.width, map.height);
634 drawAtXY(map.texgl.ptr[map.LightMask].tid, 0, 0, map.width, map.height, mirrorY:true);
639 // ////////////////////////////////////////////////////////////////////////// //
640 // messages
641 struct Message {
642 enum Phase { FadeIn, Stay, FadeOut }
643 Phase phase;
644 int alpha;
645 int pauseMsecs;
646 MonoTime removeTime;
647 char[256] text;
648 usize textlen;
651 //private import core.sync.mutex : Mutex;
653 __gshared Message[128] messages;
654 __gshared uint messagesUsed = 0;
656 //__gshared Mutex messageLock;
657 //shared static this () { messageLock = new Mutex(); }
660 void addMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
661 //messageLock.lock();
662 //scope(exit) messageLock.unlock();
663 if (msgtext.length == 0) return;
664 conwriteln(msgtext);
665 if (pauseMsecs <= 50) return;
666 if (messagesUsed == messages.length) {
667 // remove top message
668 foreach (immutable cidx; 1..messagesUsed) messages.ptr[cidx-1] = messages.ptr[cidx];
669 messages.ptr[0].alpha = 255;
670 --messagesUsed;
672 // quick replace
673 if (!noreplace && messagesUsed == 1) {
674 switch (messages.ptr[0].phase) {
675 case Message.Phase.FadeIn:
676 messages.ptr[0].phase = Message.Phase.FadeOut;
677 break;
678 case Message.Phase.Stay:
679 messages.ptr[0].phase = Message.Phase.FadeOut;
680 messages.ptr[0].alpha = 255;
681 break;
682 default:
685 auto msg = messages.ptr+messagesUsed;
686 ++messagesUsed;
687 msg.phase = Message.Phase.FadeIn;
688 msg.alpha = 0;
689 msg.pauseMsecs = pauseMsecs;
690 // copy text
691 if (msgtext.length > msg.text.length) {
692 msg.text = msgtext[0..msg.text.length];
693 msg.textlen = msg.text.length;
694 } else {
695 msg.text[0..msgtext.length] = msgtext[];
696 msg.textlen = msgtext.length;
701 void doMessages (MonoTime curtime) {
702 //messageLock.lock();
703 //scope(exit) messageLock.unlock();
705 if (messagesUsed == 0) return;
706 glEnable(GL_BLEND);
707 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
709 Message* msg;
711 again:
712 msg = messages.ptr;
713 final switch (msg.phase) {
714 case Message.Phase.FadeIn:
715 if ((msg.alpha += 10) >= 255) {
716 msg.phase = Message.Phase.Stay;
717 msg.removeTime = curtime+dur!"msecs"(msg.pauseMsecs);
718 goto case; // to stay
720 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
721 break;
722 case Message.Phase.Stay:
723 if (msg.removeTime <= curtime) {
724 msg.alpha = 255;
725 msg.phase = Message.Phase.FadeOut;
726 goto case; // to fade
728 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
729 break;
730 case Message.Phase.FadeOut:
731 if ((msg.alpha -= 10) <= 0) {
732 if (--messagesUsed == 0) return;
733 // remove this message
734 foreach (immutable cidx; 1..messagesUsed+1) messages.ptr[cidx-1] = messages.ptr[cidx];
735 goto again;
737 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
738 break;
741 smDrawText(10, 10, msg.text[0..msg.textlen]);
745 // ////////////////////////////////////////////////////////////////////////// //
746 //mixin(Actor.FieldPropMixin!("0drawlistpos", uint));
748 mixin(Actor.FieldGetMixin!("classtype", StrId)); // fget_classtype
749 mixin(Actor.FieldGetMixin!("classname", StrId)); // fget_classname
750 mixin(Actor.FieldGetMixin!("x", int));
751 mixin(Actor.FieldGetMixin!("y", int));
752 mixin(Actor.FieldGetMixin!("s", int));
753 mixin(Actor.FieldGetMixin!("radius", int));
754 mixin(Actor.FieldGetMixin!("height", int));
755 mixin(Actor.FieldGetMixin!("flags", uint));
756 mixin(Actor.FieldGetMixin!("zAnimstate", StrId));
757 mixin(Actor.FieldGetMixin!("zAnimidx", int));
758 mixin(Actor.FieldGetMixin!("dir", uint));
759 mixin(Actor.FieldGetMixin!("attLightXOfs", int));
760 mixin(Actor.FieldGetMixin!("attLightYOfs", int));
761 mixin(Actor.FieldGetMixin!("attLightRGBX", uint));
763 //mixin(Actor.FieldGetPtrMixin!("classtype", StrId)); // fget_classtype
764 //mixin(Actor.FieldGetPtrMixin!("classname", StrId)); // fget_classname
765 mixin(Actor.FieldGetPtrMixin!("x", int));
766 mixin(Actor.FieldGetPtrMixin!("y", int));
767 //mixin(Actor.FieldGetPtrMixin!("flags", uint));
768 //mixin(Actor.FieldGetPtrMixin!("zAnimstate", StrId));
769 //mixin(Actor.FieldGetPtrMixin!("zAnimidx", int));
770 //mixin(Actor.FieldGetPtrMixin!("dir", uint));
771 //mixin(Actor.FieldGetPtrMixin!("attLightXOfs", int));
772 //mixin(Actor.FieldGetPtrMixin!("attLightYOfs", int));
773 //mixin(Actor.FieldGetPtrMixin!("attLightRGBX", uint));
776 // ////////////////////////////////////////////////////////////////////////// //
777 __gshared int vportX0, vportY0, vportX1, vportY1;
780 // ////////////////////////////////////////////////////////////////////////// //
781 void renderLightAmbient() (int lightX, int lightY, int lightW, int lightH, in auto ref SVec4F lcol) {
782 //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));
783 if (lightW < 1 || lightH < 1) return;
784 int lightX1 = lightX+lightW-1;
785 int lightY1 = lightY+lightH-1;
786 // clip light to viewport
787 if (lightX < vportX0) lightX = vportX0;
788 if (lightY < vportY0) lightY = vportY0;
789 if (lightX1 > vportX1) lightX1 = vportX1;
790 if (lightY1 > vportY1) lightY1 = vportY1;
791 // is this light visible?
792 //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));
793 if (lightX1 < lightX || lightY1 < lightY || lightX > vportX1 || lightY > vportY1 || lightX1 < vportX0 || lightY1 < vportY0) return;
794 //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));
796 fboLevelLight.exec({
797 bindTexture(0);
798 glEnable(GL_BLEND);
799 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
800 //glDisable(GL_BLEND);
801 orthoCamera(map.width*TileSize, map.height*TileSize);
802 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
803 shadAmbient.exec((Shader shad) {
804 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
805 //shad["lightPos"] = SVec2F(lightX, lightY);
806 glRectf(lightX, lightY, lightX1, lightY1);
812 // ////////////////////////////////////////////////////////////////////////// //
813 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
814 if (lightRadius < 2) return;
815 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
816 int lightSize = lightRadius*2;
817 // is this light visible?
818 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*TileSize || lightY-lightRadius >= map.height*TileSize) return;
820 // out of viewport -- do nothing
821 if (lightX+lightRadius < vportX0 || lightY+lightRadius < vportY0) return;
822 if (lightX-lightRadius > vportX1 || lightY-lightRadius > vportY1) return;
824 if (lightX >= 0 && lightY >= 0 && lightX < map.width*TileSize && lightY < map.height*TileSize &&
825 map.teximgs[map.LightMask].imageData.colors.ptr[lightY*(map.width*TileSize)+lightX].a > 190) return;
827 // common color for all the following
828 glDisable(GL_BLEND);
829 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
831 // build 1d distance map to fboShadowMapId
832 fboDistMap.ptr[lightRadius].exec({
833 // no need to clear it, shader will take care of that
834 shadToPolar.exec((Shader shad) {
835 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
836 shad["lightPos"] = SVec2F(lightX, lightY);
837 orthoCamera(lightSize, 1);
838 // it doesn't matter what we will draw here, so just draw filled rect
839 glRectf(0, 0, lightSize, 1);
843 // build light texture for blending
844 fboOccluders.exec({
845 // no need to clear it, shader will take care of that
846 // debug
847 //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
848 //glClear(GL_COLOR_BUFFER_BIT);
849 shadBlur.exec((Shader shad) {
850 shad["lightTexSize"] = SVec2F(lightSize, MaxLightRadius*2); // x: size of distmap; y: size of this texture
851 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
852 shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
853 orthoCamera(fboOccluders.tex.width, fboOccluders.tex.height);
854 //drawAtXY(fboDistMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
855 bindTexture(fboDistMap.ptr[lightRadius].tex.tid);
856 glBegin(GL_QUADS);
857 glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0); // top-left
858 glTexCoord2f(1.0f, 0.0f); glVertex2i(lightSize, 0); // top-right
859 glTexCoord2f(1.0f, 1.0f); glVertex2i(lightSize, lightSize); // bottom-right
860 glTexCoord2f(0.0f, 1.0f); glVertex2i(0, lightSize); // bottom-left
861 glEnd();
865 // blend light texture
866 fboLevelLight.exec({
867 glEnable(GL_BLEND);
868 //glDisable(GL_BLEND);
869 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
870 orthoCamera(fboLevelLight.tex.width, fboLevelLight.tex.height);
871 //drawAtXY(fboOccluders.tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
872 float occe = 1.0f*lightSize/(MaxLightRadius*2);
873 float occs = 1.0f-occe;
874 int x0 = lightX-lightRadius+0;
875 int y1 = lightY-lightRadius+0;
876 int x1 = lightX+lightRadius-1+1;
877 int y0 = lightY+lightRadius-1+1;
878 bindTexture(fboOccluders.tex.tid);
879 glBegin(GL_QUADS);
881 glTexCoord2f(0.0f, 0.0f); glVertex2i(x0, y0); // top-left
882 glTexCoord2f(occe, 0.0f); glVertex2i(x1, y0); // top-right
883 glTexCoord2f(occe, occe); glVertex2i(x1, y1); // bottom-right
884 glTexCoord2f(0.0f, occe); glVertex2i(x0, y1); // bottom-left
886 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
887 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
888 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
889 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
890 glEnd();
892 bindTexture(0);
893 glRectf(x0, y0, x1, y1);
895 // and blend it again, with the shader that will touch only occluders
896 shadBlurOcc.exec((Shader shad) {
897 //shad["lightTexSize"] = SVec2F(lightSize, lightSize);
898 shad["lightTexSize"] = SVec2F(lightSize, fboOccluders.tex.height);
899 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
900 shad["lightPos"] = SVec2F(lightX, lightY);
901 //shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
902 bindTexture(fboOccluders.tex.tid);
903 glBegin(GL_QUADS);
904 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
905 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
906 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
907 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
908 glEnd();
909 //drawAtXY(fboOccluders.tex.tid, lightX-lightRadius, lightY-lightRadius, lightSize, lightSize, mirrorY:true);
915 // ////////////////////////////////////////////////////////////////////////// //
916 __gshared int testLightX = vlWidth/2, testLightY = vlHeight/2;
917 __gshared bool testLightMoved = false;
918 //__gshared int mapOfsX, mapOfsY;
919 //__gshared bool movement = false;
920 __gshared float iLiquidTime = 0.0;
921 //__gshared bool altMove = false;
924 void renderScene (MonoTime curtime) {
925 //enum BackIntens = 0.05f;
926 enum BackIntens = 0.0f;
928 gloStackClear();
929 float atob = (curtime > lastthink ? cast(float)((curtime-lastthink).total!"msecs")/cast(float)((nextthink-lastthink).total!"msecs") : 1.0f);
930 if (gamePaused || inEditMode) atob = 1.0f;
931 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
934 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
935 int curfp = cast(int)((curtime-lastthink).total!"msecs");
936 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
940 int mofsx, mofsy; // camera offset, will be set in background layer builder
942 if (mapTilesChanged != 0) rebuildMapMegaTextures();
944 // build background layer
945 fboOrigBack.exec({
946 //glDisable(GL_BLEND);
947 //glClearDepth(1.0f);
948 //glDepthFunc(GL_LESS);
949 //glDepthFunc(GL_NEVER);
950 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
951 glClear(GL_COLOR_BUFFER_BIT/*|GL_DEPTH_BUFFER_BIT*/);
952 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
953 orthoCamera(map.width*TileSize, map.height*TileSize);
955 glEnable(GL_BLEND);
956 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
957 // draw sky
959 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
960 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
961 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
962 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
964 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*TileSize, map.MapSize*TileSize);
965 // draw background
966 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
967 // draw distorted liquid areas
968 shadLiquidDistort.exec((Shader shad) {
969 shad["iDistortTime"] = iLiquidTime;
970 drawAtXY(map.texgl.ptr[map.AllLiquids], 0, 0);
972 // monsters, items; we'll do linear interpolation here
973 glColor3f(1.0f, 1.0f, 1.0f);
974 //glEnable(GL_DEPTH_TEST);
975 attachedLightCount = 0;
977 // who cares about memory?!
978 // draw order: players, items, monsters, other
979 static struct DrawInfo {
980 ActorDef adef;
981 ActorId aid;
982 int actorX, actorY;
983 @disable this (this); // no copies
985 enum { Pixels, Players, Items, Monsters, Other }
986 __gshared DrawInfo[65536][4] drawlists;
987 __gshared uint[4] dlpos;
988 DrawInfo camchickdi;
990 dlpos[] = 0;
992 Actor.forEach((ActorId me) {
993 //me.fprop_0drawlistpos = 0;
994 if (auto adef = findActorDef(me)) {
995 uint dlnum = Other;
996 switch (adef.classtype.get) {
997 case "monster": dlnum = (adef.classname.get != "Player" ? Monsters : Players); break;
998 case "item": dlnum = Items; break;
999 default: dlnum = Other; break;
1001 if (me.fget_flags&AF_PIXEL) dlnum = Pixels;
1002 int actorX, actorY; // current actor position
1004 auto ofs = prevFrameActorOfs.ptr[me.id&0xffff];
1005 if (frameInterpolation && ofs < uint.max-1 && (me.fget_flags&AF_TELEPORT) == 0 && Actor.isSameSavedActor(me.id, prevFrameActorsData.ptr, ofs)) {
1006 import core.stdc.math : roundf;
1007 auto xptr = prevFrameActorsData.ptr+ofs;
1008 int ox = xptr.fgetp_x;
1009 int nx = me.fget_x;
1010 int oy = xptr.fgetp_y;
1011 int ny = me.fget_y;
1012 actorX = cast(int)(ox+roundf((nx-ox)*atob));
1013 actorY = cast(int)(oy+roundf((ny-oy)*atob));
1014 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
1015 } else {
1016 actorX = me.fget_x;
1017 actorY = me.fget_y;
1020 if (me.id == cameraChick.id) {
1021 camchickdi.adef = adef;
1022 camchickdi.aid = me;
1023 camchickdi.actorX = actorX;
1024 camchickdi.actorY = actorY;
1026 // draw sprite
1027 if ((me.fget_flags&AF_NODRAW) == 0) {
1028 //auto dl = &drawlists[dlnum][dlpos.ptr[dlnum]];
1029 //me.fprop_0drawlistpos = (dlpos.ptr[dlnum]&0xffff)|((dlnum&0xff)<<16);
1030 auto dl = drawlists.ptr[dlnum].ptr+dlpos.ptr[dlnum];
1031 ++dlpos.ptr[dlnum];
1032 dl.adef = adef;
1033 dl.aid = me;
1034 dl.actorX = actorX;
1035 dl.actorY = actorY;
1037 // process attached lights
1038 if ((me.fget_flags&AF_NOLIGHT) == 0) {
1039 uint alr = me.fget_attLightRGBX;
1040 bool isambient = (me.fget_classtype.get == "light" && me.fget_classname.get == "Ambient");
1041 if ((alr&0xff) >= 4 || (isambient && me.fget_radius >= 1 && me.fget_height >= 1)) {
1042 //if (isambient) conwriteln("isambient: ", isambient, "; x=", me.fget_x, "; y=", me.fget_y, "; w=", me.fget_radius, "; h=", me.fget_height);
1043 // yep, add it
1044 auto li = attachedLights.ptr+attachedLightCount;
1045 ++attachedLightCount;
1046 li.type = (!isambient ? AttachedLightInfo.Type.Point : AttachedLightInfo.Type.Ambient);
1047 li.x = actorX+me.fget_attLightXOfs;
1048 li.y = actorY+me.fget_attLightYOfs;
1049 li.r = ((alr>>24)&0xff)/255.0f; // red or intensity
1050 if ((alr&0x00_ff_ff_00U) == 0x00_00_01_00U) {
1051 li.uncolored = true;
1052 } else {
1053 li.g = ((alr>>16)&0xff)/255.0f;
1054 li.b = ((alr>>8)&0xff)/255.0f;
1055 li.uncolored = false;
1057 li.radius = (alr&0xff);
1058 if (li.radius > MaxLightRadius) li.radius = MaxLightRadius;
1059 if (isambient) {
1060 li.w = me.fget_radius;
1061 li.h = me.fget_height;
1065 } else {
1066 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
1070 // draw actor lists
1071 foreach_reverse (uint dlnum; 0..drawlists.length) {
1072 if (dlnum == Pixels) continue;
1073 auto dl = drawlists.ptr[dlnum].ptr;
1074 if (dlnum == Players) dl += dlpos.ptr[dlnum]-1;
1075 foreach (uint idx; 0..dlpos.ptr[dlnum]) {
1076 auto me = dl.aid;
1077 if (auto isp = dl.adef.animSpr(me.fget_zAnimstate, me.fget_dir, me.fget_zAnimidx)) {
1078 //drawAtXY(isp.tex, dl.actorX-isp.vga.sx, dl.actorY-isp.vga.sy);
1079 isp.drawAtXY(dl.actorX, dl.actorY);
1081 if (dlnum != Players) ++dl; else --dl;
1084 // draw pixels
1085 if (dlpos[Pixels]) {
1086 bindTexture(0);
1087 bool pointsStarted = false;
1088 Color lastColor = Color(0, 0, 0, 0);
1089 auto dl = drawlists.ptr[Pixels].ptr;
1090 foreach (uint idx; 0..dlpos.ptr[Pixels]) {
1091 auto me = dl.aid;
1092 auto s = me.fget_s;
1093 if (s < 0 || s > 255) continue; //FIXME
1094 Color clr = d2dpal.ptr[s&0xff];
1095 if (clr.a == 0) continue;
1096 if (clr != lastColor) {
1097 if (pointsStarted) glEnd();
1098 glColor4f(clr.r/255.0f, clr.g/255.0f, clr.b/255.0f, clr.a/255.0f);
1099 lastColor = clr;
1100 pointsStarted = false;
1102 if (!pointsStarted) {
1103 glBegin(GL_POINTS);
1104 pointsStarted = true;
1106 glVertex2i(dl.actorX, dl.actorY);
1107 ++dl;
1109 if (pointsStarted) {
1110 glEnd();
1111 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1115 // camera movement
1116 if (/*altMove || movement ||*/ scale == 1 || !cameraChick.valid) {
1117 mofsx = 0;
1118 mofsy = 0;
1119 vportX0 = 0;
1120 vportY0 = 0;
1121 vportX1 = map.width*TileSize;
1122 vportY1 = map.height*TileSize;
1123 } else {
1124 int tiltHeight = /*getMapViewHeight()*/(vlHeight/scale)/4;
1125 int vy = cameraChick.looky!int;
1126 if (vy < -tiltHeight) vy = -tiltHeight; else if (vy > tiltHeight) vy = tiltHeight;
1127 int swdt = vlWidth/scale;
1128 int shgt = vlHeight/scale;
1129 int x = camchickdi.actorX-swdt/2;
1130 int y = (camchickdi.actorY+vy)-shgt/2;
1131 if (x < 0) x = 0; else if (x >= map.width*TileSize-swdt) x = map.width*TileSize-swdt-1;
1132 if (y < 0) y = 0; else if (y >= map.height*TileSize-shgt) y = map.height*TileSize-shgt-1;
1133 mofsx = x*2;
1134 mofsy = y*2;
1135 vportX0 = mofsx/scale;
1136 vportY0 = mofsy/scale;
1137 vportX1 = vportX0+vlWidth/scale;
1138 vportY1 = vportY0+vlHeight/scale;
1141 //glDisable(GL_DEPTH_TEST);
1142 // draw dots
1143 dotDraw(atob);
1144 // do liquid coloring
1145 drawAtXY(map.texgl.ptr[map.LiquidMask], 0, 0);
1146 // foreground -- hide secrets, draw lifts and such
1147 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
1150 enum r = 255;
1151 enum g = 0;
1152 enum b = 0;
1153 enum a = 255;
1154 bindTexture(0);
1155 glColor4f(r/255.0f, g/255.0f, b/255.0f, a/255.0f);
1156 glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
1157 if (cameraChick.valid) {
1158 glBegin(GL_POINTS);
1159 glVertex2i(camchickdi.actorX, camchickdi.actorY-70);
1160 glEnd();
1161 //glRectf(camchickdi.actorX, camchickdi.actorY-70, camchickdi.actorX+4, camchickdi.actorY-70+4);
1163 //glRectf(0, 0, 300, 300);
1164 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1170 if (doLighting) {
1171 glDisable(GL_BLEND);
1172 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1174 // make smaller occluder texture, so we can trace faster
1175 //assert(fboLMaskSmall.tex.width == map.width);
1176 //assert(fboLMaskSmall.tex.height == map.height);
1177 /+!!!
1178 fboLMaskSmall.exec({
1179 orthoCamera(map.width, map.height);
1180 drawAtXY(map.texgl.ptr[map.LightMask].tid, 0, 0, map.width, map.height, mirrorY:true);
1184 // clear light layer
1185 fboLevelLight.exec({
1186 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
1187 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
1188 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
1189 glClear(GL_COLOR_BUFFER_BIT);
1192 // texture 1 is background
1193 glActiveTexture(GL_TEXTURE0+1);
1194 glBindTexture(GL_TEXTURE_2D, fboOrigBack.tex.tid);
1195 // texture 2 is occluders
1196 glActiveTexture(GL_TEXTURE0+2);
1197 glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.LightMask].tid);
1198 // texture 3 is small occluder map
1199 glActiveTexture(GL_TEXTURE0+3);
1200 glBindTexture(GL_TEXTURE_2D, fboLMaskSmall.tex.tid);
1201 // done texture assign
1202 glActiveTexture(GL_TEXTURE0+0);
1205 enum LYOfs = 1;
1207 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
1208 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
1209 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
1210 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
1211 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
1212 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
1213 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
1214 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
1215 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
1216 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
1218 renderLight(24*TileSize+4, (24+18)*TileSize-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
1220 renderLight(280, 330, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
1223 foreach (; 0..1) {
1224 // attached lights
1225 foreach (ref li; attachedLights[0..attachedLightCount]) {
1226 if (li.type == AttachedLightInfo.Type.Ambient) {
1227 //conwriteln("ambient: x=", li.x, "; y=", li.y, "; w=", li.w, "; h=", li.h);
1228 // ambient light
1229 if (li.uncolored) {
1230 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(0.0f, 0.0f, 0.0f, li.r));
1231 } else {
1232 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(li.r, li.g, li.b, 1.0f));
1234 } else if (li.type == AttachedLightInfo.Type.Point) {
1235 // point light
1236 if (li.uncolored) {
1237 renderLight(li.x, li.y, SVec4F(0.0f, 0.0f, 0.0f, li.r), li.radius);
1238 } else {
1239 renderLight(li.x, li.y, SVec4F(li.r, li.g, li.b, 1.0f), li.radius);
1246 if (testLightMoved) {
1247 testLightX = testLightX/scale+mofsx/scale;
1248 testLightY = testLightY/scale+mofsy/scale;
1249 testLightMoved = false;
1251 foreach (immutable _; 0..1) {
1252 renderLight(testLightX, testLightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
1256 glActiveTexture(GL_TEXTURE0+1);
1257 glBindTexture(GL_TEXTURE_2D, 0);
1258 glActiveTexture(GL_TEXTURE0+2);
1259 glBindTexture(GL_TEXTURE_2D, 0);
1260 glActiveTexture(GL_TEXTURE0+3);
1261 glBindTexture(GL_TEXTURE_2D, 0);
1262 glActiveTexture(GL_TEXTURE0+0);
1265 // draw scaled level
1267 shadScanlines.exec((Shader shad) {
1268 shad["scanlines"] = scanlines;
1269 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
1270 glClear(GL_COLOR_BUFFER_BIT);
1271 orthoCamera(vlWidth, vlHeight);
1272 //orthoCamera(map.width*TileSize*scale, map.height*TileSize*scale);
1273 //glMatrixMode(GL_MODELVIEW);
1274 //glLoadIdentity();
1275 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
1276 // somehow, FBO objects are mirrored; wtf?!
1277 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1278 //glLoadIdentity();
1283 fboLevelLight.exec({
1284 smDrawText(map.width*TileSize/2, map.height*TileSize/2, "Testing...");
1289 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
1291 glDisable(GL_BLEND);
1294 fboOrigBack.exec({
1295 //auto img = smfont.ptr[0x39];
1296 auto img = fftest;
1297 if (img !is null) {
1298 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
1299 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
1306 { // http://stackoverflow.com/questions/7207422/setting-up-opengl-multiple-render-targets
1307 GLenum[1] buffers = [ GL_BACK_LEFT, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
1308 //GLenum[1] buffers = [ GL_NONE, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
1309 glDrawBuffers(1, buffers.ptr);
1314 orthoCamera(vlWidth, vlHeight);
1315 auto tex = (doLighting ? fboLevelLight.tex.tid : fboOrigBack.tex.tid);
1316 drawAtXY(tex, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1319 glEnable(GL_BLEND);
1320 //glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1321 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1322 drawAtXY(fboLMaskSmall.tex, 0, 0);
1323 //drawAtXY(map.texgl.ptr[map.LightMask], 0, 0);
1326 if (levelLoaded) {
1327 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1328 //orthoCamera(map.width*TileSize, map.height*TileSize);
1329 glEnable(GL_BLEND);
1330 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1331 hudScripts.runDraw();
1334 //drawAtXY(map.texgl.ptr[map.LightMask].tid, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1335 //drawAtXY(fboLMaskSmall.tex.tid, 0, 0, map.width*TileSize, map.height*TileSize);
1337 if (inEditMode) {
1338 glEnable(GL_BLEND);
1339 //glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1340 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1341 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1342 editorUpdateImage();
1343 fboEditor.tex.setFromImage(editorImg);
1344 drawAtXY(fboEditor.tex, 0, 0);
1345 glDisable(GL_BLEND);
1348 doMessages(curtime);
1350 if (rConsoleVisible) {
1351 renderConsoleFBO();
1352 orthoCamera(vlWidth, vlHeight);
1353 glEnable(GL_BLEND);
1354 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1355 glColor4f(1.0f, 1.0f, 1.0f, 0.7f);
1356 drawAtXY(fboConsole.tex, 0, rConsoleHeight-vlHeight, mirrorY:true);
1361 // ////////////////////////////////////////////////////////////////////////// //
1362 // returns time slept
1363 int sleepAtMaxMsecs (int msecs) {
1364 if (msecs > 0) {
1365 import core.sys.posix.signal : timespec;
1366 import core.sys.posix.time : nanosleep;
1367 timespec ts = void, tpassed = void;
1368 ts.tv_sec = 0;
1369 ts.tv_nsec = msecs*1000*1000+(500*1000); // milli to nano
1370 nanosleep(&ts, &tpassed);
1371 return (ts.tv_nsec-tpassed.tv_nsec)/(1000*1000);
1372 } else {
1373 return 0;
1378 // ////////////////////////////////////////////////////////////////////////// //
1379 mixin(import("editor.d"));
1382 // ////////////////////////////////////////////////////////////////////////// //
1383 // rendering thread
1384 enum D2DFrameTime = 55; // milliseconds
1385 enum MinFrameTime = 1000/60; // ~60 FPS
1387 public void renderThread (Tid starterTid) {
1388 enum BoolOptVarMsgMixin(string varname) =
1389 "if (msg.toggle) msg.value = !"~varname~";\n"~
1390 "if ("~varname~" != msg.value) "~varname~" = msg.value; else msg.showMessage = false;\n";
1392 send(starterTid, 42);
1393 try {
1394 MonoTime curtime = MonoTime.currTime;
1396 lastthink = curtime; // for interpolator
1397 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1398 MonoTime nextvframe = curtime;
1400 enum MaxFPSFrames = 16;
1401 float frtimes = 0.0f;
1402 int framenum = 0;
1403 int prevFPS = -1;
1404 int hushFrames = 6; // ignore first `hushFrames` frames overtime
1405 MonoTime prevFrameStartTime = curtime;
1407 bool vframeWasLost = false;
1409 void resetFrameTimers () {
1410 MonoTime curtime = MonoTime.currTime;
1411 lastthink = curtime; // for interpolator
1412 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1413 nextvframe = curtime;
1416 void loadNewLevel (string name) {
1418 if (levelLoaded) {
1419 conwriteln("ERROR: can't load new levels yet");
1420 return;
1423 if (name.length == 0) {
1424 conwriteln("ERROR: can't load empty level!");
1426 conwriteln("loading map '", name, "'");
1427 loadMap(name);
1428 resetFrameTimers();
1431 conRegFunc!({
1432 string mn = genNextMapName(0);
1433 if (mn.length) {
1434 nextmapname = null; // clear "exit" flag
1435 loadNewLevel(mn);
1436 } else {
1437 conwriteln("can't skip level");
1439 })("skiplevel", "skip current level");
1441 conRegFunc!({
1442 inEditMode = !inEditMode;
1443 if (inEditMode) sdwindow.hideCursor(); else sdwindow.showCursor();
1444 })("ed_toggle", "toggle editor");
1446 conRegFunc!({
1447 if (inEditMode) {
1448 inEditMode = false;
1449 sdwindow.showCursor();
1451 })("ed_exit", "exit from editor");
1453 conRegFunc!((string mapname) {
1454 nextmapname = null; // clear "exit" flag
1455 loadNewLevel(mapname);
1456 })("map", "load map");
1458 void receiveMessages () {
1459 for (;;) {
1460 import core.time : Duration;
1461 //conwriteln("rendering thread: waiting for messages...");
1462 auto got = receiveTimeout(
1463 Duration.zero, // don't wait
1464 (TMsgMessage msg) {
1465 addMessage(msg.text[0..msg.textlen], msg.pauseMsecs, msg.noreplace);
1467 (TMsgTestLightMove msg) {
1468 testLightX = msg.x;
1469 testLightY = msg.y;
1470 testLightMoved = true;
1472 (TMsgMouseEvent msg) { if (atomicLoad(editMode)) editorMouseEvent(msg); },
1473 (TMsgKeyEvent msg) { if (atomicLoad(editMode)) editorKeyEvent(msg); },
1474 (TMsgChar msg) { concliChar(msg.ch); },
1475 (Variant v) {
1476 conwriteln("WARNING: unknown thread message received and ignored");
1479 if (!got) {
1480 // no more messages
1481 //conwriteln("rendering thread: no more messages");
1482 break;
1485 if (nextmapname.length) {
1486 string mn = nextmapname;
1487 nextmapname = null; // clear "exit" flag
1488 loadNewLevel(mn);
1492 void processConsoleCommands () {
1493 cbufLock();
1494 scope(exit) cbufUnlock();
1495 concmdDoAll();
1498 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1499 bool doThinkFrame () {
1500 if (curtime >= nextthink) {
1501 lastthink = curtime;
1502 while (nextthink <= curtime) nextthink += dur!"msecs"(D2DFrameTime);
1503 if (levelLoaded) {
1504 // save snapshot and other data for interpolator
1505 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
1506 if (!gamePaused && !inEditMode) {
1507 // process actors
1508 doActorsThink();
1509 dotThink();
1512 // some timing
1513 auto tm = MonoTime.currTime;
1514 int thinkTime = cast(int)((tm-curtime).total!"msecs");
1515 if (thinkTime > 9) { import core.stdc.stdio; printf("spent on thinking: %d msecs\n", thinkTime); }
1516 curtime = tm;
1517 return true;
1518 } else {
1519 return false;
1523 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1524 bool doVFrame () {
1525 bool doCheckTime = void;
1526 if (!renderVBL) {
1527 // timer
1528 doCheckTime = true;
1529 } else {
1530 // vsync
1531 __gshared bool prevLost = false;
1532 doCheckTime = vframeWasLost;
1533 if (vframeWasLost) {
1534 if (!prevLost) {
1535 { import core.stdc.stdio; printf("frame was lost!\n"); }
1537 prevLost = true;
1538 } else {
1539 prevLost = false;
1542 if (doCheckTime) {
1543 if (curtime < nextvframe) return false;
1544 if (!renderVBL) {
1545 if (curtime > nextvframe) {
1546 auto overtime = cast(int)((curtime-nextvframe).total!"msecs");
1547 if (overtime > 2500) {
1548 if (hushFrames) {
1549 --hushFrames;
1550 } else {
1551 { import core.stdc.stdio; printf(" spent whole %d msecs\n", overtime); }
1557 while (nextvframe <= curtime) nextvframe += dur!"msecs"(MinFrameTime);
1558 bool ctset = false;
1560 sdwindow.mtLock();
1561 scope(exit) sdwindow.mtUnlock();
1562 ctset = sdwindow.setAsCurrentOpenGlContextNT;
1564 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
1565 if (ctset) {
1566 if (oldRenderVBL != renderVBL) {
1567 oldRenderVBL = renderVBL;
1568 sdwindow.vsync = renderVBL;
1570 // render scene
1571 iLiquidTime = cast(float)((curtime-MonoTime.zero).total!"msecs"%10000000)/18.0f*0.04f;
1572 receiveMessages(); // here, 'cause we need active OpenGL context for some messages
1573 processConsoleCommands();
1574 if (levelLoaded) {
1575 renderScene(curtime);
1576 } else {
1577 //renderLoading(curtime);
1579 sdwindow.mtLock();
1580 scope(exit) sdwindow.mtUnlock();
1581 sdwindow.swapOpenGlBuffers();
1582 glFinish();
1583 sdwindow.releaseCurrentOpenGlContext();
1584 vframeWasLost = false;
1585 } else {
1586 vframeWasLost = true;
1587 { import core.stdc.stdio; printf("xframe was lost!\n"); }
1589 curtime = MonoTime.currTime;
1590 return true;
1593 while (!quitRequested && !sdwindow.closed) {
1594 curtime = MonoTime.currTime;
1595 auto fstime = curtime;
1597 doThinkFrame(); // this will fix curtime if necessary
1598 if (doVFrame()) {
1599 if (!vframeWasLost) {
1600 // fps
1601 auto frameTime = cast(float)(curtime-prevFrameStartTime).total!"msecs"/1000.0f;
1602 prevFrameStartTime = curtime;
1603 frtimes += frameTime;
1604 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
1605 import std.string : format;
1606 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
1607 if (newFPS != prevFPS) {
1608 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
1609 prevFPS = newFPS;
1611 framenum = 0;
1612 frtimes = 0.0f;
1617 curtime = MonoTime.currTime;
1619 // now sleep until next "video" or "think" frame
1620 if (nextthink > curtime && nextvframe > curtime) {
1621 if (sdwindow.closed || quitRequested) break;
1622 // let's decide
1623 immutable nextVideoFrameSleep = cast(int)((nextvframe-curtime).total!"msecs");
1624 immutable nextThinkFrameSleep = cast(int)((nextthink-curtime).total!"msecs");
1625 immutable sleepTime = (nextVideoFrameSleep < nextThinkFrameSleep ? nextVideoFrameSleep : nextThinkFrameSleep);
1626 sleepAtMaxMsecs(sleepTime);
1627 //curtime = MonoTime.currTime;
1630 } catch (Throwable e) {
1631 // here, we are dead and fucked (the exact order doesn't matter)
1632 import core.stdc.stdlib : abort;
1633 import core.stdc.stdio : fprintf, stderr;
1634 import core.memory : GC;
1635 GC.disable(); // yeah
1636 thread_suspendAll(); // stop right here, you criminal scum!
1637 auto s = e.toString();
1638 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
1639 abort(); // die, you bitch!
1641 atomicStore(vquitRequested, true);
1642 sdwindow.close();
1646 // ////////////////////////////////////////////////////////////////////////// //
1647 __gshared Tid renderTid;
1648 shared bool renderThreadStarted = false;
1651 public void startRenderThread () {
1652 if (!cas(&renderThreadStarted, false, true)) {
1653 assert(0, "render thread already started!");
1655 renderTid = spawn(&renderThread, thisTid);
1656 setMaxMailboxSize(renderTid, 1024, OnCrowding.throwException); //FIXME
1657 // wait for "i'm ready" signal
1658 receive(
1659 (int ok) {
1660 if (ok != 42) assert(0, "wtf?!");
1663 conwriteln("rendering thread started");
1667 // ////////////////////////////////////////////////////////////////////////// //
1668 // thread messages
1671 // ////////////////////////////////////////////////////////////////////////// //
1672 struct TMsgMouseEvent {
1673 MouseEventType type;
1674 int x, y;
1675 int dx, dy;
1676 MouseButton button; /// See $(LREF MouseButton)
1677 int modifierState; /// See $(LREF ModifierState)
1680 public void postMouseEvent() (in auto ref MouseEvent evt) {
1681 if (!atomicLoad(renderThreadStarted)) return;
1682 TMsgMouseEvent msg;
1683 msg.type = evt.type;
1684 msg.x = evt.x;
1685 msg.y = evt.y;
1686 msg.dx = evt.dx;
1687 msg.dy = evt.dy;
1688 msg.button = evt.button;
1689 msg.modifierState = evt.modifierState;
1690 send(renderTid, msg);
1694 // ////////////////////////////////////////////////////////////////////////// //
1695 struct TMsgKeyEvent {
1696 Key key;
1697 uint hardwareCode;
1698 bool pressed;
1699 dchar character;
1700 uint modifierState;
1703 public void postKeyEvent() (in auto ref KeyEvent evt) {
1704 if (!atomicLoad(renderThreadStarted)) return;
1705 TMsgKeyEvent msg;
1706 msg.key = evt.key;
1707 msg.pressed = evt.pressed;
1708 msg.character = evt.character;
1709 msg.modifierState = evt.modifierState;
1710 send(renderTid, msg);
1714 // ////////////////////////////////////////////////////////////////////////// //
1715 struct TMsgTestLightMove {
1716 int x, y;
1719 public void postTestLightMove (int x, int y) {
1720 if (!atomicLoad(renderThreadStarted)) return;
1721 auto msg = TMsgTestLightMove(x, y);
1722 send(renderTid, msg);
1726 // ////////////////////////////////////////////////////////////////////////// //
1727 struct TMsgMessage {
1728 char[256] text;
1729 uint textlen;
1730 int pauseMsecs;
1731 bool noreplace;
1734 public void postAddMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
1735 if (!atomicLoad(renderThreadStarted)) return;
1736 if (msgtext.length > TMsgMessage.text.length) msgtext = msgtext[0..TMsgMessage.text.length];
1737 TMsgMessage msg;
1738 msg.textlen = cast(uint)msgtext.length;
1739 if (msg.textlen) msg.text[0..msg.textlen] = msgtext[0..msg.textlen];
1740 msg.pauseMsecs = pauseMsecs;
1741 msg.noreplace = noreplace;
1742 send(renderTid, msg);
1746 // ////////////////////////////////////////////////////////////////////////// //
1747 struct TMsgChar {
1748 char ch;
1751 public void postChar (char ch) {
1752 if (!atomicLoad(renderThreadStarted)) return;
1753 TMsgChar msg;
1754 msg.ch = ch;
1755 send(renderTid, msg);
1759 // ////////////////////////////////////////////////////////////////////////// //
1760 // add console command to execution queue
1761 public void concmd (const(char)[] cmd) {
1762 //if (!atomicLoad(renderThreadStarted)) return;
1763 cbufLock();
1764 scope(exit) cbufUnlock();
1765 concmdAdd(cmd);
1768 // get console variable value; doesn't do complex conversions!
1769 public T convar(T) (const(char)[] s) {
1770 cbufLock();
1771 scope(exit) cbufUnlock();
1772 return conGetVar!T(s);
1775 // set console variable value; doesn't do complex conversions!
1776 public void convar(T) (const(char)[] s, T val) {
1777 cbufLock();
1778 scope(exit) cbufUnlock();
1779 conSetVar!T(s, val);