really compiles! ;-)
[dd2d.git] / render.d
blobd0eb28c0ee4c97143b85394c58f2a9c1a3fbd351
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.cmdcon /*: conwriteln, consoleLock, consoleUnlock*/;
28 import iv.glbinds;
29 import glutils;
30 import wadarc;
32 import iv.vfs;
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.simpledisplay : SimpleWindow, KeyEvent, MouseEvent;
50 import arsd.color;
51 import arsd.png;
54 // ////////////////////////////////////////////////////////////////////////// //
55 public __gshared bool cheatNoDoors = false;
56 public __gshared bool cheatNoWallClip = false;
59 // ////////////////////////////////////////////////////////////////////////// //
60 public __gshared SimpleWindow sdwindow;
63 public enum vlWidth = 800;
64 public enum vlHeight = 800;
65 __gshared int scale = 2;
67 public int getScale () nothrow @trusted @nogc { pragma(inline, true); return scale; }
70 // ////////////////////////////////////////////////////////////////////////// //
71 __gshared bool levelLoaded = false;
74 // ////////////////////////////////////////////////////////////////////////// //
75 __gshared bool scanlines = false;
76 __gshared bool doLighting = true;
77 __gshared bool gamePaused = false;
78 __gshared bool rConsoleVisible = false;
79 __gshared int rConsoleHeight = 16*3;
80 __gshared uint rConTextColor = 0x00ff00; // rgb
81 __gshared uint rConCursorColor = 0xff7f00; // rgb
82 __gshared uint rConInputColor = 0xffff00; // rgb
83 __gshared uint rConPromptColor = 0xffffff; // rgb
84 __gshared bool renderVBL = true;
85 __gshared bool oldRenderVBL = false;
86 shared bool editMode = false;
87 shared bool vquitRequested = false;
89 public @property bool inEditMode () nothrow @trusted @nogc { import core.atomic; return atomicLoad(editMode); }
90 @property void inEditMode (bool v) nothrow @trusted @nogc { import core.atomic; atomicStore(editMode, v); }
92 public @property bool quitRequested () nothrow @trusted @nogc { import core.atomic; return atomicLoad(vquitRequested); }
93 public @property bool conVisible () nothrow @trusted @nogc { return rConsoleVisible; }
96 // ////////////////////////////////////////////////////////////////////////// //
97 __gshared char[] concmdbuf;
98 __gshared uint concmdbufpos;
99 private import core.sync.mutex : Mutex;
100 shared static this () { concmdbuf.length = 65536; }
102 __gshared int conskiplines = 0;
105 void concmdAdd (const(char)[] s) {
106 if (s.length) {
107 if (concmdbuf.length-concmdbufpos < s.length+1) {
108 concmdbuf.assumeSafeAppend.length += s.length-(concmdbuf.length-concmdbufpos)+512;
110 if (concmdbufpos > 0 && concmdbuf[concmdbufpos-1] != '\n') concmdbuf.ptr[concmdbufpos++] = '\n';
111 concmdbuf[concmdbufpos..concmdbufpos+s.length] = s[];
112 concmdbufpos += s.length;
117 // `null`: no more
118 void concmdDoAll () {
119 if (concmdbufpos == 0) return;
120 scope(exit) concmdbufpos = 0;
121 auto ebuf = concmdbufpos;
122 const(char)[] s = concmdbuf[0..concmdbufpos];
123 for (;;) {
124 while (s.length) {
125 auto cmd = conGetCommandStr(s);
126 if (cmd is null) break;
127 try {
128 conExecute(cmd);
129 } catch (Exception e) {
130 conwriteln("***ERROR: ", e.msg);
133 if (concmdbufpos <= ebuf) break;
134 s = concmdbuf[ebuf..concmdbufpos];
135 ebuf = concmdbufpos;
140 void concliChar (char ch) {
141 if (!ch) return;
142 consoleLock();
143 scope(exit) consoleUnlock();
145 if (ch == ConInputChar.PageUp) {
146 int lnx = (rConsoleHeight-4)/conCharHeight-2;
147 if (lnx < 1) lnx = 1;
148 conskiplines += lnx;
149 conLastChange = 0;
150 return;
153 if (ch == ConInputChar.PageDown) {
154 if (conskiplines > 0) {
155 int lnx = (rConsoleHeight-4)/conCharHeight-2;
156 if (lnx < 1) lnx = 1;
157 if ((conskiplines -= lnx) < 0) conskiplines = 0;
158 conLastChange = 0;
160 return;
163 if (ch == ConInputChar.Enter) {
164 if (conskiplines) { conskiplines = 0; conLastChange = 0; }
165 auto s = conInputBuffer;
166 if (s.length > 0) {
167 concmdAdd(s);
168 conInputBufferClear(true); // add to history
169 conLastChange = 0;
171 return;
174 if (ch == '`' && conInputBuffer.length == 0) { concmd("r_console ona"); return; }
176 auto pcc = conInputLastChange();
177 conAddInputChar(ch);
178 if (pcc != conInputLastChange()) conLastChange = 0;
182 // ////////////////////////////////////////////////////////////////////////// //
183 shared static this () {
184 conRegFunc!((const(char)[] fname) {
185 try {
186 auto s = loadTextFile(fname);
187 concmd(s);
188 } catch (Exception e) {
189 conwriteln("ERROR loading script \"", fname, "\"");
191 })("exec", "execute console script");
192 conRegVar!doLighting("r_lighting", "dynamic lighting");
193 conRegVar!renderVBL("r_vsync", "sync to vblank");
194 conRegVar!gamePaused("g_pause", "pause game");
195 conRegVar!rConsoleVisible("r_console", "console visibility");
196 conRegVar!rConsoleHeight(16*3, vlHeight, "r_conheight", "console height");
197 conRegVar!rConTextColor("r_contextcolor", "console log text color, 0xrrggbb");
198 conRegVar!rConCursorColor("r_concursorcolor", "console cursor color, 0xrrggbb");
199 conRegVar!rConInputColor("r_coninputcolor", "console input color, 0xrrggbb");
200 conRegVar!rConPromptColor("r_conpromptcolor", "console prompt color, 0xrrggbb");
201 rConsoleHeight = vlHeight-vlHeight/3;
202 rConsoleHeight = vlHeight/2;
203 conRegVar!frameInterpolation("r_interpolation", "interpolate camera and sprite movement");
204 conRegVar!scale(1, 2, "r_scale", "screen scale");
205 conRegFunc!({
206 cheatNoDoors = !cheatNoDoors;
207 if (cheatNoDoors) conwriteln("player ignores doors"); else conwriteln("player respects doors");
208 })("nodoorclip", "ignore doors");
209 conRegFunc!({
210 cheatNoWallClip = !cheatNoWallClip;
211 if (cheatNoWallClip) conwriteln("player ignores walls"); else conwriteln("player respects walls");
212 })("nowallclip", "ignore walls");
213 conRegFunc!({
214 import core.atomic;
215 atomicStore(vquitRequested, true);
216 })("quit", "quit game");
217 conRegFunc!((const(char)[] msg, int pauseMsecs=3000, bool noreplace=false) {
218 char[256] buf;
219 auto s = buf.conFormatStr(msg);
220 if (s.length) postAddMessage(s, pauseMsecs, noreplace);
221 })("hudmsg", "show hud message; hudmsg msg [pausemsecs [noreplace]]");
225 // ////////////////////////////////////////////////////////////////////////// //
226 // interpolation
227 __gshared ubyte[] prevFrameActorsData;
228 __gshared uint[65536] prevFrameActorOfs; // uint.max-1: dead; uint.max: last
229 __gshared MonoTime lastthink = MonoTime.zero; // for interpolator
230 __gshared MonoTime nextthink = MonoTime.zero;
231 __gshared bool frameInterpolation = true;
234 // ////////////////////////////////////////////////////////////////////////// //
235 // attached lights
236 struct AttachedLightInfo {
237 enum Type {
238 Point,
239 Ambient,
241 Type type;
242 int x, y;
243 int w, h; // for ambient lights
244 float r, g, b;
245 bool uncolored;
246 int radius;
249 __gshared AttachedLightInfo[65536] attachedLights;
250 __gshared uint attachedLightCount = 0;
253 // ////////////////////////////////////////////////////////////////////////// //
254 enum MaxLightRadius = 256;
257 // ////////////////////////////////////////////////////////////////////////// //
258 // for light
259 __gshared FBO[MaxLightRadius+1] fboDistMap;
260 __gshared FBO /*fboLSpot,*/ fboLSpotBG, fboLSpotSmall;
261 __gshared Shader shadLightTrace, shadLightBlur, shadLightGeom, shadLightAmbient;
262 __gshared TrueColorImage editorImg;
263 __gshared FBO fboEditor;
264 __gshared FBO fboConsole;
265 __gshared Texture texSigil;
267 __gshared FBO fboLevel, fboLevelLight, fboOrigBack, fboLMaskSmall;
268 __gshared Shader shadScanlines;
269 __gshared Shader shadLiquidDistort;
272 // ////////////////////////////////////////////////////////////////////////// //
273 // call once!
274 public void initOpenGL () {
275 gloStackClear();
277 glEnable(GL_TEXTURE_2D);
278 glDisable(GL_LIGHTING);
279 glDisable(GL_DITHER);
280 glDisable(GL_BLEND);
281 glDisable(GL_DEPTH_TEST);
283 // create shaders
284 shadScanlines = new Shader("scanlines", loadTextFile("shaders/srscanlines.frag"));
286 shadLiquidDistort = new Shader("liquid_distort", loadTextFile("shaders/srliquid_distort.frag"));
287 shadLiquidDistort.exec((Shader shad) {
288 shad["texLqMap"] = 0;
291 // lights
292 shadLightTrace = new Shader("light_trace", loadTextFile("shaders/srlight_trace.frag"));
293 shadLightTrace.exec((Shader shad) {
294 //shad["texLMap"] = 0;
295 shad["texOccFull"] = 2;
296 shad["texOccSmall"] = 3;
299 shadLightBlur = new Shader("light_blur", loadTextFile("shaders/srlight_blur.frag"));
300 shadLightBlur.exec((Shader shad) {
301 shad["texDist"] = 0;
302 shad["texBg"] = 1;
303 shad["texOccFull"] = 2;
304 shad["texOccSmall"] = 3;
307 shadLightGeom = new Shader("light_geom", loadTextFile("shaders/srlight_geom.frag"));
308 shadLightGeom.exec((Shader shad) {
309 shad["texLMap"] = 0;
310 shad["texBg"] = 1;
311 shad["texOccFull"] = 2;
312 shad["texOccSmall"] = 3;
315 shadLightAmbient = new Shader("light_ambient", loadTextFile("shaders/srlight_ambient.frag"));
316 shadLightAmbient.exec((Shader shad) {
317 shad["texBg"] = 1;
318 shad["texOccFull"] = 2;
319 shad["texOccSmall"] = 3;
322 fboLSpotBG = new FBO(MaxLightRadius*2, MaxLightRadius*2, Texture.Option.Clamp, Texture.Option.Linear/*, Texture.Option.FBO2*/);
323 //fboLSpotBG = new FBO(fboLSpot.width, fboLSpot.height, Texture.Option.Clamp, Texture.Option.Linear);
324 fboLSpotSmall = new FBO(fboLSpotBG.width/8, fboLSpotBG.height/8, Texture.Option.Clamp, Texture.Option.Nearest/*Linear*/);
325 //TODO: this sux!
326 foreach (int sz; 2..MaxLightRadius+1) {
327 fboDistMap[sz] = new FBO(sz*2, 1, Texture.Option.Clamp, Texture.Option.Linear); // create 1d distance map FBO
330 editorImg = new TrueColorImage(vlWidth, vlHeight);
331 editorImg.imageData.colors[] = Color(0, 0, 0, 0);
332 fboEditor = new FBO(vlWidth, vlHeight, Texture.Option.Nearest);
334 texSigil = new Texture("console/sigil_of_baphomet.png", Texture.Option.Nearest);
335 fboConsole = new FBO(vlWidth, vlHeight, Texture.Option.Nearest);
336 loadConFont();
338 // setup matrices
339 glMatrixMode(GL_MODELVIEW);
340 glLoadIdentity();
342 loadSmFont();
343 loadBfFont();
344 loadAllMonsterGraphics();
348 // ////////////////////////////////////////////////////////////////////////// //
349 __gshared ulong conLastChange = 0;
351 static void glColorUint (uint c) {
352 pragma(inline, true);
353 glColor4f(((c>>16)&0xff)/255.0f, ((c>>8)&0xff)/255.0f, (c&0xff)/255.0f, 1.0f);
357 void renderConsoleFBO () {
358 enum XOfs = 2;
359 if (conLastChange == cbufLastChange) return;
360 consoleLock();
361 scope(exit) consoleUnlock();
362 // rerender console
363 conLastChange = cbufLastChange;
364 //foreach (auto s; conbufLinesRev) stdout.writeln(s, "|");
365 int skipLines = conskiplines;
366 fboConsole.exec((FBO me) {
367 bindTexture(0);
368 orthoCamera(me.width, me.height);
369 // clear it
370 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
371 glClear(GL_COLOR_BUFFER_BIT);
373 glDisable(GL_BLEND);
374 glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
375 glRectf(0, 0, me.width-1, me.height-1);
377 // text
378 glEnable(GL_BLEND);
379 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
380 // draw sigil
381 glColor4f(1.0f, 1.0f, 1.0f, 0.2f);
382 drawAtXY(texSigil, (me.width-texSigil.width)/2, (vlHeight-rConsoleHeight)/2+(me.height-texSigil.height)/2);
383 // draw command line
384 int y = me.height-conCharHeight-2;
386 glPushMatrix();
387 scope(exit) glPopMatrix();
388 glTranslatef(XOfs, y, 0);
389 int w = conCharWidth('>');
390 glColorUint(rConPromptColor);
391 conDrawChar('>');
392 uint spos = conInputBuffer.length;
393 while (spos > 0) {
394 char ch = conInputBuffer.ptr[spos-1];
395 if (w+conCharWidth(ch) > me.width-XOfs*2-12) break;
396 w += conCharWidth(ch);
397 --spos;
399 glColorUint(rConInputColor);
400 foreach (char ch; conInputBuffer[spos..conInputBuffer.length]) conDrawChar(ch);
401 // cursor
402 bindTexture(0);
403 glColorUint(rConCursorColor);
404 glRectf(0, 0, 12, 16);
405 y -= conCharHeight;
407 // draw console text
408 glColorUint(rConTextColor);
409 glPushMatrix();
410 scope(exit) glPopMatrix();
411 glTranslatef(XOfs, y, 0);
413 void putLine(T) (auto ref T line, usize pos=0) {
414 if (y+conCharHeight <= 0) return;
415 int w = XOfs;
416 usize sp = pos;
417 while (sp < line.length) {
418 char ch = line[sp++];
419 int cw = conCharWidth(ch);
420 if ((w += cw) > me.width-XOfs) { w -= cw; --sp; break; }
422 if (sp < line.length) putLine(line, sp); // recursive put tail
423 // draw line
424 if (skipLines-- <= 0) {
425 while (pos < sp) conDrawChar(line[pos++]);
426 glPopMatrix();
427 glPushMatrix();
428 y -= conCharHeight;
429 glTranslatef(XOfs, y, 0);
433 foreach (auto line; conbufLinesRev) {
434 putLine(line);
435 if (y+conCharHeight <= 0) break;
441 // ////////////////////////////////////////////////////////////////////////// //
442 // should be called when OpenGL is initialized
443 void loadMap (string mapname) {
444 mapscripts.runUnloading(); // "map unloading" script
445 clearMapScripts();
447 if (map !is null) map.clear();
448 map = new LevelMap(mapname);
449 curmapname = mapname;
451 ugInit(map.width*TileSize, map.height*TileSize);
453 map.oglBuildMega();
454 mapTilesChanged = 0;
456 if (fboLevel !is null) fboLevel.clear();
457 if (fboLevelLight !is null) fboLevelLight.clear();
458 if (fboOrigBack !is null) fboOrigBack.clear();
459 if (fboLMaskSmall !is null) fboLMaskSmall.clear();
461 fboLevel = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // final level render will be here
462 fboLevelLight = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // level lights will be rendered here
463 fboOrigBack = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest/*, Texture.Option.Depth*/); // background+foreground
464 fboLMaskSmall = new FBO(map.width, map.height, Texture.Option.Nearest); // small lightmask
466 //shadLightTrace.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize-1, map.height*TileSize-1); });
467 shadLightTrace.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
468 shadLightBlur.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
469 shadLightGeom.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
470 shadLightAmbient.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
472 glActiveTexture(GL_TEXTURE0+0);
473 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
474 orthoCamera(vlWidth, vlHeight);
476 Actor.resetStorage();
478 setupMapScripts();
479 mapscripts.runInit();
480 loadMapMonsters();
481 dotInit();
482 mapscripts.runLoaded();
484 // save first snapshot
485 if (prevFrameActorsData.length == 0) prevFrameActorsData = new ubyte[](Actor.actorSize*65536); // ~15-20 megabytes
486 prevFrameActorOfs[] = uint.max; // just for fun
487 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
489 levelLoaded = true;
490 rebuildMapMegaTextures();
492 { import core.memory : GC; GC.collect(); }
496 // ////////////////////////////////////////////////////////////////////////// //
497 //FIXME: optimize!
498 __gshared uint mapTilesChanged = 0;
501 //WARNING! this can be called only from DACS, so we don't have to sync it!
502 public void mapDirty (uint layermask) { mapTilesChanged |= layermask; }
505 void rebuildMapMegaTextures () {
506 //fbo.replaceTexture
507 //mapTilesChanged = false;
508 //map.clearMegaTextures();
509 map.oglBuildMega(mapTilesChanged);
510 mapTilesChanged = 0;
511 dotsAwake(); // let dormant dots fall
512 // rebuild small occluders texture
513 glDisable(GL_BLEND);
514 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
515 fboLMaskSmall.exec({
516 orthoCamera(map.width, map.height);
517 drawAtXY(map.texgl.ptr[map.LightMask].tid, 0, 0, map.width, map.height, mirrorY:true);
522 // ////////////////////////////////////////////////////////////////////////// //
523 // messages
524 struct Message {
525 enum Phase { FadeIn, Stay, FadeOut }
526 Phase phase;
527 int alpha;
528 int pauseMsecs;
529 MonoTime removeTime;
530 char[256] text;
531 usize textlen;
534 //private import core.sync.mutex : Mutex;
536 __gshared Message[128] messages;
537 __gshared uint messagesUsed = 0;
539 //__gshared Mutex messageLock;
540 //shared static this () { messageLock = new Mutex(); }
543 void addMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
544 //messageLock.lock();
545 //scope(exit) messageLock.unlock();
546 if (msgtext.length == 0) return;
547 conwriteln(msgtext);
548 if (pauseMsecs <= 50) return;
549 if (messagesUsed == messages.length) {
550 // remove top message
551 foreach (immutable cidx; 1..messagesUsed) messages.ptr[cidx-1] = messages.ptr[cidx];
552 messages.ptr[0].alpha = 255;
553 --messagesUsed;
555 // quick replace
556 if (!noreplace && messagesUsed == 1) {
557 switch (messages.ptr[0].phase) {
558 case Message.Phase.FadeIn:
559 messages.ptr[0].phase = Message.Phase.FadeOut;
560 break;
561 case Message.Phase.Stay:
562 messages.ptr[0].phase = Message.Phase.FadeOut;
563 messages.ptr[0].alpha = 255;
564 break;
565 default:
568 auto msg = messages.ptr+messagesUsed;
569 ++messagesUsed;
570 msg.phase = Message.Phase.FadeIn;
571 msg.alpha = 0;
572 msg.pauseMsecs = pauseMsecs;
573 // copy text
574 if (msgtext.length > msg.text.length) {
575 msg.text = msgtext[0..msg.text.length];
576 msg.textlen = msg.text.length;
577 } else {
578 msg.text[0..msgtext.length] = msgtext[];
579 msg.textlen = msgtext.length;
584 void doMessages (MonoTime curtime) {
585 //messageLock.lock();
586 //scope(exit) messageLock.unlock();
588 if (messagesUsed == 0) return;
589 glEnable(GL_BLEND);
590 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
592 Message* msg;
594 again:
595 msg = messages.ptr;
596 final switch (msg.phase) {
597 case Message.Phase.FadeIn:
598 if ((msg.alpha += 10) >= 255) {
599 msg.phase = Message.Phase.Stay;
600 msg.removeTime = curtime+dur!"msecs"(msg.pauseMsecs);
601 goto case; // to stay
603 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
604 break;
605 case Message.Phase.Stay:
606 if (msg.removeTime <= curtime) {
607 msg.alpha = 255;
608 msg.phase = Message.Phase.FadeOut;
609 goto case; // to fade
611 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
612 break;
613 case Message.Phase.FadeOut:
614 if ((msg.alpha -= 10) <= 0) {
615 if (--messagesUsed == 0) return;
616 // remove this message
617 foreach (immutable cidx; 1..messagesUsed+1) messages.ptr[cidx-1] = messages.ptr[cidx];
618 goto again;
620 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
621 break;
624 smDrawText(10, 10, msg.text[0..msg.textlen]);
628 // ////////////////////////////////////////////////////////////////////////// //
629 //mixin(Actor.FieldPropMixin!("0drawlistpos", uint));
631 mixin(Actor.FieldGetMixin!("classtype", StrId)); // fget_classtype
632 mixin(Actor.FieldGetMixin!("classname", StrId)); // fget_classname
633 mixin(Actor.FieldGetMixin!("x", int));
634 mixin(Actor.FieldGetMixin!("y", int));
635 mixin(Actor.FieldGetMixin!("s", int));
636 mixin(Actor.FieldGetMixin!("radius", int));
637 mixin(Actor.FieldGetMixin!("height", int));
638 mixin(Actor.FieldGetMixin!("flags", uint));
639 mixin(Actor.FieldGetMixin!("zAnimstate", StrId));
640 mixin(Actor.FieldGetMixin!("zAnimidx", int));
641 mixin(Actor.FieldGetMixin!("dir", uint));
642 mixin(Actor.FieldGetMixin!("attLightXOfs", int));
643 mixin(Actor.FieldGetMixin!("attLightYOfs", int));
644 mixin(Actor.FieldGetMixin!("attLightRGBX", uint));
646 //mixin(Actor.FieldGetPtrMixin!("classtype", StrId)); // fget_classtype
647 //mixin(Actor.FieldGetPtrMixin!("classname", StrId)); // fget_classname
648 mixin(Actor.FieldGetPtrMixin!("x", int));
649 mixin(Actor.FieldGetPtrMixin!("y", int));
650 //mixin(Actor.FieldGetPtrMixin!("flags", uint));
651 //mixin(Actor.FieldGetPtrMixin!("zAnimstate", StrId));
652 //mixin(Actor.FieldGetPtrMixin!("zAnimidx", int));
653 //mixin(Actor.FieldGetPtrMixin!("dir", uint));
654 //mixin(Actor.FieldGetPtrMixin!("attLightXOfs", int));
655 //mixin(Actor.FieldGetPtrMixin!("attLightYOfs", int));
656 //mixin(Actor.FieldGetPtrMixin!("attLightRGBX", uint));
659 // ////////////////////////////////////////////////////////////////////////// //
660 __gshared int vportX0, vportY0, vportX1, vportY1;
663 // ////////////////////////////////////////////////////////////////////////// //
664 void renderLightAmbient() (int lightX, int lightY, int lightW, int lightH, in auto ref SVec4F lcol) {
665 //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));
666 if (lightW < 1 || lightH < 1) return;
667 int lightX1 = lightX+lightW-1;
668 int lightY1 = lightY+lightH-1;
669 // clip light to viewport
670 if (lightX < vportX0) lightX = vportX0;
671 if (lightY < vportY0) lightY = vportY0;
672 if (lightX1 > vportX1) lightX1 = vportX1;
673 if (lightY1 > vportY1) lightY1 = vportY1;
674 // is this light visible?
675 //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));
676 if (lightX1 < lightX || lightY1 < lightY || lightX > vportX1 || lightY > vportY1 || lightX1 < vportX0 || lightY1 < vportY0) return;
677 //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));
679 fboLevelLight.exec({
680 bindTexture(0);
681 glEnable(GL_BLEND);
682 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
683 //glDisable(GL_BLEND);
684 orthoCamera(map.width*TileSize, map.height*TileSize);
685 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
686 shadLightAmbient.exec((Shader shad) {
687 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
688 //shad["lightPos"] = SVec2F(lightX, lightY);
689 glRectf(lightX, lightY, lightX1, lightY1);
695 // ////////////////////////////////////////////////////////////////////////// //
696 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
697 if (lightRadius < 2) return;
698 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
699 int lightSize = lightRadius*2;
700 // is this light visible?
701 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*TileSize || lightY-lightRadius >= map.height*TileSize) return;
703 // out of viewport -- do nothing
704 if (lightX+lightRadius < vportX0 || lightY+lightRadius < vportY0) return;
705 if (lightX-lightRadius > vportX1 || lightY-lightRadius > vportY1) return;
707 if (lightX >= 0 && lightY >= 0 && lightX < map.width*TileSize && lightY < map.height*TileSize &&
708 map.teximgs[map.LightMask].imageData.colors.ptr[lightY*(map.width*TileSize)+lightX].a > 190) return;
710 // common color for all the following
711 glDisable(GL_BLEND);
712 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
714 // build 1d distance map to fboShadowMapId
715 fboDistMap.ptr[lightRadius].exec({
716 // no need to clear it, shader will take care of that
717 shadLightTrace.exec((Shader shad) {
718 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
719 shad["lightPos"] = SVec2F(lightX, lightY);
720 orthoCamera(lightSize, 1);
721 // it doesn't matter what we will draw here, so just draw filled rect
722 glRectf(0, 0, lightSize, 1);
726 // build light texture for blending
727 fboLSpotBG.exec({
728 // no need to clear it, shader will take care of that
729 // debug
730 // need to clear it, for "small"
731 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
732 glClear(GL_COLOR_BUFFER_BIT);
733 shadLightBlur.exec((Shader shad) {
734 shad["lightTexSize"] = SVec2F(lightSize, MaxLightRadius*2); // x: size of distmap; y: size of this texture
735 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
736 shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
737 orthoCamera(fboLSpotBG.tex.width, fboLSpotBG.tex.height);
738 //drawAtXY(fboDistMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
739 bindTexture(fboDistMap.ptr[lightRadius].tex.tid);
740 glBegin(GL_QUADS);
741 glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0); // top-left
742 glTexCoord2f(1.0f, 0.0f); glVertex2i(lightSize, 0); // top-right
743 glTexCoord2f(1.0f, 1.0f); glVertex2i(lightSize, lightSize); // bottom-right
744 glTexCoord2f(0.0f, 1.0f); glVertex2i(0, lightSize); // bottom-left
745 glEnd();
749 // build "small" light texture for geometry-light shader
750 //glUseProgram(0);
751 /*version(none)*/ fboLSpotSmall.exec({
752 glDisable(GL_BLEND);
753 //glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
754 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
755 glClear(GL_COLOR_BUFFER_BIT);
756 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
757 //orthoCamera(fboLSpotSmall.width, fboLSpotSmall.height);
758 //conwriteln(fboLSpotSmall.width, "x", fboLSpotSmall.height, "; ", fboLSpotBG.width, "x", fboLSpotBG.height);
759 //orthoCamera(fboLSpotBG.width, fboLSpotBG.height);
760 orthoCamera(fboLSpotSmall.width, fboLSpotSmall.height);
761 bindTexture(fboLSpotBG.tex.tid);
762 float occe = 1.0f*lightSize/(MaxLightRadius*2);
763 float occs = 1.0f-occe;
764 int w = fboLSpotSmall.width;
765 int h = fboLSpotSmall.height;
766 int h0 = fboLSpotSmall.height;
767 int h1 = fboLSpotSmall.height-h;
768 occe = 1.0f;
769 occs = 0.0f;
770 float occt = 1.0f;
771 glBegin(GL_QUADS);
772 glTexCoord2f(0.0f, occt); glVertex2i(0, h0); // top-left
773 glTexCoord2f(occe, occt); glVertex2i(w, h0); // top-right
774 glTexCoord2f(occe, occs); glVertex2i(w, h1); // bottom-right
775 glTexCoord2f(0.0f, occs); glVertex2i(0, h1); // bottom-left
776 glEnd();
778 bindTexture(0);
779 glColor4f(1.0f, 0.0f, 0.0f, 0.5f);
780 //glRectf(0, 0, lightSize, lightSize);
781 glRectf(0, fboLSpotSmall.height-8, 8, fboLSpotSmall.height);
782 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
786 // blend light texture
787 fboLevelLight.exec({
788 glEnable(GL_BLEND);
789 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
790 orthoCamera(fboLevelLight.tex.width, fboLevelLight.tex.height);
791 int x0 = lightX-lightRadius+0;
792 int y1 = lightY-lightRadius+0;
793 int x1 = lightX+lightRadius-1+1;
794 int y0 = lightY+lightRadius-1+1;
795 float occe = 1.0f*lightSize/(MaxLightRadius*2);
796 float occs = 1.0f-occe;
797 version(smspot) {
798 //glDisable(GL_BLEND);
799 float occn = 1.0f*lightSize/(MaxLightRadius*2);
800 //float occt = 1.0f-occn;
801 x0 = ((x0+3)/8)*8;
802 y1 = ((y1+3)/8)*8;
803 x1 = ((x1+3)/8)*8;
804 y0 = ((y0+3)/8)*8;
805 //x1 = x0+lightSize;
806 //y0 = y1+lightSize;
807 bindTexture(fboLSpotSmall.tex.tid);
808 glBegin(GL_QUADS);
810 glTexCoord2f(0.0f, 0.0f); glVertex2i(x0, y0); // top-left
811 glTexCoord2f(occn, 0.0f); glVertex2i(x1, y0); // top-right
812 glTexCoord2f(occn, occn); glVertex2i(x1, y1); // bottom-right
813 glTexCoord2f(0.0f, occn); glVertex2i(x0, y1); // bottom-left
815 glTexCoord2f(0.0f, occn); glVertex2i(x0, y0); // top-left
816 glTexCoord2f(occn, occn); glVertex2i(x1, y0); // top-right
817 glTexCoord2f(occn, 0.0f); glVertex2i(x1, y1); // bottom-right
818 glTexCoord2f(0.0f, 0.0f); glVertex2i(x0, y1); // bottom-left
819 glEnd();
820 } else {
821 bindTexture(fboLSpotBG.tex.tid);
822 glBegin(GL_QUADS);
823 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
824 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
825 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
826 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
827 glEnd();
830 bindTexture(0);
831 glRectf(x0, y0, x1, y1);
833 // and blend it again, with the shader that will touch only occluders
834 shadLightGeom.exec((Shader shad) {
835 //shad["lightTexSize"] = SVec2F(lightSize, lightSize);
836 shad["lightTexSize"] = SVec2F(lightSize, fboLSpotSmall.height);
837 //shad["lightTexSize"] = SVec2F(lightSize, fboLSpotBG.tex.height);
838 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
839 shad["lightPos"] = SVec2F(lightX, lightY);
840 //shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
841 bindTexture(fboLSpotSmall.tex.tid);
842 //bindTexture(fboLSpotBG.tex.tid);
843 float occn = 1.0f*lightSize/(MaxLightRadius*2);
844 //x0 = ((x0+4)/8)*8;
845 //y1 = ((y1+4)/8)*8;
846 //x1 = x0+lightSize;
847 //y0 = y1+lightSize;
849 x0 = ((x0+4)/8)*8;
850 y1 = ((y1+4)/8)*8;
851 x1 = ((x1+4)/8)*8;
852 y0 = ((y0+4)/8)*8;
854 x0 &= ~7;
855 y1 = ((y1+7)/8)*8;
856 x1 = ((x1+7)/8)*8;
857 y0 &= ~7;
858 glBegin(GL_QUADS);
859 glTexCoord2f(0.0f, occn); glVertex2i(x0, y0); // top-left
860 glTexCoord2f(occn, occn); glVertex2i(x1, y0); // top-right
861 glTexCoord2f(occn, 0.0f); glVertex2i(x1, y1); // bottom-right
862 glTexCoord2f(0.0f, 0.0f); glVertex2i(x0, y1); // bottom-left
864 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
865 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
866 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
867 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
869 glEnd();
870 //drawAtXY(fboLSpotBG.tex.tid, lightX-lightRadius, lightY-lightRadius, lightSize, lightSize, mirrorY:true);
876 // ////////////////////////////////////////////////////////////////////////// //
877 __gshared int testLightX = vlWidth/2, testLightY = vlHeight/2;
878 __gshared bool testLightMoved = false;
879 //__gshared int mapOfsX, mapOfsY;
880 //__gshared bool movement = false;
881 __gshared float iLiquidTime = 0.0;
882 //__gshared bool altMove = false;
885 void renderScene (MonoTime curtime) {
886 //enum BackIntens = 0.05f;
887 enum BackIntens = 0.0f;
889 gloStackClear();
890 float atob = (curtime > lastthink ? cast(float)((curtime-lastthink).total!"msecs")/cast(float)((nextthink-lastthink).total!"msecs") : 1.0f);
891 if (gamePaused || inEditMode) atob = 1.0f;
892 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
895 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
896 int curfp = cast(int)((curtime-lastthink).total!"msecs");
897 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
901 int mofsx, mofsy; // camera offset, will be set in background layer builder
903 if (mapTilesChanged != 0) rebuildMapMegaTextures();
905 // build background layer
906 fboOrigBack.exec({
907 //glDisable(GL_BLEND);
908 //glClearDepth(1.0f);
909 //glDepthFunc(GL_LESS);
910 //glDepthFunc(GL_NEVER);
911 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
912 glClear(GL_COLOR_BUFFER_BIT/*|GL_DEPTH_BUFFER_BIT*/);
913 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
914 orthoCamera(map.width*TileSize, map.height*TileSize);
916 glEnable(GL_BLEND);
917 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
918 // draw sky
920 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
921 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
922 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
923 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
925 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*TileSize, map.MapSize*TileSize);
926 // draw background
927 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
928 // draw distorted liquid areas
929 shadLiquidDistort.exec((Shader shad) {
930 shad["iDistortTime"] = iLiquidTime;
931 drawAtXY(map.texgl.ptr[map.AllLiquids], 0, 0);
933 // monsters, items; we'll do linear interpolation here
934 glColor3f(1.0f, 1.0f, 1.0f);
935 //glEnable(GL_DEPTH_TEST);
936 attachedLightCount = 0;
938 // who cares about memory?!
939 // draw order: players, items, monsters, other
940 static struct DrawInfo {
941 ActorDef adef;
942 ActorId aid;
943 int actorX, actorY;
944 @disable this (this); // no copies
946 enum { Pixels, Players, Items, Monsters, Other }
947 __gshared DrawInfo[65536][4] drawlists;
948 __gshared uint[4] dlpos;
949 DrawInfo camchickdi;
951 dlpos[] = 0;
953 Actor.forEach((ActorId me) {
954 //me.fprop_0drawlistpos = 0;
955 if (auto adef = findActorDef(me)) {
956 uint dlnum = Other;
957 switch (adef.classtype.get) {
958 case "monster": dlnum = (adef.classname.get != "Player" ? Monsters : Players); break;
959 case "item": dlnum = Items; break;
960 default: dlnum = Other; break;
962 if (me.fget_flags&AF_PIXEL) dlnum = Pixels;
963 int actorX, actorY; // current actor position
965 auto ofs = prevFrameActorOfs.ptr[me.id&0xffff];
966 if (frameInterpolation && ofs < uint.max-1 && (me.fget_flags&AF_TELEPORT) == 0 && Actor.isSameSavedActor(me.id, prevFrameActorsData.ptr, ofs)) {
967 import core.stdc.math : roundf;
968 auto xptr = prevFrameActorsData.ptr+ofs;
969 int ox = xptr.fgetp_x;
970 int nx = me.fget_x;
971 int oy = xptr.fgetp_y;
972 int ny = me.fget_y;
973 actorX = cast(int)(ox+roundf((nx-ox)*atob));
974 actorY = cast(int)(oy+roundf((ny-oy)*atob));
975 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
976 } else {
977 actorX = me.fget_x;
978 actorY = me.fget_y;
981 if (me.id == cameraChick.id) {
982 camchickdi.adef = adef;
983 camchickdi.aid = me;
984 camchickdi.actorX = actorX;
985 camchickdi.actorY = actorY;
987 // draw sprite
988 if ((me.fget_flags&AF_NODRAW) == 0) {
989 //auto dl = &drawlists[dlnum][dlpos.ptr[dlnum]];
990 //me.fprop_0drawlistpos = (dlpos.ptr[dlnum]&0xffff)|((dlnum&0xff)<<16);
991 auto dl = drawlists.ptr[dlnum].ptr+dlpos.ptr[dlnum];
992 ++dlpos.ptr[dlnum];
993 dl.adef = adef;
994 dl.aid = me;
995 dl.actorX = actorX;
996 dl.actorY = actorY;
998 // process attached lights
999 if ((me.fget_flags&AF_NOLIGHT) == 0) {
1000 uint alr = me.fget_attLightRGBX;
1001 bool isambient = (me.fget_classtype.get == "light" && me.fget_classname.get == "Ambient");
1002 if ((alr&0xff) >= 4 || (isambient && me.fget_radius >= 1 && me.fget_height >= 1)) {
1003 //if (isambient) conwriteln("isambient: ", isambient, "; x=", me.fget_x, "; y=", me.fget_y, "; w=", me.fget_radius, "; h=", me.fget_height);
1004 // yep, add it
1005 auto li = attachedLights.ptr+attachedLightCount;
1006 ++attachedLightCount;
1007 li.type = (!isambient ? AttachedLightInfo.Type.Point : AttachedLightInfo.Type.Ambient);
1008 li.x = actorX+me.fget_attLightXOfs;
1009 li.y = actorY+me.fget_attLightYOfs;
1010 li.r = ((alr>>24)&0xff)/255.0f; // red or intensity
1011 if ((alr&0x00_ff_ff_00U) == 0x00_00_01_00U) {
1012 li.uncolored = true;
1013 } else {
1014 li.g = ((alr>>16)&0xff)/255.0f;
1015 li.b = ((alr>>8)&0xff)/255.0f;
1016 li.uncolored = false;
1018 li.radius = (alr&0xff);
1019 if (li.radius > MaxLightRadius) li.radius = MaxLightRadius;
1020 if (isambient) {
1021 li.w = me.fget_radius;
1022 li.h = me.fget_height;
1026 } else {
1027 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
1031 // draw actor lists
1032 foreach_reverse (uint dlnum; 0..drawlists.length) {
1033 if (dlnum == Pixels) continue;
1034 auto dl = drawlists.ptr[dlnum].ptr;
1035 if (dlnum == Players) dl += dlpos.ptr[dlnum]-1;
1036 foreach (uint idx; 0..dlpos.ptr[dlnum]) {
1037 auto me = dl.aid;
1038 if (auto isp = dl.adef.animSpr(me.fget_zAnimstate, me.fget_dir, me.fget_zAnimidx)) {
1039 //drawAtXY(isp.tex, dl.actorX-isp.vga.sx, dl.actorY-isp.vga.sy);
1040 isp.drawAtXY(dl.actorX, dl.actorY);
1042 if (dlnum != Players) ++dl; else --dl;
1045 // draw pixels
1046 if (dlpos[Pixels]) {
1047 bindTexture(0);
1048 bool pointsStarted = false;
1049 Color lastColor = Color(0, 0, 0, 0);
1050 auto dl = drawlists.ptr[Pixels].ptr;
1051 foreach (uint idx; 0..dlpos.ptr[Pixels]) {
1052 auto me = dl.aid;
1053 auto s = me.fget_s;
1054 if (s < 0 || s > 255) continue; //FIXME
1055 Color clr = d2dpal.ptr[s&0xff];
1056 if (clr.a == 0) continue;
1057 if (clr != lastColor) {
1058 if (pointsStarted) glEnd();
1059 glColor4f(clr.r/255.0f, clr.g/255.0f, clr.b/255.0f, clr.a/255.0f);
1060 lastColor = clr;
1061 pointsStarted = false;
1063 if (!pointsStarted) {
1064 glBegin(GL_POINTS);
1065 pointsStarted = true;
1067 glVertex2i(dl.actorX, dl.actorY);
1068 ++dl;
1070 if (pointsStarted) {
1071 glEnd();
1072 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1076 // camera movement
1077 if (/*altMove || movement ||*/ scale == 1 || !cameraChick.valid) {
1078 mofsx = 0;
1079 mofsy = 0;
1080 vportX0 = 0;
1081 vportY0 = 0;
1082 vportX1 = map.width*TileSize;
1083 vportY1 = map.height*TileSize;
1084 } else {
1085 int tiltHeight = /*getMapViewHeight()*/(vlHeight/scale)/4;
1086 int vy = cameraChick.looky!int;
1087 if (vy < -tiltHeight) vy = -tiltHeight; else if (vy > tiltHeight) vy = tiltHeight;
1088 int swdt = vlWidth/scale;
1089 int shgt = vlHeight/scale;
1090 int x = camchickdi.actorX-swdt/2;
1091 int y = (camchickdi.actorY+vy)-shgt/2;
1092 if (x < 0) x = 0; else if (x >= map.width*TileSize-swdt) x = map.width*TileSize-swdt-1;
1093 if (y < 0) y = 0; else if (y >= map.height*TileSize-shgt) y = map.height*TileSize-shgt-1;
1094 mofsx = x*2;
1095 mofsy = y*2;
1096 vportX0 = mofsx/scale;
1097 vportY0 = mofsy/scale;
1098 vportX1 = vportX0+vlWidth/scale;
1099 vportY1 = vportY0+vlHeight/scale;
1102 //glDisable(GL_DEPTH_TEST);
1103 // draw dots
1104 dotDraw(atob);
1105 // do liquid coloring
1106 drawAtXY(map.texgl.ptr[map.LiquidMask], 0, 0);
1107 // foreground -- hide secrets, draw lifts and such
1108 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
1111 enum r = 255;
1112 enum g = 0;
1113 enum b = 0;
1114 enum a = 255;
1115 bindTexture(0);
1116 glColor4f(r/255.0f, g/255.0f, b/255.0f, a/255.0f);
1117 glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
1118 if (cameraChick.valid) {
1119 glBegin(GL_POINTS);
1120 glVertex2i(camchickdi.actorX, camchickdi.actorY-70);
1121 glEnd();
1122 //glRectf(camchickdi.actorX, camchickdi.actorY-70, camchickdi.actorX+4, camchickdi.actorY-70+4);
1124 //glRectf(0, 0, 300, 300);
1125 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1131 if (doLighting) {
1132 glDisable(GL_BLEND);
1133 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1135 // make smaller occluder texture, so we can trace faster
1136 //assert(fboLMaskSmall.tex.width == map.width);
1137 //assert(fboLMaskSmall.tex.height == map.height);
1138 /+!!!
1139 fboLMaskSmall.exec({
1140 orthoCamera(map.width, map.height);
1141 drawAtXY(map.texgl.ptr[map.LightMask].tid, 0, 0, map.width, map.height, mirrorY:true);
1145 // clear light layer
1146 fboLevelLight.exec({
1147 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
1148 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
1149 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
1150 glClear(GL_COLOR_BUFFER_BIT);
1153 // texture 1 is background
1154 glActiveTexture(GL_TEXTURE0+1);
1155 glBindTexture(GL_TEXTURE_2D, fboOrigBack.tex.tid);
1156 // texture 2 is occluders
1157 glActiveTexture(GL_TEXTURE0+2);
1158 glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.LightMask].tid);
1159 // texture 3 is small occluder map
1160 glActiveTexture(GL_TEXTURE0+3);
1161 glBindTexture(GL_TEXTURE_2D, fboLMaskSmall.tex.tid);
1162 // done texture assign
1163 glActiveTexture(GL_TEXTURE0+0);
1166 enum LYOfs = 1;
1168 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
1169 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
1170 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
1171 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
1172 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
1173 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
1174 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
1175 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
1176 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
1177 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
1179 renderLight(24*TileSize+4, (24+18)*TileSize-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
1181 renderLight(280, 330, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
1184 foreach (; 0..1) {
1185 // attached lights
1186 foreach (ref li; attachedLights[0..attachedLightCount]) {
1187 if (li.type == AttachedLightInfo.Type.Ambient) {
1188 //conwriteln("ambient: x=", li.x, "; y=", li.y, "; w=", li.w, "; h=", li.h);
1189 // ambient light
1190 if (li.uncolored) {
1191 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(0.0f, 0.0f, 0.0f, li.r));
1192 } else {
1193 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(li.r, li.g, li.b, 1.0f));
1195 } else if (li.type == AttachedLightInfo.Type.Point) {
1196 // point light
1197 if (li.uncolored) {
1198 renderLight(li.x, li.y, SVec4F(0.0f, 0.0f, 0.0f, li.r), li.radius);
1199 } else {
1200 renderLight(li.x, li.y, SVec4F(li.r, li.g, li.b, 1.0f), li.radius);
1207 if (testLightMoved) {
1208 testLightX = testLightX/scale+mofsx/scale;
1209 testLightY = testLightY/scale+mofsy/scale;
1210 testLightMoved = false;
1212 foreach (immutable _; 0..1) {
1213 renderLight(testLightX, testLightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
1217 glActiveTexture(GL_TEXTURE0+1);
1218 glBindTexture(GL_TEXTURE_2D, 0);
1219 glActiveTexture(GL_TEXTURE0+2);
1220 glBindTexture(GL_TEXTURE_2D, 0);
1221 glActiveTexture(GL_TEXTURE0+3);
1222 glBindTexture(GL_TEXTURE_2D, 0);
1223 glActiveTexture(GL_TEXTURE0+0);
1226 // draw scaled level
1228 shadScanlines.exec((Shader shad) {
1229 shad["scanlines"] = scanlines;
1230 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
1231 glClear(GL_COLOR_BUFFER_BIT);
1232 orthoCamera(vlWidth, vlHeight);
1233 //orthoCamera(map.width*TileSize*scale, map.height*TileSize*scale);
1234 //glMatrixMode(GL_MODELVIEW);
1235 //glLoadIdentity();
1236 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
1237 // somehow, FBO objects are mirrored; wtf?!
1238 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1239 //glLoadIdentity();
1244 fboLevelLight.exec({
1245 smDrawText(map.width*TileSize/2, map.height*TileSize/2, "Testing...");
1250 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
1252 glDisable(GL_BLEND);
1255 fboOrigBack.exec({
1256 //auto img = smfont.ptr[0x39];
1257 auto img = fftest;
1258 if (img !is null) {
1259 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
1260 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
1267 { // http://stackoverflow.com/questions/7207422/setting-up-opengl-multiple-render-targets
1268 GLenum[1] buffers = [ GL_BACK_LEFT, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
1269 //GLenum[1] buffers = [ GL_NONE, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
1270 glDrawBuffers(1, buffers.ptr);
1275 orthoCamera(vlWidth, vlHeight);
1276 auto tex = (doLighting ? fboLevelLight.tex.tid : fboOrigBack.tex.tid);
1277 drawAtXY(tex, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1280 glEnable(GL_BLEND);
1281 //glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1282 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1283 drawAtXY(fboLMaskSmall.tex, 0, 0);
1284 //drawAtXY(map.texgl.ptr[map.LightMask], 0, 0);
1287 if (levelLoaded) {
1288 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1289 //orthoCamera(map.width*TileSize, map.height*TileSize);
1290 glEnable(GL_BLEND);
1291 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1292 hudScripts.runDraw();
1295 //drawAtXY(map.texgl.ptr[map.LightMask].tid, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1296 //drawAtXY(fboLMaskSmall.tex.tid, 0, 0, map.width*TileSize, map.height*TileSize);
1298 if (inEditMode) {
1299 glEnable(GL_BLEND);
1300 //glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1301 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1302 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1303 editorUpdateImage();
1304 fboEditor.tex.setFromImage(editorImg);
1305 drawAtXY(fboEditor.tex, 0, 0);
1306 glDisable(GL_BLEND);
1309 doMessages(curtime);
1311 if (rConsoleVisible) {
1312 renderConsoleFBO();
1313 orthoCamera(vlWidth, vlHeight);
1314 glEnable(GL_BLEND);
1315 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1316 glColor4f(1.0f, 1.0f, 1.0f, 0.7f);
1317 drawAtXY(fboConsole.tex, 0, rConsoleHeight-vlHeight, mirrorY:true);
1322 // ////////////////////////////////////////////////////////////////////////// //
1323 // returns time slept
1324 int sleepAtMaxMsecs (int msecs) {
1325 if (msecs > 0) {
1326 import core.sys.posix.signal : timespec;
1327 import core.sys.posix.time : nanosleep;
1328 timespec ts = void, tpassed = void;
1329 ts.tv_sec = 0;
1330 ts.tv_nsec = msecs*1000*1000+(500*1000); // milli to nano
1331 nanosleep(&ts, &tpassed);
1332 return (ts.tv_nsec-tpassed.tv_nsec)/(1000*1000);
1333 } else {
1334 return 0;
1339 // ////////////////////////////////////////////////////////////////////////// //
1340 mixin(import("editor.d"));
1343 // ////////////////////////////////////////////////////////////////////////// //
1344 // rendering thread
1345 enum D2DFrameTime = 55; // milliseconds
1346 enum MinFrameTime = 1000/60; // ~60 FPS
1348 public void renderThread (Tid starterTid) {
1349 enum BoolOptVarMsgMixin(string varname) =
1350 "if (msg.toggle) msg.value = !"~varname~";\n"~
1351 "if ("~varname~" != msg.value) "~varname~" = msg.value; else msg.showMessage = false;\n";
1353 send(starterTid, 42);
1354 try {
1355 MonoTime curtime = MonoTime.currTime;
1357 lastthink = curtime; // for interpolator
1358 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1359 MonoTime nextvframe = curtime;
1361 enum MaxFPSFrames = 16;
1362 float frtimes = 0.0f;
1363 int framenum = 0;
1364 int prevFPS = -1;
1365 int hushFrames = 6; // ignore first `hushFrames` frames overtime
1366 MonoTime prevFrameStartTime = curtime;
1368 bool vframeWasLost = false;
1370 void resetFrameTimers () {
1371 MonoTime curtime = MonoTime.currTime;
1372 lastthink = curtime; // for interpolator
1373 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1374 nextvframe = curtime;
1377 void loadNewLevel (string name) {
1379 if (levelLoaded) {
1380 conwriteln("ERROR: can't load new levels yet");
1381 return;
1384 if (name.length == 0) {
1385 conwriteln("ERROR: can't load empty level!");
1387 conwriteln("loading map '", name, "'");
1388 loadMap(name);
1389 resetFrameTimers();
1392 conRegFunc!({
1393 string mn = genNextMapName(0);
1394 if (mn.length) {
1395 nextmapname = null; // clear "exit" flag
1396 loadNewLevel(mn);
1397 } else {
1398 conwriteln("can't skip level");
1400 })("skiplevel", "skip current level");
1402 conRegFunc!({
1403 inEditMode = !inEditMode;
1404 if (inEditMode) sdwindow.hideCursor(); else sdwindow.showCursor();
1405 })("ed_toggle", "toggle editor");
1407 conRegFunc!({
1408 if (inEditMode) {
1409 inEditMode = false;
1410 sdwindow.showCursor();
1412 })("ed_exit", "exit from editor");
1414 conRegFunc!((string mapname) {
1415 nextmapname = null; // clear "exit" flag
1416 loadNewLevel(mapname);
1417 })("map", "load map");
1419 void receiveMessages () {
1420 for (;;) {
1421 import core.time : Duration;
1422 //conwriteln("rendering thread: waiting for messages...");
1423 auto got = receiveTimeout(
1424 Duration.zero, // don't wait
1425 (TMsgMessage msg) {
1426 addMessage(msg.text[0..msg.textlen], msg.pauseMsecs, msg.noreplace);
1428 (TMsgTestLightMove msg) {
1429 testLightX = msg.x;
1430 testLightY = msg.y;
1431 testLightMoved = true;
1433 (TMsgMouseEvent msg) { if (atomicLoad(editMode)) editorMouseEvent(msg); },
1434 (TMsgKeyEvent msg) { if (atomicLoad(editMode)) editorKeyEvent(msg); },
1435 (TMsgChar msg) { concliChar(msg.ch); },
1436 (Variant v) {
1437 conwriteln("WARNING: unknown thread message received and ignored");
1440 if (!got) {
1441 // no more messages
1442 //conwriteln("rendering thread: no more messages");
1443 break;
1446 if (nextmapname.length) {
1447 string mn = nextmapname;
1448 nextmapname = null; // clear "exit" flag
1449 loadNewLevel(mn);
1453 void processConsoleCommands () {
1454 consoleLock();
1455 scope(exit) consoleUnlock();
1456 concmdDoAll();
1459 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1460 bool doThinkFrame () {
1461 if (curtime >= nextthink) {
1462 lastthink = curtime;
1463 while (nextthink <= curtime) nextthink += dur!"msecs"(D2DFrameTime);
1464 if (levelLoaded) {
1465 // save snapshot and other data for interpolator
1466 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
1467 if (!gamePaused && !inEditMode) {
1468 // process actors
1469 doActorsThink();
1470 dotThink();
1473 // some timing
1474 auto tm = MonoTime.currTime;
1475 int thinkTime = cast(int)((tm-curtime).total!"msecs");
1476 if (thinkTime > 9) { import core.stdc.stdio; printf("spent on thinking: %d msecs\n", thinkTime); }
1477 curtime = tm;
1478 return true;
1479 } else {
1480 return false;
1484 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1485 bool doVFrame () {
1486 bool doCheckTime = void;
1487 if (!renderVBL) {
1488 // timer
1489 doCheckTime = true;
1490 } else {
1491 // vsync
1492 __gshared bool prevLost = false;
1493 doCheckTime = vframeWasLost;
1494 if (vframeWasLost) {
1495 if (!prevLost) {
1496 { import core.stdc.stdio; printf("frame was lost!\n"); }
1498 prevLost = true;
1499 } else {
1500 prevLost = false;
1503 if (doCheckTime) {
1504 if (curtime < nextvframe) return false;
1505 if (!renderVBL) {
1506 if (curtime > nextvframe) {
1507 auto overtime = cast(int)((curtime-nextvframe).total!"msecs");
1508 if (overtime > 2500) {
1509 if (hushFrames) {
1510 --hushFrames;
1511 } else {
1512 { import core.stdc.stdio; printf(" spent whole %d msecs\n", overtime); }
1518 while (nextvframe <= curtime) nextvframe += dur!"msecs"(MinFrameTime);
1519 bool ctset = false;
1521 sdwindow.mtLock();
1522 scope(exit) sdwindow.mtUnlock();
1523 ctset = sdwindow.setAsCurrentOpenGlContextNT;
1525 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
1526 if (ctset) {
1527 if (oldRenderVBL != renderVBL) {
1528 oldRenderVBL = renderVBL;
1529 sdwindow.vsync = renderVBL;
1531 // render scene
1532 iLiquidTime = cast(float)((curtime-MonoTime.zero).total!"msecs"%10000000)/18.0f*0.04f;
1533 receiveMessages(); // here, 'cause we need active OpenGL context for some messages
1534 processConsoleCommands();
1535 if (levelLoaded) {
1536 renderScene(curtime);
1537 } else {
1538 //renderLoading(curtime);
1540 sdwindow.mtLock();
1541 scope(exit) sdwindow.mtUnlock();
1542 sdwindow.swapOpenGlBuffers();
1543 glFinish();
1544 sdwindow.releaseCurrentOpenGlContext();
1545 vframeWasLost = false;
1546 } else {
1547 vframeWasLost = true;
1548 { import core.stdc.stdio; printf("xframe was lost!\n"); }
1550 curtime = MonoTime.currTime;
1551 return true;
1554 while (!quitRequested && !sdwindow.closed) {
1555 curtime = MonoTime.currTime;
1556 auto fstime = curtime;
1558 doThinkFrame(); // this will fix curtime if necessary
1559 if (doVFrame()) {
1560 if (!vframeWasLost) {
1561 // fps
1562 auto frameTime = cast(float)(curtime-prevFrameStartTime).total!"msecs"/1000.0f;
1563 prevFrameStartTime = curtime;
1564 frtimes += frameTime;
1565 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
1566 import std.string : format;
1567 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
1568 if (newFPS != prevFPS) {
1569 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
1570 prevFPS = newFPS;
1572 framenum = 0;
1573 frtimes = 0.0f;
1578 curtime = MonoTime.currTime;
1580 // now sleep until next "video" or "think" frame
1581 if (nextthink > curtime && nextvframe > curtime) {
1582 if (sdwindow.closed || quitRequested) break;
1583 // let's decide
1584 immutable nextVideoFrameSleep = cast(int)((nextvframe-curtime).total!"msecs");
1585 immutable nextThinkFrameSleep = cast(int)((nextthink-curtime).total!"msecs");
1586 immutable sleepTime = (nextVideoFrameSleep < nextThinkFrameSleep ? nextVideoFrameSleep : nextThinkFrameSleep);
1587 sleepAtMaxMsecs(sleepTime);
1588 //curtime = MonoTime.currTime;
1591 } catch (Throwable e) {
1592 // here, we are dead and fucked (the exact order doesn't matter)
1593 import core.stdc.stdlib : abort;
1594 import core.stdc.stdio : fprintf, stderr;
1595 import core.memory : GC;
1596 GC.disable(); // yeah
1597 thread_suspendAll(); // stop right here, you criminal scum!
1598 auto s = e.toString();
1599 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
1600 abort(); // die, you bitch!
1602 atomicStore(vquitRequested, true);
1603 sdwindow.close();
1607 // ////////////////////////////////////////////////////////////////////////// //
1608 __gshared Tid renderTid;
1609 shared bool renderThreadStarted = false;
1612 public void startRenderThread () {
1613 if (!cas(&renderThreadStarted, false, true)) {
1614 assert(0, "render thread already started!");
1616 renderTid = spawn(&renderThread, thisTid);
1617 setMaxMailboxSize(renderTid, 1024, OnCrowding.throwException); //FIXME
1618 // wait for "i'm ready" signal
1619 receive(
1620 (int ok) {
1621 if (ok != 42) assert(0, "wtf?!");
1624 conwriteln("rendering thread started");
1628 // ////////////////////////////////////////////////////////////////////////// //
1629 // thread messages
1632 // ////////////////////////////////////////////////////////////////////////// //
1633 struct TMsgMouseEvent {
1634 private import arsd.simpledisplay : MouseEventType, MouseButton;
1635 MouseEventType type;
1636 int x, y;
1637 int dx, dy;
1638 MouseButton button; /// See $(LREF MouseButton)
1639 int modifierState; /// See $(LREF ModifierState)
1642 public void postMouseEvent() (in auto ref MouseEvent evt) {
1643 if (!atomicLoad(renderThreadStarted)) return;
1644 TMsgMouseEvent msg;
1645 msg.type = evt.type;
1646 msg.x = evt.x;
1647 msg.y = evt.y;
1648 msg.dx = evt.dx;
1649 msg.dy = evt.dy;
1650 msg.button = evt.button;
1651 msg.modifierState = evt.modifierState;
1652 send(renderTid, msg);
1656 // ////////////////////////////////////////////////////////////////////////// //
1657 struct TMsgKeyEvent {
1658 private import arsd.simpledisplay : Key;
1659 Key key;
1660 uint hardwareCode;
1661 bool pressed;
1662 dchar character;
1663 uint modifierState;
1666 public void postKeyEvent() (in auto ref KeyEvent evt) {
1667 if (!atomicLoad(renderThreadStarted)) return;
1668 TMsgKeyEvent msg;
1669 msg.key = evt.key;
1670 msg.pressed = evt.pressed;
1671 msg.character = evt.character;
1672 msg.modifierState = evt.modifierState;
1673 send(renderTid, msg);
1677 // ////////////////////////////////////////////////////////////////////////// //
1678 struct TMsgTestLightMove {
1679 int x, y;
1682 public void postTestLightMove (int x, int y) {
1683 if (!atomicLoad(renderThreadStarted)) return;
1684 auto msg = TMsgTestLightMove(x, y);
1685 send(renderTid, msg);
1689 // ////////////////////////////////////////////////////////////////////////// //
1690 struct TMsgMessage {
1691 char[256] text;
1692 uint textlen;
1693 int pauseMsecs;
1694 bool noreplace;
1697 public void postAddMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
1698 if (!atomicLoad(renderThreadStarted)) return;
1699 if (msgtext.length > TMsgMessage.text.length) msgtext = msgtext[0..TMsgMessage.text.length];
1700 TMsgMessage msg;
1701 msg.textlen = cast(uint)msgtext.length;
1702 if (msg.textlen) msg.text[0..msg.textlen] = msgtext[0..msg.textlen];
1703 msg.pauseMsecs = pauseMsecs;
1704 msg.noreplace = noreplace;
1705 send(renderTid, msg);
1709 // ////////////////////////////////////////////////////////////////////////// //
1710 struct TMsgChar {
1711 char ch;
1714 public void postChar (char ch) {
1715 if (!atomicLoad(renderThreadStarted)) return;
1716 TMsgChar msg;
1717 msg.ch = ch;
1718 send(renderTid, msg);
1722 // ////////////////////////////////////////////////////////////////////////// //
1723 // add console command to execution queue
1724 public void concmd (const(char)[] cmd) {
1725 //if (!atomicLoad(renderThreadStarted)) return;
1726 consoleLock();
1727 scope(exit) consoleUnlock();
1728 concmdAdd(cmd);
1731 // get console variable value; doesn't do complex conversions!
1732 public T convar(T) (const(char)[] s) {
1733 consoleLock();
1734 scope(exit) consoleUnlock();
1735 return conGetVar!T(s);
1738 // set console variable value; doesn't do complex conversions!
1739 public void convar(T) (const(char)[] s, T val) {
1740 consoleLock();
1741 scope(exit) consoleUnlock();
1742 conSetVar!T(s, val);