1 /* DooM2D: Midnight on the Firing Line
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module render
is aliced
;
20 //version = dont_use_vsync;
21 //version = old_light;
28 import std
.concurrency
;
52 // ////////////////////////////////////////////////////////////////////////// //
57 // ////////////////////////////////////////////////////////////////////////// //
58 public __gshared
bool cheatNoDoors
= false;
59 public __gshared
bool cheatNoWallClip
= false;
62 // ////////////////////////////////////////////////////////////////////////// //
63 public __gshared SimpleWindow sdwindow
;
66 public enum vlWidth
= 800;
67 public enum vlHeight
= 800;
68 __gshared
int scale
= 2;
70 public int getScale () nothrow @trusted @nogc { pragma(inline
, true); return scale
; }
73 // ////////////////////////////////////////////////////////////////////////// //
74 __gshared
bool levelLoaded
= false;
77 // ////////////////////////////////////////////////////////////////////////// //
78 __gshared
bool scanlines
= false;
79 __gshared
bool doLighting
= true;
82 // ////////////////////////////////////////////////////////////////////////// //
84 __gshared
ubyte[] prevFrameActorsData
;
85 __gshared
uint[65536] prevFrameActorOfs
; // uint.max-1: dead; uint.max: last
86 __gshared MonoTime lastthink
= MonoTime
.zero
; // for interpolator
87 __gshared MonoTime nextthink
= MonoTime
.zero
;
88 __gshared
bool frameInterpolation
= true;
91 __gshared
int[2] mapViewPosX
, mapViewPosY
; // [0]: previous frame -- for interpolator
94 // this should be screen center
95 public void setMapViewPos (int x
, int y
) {
101 int swdt
= vlWidth
/scale
;
102 int shgt
= vlHeight
/scale
;
105 if (x
< 0) x
= 0; else if (x
>= map
.width
*8-swdt
) x
= map
.width
*8-swdt
-1;
106 if (y
< 0) y
= 0; else if (y
>= map
.height
*8-shgt
) y
= map
.height
*8-shgt
-1;
107 mapViewPosX
[1] = x
*scale
;
108 mapViewPosY
[1] = y
*scale
;
112 // ////////////////////////////////////////////////////////////////////////// //
114 struct AttachedLightInfo
{
121 __gshared AttachedLightInfo
[65536] attachedLights
;
122 __gshared
uint attachedLightCount
= 0;
125 // ////////////////////////////////////////////////////////////////////////// //
126 enum MaxLightRadius
= 512;
129 // ////////////////////////////////////////////////////////////////////////// //
131 __gshared FBO
[MaxLightRadius
+1] fboOccluders
, fboDistMap
;
132 __gshared Shader shadToPolar
, shadBlur
, shadBlurOcc
;
134 __gshared FBO fboLevel
, fboLevelLight
, fboOrigBack
;
135 __gshared Shader shadScanlines
;
136 __gshared Shader shadLiquidDistort
;
139 // ////////////////////////////////////////////////////////////////////////// //
141 public void initOpenGL () {
144 glEnable(GL_TEXTURE_2D
);
145 glDisable(GL_LIGHTING
);
146 glDisable(GL_DITHER
);
148 glDisable(GL_DEPTH_TEST
);
151 shadScanlines
= new Shader("scanlines", loadTextFile("shaders/srscanlines.frag"));
153 shadLiquidDistort
= new Shader("liquid_distort", loadTextFile("shaders/srliquid_distort.frag"));
154 shadLiquidDistort
.exec((Shader shad
) {
155 shad
["texLqMap"] = 0;
160 shadToPolar
= new Shader("light_topolar", loadTextFile("shaders/srlight_topolar.frag"));
162 shadToPolar
= new Shader("light_topolar", loadTextFile("shaders/srlight_topolar_new.frag"));
164 shadToPolar
.exec((Shader shad
) {
166 shad
["texOccFull"] = 2;
169 shadBlur
= new Shader("light_blur", loadTextFile("shaders/srlight_blur.frag"));
170 shadBlur
.exec((Shader shad
) {
176 shadBlurOcc
= new Shader("light_blur_occ", loadTextFile("shaders/srlight_blur_occ.frag"));
177 shadBlurOcc
.exec((Shader shad
) {
184 foreach (int sz
; 2..MaxLightRadius
+1) {
185 fboOccluders
[sz
] = new FBO(sz
*2, sz
*2, Texture
.Option
.Clamp
, Texture
.Option
.Linear
); // create occluders FBO
186 fboDistMap
[sz
] = new FBO(sz
*2, 1, Texture
.Option
.Clamp
); // create 1d distance map FBO
190 glMatrixMode(GL_MODELVIEW
);
194 loadAllMonsterGraphics();
198 // ////////////////////////////////////////////////////////////////////////// //
199 // should be called when OpenGL is initialized
200 void loadMap (string mapname
) {
201 if (map
!is null) map
.clear();
202 map
= new LevelMap(mapname
);
203 curmapname
= mapname
;
205 ugInit(map
.width
*8, map
.height
*8);
210 if (fboLevel
!is null) fboLevel
.clear();
211 if (fboLevelLight
!is null) fboLevelLight
.clear();
212 if (fboOrigBack
!is null) fboOrigBack
.clear();
214 fboLevel
= new FBO(map
.width
*8, map
.height
*8, Texture
.Option
.Nearest
); // final level render will be here
215 fboLevelLight
= new FBO(map
.width
*8, map
.height
*8, Texture
.Option
.Nearest
); // level lights will be rendered here
216 fboOrigBack
= new FBO(map
.width
*8, map
.height
*8, Texture
.Option
.Nearest
, Texture
.Option
.Depth
); // background+foreground
218 shadToPolar
.exec((Shader shad
) { shad
["mapPixSize"] = SVec2F(map
.width
*8-1, map
.height
*8-1); });
219 shadBlur
.exec((Shader shad
) { shad
["mapPixSize"] = SVec2F(map
.width
*8, map
.height
*8); });
220 shadBlurOcc
.exec((Shader shad
) { shad
["mapPixSize"] = SVec2F(map
.width
*8, map
.height
*8); });
222 glActiveTexture(GL_TEXTURE0
+0);
223 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, 0);
224 orthoCamera(vlWidth
, vlHeight
);
226 Actor
.resetStorage();
231 // save first snapshot
232 if (prevFrameActorsData
.length
== 0) prevFrameActorsData
= new ubyte[](Actor
.actorSize
*65536); // ~15-20 megabytes
233 prevFrameActorOfs
[] = uint.max
; // just for fun
234 Actor
.saveSnapshot(prevFrameActorsData
[], prevFrameActorOfs
.ptr
);
235 mapViewPosX
[0] = mapViewPosX
[1];
236 mapViewPosY
[0] = mapViewPosY
[1];
240 { import core
.memory
: GC
; GC
.collect(); }
244 // ////////////////////////////////////////////////////////////////////////// //
246 __gshared
uint mapTilesChanged
= 0;
249 //WARNING! this can be called only from DACS, so we don't have to sync it!
250 public void mapDirty (uint layermask
) { mapTilesChanged |
= layermask
; }
253 void rebuildMapMegaTextures () {
255 //mapTilesChanged = false;
256 //map.clearMegaTextures();
257 map
.oglBuildMega(mapTilesChanged
);
262 // ////////////////////////////////////////////////////////////////////////// //
265 enum Phase
{ FadeIn
, Stay
, FadeOut
}
274 private import core
.sync
.mutex
: Mutex
;
276 __gshared Message
[128] messages
;
277 __gshared
uint messagesUsed
= 0;
279 //__gshared Mutex messageLock;
280 //shared static this () { messageLock = new Mutex(); }
283 void addMessage (const(char)[] msgtext
, int pauseMsecs
=3000, bool noreplace
=false) {
284 //messageLock.lock();
285 //scope(exit) messageLock.unlock();
286 if (msgtext
.length
== 0) return;
288 if (pauseMsecs
<= 50) return;
289 if (messagesUsed
== messages
.length
) {
290 // remove top message
291 foreach (immutable cidx
; 1..messagesUsed
) messages
.ptr
[cidx
-1] = messages
.ptr
[cidx
];
292 messages
.ptr
[0].alpha
= 255;
296 if (!noreplace
&& messagesUsed
== 1) {
297 switch (messages
.ptr
[0].phase
) {
298 case Message
.Phase
.FadeIn
:
299 messages
.ptr
[0].phase
= Message
.Phase
.FadeOut
;
301 case Message
.Phase
.Stay
:
302 messages
.ptr
[0].phase
= Message
.Phase
.FadeOut
;
303 messages
.ptr
[0].alpha
= 255;
308 auto msg
= messages
.ptr
+messagesUsed
;
310 msg
.phase
= Message
.Phase
.FadeIn
;
312 msg
.pauseMsecs
= pauseMsecs
;
314 if (msgtext
.length
> msg
.text
.length
) {
315 msg
.text
= msgtext
[0..msg
.text
.length
];
316 msg
.textlen
= msg
.text
.length
;
318 msg
.text
[0..msgtext
.length
] = msgtext
[];
319 msg
.textlen
= msgtext
.length
;
324 void doMessages (MonoTime curtime
) {
325 //messageLock.lock();
326 //scope(exit) messageLock.unlock();
328 if (messagesUsed
== 0) return;
330 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
336 final switch (msg
.phase
) {
337 case Message
.Phase
.FadeIn
:
338 if ((msg
.alpha
+= 10) >= 255) {
339 msg
.phase
= Message
.Phase
.Stay
;
340 msg
.removeTime
= curtime
+dur
!"msecs"(msg
.pauseMsecs
);
341 goto case; // to stay
343 glColor4f(1.0f, 1.0f, 1.0f, msg
.alpha
/255.0f);
345 case Message
.Phase
.Stay
:
346 if (msg
.removeTime
<= curtime
) {
348 msg
.phase
= Message
.Phase
.FadeOut
;
349 goto case; // to fade
351 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
353 case Message
.Phase
.FadeOut
:
354 if ((msg
.alpha
-= 10) <= 0) {
355 if (--messagesUsed
== 0) return;
356 // remove this message
357 foreach (immutable cidx
; 1..messagesUsed
+1) messages
.ptr
[cidx
-1] = messages
.ptr
[cidx
];
360 glColor4f(1.0f, 1.0f, 1.0f, msg
.alpha
/255.0f);
364 smDrawText(10, 10, msg
.text
[0..msg
.textlen
]);
368 // ////////////////////////////////////////////////////////////////////////// //
369 //mixin(Actor.FieldPropMixin!("0drawlistpos", uint));
371 mixin(Actor
.FieldGetMixin
!("classtype", StrId
)); // fget_classtype
372 mixin(Actor
.FieldGetMixin
!("classname", StrId
)); // fget_classname
373 mixin(Actor
.FieldGetMixin
!("x", int));
374 mixin(Actor
.FieldGetMixin
!("y", int));
375 mixin(Actor
.FieldGetMixin
!("flags", uint));
376 mixin(Actor
.FieldGetMixin
!("zAnimstate", StrId
));
377 mixin(Actor
.FieldGetMixin
!("zAnimidx", int));
378 mixin(Actor
.FieldGetMixin
!("dir", uint));
379 mixin(Actor
.FieldGetMixin
!("attLightXOfs", int));
380 mixin(Actor
.FieldGetMixin
!("attLightYOfs", int));
381 mixin(Actor
.FieldGetMixin
!("attLightRGBX", uint));
383 //mixin(Actor.FieldGetPtrMixin!("classtype", StrId)); // fget_classtype
384 //mixin(Actor.FieldGetPtrMixin!("classname", StrId)); // fget_classname
385 mixin(Actor
.FieldGetPtrMixin
!("x", int));
386 mixin(Actor
.FieldGetPtrMixin
!("y", int));
387 //mixin(Actor.FieldGetPtrMixin!("flags", uint));
388 //mixin(Actor.FieldGetPtrMixin!("zAnimstate", StrId));
389 //mixin(Actor.FieldGetPtrMixin!("zAnimidx", int));
390 //mixin(Actor.FieldGetPtrMixin!("dir", uint));
391 //mixin(Actor.FieldGetPtrMixin!("attLightXOfs", int));
392 //mixin(Actor.FieldGetPtrMixin!("attLightYOfs", int));
393 //mixin(Actor.FieldGetPtrMixin!("attLightRGBX", uint));
396 // ////////////////////////////////////////////////////////////////////////// //
397 __gshared
int vportX0
, vportY0
, vportX1
, vportY1
;
400 void renderLight() (int lightX
, int lightY
, in auto ref SVec4F lcol
, int lightRadius
) {
401 if (lightRadius
< 2) return;
402 if (lightRadius
> MaxLightRadius
) lightRadius
= MaxLightRadius
;
403 int lightSize
= lightRadius
*2;
404 // is this light visible?
405 if (lightX
<= -lightRadius || lightY
<= -lightRadius || lightX
-lightRadius
>= map
.width
*8 || lightY
-lightRadius
>= map
.height
*8) return;
407 // out of viewport -- do nothing
408 if (lightX
+lightRadius
< vportX0 || lightY
+lightRadius
< vportY0
) return;
409 if (lightX
-lightRadius
> vportX1 || lightY
-lightRadius
> vportY1
) return;
411 if (lightX
>= 0 && lightY
>= 0 && lightX
< map
.width
*8 && lightY
< map
.height
*8 &&
412 map
.teximgs
[map
.LightMask
].imageData
.colors
.ptr
[lightY
*(map
.width
*8)+lightX
].a
> 190) return;
417 // draw shadow casters to fboOccludersId, light should be in the center
419 fboOccluders
[lightRadius
].exec({
420 glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
421 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
422 glClear(GL_COLOR_BUFFER_BIT
);
423 orthoCamera(lightSize
, lightSize
);
424 drawAtXY(map
.texgl
.ptr
[map
.LightMask
], lightRadius
-lightX
, lightRadius
-lightY
);
427 // common color for all the following
428 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
430 // build 1d distance map to fboShadowMapId
431 fboDistMap
[lightRadius
].exec({
432 shadToPolar
.exec((Shader shad
) {
433 shad
["lightTexSize"] = SVec2F(lightSize
, lightSize
);
434 shad
["lightPos"] = SVec2F(lightX
, lightY
);
435 // no need to clear it, shader will take care of that
436 orthoCamera(lightSize
, 1);
438 drawAtXY(fboOccluders
[lightRadius
].tex
.tid
, 0, 0, lightSize
, 1);
440 // it doesn't matter what we will draw here, so just draw filled rect
441 glRectf(0, 0, lightSize
, 1);
446 // build light texture for blending
447 fboOccluders
[lightRadius
].exec({
448 // no need to clear it, shader will take care of that
449 shadBlur
.exec((Shader shad
) {
450 shad
["lightTexSize"] = SVec2F(lightSize
, lightSize
);
451 shad
["lightColor"] = SVec4F(lcol
.x
, lcol
.y
, lcol
.z
, lcol
.w
);
452 shad
["lightPos"] = SVec2F(lightX
, lightY
);
453 orthoCamera(lightSize
, lightSize
);
454 drawAtXY(fboDistMap
[lightRadius
].tex
.tid
, 0, 0, lightSize
, lightSize
);
458 // blend light texture
461 //glDisable(GL_BLEND);
462 glBlendFunc(GL_SRC_ALPHA
, GL_ONE
);
463 orthoCamera(map
.width
*8, map
.height
*8);
464 drawAtXY(fboOccluders
[lightRadius
].tex
, lightX
-lightRadius
, lightY
-lightRadius
, mirrorY
:true);
465 // and blend it again, with the shader that will touch only occluders
466 shadBlurOcc
.exec((Shader shad
) {
467 shad
["lightTexSize"] = SVec2F(lightSize
, lightSize
);
468 shad
["lightColor"] = SVec4F(lcol
.x
, lcol
.y
, lcol
.z
, lcol
.w
);
469 shad
["lightPos"] = SVec2F(lightX
, lightY
);
470 drawAtXY(fboOccluders
[lightRadius
].tex
.tid
, lightX
-lightRadius
, lightY
-lightRadius
, lightSize
, lightSize
, mirrorY
:true);
476 // ////////////////////////////////////////////////////////////////////////// //
477 __gshared
int testLightX
= vlWidth
/2, testLightY
= vlHeight
/2;
478 __gshared
bool testLightMoved
= false;
479 //__gshared int mapOfsX, mapOfsY;
480 //__gshared bool movement = false;
481 __gshared
float iLiquidTime
= 0.0;
482 //__gshared bool altMove = false;
485 void renderScene (MonoTime curtime
) {
486 //enum BackIntens = 0.05f;
487 enum BackIntens
= 0.0f;
490 float atob
= (curtime
> lastthink ?
cast(float)((curtime
-lastthink
).total
!"msecs")/cast(float)((nextthink
-lastthink
).total
!"msecs") : 1.0f);
491 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
494 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
495 int curfp = cast(int)((curtime-lastthink).total!"msecs");
496 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
502 bool camCenter
= true;
503 if (/*altMove || movement ||*/ scale
== 1) {
510 vportX1
= map
.width
*8;
511 vportY1
= map
.height
*8;
514 if (frameInterpolation
) {
515 import core
.stdc
.math
: roundf
;
516 mofsx
= cast(int)(mapViewPosX
[0]+roundf((mapViewPosX
[1]-mapViewPosX
[0])*atob
));
517 mofsy
= cast(int)(mapViewPosY
[0]+roundf((mapViewPosY
[1]-mapViewPosY
[0])*atob
));
519 mofsx
= mapViewPosX
[1];
520 mofsy
= mapViewPosY
[1];
522 vportX0
= mofsx
/scale
;
523 vportY0
= mofsy
/scale
;
524 vportX1
= vportX0
+vlWidth
/scale
;
525 vportY1
= vportY0
+vlHeight
/scale
;
528 if (mapTilesChanged
!= 0) rebuildMapMegaTextures();
532 // build background layer
534 //glDisable(GL_BLEND);
535 //glClearDepth(1.0f);
536 //glDepthFunc(GL_LESS);
537 //glDepthFunc(GL_NEVER);
538 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
539 glClear(GL_COLOR_BUFFER_BIT
/*|GL_DEPTH_BUFFER_BIT*/);
540 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
541 orthoCamera(map
.width
*8, map
.height
*8);
544 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
547 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
548 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2, map.MapSize*8, map.MapSize*8);
549 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
550 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*8, map.MapSize*8);
552 drawAtXY(map
.skytexgl
.tid
, 0, 0, map
.MapSize
*8, map
.MapSize
*8);
554 drawAtXY(map
.texgl
.ptr
[map
.Back
], 0, 0);
555 // draw distorted liquid areas
556 shadLiquidDistort
.exec((Shader shad
) {
557 shad
["iDistortTime"] = iLiquidTime
;
558 drawAtXY(map
.texgl
.ptr
[map
.AllLiquids
], 0, 0);
560 // monsters, items; we'll do linear interpolation here
561 glColor3f(1.0f, 1.0f, 1.0f);
562 //glEnable(GL_DEPTH_TEST);
563 attachedLightCount
= 0;
565 // who cares about memory?!
566 // draw order: players, items, monsters, other
567 static struct DrawInfo
{
571 @disable this (this); // no copies
573 enum { Players
, Items
, Monsters
, Other
}
574 __gshared DrawInfo
[65536][4] drawlists
;
575 __gshared
uint[4] dlpos
;
581 Actor
.forEach((ActorId me
) {
582 //me.fprop_0drawlistpos = 0;
583 if (auto adef
= findActorDef(me
)) {
585 switch (adef
.classtype
.get
) {
586 case "monster": dlnum
= (adef
.classname
.get
!= "Player" ? Monsters
: Players
); break;
587 case "item": dlnum
= Items
; break;
588 default: dlnum
= Other
; break;
590 int actorX
, actorY
; // current actor position
592 auto ofs
= prevFrameActorOfs
.ptr
[me
.id
&0xffff];
593 if (frameInterpolation
&& ofs
< uint.max
-1 && Actor
.isSameSavedActor(me
.id
, prevFrameActorsData
.ptr
, ofs
)) {
594 import core
.stdc
.math
: roundf
;
595 auto xptr
= prevFrameActorsData
.ptr
+ofs
;
596 int ox
= xptr
.fgetp_x
;
598 int oy
= xptr
.fgetp_y
;
600 actorX
= cast(int)(ox
+roundf((nx
-ox
)*atob
));
601 actorY
= cast(int)(oy
+roundf((ny
-oy
)*atob
));
602 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
608 if (!camchick
.valid
&& me
.flags
!uint&AF_CAMERACHICK
) {
610 camchickdi
.adef
= adef
;
612 camchickdi
.actorX
= actorX
;
613 camchickdi
.actorY
= actorY
;
616 if ((me
.fget_flags
&AF_NODRAW
) == 0) {
617 //auto dl = &drawlists[dlnum][dlpos.ptr[dlnum]];
618 //me.fprop_0drawlistpos = (dlpos.ptr[dlnum]&0xffff)|((dlnum&0xff)<<16);
619 auto dl = drawlists
.ptr
[dlnum
].ptr
+dlpos
.ptr
[dlnum
];
626 if (auto isp = adef.animSpr(me.fget_zAnimstate, me.fget_dir, me.fget_zAnimidx)) {
627 drawAtXY(isp.tex, actorX-isp.vga.sx, actorY-isp.vga.sy, adef.zz);
629 //conwriteln("no animation for actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
633 // process attached lights
634 if ((me
.fget_flags
&AF_NOLIGHT
) == 0) {
635 uint alr
= me
.fget_attLightRGBX
;
636 if ((alr
&0xff) >= 4) {
638 auto li
= attachedLights
.ptr
+attachedLightCount
;
639 ++attachedLightCount
;
640 li
.x
= actorX
+me
.fget_attLightXOfs
;
641 li
.y
= actorY
+me
.fget_attLightYOfs
;
642 li
.r
= ((alr
>>24)&0xff)/255.0f; // red or intensity
643 if ((alr
&0x00_ff_ff
_00U) == 0x00_00_01_00U) {
646 li
.g
= ((alr
>>16)&0xff)/255.0f;
647 li
.b
= ((alr
>>8)&0xff)/255.0f;
648 li
.uncolored
= false;
650 li
.radius
= (alr
&0xff);
651 if (li
.radius
> MaxLightRadius
) li
.radius
= MaxLightRadius
;
655 conwriteln("not found actor ", me
.id
, " (", me
.classtype
!string
, ":", me
.classname
!string
, ")");
659 foreach_reverse (uint dlnum
; 0..drawlists
.length
) {
660 auto dl = drawlists
.ptr
[dlnum
].ptr
;
661 if (dlnum
== Players
) dl += dlpos
.ptr
[dlnum
]-1;
662 foreach (uint idx
; 0..dlpos
.ptr
[dlnum
]) {
664 if (auto isp
= dl.adef
.animSpr(me
.fget_zAnimstate
, me
.fget_dir
, me
.fget_zAnimidx
)) {
665 //drawAtXY(isp.tex, dl.actorX-isp.vga.sx, dl.actorY-isp.vga.sy);
666 isp
.drawAtXY(dl.actorX
, dl.actorY
);
668 if (dlnum
!= Players
) ++dl; else --dl;
671 if (camCenter
&& camchick
.valid
) {
672 int tiltHeight
= /*getMapViewHeight()*/(vlHeight
/scale
)/4;
673 int vy
= camchick
.looky
!int;
674 if (vy
< -tiltHeight
) vy
= -tiltHeight
; else if (vy
> tiltHeight
) vy
= tiltHeight
;
675 int swdt
= vlWidth
/scale
;
676 int shgt
= vlHeight
/scale
;
677 int x
= camchickdi
.actorX
-swdt
/2;
678 int y
= (camchickdi
.actorY
+vy
)-shgt
/2;
679 if (x
< 0) x
= 0; else if (x
>= map
.width
*8-swdt
) x
= map
.width
*8-swdt
-1;
680 if (y
< 0) y
= 0; else if (y
>= map
.height
*8-shgt
) y
= map
.height
*8-shgt
-1;
683 vportX0
= mofsx
/scale
;
684 vportY0
= mofsy
/scale
;
685 vportX1
= vportX0
+vlWidth
/scale
;
686 vportY1
= vportY0
+vlHeight
/scale
;
688 //glDisable(GL_DEPTH_TEST);
690 drawAtXY(texParts
, 0, 0);
691 // do liquid coloring
692 drawAtXY(map
.texgl
.ptr
[map
.LiquidMask
], 0, 0);
693 // foreground -- hide secrets, draw lifts and such
694 drawAtXY(map
.texgl
.ptr
[map
.Front
], 0, 0);
702 glClearColor(BackIntens
, BackIntens
, BackIntens
, 1.0f);
703 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
704 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
705 glClear(GL_COLOR_BUFFER_BIT
);
708 // texture 1 is background
709 glActiveTexture(GL_TEXTURE0
+1);
710 glBindTexture(GL_TEXTURE_2D
, fboOrigBack
.tex
.tid
);
711 // texture 2 is occluders
712 glActiveTexture(GL_TEXTURE0
+2);
713 glBindTexture(GL_TEXTURE_2D
, map
.texgl
.ptr
[map
.LightMask
].tid
);
714 // done texture assign
715 glActiveTexture(GL_TEXTURE0
+0);
719 renderLight( 27, 391-0+LYOfs
, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
720 renderLight(542, 424-0+LYOfs
, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
721 renderLight(377, 368-0+LYOfs
, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
722 renderLight(147, 288-0+LYOfs
, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
723 renderLight( 71, 200-0+LYOfs
, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
724 renderLight(249, 200-0+LYOfs
, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
725 renderLight(426, 200-0+LYOfs
, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
726 renderLight(624, 200-0+LYOfs
, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
727 renderLight(549, 298-0+LYOfs
, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
728 renderLight( 74, 304-0+LYOfs
, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
730 renderLight(24*8+4, (24+18)*8-2+LYOfs
, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
732 renderLight(280, 330, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
735 foreach (ref li
; attachedLights
[0..attachedLightCount
]) {
737 renderLight(li
.x
, li
.y
, SVec4F(0.0f, 0.0f, 0.0f, li
.r
), li
.radius
);
739 renderLight(li
.x
, li
.y
, SVec4F(li
.r
, li
.g
, li
.b
, 1.0f), li
.radius
);
744 if (testLightMoved
) {
745 testLightX
= testLightX
/scale
+mofsx
/scale
;
746 testLightY
= testLightY
/scale
+mofsy
/scale
;
747 testLightMoved
= false;
749 foreach (immutable _
; 0..1) {
750 renderLight(testLightX
, testLightY
, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
754 glActiveTexture(GL_TEXTURE0
+1);
755 glBindTexture(GL_TEXTURE_2D
, 0);
756 glActiveTexture(GL_TEXTURE0
+2);
757 glBindTexture(GL_TEXTURE_2D
, 0);
758 glActiveTexture(GL_TEXTURE0
+0);
763 shadScanlines.exec((Shader shad) {
764 shad["scanlines"] = scanlines;
765 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
766 glClear(GL_COLOR_BUFFER_BIT);
767 orthoCamera(vlWidth, vlHeight);
768 //orthoCamera(map.width*8*scale, map.height*8*scale);
769 //glMatrixMode(GL_MODELVIEW);
771 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
772 // somehow, FBO objects are mirrored; wtf?!
773 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*8*scale, map.height*8*scale, mirrorY:true);
780 smDrawText(map.width*8/2, map.height*8/2, "Testing...");
785 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, 0);
790 //auto img = smfont.ptr[0x39];
793 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
794 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
801 { // http://stackoverflow.com/questions/7207422/setting-up-opengl-multiple-render-targets
802 GLenum[1] buffers = [ GL_BACK_LEFT, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
803 //GLenum[1] buffers = [ GL_NONE, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
804 glDrawBuffers(1, buffers.ptr);
808 orthoCamera(vlWidth
, vlHeight
);
809 auto tex
= (doLighting ? fboLevelLight
.tex
.tid
: fboOrigBack
.tex
.tid
);
812 drawAtXY(tex
, -mofsx
, -mofsy
, map
.width
*8*scale
, map
.height
*8*scale
, mirrorY
:true);
818 // ////////////////////////////////////////////////////////////////////////// //
819 // returns time slept
820 int sleepAtMaxMsecs (int msecs
) {
822 import core
.sys
.posix
.signal
: timespec
;
823 import core
.sys
.posix
.time
: nanosleep
;
824 timespec ts
= void, tpassed
= void;
826 ts
.tv_nsec
= msecs
*1000*1000+(500*1000); // milli to nano
827 nanosleep(&ts
, &tpassed
);
828 return (ts
.tv_nsec
-tpassed
.tv_nsec
)/(1000*1000);
835 // ////////////////////////////////////////////////////////////////////////// //
837 shared int diedie
= 0;
839 enum D2DFrameTime
= 55; // milliseconds
840 enum MinFrameTime
= 1000/60; // ~60 FPS
842 public void renderThread (Tid starterTid
) {
843 send(starterTid
, 42);
845 MonoTime curtime
= MonoTime
.currTime
;
847 lastthink
= curtime
; // for interpolator
848 nextthink
= curtime
+dur
!"msecs"(D2DFrameTime
);
849 MonoTime nextvframe
= curtime
;
851 enum MaxFPSFrames
= 16;
852 float frtimes
= 0.0f;
855 int hushFrames
= 6; // ignore first `hushFrames` frames overtime
856 MonoTime prevFrameStartTime
= curtime
;
858 bool vframeWasLost
= false;
860 void resetFrameTimers () {
861 MonoTime curtime
= MonoTime
.currTime
;
862 lastthink
= curtime
; // for interpolator
863 nextthink
= curtime
+dur
!"msecs"(D2DFrameTime
);
864 nextvframe
= curtime
;
867 void loadNewLevel (string name
) {
870 conwriteln("ERROR: can't load new levels yet");
874 if (name
.length
== 0) {
875 conwriteln("ERROR: can't load empty level!");
877 conwriteln("loading map '", name
, "'");
882 void receiveMessages () {
884 import core
.time
: Duration
;
885 //conwriteln("rendering thread: waiting for messages...");
886 auto got
= receiveTimeout(
887 Duration
.zero
, // don't wait
889 addMessage(msg
.text
[0..msg
.textlen
], msg
.pauseMsecs
, msg
.noreplace
);
891 (TMsgLoadLevel msg
) {
892 nextmapname
= null; // clear "exit" flag
893 loadNewLevel(msg
.mapfile
[0..msg
.textlen
].idup
);
895 (TMsgSkipLevel msg
) {
896 string mn
= genNextMapName(0);
898 nextmapname
= null; // clear "exit" flag
902 (TMsgIntOption msg
) {
903 final switch (msg
.type
) with (TMsgIntOption
.Type
) {
905 if (msg
.value
>= 1 && msg
.value
<= 2) scale
= msg
.value
;
909 (TMsgBoolOption msg
) {
910 final switch (msg
.type
) with (TMsgBoolOption
.Type
) {
912 if (msg
.toggle
) msg
.value
= !frameInterpolation
;
913 if (frameInterpolation
!= msg
.value
) {
914 frameInterpolation
= msg
.value
;
916 msg
.showMessage
= false;
920 if (msg
.toggle
) msg
.value
= !doLighting
;
921 if (doLighting
!= msg
.value
) {
922 doLighting
= msg
.value
;
924 msg
.showMessage
= false;
928 if (msg
.toggle
) msg
.value
= !cheatNoDoors
;
929 if (cheatNoDoors
!= msg
.value
) {
930 cheatNoDoors
= msg
.value
;
932 msg
.showMessage
= false;
935 case CheatNoWallClip
:
936 if (msg
.toggle
) msg
.value
= !cheatNoWallClip
;
937 if (CheatNoWallClip
!= msg
.value
) {
938 cheatNoWallClip
= msg
.value
;
940 msg
.showMessage
= false;
944 if (msg
.showMessage
) {
947 void putStr(T
) (T s
) if (is(T
: const(char)[])) {
948 foreach (char ch
; s
) {
949 if (msgpos
>= mbuf
.length
) break;
950 mbuf
.ptr
[msgpos
++] = ch
;
954 { import std
.conv
: to
; putStr(to
!string(msg
.type
)); }
956 if (msg
.value
) putStr("N"); else putStr("FF");
957 addMessage(mbuf
[0..msgpos
]);
960 (TMsgTestLightMove msg
) {
963 testLightMoved
= true;
966 conwriteln("WARNING: unknown thread message received and ignored");
971 //conwriteln("rendering thread: no more messages");
975 if (nextmapname
.length
) {
976 string mn
= nextmapname
;
977 nextmapname
= null; // clear "exit" flag
982 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
983 bool doThinkFrame () {
984 if (curtime
>= nextthink
) {
986 while (nextthink
<= curtime
) nextthink
+= dur
!"msecs"(D2DFrameTime
);
988 // save snapshot and other data for interpolator
989 Actor
.saveSnapshot(prevFrameActorsData
[], prevFrameActorOfs
.ptr
);
990 mapViewPosX
[0] = mapViewPosX
[1];
991 mapViewPosY
[0] = mapViewPosY
[1];
997 auto tm
= MonoTime
.currTime
;
998 int thinkTime
= cast(int)((tm
-curtime
).total
!"msecs");
999 if (thinkTime
> 9) { import core
.stdc
.stdio
; printf("spent on thinking: %d msecs\n", thinkTime
); }
1007 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1009 version(dont_use_vsync
) {
1011 enum doCheckTime
= true;
1014 __gshared
bool prevLost
= false;
1015 bool doCheckTime
= vframeWasLost
;
1016 if (vframeWasLost
) {
1018 { import core
.stdc
.stdio
; printf("frame was lost!\n"); }
1026 if (curtime
< nextvframe
) return false;
1027 version(dont_use_vsync
) {
1028 if (curtime
> nextvframe
) {
1029 auto overtime
= cast(int)((curtime
-nextvframe
).total
!"msecs");
1030 if (overtime
> 2500) {
1034 { import core
.stdc
.stdio
; printf(" spent whole %d msecs\n", overtime
); }
1040 while (nextvframe
<= curtime
) nextvframe
+= dur
!"msecs"(MinFrameTime
);
1044 scope(exit
) sdwindow
.mtUnlock();
1045 ctset
= sdwindow
.setAsCurrentOpenGlContextNT
;
1047 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
1050 iLiquidTime
= cast(float)((curtime
-MonoTime
.zero
).total
!"msecs"%10000000)/18.0f*0.04f;
1051 receiveMessages(); // here, 'cause we need active OpenGL context for some messages
1053 renderScene(curtime
);
1055 //renderLoading(curtime);
1058 scope(exit
) sdwindow
.mtUnlock();
1059 sdwindow
.swapOpenGlBuffers();
1061 sdwindow
.releaseCurrentOpenGlContext();
1062 vframeWasLost
= false;
1064 vframeWasLost
= true;
1065 { import core
.stdc
.stdio
; printf("xframe was lost!\n"); }
1067 curtime
= MonoTime
.currTime
;
1072 if (sdwindow
.closed
) break;
1073 if (atomicLoad(diedie
) > 0) break;
1075 curtime
= MonoTime
.currTime
;
1076 auto fstime
= curtime
;
1078 doThinkFrame(); // this will fix curtime if necessary
1080 if (!vframeWasLost
) {
1082 auto frameTime
= cast(float)(curtime
-prevFrameStartTime
).total
!"msecs"/1000.0f;
1083 prevFrameStartTime
= curtime
;
1084 frtimes
+= frameTime
;
1085 if (++framenum
>= MaxFPSFrames || frtimes
>= 3.0f) {
1086 import std
.string
: format
;
1087 int newFPS
= cast(int)(cast(float)MaxFPSFrames
/frtimes
+0.5);
1088 if (newFPS
!= prevFPS
) {
1089 sdwindow
.title
= "%s / FPS:%s".format("D2D", newFPS
);
1098 curtime
= MonoTime
.currTime
;
1100 // now sleep until next "video" or "think" frame
1101 if (nextthink
> curtime
&& nextvframe
> curtime
) {
1103 immutable nextVideoFrameSleep
= cast(int)((nextvframe
-curtime
).total
!"msecs");
1104 immutable nextThinkFrameSleep
= cast(int)((nextthink
-curtime
).total
!"msecs");
1105 immutable sleepTime
= (nextVideoFrameSleep
< nextThinkFrameSleep ? nextVideoFrameSleep
: nextThinkFrameSleep
);
1106 sleepAtMaxMsecs(sleepTime
);
1107 //curtime = MonoTime.currTime;
1110 } catch (Throwable e
) {
1111 // here, we are dead and fucked (the exact order doesn't matter)
1112 import core
.stdc
.stdlib
: abort
;
1113 import core
.stdc
.stdio
: fprintf
, stderr
;
1114 import core
.memory
: GC
;
1115 GC
.disable(); // yeah
1116 thread_suspendAll(); // stop right here, you criminal scum!
1117 auto s
= e
.toString();
1118 fprintf(stderr
, "\n=== FATAL ===\n%.*s\n", cast(uint)s
.length
, s
.ptr
);
1119 abort(); // die, you bitch!
1121 import core.stdc.stdio;
1122 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
1123 import std.stdio : stderr;
1126 if (sdwindow.closed) break;
1127 if (atomicLoad(diedie) > 0) break;
1128 sleepAtMaxMsecs(100);
1132 atomicStore(diedie
, 2);
1136 // ////////////////////////////////////////////////////////////////////////// //
1137 __gshared Tid renderTid
;
1138 shared bool renderThreadStarted
= false;
1141 public void startRenderThread () {
1142 if (!cas(&renderThreadStarted
, false, true)) {
1143 assert(0, "render thread already started!");
1145 renderTid
= spawn(&renderThread
, thisTid
);
1146 setMaxMailboxSize(renderTid
, 1024, OnCrowding
.throwException
); //FIXME
1147 // wait for "i'm ready" signal
1150 if (ok
!= 42) assert(0, "wtf?!");
1153 conwriteln("rendering thread started");
1157 // ////////////////////////////////////////////////////////////////////////// //
1158 public void closeWindow () {
1159 if (atomicLoad(diedie
) != 2) {
1160 atomicStore(diedie
, 1);
1161 while (atomicLoad(diedie
) != 2) {}
1163 if (!sdwindow
.closed
) {
1170 // ////////////////////////////////////////////////////////////////////////// //
1174 // ////////////////////////////////////////////////////////////////////////// //
1175 struct TMsgTestLightMove
{
1179 public void postTestLightMove (int x
, int y
) {
1180 if (!atomicLoad(renderThreadStarted
)) return;
1181 auto msg
= TMsgTestLightMove(x
, y
);
1182 send(renderTid
, msg
);
1186 // ////////////////////////////////////////////////////////////////////////// //
1187 struct TMsgMessage
{
1194 public void postAddMessage (const(char)[] msgtext
, int pauseMsecs
=3000, bool noreplace
=false) {
1195 if (!atomicLoad(renderThreadStarted
)) return;
1196 if (msgtext
.length
> TMsgMessage
.text
.length
) msgtext
= msgtext
[0..TMsgMessage
.text
.length
];
1198 msg
.textlen
= cast(uint)msgtext
.length
;
1199 if (msg
.textlen
) msg
.text
[0..msg
.textlen
] = msgtext
[0..msg
.textlen
];
1200 msg
.pauseMsecs
= pauseMsecs
;
1201 msg
.noreplace
= noreplace
;
1202 send(renderTid
, msg
);
1206 // ////////////////////////////////////////////////////////////////////////// //
1207 struct TMsgLoadLevel
{
1212 public void postLoadLevel (const(char)[] mapfile
) {
1213 if (!atomicLoad(renderThreadStarted
)) return;
1214 if (mapfile
.length
> TMsgLoadLevel
.mapfile
.length
) mapfile
= mapfile
[0..TMsgLoadLevel
.mapfile
.length
];
1216 msg
.textlen
= cast(uint)mapfile
.length
;
1217 if (msg
.textlen
) msg
.mapfile
[0..msg
.textlen
] = mapfile
[0..msg
.textlen
];
1218 send(renderTid
, msg
);
1222 // ////////////////////////////////////////////////////////////////////////// //
1223 struct TMsgSkipLevel
{
1226 public void postSkipLevel () {
1227 if (!atomicLoad(renderThreadStarted
)) return;
1229 send(renderTid
, msg
);
1233 // ////////////////////////////////////////////////////////////////////////// //
1234 struct TMsgIntOption
{
1244 struct TMsgBoolOption
{
1258 bool strCaseEqu (const(char)[] s0
, const(char)[] s1
) {
1259 if (s0
.length
!= s1
.length
) return false;
1260 foreach (immutable idx
, char ch
; s0
) {
1261 if (ch
>= 'A' && ch
<= 'Z') ch
+= 32;
1262 char c1
= s1
.ptr
[idx
];
1263 if (c1
>= 'A' && c1
<= 'Z') c1
+= 32;
1264 if (ch
!= c1
) return false;
1270 public bool postToggleOption (const(char)[] name
, bool showMessage
=false) { pragma(inline
, true); return postSetOption(name
, true, showMessage
:showMessage
, toggle
:true); }
1272 public bool postSetOption(T
) (const(char)[] name
, T value
, bool showMessage
=false, bool toggle
=false) if ((is(T
== int) ||
is(T
== bool))) {
1273 if (!atomicLoad(renderThreadStarted
)) return false;
1274 if (name
.length
== 0 || name
.length
> 127) return false;
1275 static if (is(T
== int)) {
1277 foreach (string oname
; __traits(allMembers
, TMsgIntOption
.Type
)) {
1278 if (strCaseEqu(oname
, name
)) {
1279 msg
.type
= __traits(getMember
, TMsgIntOption
.Type
, oname
);
1281 msg
.showMessage
= showMessage
;
1282 send(renderTid
, msg
);
1286 } else static if (is(T
== bool)) {
1288 foreach (string oname
; __traits(allMembers
, TMsgBoolOption
.Type
)) {
1289 if (strCaseEqu(oname
, name
)) {
1290 msg
.type
= __traits(getMember
, TMsgBoolOption
.Type
, oname
);
1292 msg
.toggle
= toggle
;
1293 msg
.showMessage
= showMessage
;
1294 send(renderTid
, msg
);
1299 static assert(0, "invalid option type '"~T
.stringof
~"'");