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;
27 import std
.concurrency
;
51 // ////////////////////////////////////////////////////////////////////////// //
56 // ////////////////////////////////////////////////////////////////////////// //
57 public __gshared
bool cheatNoDoors
= false;
58 public __gshared
bool cheatNoWallClip
= false;
61 // ////////////////////////////////////////////////////////////////////////// //
62 public __gshared SimpleWindow sdwindow
;
65 public enum vlWidth
= 800;
66 public enum vlHeight
= 800;
67 __gshared
int scale
= 2;
69 public int getScale () nothrow @trusted @nogc { pragma(inline
, true); return scale
; }
72 // ////////////////////////////////////////////////////////////////////////// //
73 __gshared
bool levelLoaded
= false;
76 // ////////////////////////////////////////////////////////////////////////// //
77 __gshared
bool scanlines
= false;
78 __gshared
bool doLighting
= true;
79 __gshared
bool gamePaused
= false;
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 // ////////////////////////////////////////////////////////////////////////// //
93 struct AttachedLightInfo
{
100 int w
, h
; // for ambient lights
106 __gshared AttachedLightInfo
[65536] attachedLights
;
107 __gshared
uint attachedLightCount
= 0;
110 // ////////////////////////////////////////////////////////////////////////// //
111 enum MaxLightRadius
= 255;
114 // ////////////////////////////////////////////////////////////////////////// //
116 __gshared FBO
[MaxLightRadius
+1] fboDistMap
;
117 __gshared FBO fboOccluders
;
118 __gshared Shader shadToPolar
, shadBlur
, shadBlurOcc
, shadAmbient
;
120 __gshared FBO fboLevel
, fboLevelLight
, fboOrigBack
, fboLMaskSmall
;
121 __gshared Shader shadScanlines
;
122 __gshared Shader shadLiquidDistort
;
125 // ////////////////////////////////////////////////////////////////////////// //
127 public void initOpenGL () {
130 glEnable(GL_TEXTURE_2D
);
131 glDisable(GL_LIGHTING
);
132 glDisable(GL_DITHER
);
134 glDisable(GL_DEPTH_TEST
);
137 shadScanlines
= new Shader("scanlines", loadTextFile("shaders/srscanlines.frag"));
139 shadLiquidDistort
= new Shader("liquid_distort", loadTextFile("shaders/srliquid_distort.frag"));
140 shadLiquidDistort
.exec((Shader shad
) {
141 shad
["texLqMap"] = 0;
145 shadToPolar
= new Shader("light_trace", loadTextFile("shaders/srlight_trace.frag"));
146 shadToPolar
.exec((Shader shad
) {
148 shad
["texOccFull"] = 2;
149 shad
["texOccSmall"] = 3;
152 shadBlur
= new Shader("light_blur", loadTextFile("shaders/srlight_blur.frag"));
153 shadBlur
.exec((Shader shad
) {
157 shad
["texOccSmall"] = 3;
160 shadBlurOcc
= new Shader("light_blur_occ", loadTextFile("shaders/srlight_blur_occ.frag"));
161 shadBlurOcc
.exec((Shader shad
) {
165 shad
["texOccSmall"] = 3;
168 shadAmbient
= new Shader("light_ambient", loadTextFile("shaders/srlight_ambient.frag"));
169 shadAmbient
.exec((Shader shad
) {
172 shad
["texOccSmall"] = 3;
175 fboOccluders
= new FBO(MaxLightRadius
*2, MaxLightRadius
*2, Texture
.Option
.Clamp
, Texture
.Option
.Linear
);
177 foreach (int sz
; 2..MaxLightRadius
+1) {
178 fboDistMap
[sz
] = new FBO(sz
*2, 1, Texture
.Option
.Clamp
, Texture
.Option
.Linear
); // create 1d distance map FBO
182 glMatrixMode(GL_MODELVIEW
);
186 loadAllMonsterGraphics();
190 // ////////////////////////////////////////////////////////////////////////// //
191 // should be called when OpenGL is initialized
192 void loadMap (string mapname
) {
193 mapscripts
.runUnloading(); // "map unloading" script
196 if (map
!is null) map
.clear();
197 map
= new LevelMap(mapname
);
198 curmapname
= mapname
;
200 ugInit(map
.width
*TileSize
, map
.height
*TileSize
);
205 if (fboLevel
!is null) fboLevel
.clear();
206 if (fboLevelLight
!is null) fboLevelLight
.clear();
207 if (fboOrigBack
!is null) fboOrigBack
.clear();
208 if (fboLMaskSmall
!is null) fboLMaskSmall
.clear();
210 fboLevel
= new FBO(map
.width
*TileSize
, map
.height
*TileSize
, Texture
.Option
.Nearest
); // final level render will be here
211 fboLevelLight
= new FBO(map
.width
*TileSize
, map
.height
*TileSize
, Texture
.Option
.Nearest
); // level lights will be rendered here
212 fboOrigBack
= new FBO(map
.width
*TileSize
, map
.height
*TileSize
, Texture
.Option
.Nearest
/*, Texture.Option.Depth*/); // background+foreground
213 fboLMaskSmall
= new FBO(map
.width
, map
.height
, Texture
.Option
.Nearest
); // small lightmask
215 shadToPolar
.exec((Shader shad
) { shad
["mapPixSize"] = SVec2F(map
.width
*TileSize
-1, map
.height
*TileSize
-1); });
216 shadBlur
.exec((Shader shad
) { shad
["mapPixSize"] = SVec2F(map
.width
*TileSize
, map
.height
*TileSize
); });
217 shadBlurOcc
.exec((Shader shad
) { shad
["mapPixSize"] = SVec2F(map
.width
*TileSize
, map
.height
*TileSize
); });
218 shadAmbient
.exec((Shader shad
) { shad
["mapPixSize"] = SVec2F(map
.width
*TileSize
, map
.height
*TileSize
); });
220 glActiveTexture(GL_TEXTURE0
+0);
221 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, 0);
222 orthoCamera(vlWidth
, vlHeight
);
224 Actor
.resetStorage();
227 mapscripts
.runInit();
230 mapscripts
.runLoaded();
232 // save first snapshot
233 if (prevFrameActorsData
.length
== 0) prevFrameActorsData
= new ubyte[](Actor
.actorSize
*65536); // ~15-20 megabytes
234 prevFrameActorOfs
[] = uint.max
; // just for fun
235 Actor
.saveSnapshot(prevFrameActorsData
[], prevFrameActorOfs
.ptr
);
239 { import core
.memory
: GC
; GC
.collect(); }
243 // ////////////////////////////////////////////////////////////////////////// //
245 __gshared
uint mapTilesChanged
= 0;
248 //WARNING! this can be called only from DACS, so we don't have to sync it!
249 public void mapDirty (uint layermask
) { mapTilesChanged |
= layermask
; }
252 void rebuildMapMegaTextures () {
254 //mapTilesChanged = false;
255 //map.clearMegaTextures();
256 map
.oglBuildMega(mapTilesChanged
);
261 // ////////////////////////////////////////////////////////////////////////// //
264 enum Phase
{ FadeIn
, Stay
, FadeOut
}
273 private import core
.sync
.mutex
: Mutex
;
275 __gshared Message
[128] messages
;
276 __gshared
uint messagesUsed
= 0;
278 //__gshared Mutex messageLock;
279 //shared static this () { messageLock = new Mutex(); }
282 void addMessage (const(char)[] msgtext
, int pauseMsecs
=3000, bool noreplace
=false) {
283 //messageLock.lock();
284 //scope(exit) messageLock.unlock();
285 if (msgtext
.length
== 0) return;
287 if (pauseMsecs
<= 50) return;
288 if (messagesUsed
== messages
.length
) {
289 // remove top message
290 foreach (immutable cidx
; 1..messagesUsed
) messages
.ptr
[cidx
-1] = messages
.ptr
[cidx
];
291 messages
.ptr
[0].alpha
= 255;
295 if (!noreplace
&& messagesUsed
== 1) {
296 switch (messages
.ptr
[0].phase
) {
297 case Message
.Phase
.FadeIn
:
298 messages
.ptr
[0].phase
= Message
.Phase
.FadeOut
;
300 case Message
.Phase
.Stay
:
301 messages
.ptr
[0].phase
= Message
.Phase
.FadeOut
;
302 messages
.ptr
[0].alpha
= 255;
307 auto msg
= messages
.ptr
+messagesUsed
;
309 msg
.phase
= Message
.Phase
.FadeIn
;
311 msg
.pauseMsecs
= pauseMsecs
;
313 if (msgtext
.length
> msg
.text
.length
) {
314 msg
.text
= msgtext
[0..msg
.text
.length
];
315 msg
.textlen
= msg
.text
.length
;
317 msg
.text
[0..msgtext
.length
] = msgtext
[];
318 msg
.textlen
= msgtext
.length
;
323 void doMessages (MonoTime curtime
) {
324 //messageLock.lock();
325 //scope(exit) messageLock.unlock();
327 if (messagesUsed
== 0) return;
329 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
335 final switch (msg
.phase
) {
336 case Message
.Phase
.FadeIn
:
337 if ((msg
.alpha
+= 10) >= 255) {
338 msg
.phase
= Message
.Phase
.Stay
;
339 msg
.removeTime
= curtime
+dur
!"msecs"(msg
.pauseMsecs
);
340 goto case; // to stay
342 glColor4f(1.0f, 1.0f, 1.0f, msg
.alpha
/255.0f);
344 case Message
.Phase
.Stay
:
345 if (msg
.removeTime
<= curtime
) {
347 msg
.phase
= Message
.Phase
.FadeOut
;
348 goto case; // to fade
350 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
352 case Message
.Phase
.FadeOut
:
353 if ((msg
.alpha
-= 10) <= 0) {
354 if (--messagesUsed
== 0) return;
355 // remove this message
356 foreach (immutable cidx
; 1..messagesUsed
+1) messages
.ptr
[cidx
-1] = messages
.ptr
[cidx
];
359 glColor4f(1.0f, 1.0f, 1.0f, msg
.alpha
/255.0f);
363 smDrawText(10, 10, msg
.text
[0..msg
.textlen
]);
367 // ////////////////////////////////////////////////////////////////////////// //
368 //mixin(Actor.FieldPropMixin!("0drawlistpos", uint));
370 mixin(Actor
.FieldGetMixin
!("classtype", StrId
)); // fget_classtype
371 mixin(Actor
.FieldGetMixin
!("classname", StrId
)); // fget_classname
372 mixin(Actor
.FieldGetMixin
!("x", int));
373 mixin(Actor
.FieldGetMixin
!("y", int));
374 mixin(Actor
.FieldGetMixin
!("radius", int));
375 mixin(Actor
.FieldGetMixin
!("height", int));
376 mixin(Actor
.FieldGetMixin
!("flags", uint));
377 mixin(Actor
.FieldGetMixin
!("zAnimstate", StrId
));
378 mixin(Actor
.FieldGetMixin
!("zAnimidx", int));
379 mixin(Actor
.FieldGetMixin
!("dir", uint));
380 mixin(Actor
.FieldGetMixin
!("attLightXOfs", int));
381 mixin(Actor
.FieldGetMixin
!("attLightYOfs", int));
382 mixin(Actor
.FieldGetMixin
!("attLightRGBX", uint));
384 //mixin(Actor.FieldGetPtrMixin!("classtype", StrId)); // fget_classtype
385 //mixin(Actor.FieldGetPtrMixin!("classname", StrId)); // fget_classname
386 mixin(Actor
.FieldGetPtrMixin
!("x", int));
387 mixin(Actor
.FieldGetPtrMixin
!("y", int));
388 //mixin(Actor.FieldGetPtrMixin!("flags", uint));
389 //mixin(Actor.FieldGetPtrMixin!("zAnimstate", StrId));
390 //mixin(Actor.FieldGetPtrMixin!("zAnimidx", int));
391 //mixin(Actor.FieldGetPtrMixin!("dir", uint));
392 //mixin(Actor.FieldGetPtrMixin!("attLightXOfs", int));
393 //mixin(Actor.FieldGetPtrMixin!("attLightYOfs", int));
394 //mixin(Actor.FieldGetPtrMixin!("attLightRGBX", uint));
397 // ////////////////////////////////////////////////////////////////////////// //
398 __gshared
int vportX0
, vportY0
, vportX1
, vportY1
;
401 // ////////////////////////////////////////////////////////////////////////// //
402 void renderLightAmbient() (int lightX
, int lightY
, int lightW
, int lightH
, in auto ref SVec4F lcol
) {
403 //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));
404 if (lightW
< 1 || lightH
< 1) return;
405 int lightX1
= lightX
+lightW
-1;
406 int lightY1
= lightY
+lightH
-1;
407 // clip light to viewport
408 if (lightX
< vportX0
) lightX
= vportX0
;
409 if (lightY
< vportY0
) lightY
= vportY0
;
410 if (lightX1
> vportX1
) lightX1
= vportX1
;
411 if (lightY1
> vportY1
) lightY1
= vportY1
;
412 // is this light visible?
413 //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));
414 if (lightX1
< lightX || lightY1
< lightY || lightX
> vportX1 || lightY
> vportY1 || lightX1
< vportX0 || lightY1
< vportY0
) return;
415 //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));
419 glBlendFunc(GL_SRC_ALPHA
, GL_ONE
);
420 //glDisable(GL_BLEND);
421 orthoCamera(map
.width
*TileSize
, map
.height
*TileSize
);
422 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
423 shadAmbient
.exec((Shader shad
) {
424 shad
["lightColor"] = SVec4F(lcol
.x
, lcol
.y
, lcol
.z
, lcol
.w
);
425 //shad["lightPos"] = SVec2F(lightX, lightY);
426 glRectf(lightX
, lightY
, lightX1
, lightY1
);
432 // ////////////////////////////////////////////////////////////////////////// //
433 void renderLight() (int lightX
, int lightY
, in auto ref SVec4F lcol
, int lightRadius
) {
434 if (lightRadius
< 2) return;
435 if (lightRadius
> MaxLightRadius
) lightRadius
= MaxLightRadius
;
436 int lightSize
= lightRadius
*2;
437 // is this light visible?
438 if (lightX
<= -lightRadius || lightY
<= -lightRadius || lightX
-lightRadius
>= map
.width
*TileSize || lightY
-lightRadius
>= map
.height
*TileSize
) return;
440 // out of viewport -- do nothing
441 if (lightX
+lightRadius
< vportX0 || lightY
+lightRadius
< vportY0
) return;
442 if (lightX
-lightRadius
> vportX1 || lightY
-lightRadius
> vportY1
) return;
444 if (lightX
>= 0 && lightY
>= 0 && lightX
< map
.width
*TileSize
&& lightY
< map
.height
*TileSize
&&
445 map
.teximgs
[map
.LightMask
].imageData
.colors
.ptr
[lightY
*(map
.width
*TileSize
)+lightX
].a
> 190) return;
447 // common color for all the following
449 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
451 // build 1d distance map to fboShadowMapId
452 fboDistMap
.ptr
[lightRadius
].exec({
453 // no need to clear it, shader will take care of that
454 shadToPolar
.exec((Shader shad
) {
455 shad
["lightTexSize"] = SVec2F(lightSize
, lightSize
);
456 shad
["lightPos"] = SVec2F(lightX
, lightY
);
457 orthoCamera(lightSize
, 1);
458 // it doesn't matter what we will draw here, so just draw filled rect
459 glRectf(0, 0, lightSize
, 1);
463 // build light texture for blending
465 // no need to clear it, shader will take care of that
467 //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
468 //glClear(GL_COLOR_BUFFER_BIT);
469 shadBlur
.exec((Shader shad
) {
470 shad
["lightTexSize"] = SVec2F(lightSize
, MaxLightRadius
*2); // x: size of distmap; y: size of this texture
471 shad
["lightColor"] = SVec4F(lcol
.x
, lcol
.y
, lcol
.z
, lcol
.w
);
472 shad
["lightPos"] = SVec2F(lightX
-lightRadius
, lightY
-lightRadius
);
473 orthoCamera(fboOccluders
.tex
.width
, fboOccluders
.tex
.height
);
474 //drawAtXY(fboDistMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
475 glBindTexture(GL_TEXTURE_2D
, fboDistMap
.ptr
[lightRadius
].tex
.tid
);
477 glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0); // top-left
478 glTexCoord2f(1.0f, 0.0f); glVertex2i(lightSize
, 0); // top-right
479 glTexCoord2f(1.0f, 1.0f); glVertex2i(lightSize
, lightSize
); // bottom-right
480 glTexCoord2f(0.0f, 1.0f); glVertex2i(0, lightSize
); // bottom-left
485 // blend light texture
488 //glDisable(GL_BLEND);
489 glBlendFunc(GL_SRC_ALPHA
, GL_ONE
);
490 orthoCamera(fboLevelLight
.tex
.width
, fboLevelLight
.tex
.height
);
491 //drawAtXY(fboOccluders.tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
492 float occe
= 1.0f*lightSize
/(MaxLightRadius
*2);
493 float occs
= 1.0f-occe
;
494 int x0
= lightX
-lightRadius
+0;
495 int y1
= lightY
-lightRadius
+0;
496 int x1
= lightX
+lightRadius
-1+1;
497 int y0
= lightY
+lightRadius
-1+1;
498 glBindTexture(GL_TEXTURE_2D
, fboOccluders
.tex
.tid
);
501 glTexCoord2f(0.0f, 0.0f); glVertex2i(x0, y0); // top-left
502 glTexCoord2f(occe, 0.0f); glVertex2i(x1, y0); // top-right
503 glTexCoord2f(occe, occe); glVertex2i(x1, y1); // bottom-right
504 glTexCoord2f(0.0f, occe); glVertex2i(x0, y1); // bottom-left
506 glTexCoord2f(0.0f, occs
); glVertex2i(x0
, y0
); // top-left
507 glTexCoord2f(occe
, occs
); glVertex2i(x1
, y0
); // top-right
508 glTexCoord2f(occe
, 1.0f); glVertex2i(x1
, y1
); // bottom-right
509 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0
, y1
); // bottom-left
512 glBindTexture(GL_TEXTURE_2D, 0);
513 glRectf(x0, y0, x1, y1);
515 // and blend it again, with the shader that will touch only occluders
516 shadBlurOcc
.exec((Shader shad
) {
517 //shad["lightTexSize"] = SVec2F(lightSize, lightSize);
518 shad
["lightTexSize"] = SVec2F(lightSize
, fboOccluders
.tex
.height
);
519 shad
["lightColor"] = SVec4F(lcol
.x
, lcol
.y
, lcol
.z
, lcol
.w
);
520 shad
["lightPos"] = SVec2F(lightX
, lightY
);
521 //shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
522 glBindTexture(GL_TEXTURE_2D
, fboOccluders
.tex
.tid
);
524 glTexCoord2f(0.0f, occs
); glVertex2i(x0
, y0
); // top-left
525 glTexCoord2f(occe
, occs
); glVertex2i(x1
, y0
); // top-right
526 glTexCoord2f(occe
, 1.0f); glVertex2i(x1
, y1
); // bottom-right
527 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0
, y1
); // bottom-left
529 //drawAtXY(fboOccluders.tex.tid, lightX-lightRadius, lightY-lightRadius, lightSize, lightSize, mirrorY:true);
535 // ////////////////////////////////////////////////////////////////////////// //
536 __gshared
int testLightX
= vlWidth
/2, testLightY
= vlHeight
/2;
537 __gshared
bool testLightMoved
= false;
538 //__gshared int mapOfsX, mapOfsY;
539 //__gshared bool movement = false;
540 __gshared
float iLiquidTime
= 0.0;
541 //__gshared bool altMove = false;
544 void renderScene (MonoTime curtime
) {
545 //enum BackIntens = 0.05f;
546 enum BackIntens
= 0.0f;
549 float atob
= (curtime
> lastthink ?
cast(float)((curtime
-lastthink
).total
!"msecs")/cast(float)((nextthink
-lastthink
).total
!"msecs") : 1.0f);
550 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
553 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
554 int curfp = cast(int)((curtime-lastthink).total!"msecs");
555 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
559 int mofsx
, mofsy
; // camera offset, will be set in background layer builder
561 if (mapTilesChanged
!= 0) rebuildMapMegaTextures();
565 // build background layer
567 //glDisable(GL_BLEND);
568 //glClearDepth(1.0f);
569 //glDepthFunc(GL_LESS);
570 //glDepthFunc(GL_NEVER);
571 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
572 glClear(GL_COLOR_BUFFER_BIT
/*|GL_DEPTH_BUFFER_BIT*/);
573 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
574 orthoCamera(map
.width
*TileSize
, map
.height
*TileSize
);
577 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
580 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
581 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
582 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
583 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
585 drawAtXY(map
.skytexgl
.tid
, 0, 0, map
.MapSize
*TileSize
, map
.MapSize
*TileSize
);
587 drawAtXY(map
.texgl
.ptr
[map
.Back
], 0, 0);
588 // draw distorted liquid areas
589 shadLiquidDistort
.exec((Shader shad
) {
590 shad
["iDistortTime"] = iLiquidTime
;
591 drawAtXY(map
.texgl
.ptr
[map
.AllLiquids
], 0, 0);
593 // monsters, items; we'll do linear interpolation here
594 glColor3f(1.0f, 1.0f, 1.0f);
595 //glEnable(GL_DEPTH_TEST);
596 attachedLightCount
= 0;
598 // who cares about memory?!
599 // draw order: players, items, monsters, other
600 static struct DrawInfo
{
604 @disable this (this); // no copies
606 enum { Players
, Items
, Monsters
, Other
}
607 __gshared DrawInfo
[65536][4] drawlists
;
608 __gshared
uint[4] dlpos
;
613 Actor
.forEach((ActorId me
) {
614 //me.fprop_0drawlistpos = 0;
615 if (auto adef
= findActorDef(me
)) {
617 switch (adef
.classtype
.get
) {
618 case "monster": dlnum
= (adef
.classname
.get
!= "Player" ? Monsters
: Players
); break;
619 case "item": dlnum
= Items
; break;
620 default: dlnum
= Other
; break;
622 int actorX
, actorY
; // current actor position
624 auto ofs
= prevFrameActorOfs
.ptr
[me
.id
&0xffff];
625 if (frameInterpolation
&& ofs
< uint.max
-1 && Actor
.isSameSavedActor(me
.id
, prevFrameActorsData
.ptr
, ofs
)) {
626 import core
.stdc
.math
: roundf
;
627 auto xptr
= prevFrameActorsData
.ptr
+ofs
;
628 int ox
= xptr
.fgetp_x
;
630 int oy
= xptr
.fgetp_y
;
632 actorX
= cast(int)(ox
+roundf((nx
-ox
)*atob
));
633 actorY
= cast(int)(oy
+roundf((ny
-oy
)*atob
));
634 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
640 if (me
.id
== cameraChick
.id
) {
641 camchickdi
.adef
= adef
;
643 camchickdi
.actorX
= actorX
;
644 camchickdi
.actorY
= actorY
;
647 if ((me
.fget_flags
&AF_NODRAW
) == 0) {
648 //auto dl = &drawlists[dlnum][dlpos.ptr[dlnum]];
649 //me.fprop_0drawlistpos = (dlpos.ptr[dlnum]&0xffff)|((dlnum&0xff)<<16);
650 auto dl = drawlists
.ptr
[dlnum
].ptr
+dlpos
.ptr
[dlnum
];
657 // process attached lights
658 if ((me
.fget_flags
&AF_NOLIGHT
) == 0) {
659 uint alr
= me
.fget_attLightRGBX
;
660 bool isambient
= (me
.fget_classtype
.get
== "light" && me
.fget_classname
.get
== "Ambient");
661 if ((alr
&0xff) >= 4 ||
(isambient
&& me
.fget_radius
>= 1 && me
.fget_height
>= 1)) {
662 //if (isambient) conwriteln("isambient: ", isambient, "; x=", me.fget_x, "; y=", me.fget_y, "; w=", me.fget_radius, "; h=", me.fget_height);
664 auto li
= attachedLights
.ptr
+attachedLightCount
;
665 ++attachedLightCount
;
666 li
.type
= (!isambient ? AttachedLightInfo
.Type
.Point
: AttachedLightInfo
.Type
.Ambient
);
667 li
.x
= actorX
+me
.fget_attLightXOfs
;
668 li
.y
= actorY
+me
.fget_attLightYOfs
;
669 li
.r
= ((alr
>>24)&0xff)/255.0f; // red or intensity
670 if ((alr
&0x00_ff_ff
_00U) == 0x00_00_01_00U) {
673 li
.g
= ((alr
>>16)&0xff)/255.0f;
674 li
.b
= ((alr
>>8)&0xff)/255.0f;
675 li
.uncolored
= false;
677 li
.radius
= (alr
&0xff);
678 if (li
.radius
> MaxLightRadius
) li
.radius
= MaxLightRadius
;
680 li
.w
= me
.fget_radius
;
681 li
.h
= me
.fget_height
;
686 conwriteln("not found actor ", me
.id
, " (", me
.classtype
!string
, ":", me
.classname
!string
, ")");
691 foreach_reverse (uint dlnum
; 0..drawlists
.length
) {
692 auto dl = drawlists
.ptr
[dlnum
].ptr
;
693 if (dlnum
== Players
) dl += dlpos
.ptr
[dlnum
]-1;
694 foreach (uint idx
; 0..dlpos
.ptr
[dlnum
]) {
696 if (auto isp
= dl.adef
.animSpr(me
.fget_zAnimstate
, me
.fget_dir
, me
.fget_zAnimidx
)) {
697 //drawAtXY(isp.tex, dl.actorX-isp.vga.sx, dl.actorY-isp.vga.sy);
698 isp
.drawAtXY(dl.actorX
, dl.actorY
);
700 if (dlnum
!= Players
) ++dl; else --dl;
705 if (/*altMove || movement ||*/ scale
== 1 ||
!cameraChick
.valid
) {
710 vportX1
= map
.width
*TileSize
;
711 vportY1
= map
.height
*TileSize
;
713 int tiltHeight
= /*getMapViewHeight()*/(vlHeight
/scale
)/4;
714 int vy
= cameraChick
.looky
!int;
715 if (vy
< -tiltHeight
) vy
= -tiltHeight
; else if (vy
> tiltHeight
) vy
= tiltHeight
;
716 int swdt
= vlWidth
/scale
;
717 int shgt
= vlHeight
/scale
;
718 int x
= camchickdi
.actorX
-swdt
/2;
719 int y
= (camchickdi
.actorY
+vy
)-shgt
/2;
720 if (x
< 0) x
= 0; else if (x
>= map
.width
*TileSize
-swdt
) x
= map
.width
*TileSize
-swdt
-1;
721 if (y
< 0) y
= 0; else if (y
>= map
.height
*TileSize
-shgt
) y
= map
.height
*TileSize
-shgt
-1;
724 vportX0
= mofsx
/scale
;
725 vportY0
= mofsy
/scale
;
726 vportX1
= vportX0
+vlWidth
/scale
;
727 vportY1
= vportY0
+vlHeight
/scale
;
730 //glDisable(GL_DEPTH_TEST);
732 drawAtXY(texParts
, 0, 0);
733 // do liquid coloring
734 drawAtXY(map
.texgl
.ptr
[map
.LiquidMask
], 0, 0);
735 // foreground -- hide secrets, draw lifts and such
736 drawAtXY(map
.texgl
.ptr
[map
.Front
], 0, 0);
742 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
744 // make smaller occluder texture, so we can trace faster
745 //assert(fboLMaskSmall.tex.width == map.width);
746 //assert(fboLMaskSmall.tex.height == map.height);
748 orthoCamera(map
.width
, map
.height
);
749 drawAtXY(map
.texgl
.ptr
[map
.LightMask
].tid
, 0, 0, map
.width
, map
.height
, mirrorY
:true);
754 glClearColor(BackIntens
, BackIntens
, BackIntens
, 1.0f);
755 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
756 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
757 glClear(GL_COLOR_BUFFER_BIT
);
760 // texture 1 is background
761 glActiveTexture(GL_TEXTURE0
+1);
762 glBindTexture(GL_TEXTURE_2D
, fboOrigBack
.tex
.tid
);
763 // texture 2 is occluders
764 glActiveTexture(GL_TEXTURE0
+2);
765 glBindTexture(GL_TEXTURE_2D
, map
.texgl
.ptr
[map
.LightMask
].tid
);
766 // texture 3 is small occluder map
767 glActiveTexture(GL_TEXTURE0
+3);
768 glBindTexture(GL_TEXTURE_2D
, fboLMaskSmall
.tex
.tid
);
769 // done texture assign
770 glActiveTexture(GL_TEXTURE0
+0);
775 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
776 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
777 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
778 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
779 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
780 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
781 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
782 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
783 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
784 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
786 renderLight(24*TileSize+4, (24+18)*TileSize-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
788 renderLight(280, 330, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
792 foreach (ref li
; attachedLights
[0..attachedLightCount
]) {
793 if (li
.type
== AttachedLightInfo
.Type
.Ambient
) {
794 //conwriteln("ambient: x=", li.x, "; y=", li.y, "; w=", li.w, "; h=", li.h);
797 renderLightAmbient(li
.x
, li
.y
, li
.w
, li
.h
, SVec4F(0.0f, 0.0f, 0.0f, li
.r
));
799 renderLightAmbient(li
.x
, li
.y
, li
.w
, li
.h
, SVec4F(li
.r
, li
.g
, li
.b
, 1.0f));
801 } else if (li
.type
== AttachedLightInfo
.Type
.Point
) {
804 renderLight(li
.x
, li
.y
, SVec4F(0.0f, 0.0f, 0.0f, li
.r
), li
.radius
);
806 renderLight(li
.x
, li
.y
, SVec4F(li
.r
, li
.g
, li
.b
, 1.0f), li
.radius
);
812 if (testLightMoved
) {
813 testLightX
= testLightX
/scale
+mofsx
/scale
;
814 testLightY
= testLightY
/scale
+mofsy
/scale
;
815 testLightMoved
= false;
817 foreach (immutable _
; 0..1) {
818 renderLight(testLightX
, testLightY
, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
822 glActiveTexture(GL_TEXTURE0
+1);
823 glBindTexture(GL_TEXTURE_2D
, 0);
824 glActiveTexture(GL_TEXTURE0
+2);
825 glBindTexture(GL_TEXTURE_2D
, 0);
826 glActiveTexture(GL_TEXTURE0
+3);
827 glBindTexture(GL_TEXTURE_2D
, 0);
828 glActiveTexture(GL_TEXTURE0
+0);
833 shadScanlines.exec((Shader shad) {
834 shad["scanlines"] = scanlines;
835 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
836 glClear(GL_COLOR_BUFFER_BIT);
837 orthoCamera(vlWidth, vlHeight);
838 //orthoCamera(map.width*TileSize*scale, map.height*TileSize*scale);
839 //glMatrixMode(GL_MODELVIEW);
841 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
842 // somehow, FBO objects are mirrored; wtf?!
843 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
850 smDrawText(map.width*TileSize/2, map.height*TileSize/2, "Testing...");
855 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT
, 0);
860 //auto img = smfont.ptr[0x39];
863 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
864 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
871 { // http://stackoverflow.com/questions/7207422/setting-up-opengl-multiple-render-targets
872 GLenum[1] buffers = [ GL_BACK_LEFT, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
873 //GLenum[1] buffers = [ GL_NONE, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
874 glDrawBuffers(1, buffers.ptr);
879 orthoCamera(vlWidth
, vlHeight
);
880 auto tex
= (doLighting ? fboLevelLight
.tex
.tid
: fboOrigBack
.tex
.tid
);
881 drawAtXY(tex
, -mofsx
, -mofsy
, map
.width
*TileSize
*scale
, map
.height
*TileSize
*scale
, mirrorY
:true);
883 //drawAtXY(map.texgl.ptr[map.LightMask].tid, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
884 //drawAtXY(fboLMaskSmall.tex.tid, 0, 0, map.width*TileSize, map.height*TileSize);
890 // ////////////////////////////////////////////////////////////////////////// //
891 // returns time slept
892 int sleepAtMaxMsecs (int msecs
) {
894 import core
.sys
.posix
.signal
: timespec
;
895 import core
.sys
.posix
.time
: nanosleep
;
896 timespec ts
= void, tpassed
= void;
898 ts
.tv_nsec
= msecs
*1000*1000+(500*1000); // milli to nano
899 nanosleep(&ts
, &tpassed
);
900 return (ts
.tv_nsec
-tpassed
.tv_nsec
)/(1000*1000);
907 // ////////////////////////////////////////////////////////////////////////// //
909 shared int diedie
= 0;
911 enum D2DFrameTime
= 55; // milliseconds
912 enum MinFrameTime
= 1000/60; // ~60 FPS
914 public void renderThread (Tid starterTid
) {
915 enum BoolOptVarMsgMixin(string varname
) =
916 "if (msg.toggle) msg.value = !"~varname
~";\n"~
917 "if ("~varname
~" != msg.value) "~varname
~" = msg.value; else msg.showMessage = false;\n";
919 send(starterTid
, 42);
921 MonoTime curtime
= MonoTime
.currTime
;
923 lastthink
= curtime
; // for interpolator
924 nextthink
= curtime
+dur
!"msecs"(D2DFrameTime
);
925 MonoTime nextvframe
= curtime
;
927 enum MaxFPSFrames
= 16;
928 float frtimes
= 0.0f;
931 int hushFrames
= 6; // ignore first `hushFrames` frames overtime
932 MonoTime prevFrameStartTime
= curtime
;
934 bool vframeWasLost
= false;
936 void resetFrameTimers () {
937 MonoTime curtime
= MonoTime
.currTime
;
938 lastthink
= curtime
; // for interpolator
939 nextthink
= curtime
+dur
!"msecs"(D2DFrameTime
);
940 nextvframe
= curtime
;
943 void loadNewLevel (string name
) {
946 conwriteln("ERROR: can't load new levels yet");
950 if (name
.length
== 0) {
951 conwriteln("ERROR: can't load empty level!");
953 conwriteln("loading map '", name
, "'");
958 void receiveMessages () {
960 import core
.time
: Duration
;
961 //conwriteln("rendering thread: waiting for messages...");
962 auto got
= receiveTimeout(
963 Duration
.zero
, // don't wait
965 addMessage(msg
.text
[0..msg
.textlen
], msg
.pauseMsecs
, msg
.noreplace
);
967 (TMsgLoadLevel msg
) {
968 nextmapname
= null; // clear "exit" flag
969 loadNewLevel(msg
.mapfile
[0..msg
.textlen
].idup
);
971 (TMsgSkipLevel msg
) {
972 string mn
= genNextMapName(0);
974 nextmapname
= null; // clear "exit" flag
978 (TMsgIntOption msg
) {
979 final switch (msg
.type
) with (TMsgIntOption
.Type
) {
981 if (msg
.value
>= 1 && msg
.value
<= 2) scale
= msg
.value
;
985 (TMsgBoolOption msg
) {
986 final switch (msg
.type
) with (TMsgBoolOption
.Type
) {
987 case Interpolation
: mixin(BoolOptVarMsgMixin
!"frameInterpolation"); break;
988 case Lighting
: mixin(BoolOptVarMsgMixin
!"doLighting"); break;
989 case Pause
: mixin(BoolOptVarMsgMixin
!"gamePaused"); break;
990 case CheatNoDoors
: mixin(BoolOptVarMsgMixin
!"cheatNoDoors"); break;
991 case CheatNoWallClip
: mixin(BoolOptVarMsgMixin
!"cheatNoWallClip"); break;
993 if (msg
.showMessage
) {
996 void putStr(T
) (T s
) if (is(T
: const(char)[])) {
997 foreach (char ch
; s
) {
998 if (msgpos
>= mbuf
.length
) break;
999 mbuf
.ptr
[msgpos
++] = ch
;
1002 putStr("Option \"");
1003 { import std
.conv
: to
; putStr(to
!string(msg
.type
)); }
1005 if (msg
.value
) putStr("N"); else putStr("FF");
1006 addMessage(mbuf
[0..msgpos
]);
1009 (TMsgTestLightMove msg
) {
1012 testLightMoved
= true;
1015 conwriteln("WARNING: unknown thread message received and ignored");
1020 //conwriteln("rendering thread: no more messages");
1024 if (nextmapname
.length
) {
1025 string mn
= nextmapname
;
1026 nextmapname
= null; // clear "exit" flag
1031 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1032 bool doThinkFrame () {
1033 if (curtime
>= nextthink
) {
1034 lastthink
= curtime
;
1035 while (nextthink
<= curtime
) nextthink
+= dur
!"msecs"(D2DFrameTime
);
1036 if (levelLoaded
&& !gamePaused
) {
1037 // save snapshot and other data for interpolator
1038 Actor
.saveSnapshot(prevFrameActorsData
[], prevFrameActorOfs
.ptr
);
1044 auto tm
= MonoTime
.currTime
;
1045 int thinkTime
= cast(int)((tm
-curtime
).total
!"msecs");
1046 if (thinkTime
> 9) { import core
.stdc
.stdio
; printf("spent on thinking: %d msecs\n", thinkTime
); }
1054 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1056 version(dont_use_vsync
) {
1058 enum doCheckTime
= true;
1061 __gshared
bool prevLost
= false;
1062 bool doCheckTime
= vframeWasLost
;
1063 if (vframeWasLost
) {
1065 { import core
.stdc
.stdio
; printf("frame was lost!\n"); }
1073 if (curtime
< nextvframe
) return false;
1074 version(dont_use_vsync
) {
1075 if (curtime
> nextvframe
) {
1076 auto overtime
= cast(int)((curtime
-nextvframe
).total
!"msecs");
1077 if (overtime
> 2500) {
1081 { import core
.stdc
.stdio
; printf(" spent whole %d msecs\n", overtime
); }
1087 while (nextvframe
<= curtime
) nextvframe
+= dur
!"msecs"(MinFrameTime
);
1091 scope(exit
) sdwindow
.mtUnlock();
1092 ctset
= sdwindow
.setAsCurrentOpenGlContextNT
;
1094 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
1097 iLiquidTime
= cast(float)((curtime
-MonoTime
.zero
).total
!"msecs"%10000000)/18.0f*0.04f;
1098 receiveMessages(); // here, 'cause we need active OpenGL context for some messages
1100 renderScene(curtime
);
1102 //renderLoading(curtime);
1105 scope(exit
) sdwindow
.mtUnlock();
1106 sdwindow
.swapOpenGlBuffers();
1108 sdwindow
.releaseCurrentOpenGlContext();
1109 vframeWasLost
= false;
1111 vframeWasLost
= true;
1112 { import core
.stdc
.stdio
; printf("xframe was lost!\n"); }
1114 curtime
= MonoTime
.currTime
;
1119 if (sdwindow
.closed
) break;
1120 if (atomicLoad(diedie
) > 0) break;
1122 curtime
= MonoTime
.currTime
;
1123 auto fstime
= curtime
;
1125 doThinkFrame(); // this will fix curtime if necessary
1127 if (!vframeWasLost
) {
1129 auto frameTime
= cast(float)(curtime
-prevFrameStartTime
).total
!"msecs"/1000.0f;
1130 prevFrameStartTime
= curtime
;
1131 frtimes
+= frameTime
;
1132 if (++framenum
>= MaxFPSFrames || frtimes
>= 3.0f) {
1133 import std
.string
: format
;
1134 int newFPS
= cast(int)(cast(float)MaxFPSFrames
/frtimes
+0.5);
1135 if (newFPS
!= prevFPS
) {
1136 sdwindow
.title
= "%s / FPS:%s".format("D2D", newFPS
);
1145 curtime
= MonoTime
.currTime
;
1147 // now sleep until next "video" or "think" frame
1148 if (nextthink
> curtime
&& nextvframe
> curtime
) {
1150 immutable nextVideoFrameSleep
= cast(int)((nextvframe
-curtime
).total
!"msecs");
1151 immutable nextThinkFrameSleep
= cast(int)((nextthink
-curtime
).total
!"msecs");
1152 immutable sleepTime
= (nextVideoFrameSleep
< nextThinkFrameSleep ? nextVideoFrameSleep
: nextThinkFrameSleep
);
1153 sleepAtMaxMsecs(sleepTime
);
1154 //curtime = MonoTime.currTime;
1157 } catch (Throwable e
) {
1158 // here, we are dead and fucked (the exact order doesn't matter)
1159 import core
.stdc
.stdlib
: abort
;
1160 import core
.stdc
.stdio
: fprintf
, stderr
;
1161 import core
.memory
: GC
;
1162 GC
.disable(); // yeah
1163 thread_suspendAll(); // stop right here, you criminal scum!
1164 auto s
= e
.toString();
1165 fprintf(stderr
, "\n=== FATAL ===\n%.*s\n", cast(uint)s
.length
, s
.ptr
);
1166 abort(); // die, you bitch!
1168 import core.stdc.stdio;
1169 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
1170 import std.stdio : stderr;
1173 if (sdwindow.closed) break;
1174 if (atomicLoad(diedie) > 0) break;
1175 sleepAtMaxMsecs(100);
1179 atomicStore(diedie
, 2);
1183 // ////////////////////////////////////////////////////////////////////////// //
1184 __gshared Tid renderTid
;
1185 shared bool renderThreadStarted
= false;
1188 public void startRenderThread () {
1189 if (!cas(&renderThreadStarted
, false, true)) {
1190 assert(0, "render thread already started!");
1192 renderTid
= spawn(&renderThread
, thisTid
);
1193 setMaxMailboxSize(renderTid
, 1024, OnCrowding
.throwException
); //FIXME
1194 // wait for "i'm ready" signal
1197 if (ok
!= 42) assert(0, "wtf?!");
1200 conwriteln("rendering thread started");
1204 // ////////////////////////////////////////////////////////////////////////// //
1205 public void closeWindow () {
1206 if (atomicLoad(diedie
) != 2) {
1207 atomicStore(diedie
, 1);
1208 while (atomicLoad(diedie
) != 2) {}
1210 if (!sdwindow
.closed
) {
1217 // ////////////////////////////////////////////////////////////////////////// //
1221 // ////////////////////////////////////////////////////////////////////////// //
1222 struct TMsgTestLightMove
{
1226 public void postTestLightMove (int x
, int y
) {
1227 if (!atomicLoad(renderThreadStarted
)) return;
1228 auto msg
= TMsgTestLightMove(x
, y
);
1229 send(renderTid
, msg
);
1233 // ////////////////////////////////////////////////////////////////////////// //
1234 struct TMsgMessage
{
1241 public void postAddMessage (const(char)[] msgtext
, int pauseMsecs
=3000, bool noreplace
=false) {
1242 if (!atomicLoad(renderThreadStarted
)) return;
1243 if (msgtext
.length
> TMsgMessage
.text
.length
) msgtext
= msgtext
[0..TMsgMessage
.text
.length
];
1245 msg
.textlen
= cast(uint)msgtext
.length
;
1246 if (msg
.textlen
) msg
.text
[0..msg
.textlen
] = msgtext
[0..msg
.textlen
];
1247 msg
.pauseMsecs
= pauseMsecs
;
1248 msg
.noreplace
= noreplace
;
1249 send(renderTid
, msg
);
1253 // ////////////////////////////////////////////////////////////////////////// //
1254 struct TMsgLoadLevel
{
1259 public void postLoadLevel (const(char)[] mapfile
) {
1260 if (!atomicLoad(renderThreadStarted
)) return;
1261 if (mapfile
.length
> TMsgLoadLevel
.mapfile
.length
) mapfile
= mapfile
[0..TMsgLoadLevel
.mapfile
.length
];
1263 msg
.textlen
= cast(uint)mapfile
.length
;
1264 if (msg
.textlen
) msg
.mapfile
[0..msg
.textlen
] = mapfile
[0..msg
.textlen
];
1265 send(renderTid
, msg
);
1269 // ////////////////////////////////////////////////////////////////////////// //
1270 struct TMsgSkipLevel
{
1273 public void postSkipLevel () {
1274 if (!atomicLoad(renderThreadStarted
)) return;
1276 send(renderTid
, msg
);
1280 // ////////////////////////////////////////////////////////////////////////// //
1281 struct TMsgIntOption
{
1291 struct TMsgBoolOption
{
1306 bool strCaseEqu (const(char)[] s0
, const(char)[] s1
) {
1307 if (s0
.length
!= s1
.length
) return false;
1308 foreach (immutable idx
, char ch
; s0
) {
1309 if (ch
>= 'A' && ch
<= 'Z') ch
+= 32;
1310 char c1
= s1
.ptr
[idx
];
1311 if (c1
>= 'A' && c1
<= 'Z') c1
+= 32;
1312 if (ch
!= c1
) return false;
1318 public bool postToggleOption (const(char)[] name
, bool showMessage
=false) { pragma(inline
, true); return postSetOption(name
, true, showMessage
:showMessage
, toggle
:true); }
1320 public bool postSetOption(T
) (const(char)[] name
, T value
, bool showMessage
=false, bool toggle
=false) if ((is(T
== int) ||
is(T
== bool))) {
1321 if (!atomicLoad(renderThreadStarted
)) return false;
1322 if (name
.length
== 0 || name
.length
> 127) return false;
1323 static if (is(T
== int)) {
1325 foreach (string oname
; __traits(allMembers
, TMsgIntOption
.Type
)) {
1326 if (strCaseEqu(oname
, name
)) {
1327 msg
.type
= __traits(getMember
, TMsgIntOption
.Type
, oname
);
1329 msg
.showMessage
= showMessage
;
1330 send(renderTid
, msg
);
1334 } else static if (is(T
== bool)) {
1336 foreach (string oname
; __traits(allMembers
, TMsgBoolOption
.Type
)) {
1337 if (strCaseEqu(oname
, name
)) {
1338 msg
.type
= __traits(getMember
, TMsgBoolOption
.Type
, oname
);
1340 msg
.toggle
= toggle
;
1341 msg
.showMessage
= showMessage
;
1342 send(renderTid
, msg
);
1347 static assert(0, "invalid option type '"~T
.stringof
~"'");