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
;
25 import std
.concurrency
;
27 import iv
.cmdcon
/*: conwriteln, consoleLock, consoleUnlock*/;
48 // ////////////////////////////////////////////////////////////////////////// //
49 import arsd
.simpledisplay
: SimpleWindow
, KeyEvent
, MouseEvent
;
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
) {
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
;
118 void concmdDoAll () {
119 if (concmdbufpos
== 0) return;
120 scope(exit
) concmdbufpos
= 0;
121 auto ebuf
= concmdbufpos
;
122 const(char)[] s
= concmdbuf
[0..concmdbufpos
];
125 auto cmd
= conGetCommandStr(s
);
126 if (cmd
is null) break;
129 } catch (Exception e
) {
130 conwriteln("***ERROR: ", e
.msg
);
133 if (concmdbufpos
<= ebuf
) break;
134 s
= concmdbuf
[ebuf
..concmdbufpos
];
140 void concliChar (char ch
) {
143 scope(exit
) consoleUnlock();
145 if (ch
== ConInputChar
.PageUp
) {
146 int lnx
= (rConsoleHeight
-4)/conCharHeight
-2;
147 if (lnx
< 1) lnx
= 1;
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;
163 if (ch
== ConInputChar
.Enter
) {
164 if (conskiplines
) { conskiplines
= 0; conLastChange
= 0; }
165 auto s
= conInputBuffer
;
168 conInputBufferClear(true); // add to history
174 if (ch
== '`' && conInputBuffer
.length
== 0) { concmd("r_console ona"); return; }
176 auto pcc
= conInputLastChange();
178 if (pcc
!= conInputLastChange()) conLastChange
= 0;
182 // ////////////////////////////////////////////////////////////////////////// //
183 shared static this () {
184 conRegFunc
!((const(char)[] fname
) {
186 auto s
= loadTextFile(fname
);
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");
206 cheatNoDoors
= !cheatNoDoors
;
207 if (cheatNoDoors
) conwriteln("player ignores doors"); else conwriteln("player respects doors");
208 })("nodoorclip", "ignore doors");
210 cheatNoWallClip
= !cheatNoWallClip
;
211 if (cheatNoWallClip
) conwriteln("player ignores walls"); else conwriteln("player respects walls");
212 })("nowallclip", "ignore walls");
215 atomicStore(vquitRequested
, true);
216 })("quit", "quit game");
217 conRegFunc
!((const(char)[] msg
, int pauseMsecs
=3000, bool noreplace
=false) {
219 auto s
= buf
.conFormatStr(msg
);
220 if (s
.length
) postAddMessage(s
, pauseMsecs
, noreplace
);
221 })("hudmsg", "show hud message; hudmsg msg [pausemsecs [noreplace]]");
225 // ////////////////////////////////////////////////////////////////////////// //
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 // ////////////////////////////////////////////////////////////////////////// //
236 struct AttachedLightInfo
{
243 int w
, h
; // for ambient lights
249 __gshared AttachedLightInfo
[65536] attachedLights
;
250 __gshared
uint attachedLightCount
= 0;
253 // ////////////////////////////////////////////////////////////////////////// //
254 enum MaxLightRadius
= 256;
257 // ////////////////////////////////////////////////////////////////////////// //
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 // ////////////////////////////////////////////////////////////////////////// //
274 public void initOpenGL () {
277 glEnable(GL_TEXTURE_2D
);
278 glDisable(GL_LIGHTING
);
279 glDisable(GL_DITHER
);
281 glDisable(GL_DEPTH_TEST
);
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;
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
) {
303 shad
["texOccFull"] = 2;
304 shad
["texOccSmall"] = 3;
307 shadLightGeom
= new Shader("light_geom", loadTextFile("shaders/srlight_geom.frag"));
308 shadLightGeom
.exec((Shader shad
) {
311 shad
["texOccFull"] = 2;
312 shad
["texOccSmall"] = 3;
315 shadLightAmbient
= new Shader("light_ambient", loadTextFile("shaders/srlight_ambient.frag"));
316 shadLightAmbient
.exec((Shader shad
) {
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*/);
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
);
339 glMatrixMode(GL_MODELVIEW
);
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 () {
359 if (conLastChange
== cbufLastChange
) return;
361 scope(exit
) consoleUnlock();
363 conLastChange
= cbufLastChange
;
364 //foreach (auto s; conbufLinesRev) stdout.writeln(s, "|");
365 int skipLines
= conskiplines
;
366 fboConsole
.exec((FBO me
) {
368 orthoCamera(me
.width
, me
.height
);
370 glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
371 glClear(GL_COLOR_BUFFER_BIT
);
374 glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
375 glRectf(0, 0, me.width-1, me.height-1);
379 glBlendFunc(GL_SRC_ALPHA
, GL_ONE
);
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);
384 int y
= me
.height
-conCharHeight
-2;
387 scope(exit
) glPopMatrix();
388 glTranslatef(XOfs
, y
, 0);
389 int w
= conCharWidth('>');
390 glColorUint(rConPromptColor
);
392 uint spos
= conclilen
;
394 char ch
= concli
.ptr
[spos
-1];
395 if (w
+conCharWidth(ch
) > me
.width
-XOfs
*2-12) break;
396 w
+= conCharWidth(ch
);
399 glColorUint(rConInputColor
);
400 foreach (char ch
; concli
[spos
..conclilen
]) conDrawChar(ch
);
403 glColorUint(rConCursorColor
);
404 glRectf(0, 0, 12, 16);
408 glColorUint(rConTextColor
);
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;
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
424 if (skipLines
-- <= 0) {
425 while (pos
< sp
) conDrawChar(line
[pos
++]);
429 glTranslatef(XOfs
, y
, 0);
433 foreach (auto line
; conbufLinesRev
) {
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
447 if (map
!is null) map
.clear();
448 map
= new LevelMap(mapname
);
449 curmapname
= mapname
;
451 ugInit(map
.width
*TileSize
, map
.height
*TileSize
);
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();
479 mapscripts
.runInit();
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
);
490 rebuildMapMegaTextures();
492 { import core
.memory
: GC
; GC
.collect(); }
496 // ////////////////////////////////////////////////////////////////////////// //
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 () {
507 //mapTilesChanged = false;
508 //map.clearMegaTextures();
509 map
.oglBuildMega(mapTilesChanged
);
511 dotsAwake(); // let dormant dots fall
512 // rebuild small occluders texture
514 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
516 orthoCamera(map
.width
, map
.height
);
517 drawAtXY(map
.texgl
.ptr
[map
.LightMask
].tid
, 0, 0, map
.width
, map
.height
, mirrorY
:true);
522 // ////////////////////////////////////////////////////////////////////////// //
525 enum Phase
{ FadeIn
, Stay
, FadeOut
}
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;
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;
556 if (!noreplace
&& messagesUsed
== 1) {
557 switch (messages
.ptr
[0].phase
) {
558 case Message
.Phase
.FadeIn
:
559 messages
.ptr
[0].phase
= Message
.Phase
.FadeOut
;
561 case Message
.Phase
.Stay
:
562 messages
.ptr
[0].phase
= Message
.Phase
.FadeOut
;
563 messages
.ptr
[0].alpha
= 255;
568 auto msg
= messages
.ptr
+messagesUsed
;
570 msg
.phase
= Message
.Phase
.FadeIn
;
572 msg
.pauseMsecs
= pauseMsecs
;
574 if (msgtext
.length
> msg
.text
.length
) {
575 msg
.text
= msgtext
[0..msg
.text
.length
];
576 msg
.textlen
= msg
.text
.length
;
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;
590 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
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);
605 case Message
.Phase
.Stay
:
606 if (msg
.removeTime
<= curtime
) {
608 msg
.phase
= Message
.Phase
.FadeOut
;
609 goto case; // to fade
611 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
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
];
620 glColor4f(1.0f, 1.0f, 1.0f, msg
.alpha
/255.0f);
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));
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
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
728 // no need to clear it, shader will take care of that
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
);
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
749 // build "small" light texture for geometry-light shader
751 /*version(none)*/ fboLSpotSmall
.exec({
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
;
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
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
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
;
798 //glDisable(GL_BLEND);
799 float occn
= 1.0f*lightSize
/(MaxLightRadius
*2);
800 //float occt = 1.0f-occn;
807 bindTexture(fboLSpotSmall
.tex
.tid
);
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
821 bindTexture(fboLSpotBG
.tex
.tid
);
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
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);
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
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;
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
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
);
917 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
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
);
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
{
944 @disable this (this); // no copies
946 enum { Pixels
, Players
, Items
, Monsters
, Other
}
947 __gshared DrawInfo
[65536][4] drawlists
;
948 __gshared
uint[4] dlpos
;
953 Actor
.forEach((ActorId me
) {
954 //me.fprop_0drawlistpos = 0;
955 if (auto adef
= findActorDef(me
)) {
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
;
971 int oy
= xptr
.fgetp_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, ")");
981 if (me
.id
== cameraChick
.id
) {
982 camchickdi
.adef
= adef
;
984 camchickdi
.actorX
= actorX
;
985 camchickdi
.actorY
= actorY
;
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
];
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);
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;
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
;
1021 li
.w
= me
.fget_radius
;
1022 li
.h
= me
.fget_height
;
1027 conwriteln("not found actor ", me
.id
, " (", me
.classtype
!string
, ":", me
.classname
!string
, ")");
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
]) {
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;
1046 if (dlpos
[Pixels
]) {
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
]) {
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);
1061 pointsStarted
= false;
1063 if (!pointsStarted
) {
1065 pointsStarted
= true;
1067 glVertex2i(dl.actorX
, dl.actorY
);
1070 if (pointsStarted
) {
1072 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1077 if (/*altMove || movement ||*/ scale
== 1 ||
!cameraChick
.valid
) {
1082 vportX1
= map
.width
*TileSize
;
1083 vportY1
= map
.height
*TileSize
;
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;
1096 vportX0
= mofsx
/scale
;
1097 vportY0
= mofsy
/scale
;
1098 vportX1
= vportX0
+vlWidth
/scale
;
1099 vportY1
= vportY0
+vlHeight
/scale
;
1102 //glDisable(GL_DEPTH_TEST);
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);
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) {
1120 glVertex2i(camchickdi.actorX, camchickdi.actorY-70);
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);
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);
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);
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);
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);
1191 renderLightAmbient(li
.x
, li
.y
, li
.w
, li
.h
, SVec4F(0.0f, 0.0f, 0.0f, li
.r
));
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
) {
1198 renderLight(li
.x
, li
.y
, SVec4F(0.0f, 0.0f, 0.0f, li
.r
), li
.radius
);
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);
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);
1244 fboLevelLight.exec({
1245 smDrawText(map.width*TileSize/2, map.height*TileSize/2, "Testing...");
1250 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, 0);
1252 glDisable(GL_BLEND
);
1256 //auto img = smfont.ptr[0x39];
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);
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);
1288 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1289 //orthoCamera(map.width*TileSize, map.height*TileSize);
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);
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
) {
1313 orthoCamera(vlWidth
, vlHeight
);
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
) {
1326 import core
.sys
.posix
.signal
: timespec
;
1327 import core
.sys
.posix
.time
: nanosleep
;
1328 timespec ts
= void, tpassed
= void;
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);
1339 // ////////////////////////////////////////////////////////////////////////// //
1340 mixin(import("editor.d"));
1343 // ////////////////////////////////////////////////////////////////////////// //
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);
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;
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
) {
1380 conwriteln("ERROR: can't load new levels yet");
1384 if (name
.length
== 0) {
1385 conwriteln("ERROR: can't load empty level!");
1387 conwriteln("loading map '", name
, "'");
1393 string mn
= genNextMapName(0);
1395 nextmapname
= null; // clear "exit" flag
1398 conwriteln("can't skip level");
1400 })("skiplevel", "skip current level");
1403 inEditMode
= !inEditMode
;
1404 if (inEditMode
) sdwindow
.hideCursor(); else sdwindow
.showCursor();
1405 })("ed_toggle", "toggle editor");
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 () {
1421 import core
.time
: Duration
;
1422 //conwriteln("rendering thread: waiting for messages...");
1423 auto got
= receiveTimeout(
1424 Duration
.zero
, // don't wait
1426 addMessage(msg
.text
[0..msg
.textlen
], msg
.pauseMsecs
, msg
.noreplace
);
1428 (TMsgTestLightMove msg
) {
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
); },
1437 conwriteln("WARNING: unknown thread message received and ignored");
1442 //conwriteln("rendering thread: no more messages");
1446 if (nextmapname
.length
) {
1447 string mn
= nextmapname
;
1448 nextmapname
= null; // clear "exit" flag
1453 void processConsoleCommands () {
1455 scope(exit
) consoleUnlock();
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
);
1465 // save snapshot and other data for interpolator
1466 Actor
.saveSnapshot(prevFrameActorsData
[], prevFrameActorOfs
.ptr
);
1467 if (!gamePaused
&& !inEditMode
) {
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
); }
1484 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1486 bool doCheckTime
= void;
1492 __gshared
bool prevLost
= false;
1493 doCheckTime
= vframeWasLost
;
1494 if (vframeWasLost
) {
1496 { import core
.stdc
.stdio
; printf("frame was lost!\n"); }
1504 if (curtime
< nextvframe
) return false;
1506 if (curtime
> nextvframe
) {
1507 auto overtime
= cast(int)((curtime
-nextvframe
).total
!"msecs");
1508 if (overtime
> 2500) {
1512 { import core
.stdc
.stdio
; printf(" spent whole %d msecs\n", overtime
); }
1518 while (nextvframe
<= curtime
) nextvframe
+= dur
!"msecs"(MinFrameTime
);
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
1527 if (oldRenderVBL
!= renderVBL
) {
1528 oldRenderVBL
= renderVBL
;
1529 sdwindow
.vsync
= renderVBL
;
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();
1536 renderScene(curtime
);
1538 //renderLoading(curtime);
1541 scope(exit
) sdwindow
.mtUnlock();
1542 sdwindow
.swapOpenGlBuffers();
1544 sdwindow
.releaseCurrentOpenGlContext();
1545 vframeWasLost
= false;
1547 vframeWasLost
= true;
1548 { import core
.stdc
.stdio
; printf("xframe was lost!\n"); }
1550 curtime
= MonoTime
.currTime
;
1554 while (!quitRequested
&& !sdwindow
.closed
) {
1555 curtime
= MonoTime
.currTime
;
1556 auto fstime
= curtime
;
1558 doThinkFrame(); // this will fix curtime if necessary
1560 if (!vframeWasLost
) {
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
);
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;
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);
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
1621 if (ok
!= 42) assert(0, "wtf?!");
1624 conwriteln("rendering thread started");
1628 // ////////////////////////////////////////////////////////////////////////// //
1632 // ////////////////////////////////////////////////////////////////////////// //
1633 struct TMsgMouseEvent
{
1634 private import arsd
.simpledisplay
: MouseEventType
, MouseButton
;
1635 MouseEventType type
;
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;
1645 msg
.type
= evt
.type
;
1650 msg
.button
= evt
.button
;
1651 msg
.modifierState
= evt
.modifierState
;
1652 send(renderTid
, msg
);
1656 // ////////////////////////////////////////////////////////////////////////// //
1657 struct TMsgKeyEvent
{
1658 private import arsd
.simpledisplay
: Key
;
1666 public void postKeyEvent() (in auto ref KeyEvent evt
) {
1667 if (!atomicLoad(renderThreadStarted
)) return;
1670 msg
.pressed
= evt
.pressed
;
1671 msg
.character
= evt
.character
;
1672 msg
.modifierState
= evt
.modifierState
;
1673 send(renderTid
, msg
);
1677 // ////////////////////////////////////////////////////////////////////////// //
1678 struct TMsgTestLightMove
{
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
{
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
];
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 // ////////////////////////////////////////////////////////////////////////// //
1714 public void postChar (char ch
) {
1715 if (!atomicLoad(renderThreadStarted
)) return;
1718 send(renderTid
, msg
);
1722 // ////////////////////////////////////////////////////////////////////////// //
1723 // add console command to execution queue
1724 public void concmd (const(char)[] cmd
) {
1725 //if (!atomicLoad(renderThreadStarted)) return;
1727 scope(exit
) consoleUnlock();
1731 // get console variable value; doesn't do complex conversions!
1732 public T
convar(T
) (const(char)[] s
) {
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
) {
1741 scope(exit
) consoleUnlock();
1742 conSetVar
!T(s
, val
);