added ambient light actors
[dd2d.git] / render.d
blob768ec470aeeb35daae1269ea3ab3976bbd613d0b
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;
23 private:
24 import core.atomic;
25 import core.thread;
26 import core.time;
28 import std.concurrency;
30 import iv.glbinds;
31 import glutils;
32 import console;
33 import wadarc;
35 import iv.stream;
37 import d2dmap;
38 import d2dadefs;
39 //import d2dactors;
40 import d2dgfx;
41 import d2dfont;
42 import dacs;
44 import d2dunigrid;
46 // `map` is there
47 import dengapi;
49 import d2dparts;
52 // ////////////////////////////////////////////////////////////////////////// //
53 import arsd.color;
54 import arsd.png;
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;
80 __gshared bool gamePaused = false;
83 // ////////////////////////////////////////////////////////////////////////// //
84 // interpolation
85 __gshared ubyte[] prevFrameActorsData;
86 __gshared uint[65536] prevFrameActorOfs; // uint.max-1: dead; uint.max: last
87 __gshared MonoTime lastthink = MonoTime.zero; // for interpolator
88 __gshared MonoTime nextthink = MonoTime.zero;
89 __gshared bool frameInterpolation = true;
92 // ////////////////////////////////////////////////////////////////////////// //
93 // attached lights
94 struct AttachedLightInfo {
95 enum Type {
96 Point,
97 Ambient,
99 Type type;
100 int x, y;
101 int w, h; // for ambient lights
102 float r, g, b;
103 bool uncolored;
104 int radius;
107 __gshared AttachedLightInfo[65536] attachedLights;
108 __gshared uint attachedLightCount = 0;
111 // ////////////////////////////////////////////////////////////////////////// //
112 enum MaxLightRadius = 255;
115 // ////////////////////////////////////////////////////////////////////////// //
116 // for light
117 __gshared FBO[MaxLightRadius+1] fboOccluders, fboDistMap;
118 __gshared Shader shadToPolar, shadBlur, shadBlurOcc, shadAmbient;
120 __gshared FBO fboLevel, fboLevelLight, fboOrigBack;
121 __gshared Shader shadScanlines;
122 __gshared Shader shadLiquidDistort;
125 // ////////////////////////////////////////////////////////////////////////// //
126 // call once!
127 public void initOpenGL () {
128 gloStackClear();
130 glEnable(GL_TEXTURE_2D);
131 glDisable(GL_LIGHTING);
132 glDisable(GL_DITHER);
133 glDisable(GL_BLEND);
134 glDisable(GL_DEPTH_TEST);
136 // create shaders
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;
144 // lights
145 version(old_light) {
146 shadToPolar = new Shader("light_topolar", loadTextFile("shaders/srlight_topolar.frag"));
147 } else {
148 shadToPolar = new Shader("light_topolar", loadTextFile("shaders/srlight_topolar_new.frag"));
150 shadToPolar.exec((Shader shad) {
151 shad["texOcc"] = 0;
152 shad["texOccFull"] = 2;
155 shadBlur = new Shader("light_blur", loadTextFile("shaders/srlight_blur.frag"));
156 shadBlur.exec((Shader shad) {
157 shad["texDist"] = 0;
158 shad["texBg"] = 1;
159 shad["texOcc"] = 2;
162 shadBlurOcc = new Shader("light_blur_occ", loadTextFile("shaders/srlight_blur_occ.frag"));
163 shadBlurOcc.exec((Shader shad) {
164 shad["texLMap"] = 0;
165 shad["texBg"] = 1;
166 shad["texOcc"] = 2;
169 shadAmbient = new Shader("light_ambient", loadTextFile("shaders/srlight_ambient.frag"));
170 shadAmbient.exec((Shader shad) {
171 shad["texBg"] = 1;
172 shad["texOcc"] = 2;
175 //TODO: this sux!
176 foreach (int sz; 2..MaxLightRadius+1) {
177 fboOccluders[sz] = new FBO(sz*2, sz*2, Texture.Option.Clamp, Texture.Option.Linear); // create occluders FBO
178 fboDistMap[sz] = new FBO(sz*2, 1, Texture.Option.Clamp); // create 1d distance map FBO
181 // setup matrices
182 glMatrixMode(GL_MODELVIEW);
183 glLoadIdentity();
185 loadSmFont();
186 loadAllMonsterGraphics();
190 // ////////////////////////////////////////////////////////////////////////// //
191 // should be called when OpenGL is initialized
192 void loadMap (string mapname) {
193 mapscripts.runUnloading(); // "map unloading" script
194 clearMapScripts();
196 if (map !is null) map.clear();
197 map = new LevelMap(mapname);
198 curmapname = mapname;
200 ugInit(map.width*8, map.height*8);
202 map.oglBuildMega();
203 mapTilesChanged = 0;
205 if (fboLevel !is null) fboLevel.clear();
206 if (fboLevelLight !is null) fboLevelLight.clear();
207 if (fboOrigBack !is null) fboOrigBack.clear();
209 fboLevel = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // final level render will be here
210 fboLevelLight = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // level lights will be rendered here
211 fboOrigBack = new FBO(map.width*8, map.height*8, Texture.Option.Nearest, Texture.Option.Depth); // background+foreground
213 shadToPolar.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*8-1, map.height*8-1); });
214 shadBlur.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*8, map.height*8); });
215 shadBlurOcc.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*8, map.height*8); });
216 shadAmbient.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*8, map.height*8); });
218 glActiveTexture(GL_TEXTURE0+0);
219 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
220 orthoCamera(vlWidth, vlHeight);
222 Actor.resetStorage();
224 setupMapScripts();
225 mapscripts.runInit();
226 loadMapMonsters();
227 dotInit();
228 mapscripts.runLoaded();
230 // save first snapshot
231 if (prevFrameActorsData.length == 0) prevFrameActorsData = new ubyte[](Actor.actorSize*65536); // ~15-20 megabytes
232 prevFrameActorOfs[] = uint.max; // just for fun
233 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
235 levelLoaded = true;
237 { import core.memory : GC; GC.collect(); }
241 // ////////////////////////////////////////////////////////////////////////// //
242 //FIXME: optimize!
243 __gshared uint mapTilesChanged = 0;
246 //WARNING! this can be called only from DACS, so we don't have to sync it!
247 public void mapDirty (uint layermask) { mapTilesChanged |= layermask; }
250 void rebuildMapMegaTextures () {
251 //fbo.replaceTexture
252 //mapTilesChanged = false;
253 //map.clearMegaTextures();
254 map.oglBuildMega(mapTilesChanged);
255 mapTilesChanged = 0;
259 // ////////////////////////////////////////////////////////////////////////// //
260 // messages
261 struct Message {
262 enum Phase { FadeIn, Stay, FadeOut }
263 Phase phase;
264 int alpha;
265 int pauseMsecs;
266 MonoTime removeTime;
267 char[256] text;
268 usize textlen;
271 private import core.sync.mutex : Mutex;
273 __gshared Message[128] messages;
274 __gshared uint messagesUsed = 0;
276 //__gshared Mutex messageLock;
277 //shared static this () { messageLock = new Mutex(); }
280 void addMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
281 //messageLock.lock();
282 //scope(exit) messageLock.unlock();
283 if (msgtext.length == 0) return;
284 conwriteln(msgtext);
285 if (pauseMsecs <= 50) return;
286 if (messagesUsed == messages.length) {
287 // remove top message
288 foreach (immutable cidx; 1..messagesUsed) messages.ptr[cidx-1] = messages.ptr[cidx];
289 messages.ptr[0].alpha = 255;
290 --messagesUsed;
292 // quick replace
293 if (!noreplace && messagesUsed == 1) {
294 switch (messages.ptr[0].phase) {
295 case Message.Phase.FadeIn:
296 messages.ptr[0].phase = Message.Phase.FadeOut;
297 break;
298 case Message.Phase.Stay:
299 messages.ptr[0].phase = Message.Phase.FadeOut;
300 messages.ptr[0].alpha = 255;
301 break;
302 default:
305 auto msg = messages.ptr+messagesUsed;
306 ++messagesUsed;
307 msg.phase = Message.Phase.FadeIn;
308 msg.alpha = 0;
309 msg.pauseMsecs = pauseMsecs;
310 // copy text
311 if (msgtext.length > msg.text.length) {
312 msg.text = msgtext[0..msg.text.length];
313 msg.textlen = msg.text.length;
314 } else {
315 msg.text[0..msgtext.length] = msgtext[];
316 msg.textlen = msgtext.length;
321 void doMessages (MonoTime curtime) {
322 //messageLock.lock();
323 //scope(exit) messageLock.unlock();
325 if (messagesUsed == 0) return;
326 glEnable(GL_BLEND);
327 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
329 Message* msg;
331 again:
332 msg = messages.ptr;
333 final switch (msg.phase) {
334 case Message.Phase.FadeIn:
335 if ((msg.alpha += 10) >= 255) {
336 msg.phase = Message.Phase.Stay;
337 msg.removeTime = curtime+dur!"msecs"(msg.pauseMsecs);
338 goto case; // to stay
340 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
341 break;
342 case Message.Phase.Stay:
343 if (msg.removeTime <= curtime) {
344 msg.alpha = 255;
345 msg.phase = Message.Phase.FadeOut;
346 goto case; // to fade
348 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
349 break;
350 case Message.Phase.FadeOut:
351 if ((msg.alpha -= 10) <= 0) {
352 if (--messagesUsed == 0) return;
353 // remove this message
354 foreach (immutable cidx; 1..messagesUsed+1) messages.ptr[cidx-1] = messages.ptr[cidx];
355 goto again;
357 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
358 break;
361 smDrawText(10, 10, msg.text[0..msg.textlen]);
365 // ////////////////////////////////////////////////////////////////////////// //
366 //mixin(Actor.FieldPropMixin!("0drawlistpos", uint));
368 mixin(Actor.FieldGetMixin!("classtype", StrId)); // fget_classtype
369 mixin(Actor.FieldGetMixin!("classname", StrId)); // fget_classname
370 mixin(Actor.FieldGetMixin!("x", int));
371 mixin(Actor.FieldGetMixin!("y", int));
372 mixin(Actor.FieldGetMixin!("radius", int));
373 mixin(Actor.FieldGetMixin!("height", int));
374 mixin(Actor.FieldGetMixin!("flags", uint));
375 mixin(Actor.FieldGetMixin!("zAnimstate", StrId));
376 mixin(Actor.FieldGetMixin!("zAnimidx", int));
377 mixin(Actor.FieldGetMixin!("dir", uint));
378 mixin(Actor.FieldGetMixin!("attLightXOfs", int));
379 mixin(Actor.FieldGetMixin!("attLightYOfs", int));
380 mixin(Actor.FieldGetMixin!("attLightRGBX", uint));
382 //mixin(Actor.FieldGetPtrMixin!("classtype", StrId)); // fget_classtype
383 //mixin(Actor.FieldGetPtrMixin!("classname", StrId)); // fget_classname
384 mixin(Actor.FieldGetPtrMixin!("x", int));
385 mixin(Actor.FieldGetPtrMixin!("y", int));
386 //mixin(Actor.FieldGetPtrMixin!("flags", uint));
387 //mixin(Actor.FieldGetPtrMixin!("zAnimstate", StrId));
388 //mixin(Actor.FieldGetPtrMixin!("zAnimidx", int));
389 //mixin(Actor.FieldGetPtrMixin!("dir", uint));
390 //mixin(Actor.FieldGetPtrMixin!("attLightXOfs", int));
391 //mixin(Actor.FieldGetPtrMixin!("attLightYOfs", int));
392 //mixin(Actor.FieldGetPtrMixin!("attLightRGBX", uint));
395 // ////////////////////////////////////////////////////////////////////////// //
396 __gshared int vportX0, vportY0, vportX1, vportY1;
399 // ////////////////////////////////////////////////////////////////////////// //
400 void renderLightAmbient() (int lightX, int lightY, int lightW, int lightH, in auto ref SVec4F lcol) {
401 //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));
402 if (lightW < 1 || lightH < 1) return;
403 int lightX1 = lightX+lightW-1;
404 int lightY1 = lightY+lightH-1;
405 // clip light to viewport
406 if (lightX < vportX0) lightX = vportX0;
407 if (lightY < vportY0) lightY = vportY0;
408 if (lightX1 > vportX1) lightX1 = vportX1;
409 if (lightY1 > vportY1) lightY1 = vportY1;
410 // is this light visible?
411 //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));
412 if (lightX1 < lightX || lightY1 < lightY || lightX > vportX1 || lightY > vportY1 || lightX1 < vportX0 || lightY1 < vportY0) return;
413 //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));
415 fboLevelLight.exec({
416 glEnable(GL_BLEND);
417 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
418 //glDisable(GL_BLEND);
419 orthoCamera(map.width*8, map.height*8);
420 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
421 shadAmbient.exec((Shader shad) {
422 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
423 //shad["lightPos"] = SVec2F(lightX, lightY);
424 glRectf(lightX, lightY, lightX1, lightY1);
427 if (lcol.r == 0 && lcol.g == 0 && lcol.b == 0) {
428 //conwriteln("ambient: (", lightX, ",", lightY, ")-(", lightX1, ",", lightY1, "): intens=", cast(uint)(255.0f*lcol.a));
429 glColor4f(lcol.a, lcol.a, lcol.a, lcol.a);
430 } else {
431 //conwriteln("ambient: (", 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));
432 glColor4f(lcol.r, lcol.g, lcol.b, 1.0f);
434 glRectf(lightX, lightY, lightX1, lightY1);
440 // ////////////////////////////////////////////////////////////////////////// //
441 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
442 if (lightRadius < 2) return;
443 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
444 int lightSize = lightRadius*2;
445 // is this light visible?
446 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*8 || lightY-lightRadius >= map.height*8) return;
448 // out of viewport -- do nothing
449 if (lightX+lightRadius < vportX0 || lightY+lightRadius < vportY0) return;
450 if (lightX-lightRadius > vportX1 || lightY-lightRadius > vportY1) return;
452 if (lightX >= 0 && lightY >= 0 && lightX < map.width*8 && lightY < map.height*8 &&
453 map.teximgs[map.LightMask].imageData.colors.ptr[lightY*(map.width*8)+lightX].a > 190) return;
455 //glUseProgram(0);
456 glDisable(GL_BLEND);
458 // draw shadow casters to fboOccludersId, light should be in the center
459 version(old_light) {
460 fboOccluders[lightRadius].exec({
461 glColor4f(0.0f, 0.0f, 0.0f, 1.0f);
462 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
463 glClear(GL_COLOR_BUFFER_BIT);
464 orthoCamera(lightSize, lightSize);
465 drawAtXY(map.texgl.ptr[map.LightMask], lightRadius-lightX, lightRadius-lightY);
468 // common color for all the following
469 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
471 // build 1d distance map to fboShadowMapId
472 fboDistMap[lightRadius].exec({
473 shadToPolar.exec((Shader shad) {
474 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
475 shad["lightPos"] = SVec2F(lightX, lightY);
476 // no need to clear it, shader will take care of that
477 orthoCamera(lightSize, 1);
478 version(old_light) {
479 drawAtXY(fboOccluders[lightRadius].tex.tid, 0, 0, lightSize, 1);
480 } else {
481 // it doesn't matter what we will draw here, so just draw filled rect
482 glRectf(0, 0, lightSize, 1);
487 // build light texture for blending
488 fboOccluders[lightRadius].exec({
489 // no need to clear it, shader will take care of that
490 shadBlur.exec((Shader shad) {
491 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
492 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
493 shad["lightPos"] = SVec2F(lightX, lightY);
494 orthoCamera(lightSize, lightSize);
495 drawAtXY(fboDistMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
499 // blend light texture
500 fboLevelLight.exec({
501 glEnable(GL_BLEND);
502 //glDisable(GL_BLEND);
503 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
504 orthoCamera(map.width*8, map.height*8);
505 drawAtXY(fboOccluders[lightRadius].tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
506 // and blend it again, with the shader that will touch only occluders
507 shadBlurOcc.exec((Shader shad) {
508 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
509 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
510 shad["lightPos"] = SVec2F(lightX, lightY);
511 drawAtXY(fboOccluders[lightRadius].tex.tid, lightX-lightRadius, lightY-lightRadius, lightSize, lightSize, mirrorY:true);
517 // ////////////////////////////////////////////////////////////////////////// //
518 __gshared int testLightX = vlWidth/2, testLightY = vlHeight/2;
519 __gshared bool testLightMoved = false;
520 //__gshared int mapOfsX, mapOfsY;
521 //__gshared bool movement = false;
522 __gshared float iLiquidTime = 0.0;
523 //__gshared bool altMove = false;
526 void renderScene (MonoTime curtime) {
527 //enum BackIntens = 0.05f;
528 enum BackIntens = 0.0f;
530 gloStackClear();
531 float atob = (curtime > lastthink ? cast(float)((curtime-lastthink).total!"msecs")/cast(float)((nextthink-lastthink).total!"msecs") : 1.0f);
532 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
535 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
536 int curfp = cast(int)((curtime-lastthink).total!"msecs");
537 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
541 int mofsx, mofsy; // camera offset, will be set in background layer builder
543 if (mapTilesChanged != 0) rebuildMapMegaTextures();
545 dotDraw(atob);
547 // build background layer
548 fboOrigBack.exec({
549 //glDisable(GL_BLEND);
550 //glClearDepth(1.0f);
551 //glDepthFunc(GL_LESS);
552 //glDepthFunc(GL_NEVER);
553 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
554 glClear(GL_COLOR_BUFFER_BIT/*|GL_DEPTH_BUFFER_BIT*/);
555 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
556 orthoCamera(map.width*8, map.height*8);
558 glEnable(GL_BLEND);
559 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
560 // draw sky
562 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
563 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2, map.MapSize*8, map.MapSize*8);
564 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
565 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*8, map.MapSize*8);
567 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*8, map.MapSize*8);
568 // draw background
569 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
570 // draw distorted liquid areas
571 shadLiquidDistort.exec((Shader shad) {
572 shad["iDistortTime"] = iLiquidTime;
573 drawAtXY(map.texgl.ptr[map.AllLiquids], 0, 0);
575 // monsters, items; we'll do linear interpolation here
576 glColor3f(1.0f, 1.0f, 1.0f);
577 //glEnable(GL_DEPTH_TEST);
578 attachedLightCount = 0;
580 // who cares about memory?!
581 // draw order: players, items, monsters, other
582 static struct DrawInfo {
583 ActorDef adef;
584 ActorId aid;
585 int actorX, actorY;
586 @disable this (this); // no copies
588 enum { Players, Items, Monsters, Other }
589 __gshared DrawInfo[65536][4] drawlists;
590 __gshared uint[4] dlpos;
591 DrawInfo camchickdi;
593 dlpos[] = 0;
595 Actor.forEach((ActorId me) {
596 //me.fprop_0drawlistpos = 0;
597 if (auto adef = findActorDef(me)) {
598 uint dlnum = Other;
599 switch (adef.classtype.get) {
600 case "monster": dlnum = (adef.classname.get != "Player" ? Monsters : Players); break;
601 case "item": dlnum = Items; break;
602 default: dlnum = Other; break;
604 int actorX, actorY; // current actor position
606 auto ofs = prevFrameActorOfs.ptr[me.id&0xffff];
607 if (frameInterpolation && ofs < uint.max-1 && Actor.isSameSavedActor(me.id, prevFrameActorsData.ptr, ofs)) {
608 import core.stdc.math : roundf;
609 auto xptr = prevFrameActorsData.ptr+ofs;
610 int ox = xptr.fgetp_x;
611 int nx = me.fget_x;
612 int oy = xptr.fgetp_y;
613 int ny = me.fget_y;
614 actorX = cast(int)(ox+roundf((nx-ox)*atob));
615 actorY = cast(int)(oy+roundf((ny-oy)*atob));
616 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
617 } else {
618 actorX = me.fget_x;
619 actorY = me.fget_y;
622 if (me.id == cameraChick.id) {
623 camchickdi.adef = adef;
624 camchickdi.aid = me;
625 camchickdi.actorX = actorX;
626 camchickdi.actorY = actorY;
628 // draw sprite
629 if ((me.fget_flags&AF_NODRAW) == 0) {
630 //auto dl = &drawlists[dlnum][dlpos.ptr[dlnum]];
631 //me.fprop_0drawlistpos = (dlpos.ptr[dlnum]&0xffff)|((dlnum&0xff)<<16);
632 auto dl = drawlists.ptr[dlnum].ptr+dlpos.ptr[dlnum];
633 ++dlpos.ptr[dlnum];
634 dl.adef = adef;
635 dl.aid = me;
636 dl.actorX = actorX;
637 dl.actorY = actorY;
639 // process attached lights
640 if ((me.fget_flags&AF_NOLIGHT) == 0) {
641 uint alr = me.fget_attLightRGBX;
642 bool isambient = (me.fget_classtype.get == "light" && me.fget_classname.get == "Ambient");
643 if ((alr&0xff) >= 4 || (isambient && me.fget_radius >= 1 && me.fget_height >= 1)) {
644 //if (isambient) conwriteln("isambient: ", isambient, "; x=", me.fget_x, "; y=", me.fget_y, "; w=", me.fget_radius, "; h=", me.fget_height);
645 // yep, add it
646 auto li = attachedLights.ptr+attachedLightCount;
647 ++attachedLightCount;
648 li.type = (!isambient ? AttachedLightInfo.Type.Point : AttachedLightInfo.Type.Ambient);
649 li.x = actorX+me.fget_attLightXOfs;
650 li.y = actorY+me.fget_attLightYOfs;
651 li.r = ((alr>>24)&0xff)/255.0f; // red or intensity
652 if ((alr&0x00_ff_ff_00U) == 0x00_00_01_00U) {
653 li.uncolored = true;
654 } else {
655 li.g = ((alr>>16)&0xff)/255.0f;
656 li.b = ((alr>>8)&0xff)/255.0f;
657 li.uncolored = false;
659 li.radius = (alr&0xff);
660 if (li.radius > MaxLightRadius) li.radius = MaxLightRadius;
661 if (isambient) {
662 li.w = me.fget_radius;
663 li.h = me.fget_height;
667 } else {
668 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
672 // draw actor lists
673 foreach_reverse (uint dlnum; 0..drawlists.length) {
674 auto dl = drawlists.ptr[dlnum].ptr;
675 if (dlnum == Players) dl += dlpos.ptr[dlnum]-1;
676 foreach (uint idx; 0..dlpos.ptr[dlnum]) {
677 auto me = dl.aid;
678 if (auto isp = dl.adef.animSpr(me.fget_zAnimstate, me.fget_dir, me.fget_zAnimidx)) {
679 //drawAtXY(isp.tex, dl.actorX-isp.vga.sx, dl.actorY-isp.vga.sy);
680 isp.drawAtXY(dl.actorX, dl.actorY);
682 if (dlnum != Players) ++dl; else --dl;
686 // camera movement
687 if (/*altMove || movement ||*/ scale == 1 || !cameraChick.valid) {
688 mofsx = 0;
689 mofsy = 0;
690 vportX0 = 0;
691 vportY0 = 0;
692 vportX1 = map.width*8;
693 vportY1 = map.height*8;
694 } else {
695 int tiltHeight = /*getMapViewHeight()*/(vlHeight/scale)/4;
696 int vy = cameraChick.looky!int;
697 if (vy < -tiltHeight) vy = -tiltHeight; else if (vy > tiltHeight) vy = tiltHeight;
698 int swdt = vlWidth/scale;
699 int shgt = vlHeight/scale;
700 int x = camchickdi.actorX-swdt/2;
701 int y = (camchickdi.actorY+vy)-shgt/2;
702 if (x < 0) x = 0; else if (x >= map.width*8-swdt) x = map.width*8-swdt-1;
703 if (y < 0) y = 0; else if (y >= map.height*8-shgt) y = map.height*8-shgt-1;
704 mofsx = x*2;
705 mofsy = y*2;
706 vportX0 = mofsx/scale;
707 vportY0 = mofsy/scale;
708 vportX1 = vportX0+vlWidth/scale;
709 vportY1 = vportY0+vlHeight/scale;
712 //glDisable(GL_DEPTH_TEST);
713 // draw dots
714 drawAtXY(texParts, 0, 0);
715 // do liquid coloring
716 drawAtXY(map.texgl.ptr[map.LiquidMask], 0, 0);
717 // foreground -- hide secrets, draw lifts and such
718 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
722 if (doLighting) {
723 // clear light layer
724 fboLevelLight.exec({
725 glDisable(GL_BLEND);
726 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
727 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
728 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
729 glClear(GL_COLOR_BUFFER_BIT);
732 // texture 1 is background
733 glActiveTexture(GL_TEXTURE0+1);
734 glBindTexture(GL_TEXTURE_2D, fboOrigBack.tex.tid);
735 // texture 2 is occluders
736 glActiveTexture(GL_TEXTURE0+2);
737 glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.LightMask].tid);
738 // done texture assign
739 glActiveTexture(GL_TEXTURE0+0);
742 enum LYOfs = 1;
744 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
745 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
746 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
747 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
748 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
749 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
750 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
751 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
752 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
753 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
755 renderLight(24*8+4, (24+18)*8-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
757 renderLight(280, 330, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
760 // attached lights
761 foreach (ref li; attachedLights[0..attachedLightCount]) {
762 if (li.type == AttachedLightInfo.Type.Ambient) {
763 //conwriteln("ambient: x=", li.x, "; y=", li.y, "; w=", li.w, "; h=", li.h);
764 // ambient light
765 if (li.uncolored) {
766 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(0.0f, 0.0f, 0.0f, li.r));
767 } else {
768 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(li.r, li.g, li.b, 1.0f));
770 } else if (li.type == AttachedLightInfo.Type.Point) {
771 // point light
772 if (li.uncolored) {
773 renderLight(li.x, li.y, SVec4F(0.0f, 0.0f, 0.0f, li.r), li.radius);
774 } else {
775 renderLight(li.x, li.y, SVec4F(li.r, li.g, li.b, 1.0f), li.radius);
781 if (testLightMoved) {
782 testLightX = testLightX/scale+mofsx/scale;
783 testLightY = testLightY/scale+mofsy/scale;
784 testLightMoved = false;
786 foreach (immutable _; 0..1) {
787 renderLight(testLightX, testLightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
791 glActiveTexture(GL_TEXTURE0+1);
792 glBindTexture(GL_TEXTURE_2D, 0);
793 glActiveTexture(GL_TEXTURE0+2);
794 glBindTexture(GL_TEXTURE_2D, 0);
795 glActiveTexture(GL_TEXTURE0+0);
798 // draw scaled level
800 shadScanlines.exec((Shader shad) {
801 shad["scanlines"] = scanlines;
802 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
803 glClear(GL_COLOR_BUFFER_BIT);
804 orthoCamera(vlWidth, vlHeight);
805 //orthoCamera(map.width*8*scale, map.height*8*scale);
806 //glMatrixMode(GL_MODELVIEW);
807 //glLoadIdentity();
808 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
809 // somehow, FBO objects are mirrored; wtf?!
810 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*8*scale, map.height*8*scale, mirrorY:true);
811 //glLoadIdentity();
816 fboLevelLight.exec({
817 smDrawText(map.width*8/2, map.height*8/2, "Testing...");
822 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
823 glDisable(GL_BLEND);
826 fboOrigBack.exec({
827 //auto img = smfont.ptr[0x39];
828 auto img = fftest;
829 if (img !is null) {
830 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
831 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
838 { // http://stackoverflow.com/questions/7207422/setting-up-opengl-multiple-render-targets
839 GLenum[1] buffers = [ GL_BACK_LEFT, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
840 //GLenum[1] buffers = [ GL_NONE, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
841 glDrawBuffers(1, buffers.ptr);
845 orthoCamera(vlWidth, vlHeight);
846 auto tex = (doLighting ? fboLevelLight.tex.tid : fboOrigBack.tex.tid);
847 //mofsx &= ~1;
848 //mofsy &= ~1;
849 drawAtXY(tex, -mofsx, -mofsy, map.width*8*scale, map.height*8*scale, mirrorY:true);
851 doMessages(curtime);
855 // ////////////////////////////////////////////////////////////////////////// //
856 // returns time slept
857 int sleepAtMaxMsecs (int msecs) {
858 if (msecs > 0) {
859 import core.sys.posix.signal : timespec;
860 import core.sys.posix.time : nanosleep;
861 timespec ts = void, tpassed = void;
862 ts.tv_sec = 0;
863 ts.tv_nsec = msecs*1000*1000+(500*1000); // milli to nano
864 nanosleep(&ts, &tpassed);
865 return (ts.tv_nsec-tpassed.tv_nsec)/(1000*1000);
866 } else {
867 return 0;
872 // ////////////////////////////////////////////////////////////////////////// //
873 // rendering thread
874 shared int diedie = 0;
876 enum D2DFrameTime = 55; // milliseconds
877 enum MinFrameTime = 1000/60; // ~60 FPS
879 public void renderThread (Tid starterTid) {
880 enum BoolOptVarMsgMixin(string varname) =
881 "if (msg.toggle) msg.value = !"~varname~";\n"~
882 "if ("~varname~" != msg.value) "~varname~" = msg.value; else msg.showMessage = false;\n";
884 send(starterTid, 42);
885 try {
886 MonoTime curtime = MonoTime.currTime;
888 lastthink = curtime; // for interpolator
889 nextthink = curtime+dur!"msecs"(D2DFrameTime);
890 MonoTime nextvframe = curtime;
892 enum MaxFPSFrames = 16;
893 float frtimes = 0.0f;
894 int framenum = 0;
895 int prevFPS = -1;
896 int hushFrames = 6; // ignore first `hushFrames` frames overtime
897 MonoTime prevFrameStartTime = curtime;
899 bool vframeWasLost = false;
901 void resetFrameTimers () {
902 MonoTime curtime = MonoTime.currTime;
903 lastthink = curtime; // for interpolator
904 nextthink = curtime+dur!"msecs"(D2DFrameTime);
905 nextvframe = curtime;
908 void loadNewLevel (string name) {
910 if (levelLoaded) {
911 conwriteln("ERROR: can't load new levels yet");
912 return;
915 if (name.length == 0) {
916 conwriteln("ERROR: can't load empty level!");
918 conwriteln("loading map '", name, "'");
919 loadMap(name);
920 resetFrameTimers();
923 void receiveMessages () {
924 for (;;) {
925 import core.time : Duration;
926 //conwriteln("rendering thread: waiting for messages...");
927 auto got = receiveTimeout(
928 Duration.zero, // don't wait
929 (TMsgMessage msg) {
930 addMessage(msg.text[0..msg.textlen], msg.pauseMsecs, msg.noreplace);
932 (TMsgLoadLevel msg) {
933 nextmapname = null; // clear "exit" flag
934 loadNewLevel(msg.mapfile[0..msg.textlen].idup);
936 (TMsgSkipLevel msg) {
937 string mn = genNextMapName(0);
938 if (mn.length) {
939 nextmapname = null; // clear "exit" flag
940 loadNewLevel(mn);
943 (TMsgIntOption msg) {
944 final switch (msg.type) with (TMsgIntOption.Type) {
945 case Scale:
946 if (msg.value >= 1 && msg.value <= 2) scale = msg.value;
947 break;
950 (TMsgBoolOption msg) {
951 final switch (msg.type) with (TMsgBoolOption.Type) {
952 case Interpolation: mixin(BoolOptVarMsgMixin!"frameInterpolation"); break;
953 case Lighting: mixin(BoolOptVarMsgMixin!"doLighting"); break;
954 case Pause: mixin(BoolOptVarMsgMixin!"gamePaused"); break;
955 case CheatNoDoors: mixin(BoolOptVarMsgMixin!"cheatNoDoors"); break;
956 case CheatNoWallClip: mixin(BoolOptVarMsgMixin!"cheatNoWallClip"); break;
958 if (msg.showMessage) {
959 char[128] mbuf;
960 uint msgpos;
961 void putStr(T) (T s) if (is(T : const(char)[])) {
962 foreach (char ch; s) {
963 if (msgpos >= mbuf.length) break;
964 mbuf.ptr[msgpos++] = ch;
967 putStr("Option \"");
968 { import std.conv : to; putStr(to!string(msg.type)); }
969 putStr("\": O");
970 if (msg.value) putStr("N"); else putStr("FF");
971 addMessage(mbuf[0..msgpos]);
974 (TMsgTestLightMove msg) {
975 testLightX = msg.x;
976 testLightY = msg.y;
977 testLightMoved = true;
979 (Variant v) {
980 conwriteln("WARNING: unknown thread message received and ignored");
983 if (!got) {
984 // no more messages
985 //conwriteln("rendering thread: no more messages");
986 break;
989 if (nextmapname.length) {
990 string mn = nextmapname;
991 nextmapname = null; // clear "exit" flag
992 loadNewLevel(mn);
996 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
997 bool doThinkFrame () {
998 if (curtime >= nextthink) {
999 lastthink = curtime;
1000 while (nextthink <= curtime) nextthink += dur!"msecs"(D2DFrameTime);
1001 if (levelLoaded && !gamePaused) {
1002 // save snapshot and other data for interpolator
1003 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
1004 // process actors
1005 doActorsThink();
1006 dotThink();
1008 // some timing
1009 auto tm = MonoTime.currTime;
1010 int thinkTime = cast(int)((tm-curtime).total!"msecs");
1011 if (thinkTime > 9) { import core.stdc.stdio; printf("spent on thinking: %d msecs\n", thinkTime); }
1012 curtime = tm;
1013 return true;
1014 } else {
1015 return false;
1019 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1020 bool doVFrame () {
1021 version(dont_use_vsync) {
1022 // timer
1023 enum doCheckTime = true;
1024 } else {
1025 // vsync
1026 __gshared bool prevLost = false;
1027 bool doCheckTime = vframeWasLost;
1028 if (vframeWasLost) {
1029 if (!prevLost) {
1030 { import core.stdc.stdio; printf("frame was lost!\n"); }
1032 prevLost = true;
1033 } else {
1034 prevLost = false;
1037 if (doCheckTime) {
1038 if (curtime < nextvframe) return false;
1039 version(dont_use_vsync) {
1040 if (curtime > nextvframe) {
1041 auto overtime = cast(int)((curtime-nextvframe).total!"msecs");
1042 if (overtime > 2500) {
1043 if (hushFrames) {
1044 --hushFrames;
1045 } else {
1046 { import core.stdc.stdio; printf(" spent whole %d msecs\n", overtime); }
1052 while (nextvframe <= curtime) nextvframe += dur!"msecs"(MinFrameTime);
1053 bool ctset = false;
1055 sdwindow.mtLock();
1056 scope(exit) sdwindow.mtUnlock();
1057 ctset = sdwindow.setAsCurrentOpenGlContextNT;
1059 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
1060 if (ctset) {
1061 // render scene
1062 iLiquidTime = cast(float)((curtime-MonoTime.zero).total!"msecs"%10000000)/18.0f*0.04f;
1063 receiveMessages(); // here, 'cause we need active OpenGL context for some messages
1064 if (levelLoaded) {
1065 renderScene(curtime);
1066 } else {
1067 //renderLoading(curtime);
1069 sdwindow.mtLock();
1070 scope(exit) sdwindow.mtUnlock();
1071 sdwindow.swapOpenGlBuffers();
1072 glFinish();
1073 sdwindow.releaseCurrentOpenGlContext();
1074 vframeWasLost = false;
1075 } else {
1076 vframeWasLost = true;
1077 { import core.stdc.stdio; printf("xframe was lost!\n"); }
1079 curtime = MonoTime.currTime;
1080 return true;
1083 for (;;) {
1084 if (sdwindow.closed) break;
1085 if (atomicLoad(diedie) > 0) break;
1087 curtime = MonoTime.currTime;
1088 auto fstime = curtime;
1090 doThinkFrame(); // this will fix curtime if necessary
1091 if (doVFrame()) {
1092 if (!vframeWasLost) {
1093 // fps
1094 auto frameTime = cast(float)(curtime-prevFrameStartTime).total!"msecs"/1000.0f;
1095 prevFrameStartTime = curtime;
1096 frtimes += frameTime;
1097 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
1098 import std.string : format;
1099 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
1100 if (newFPS != prevFPS) {
1101 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
1102 prevFPS = newFPS;
1104 framenum = 0;
1105 frtimes = 0.0f;
1110 curtime = MonoTime.currTime;
1112 // now sleep until next "video" or "think" frame
1113 if (nextthink > curtime && nextvframe > curtime) {
1114 // let's decide
1115 immutable nextVideoFrameSleep = cast(int)((nextvframe-curtime).total!"msecs");
1116 immutable nextThinkFrameSleep = cast(int)((nextthink-curtime).total!"msecs");
1117 immutable sleepTime = (nextVideoFrameSleep < nextThinkFrameSleep ? nextVideoFrameSleep : nextThinkFrameSleep);
1118 sleepAtMaxMsecs(sleepTime);
1119 //curtime = MonoTime.currTime;
1122 } catch (Throwable e) {
1123 // here, we are dead and fucked (the exact order doesn't matter)
1124 import core.stdc.stdlib : abort;
1125 import core.stdc.stdio : fprintf, stderr;
1126 import core.memory : GC;
1127 GC.disable(); // yeah
1128 thread_suspendAll(); // stop right here, you criminal scum!
1129 auto s = e.toString();
1130 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
1131 abort(); // die, you bitch!
1133 import core.stdc.stdio;
1134 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
1135 import std.stdio : stderr;
1136 writeln(
1137 for (;;) {
1138 if (sdwindow.closed) break;
1139 if (atomicLoad(diedie) > 0) break;
1140 sleepAtMaxMsecs(100);
1144 atomicStore(diedie, 2);
1148 // ////////////////////////////////////////////////////////////////////////// //
1149 __gshared Tid renderTid;
1150 shared bool renderThreadStarted = false;
1153 public void startRenderThread () {
1154 if (!cas(&renderThreadStarted, false, true)) {
1155 assert(0, "render thread already started!");
1157 renderTid = spawn(&renderThread, thisTid);
1158 setMaxMailboxSize(renderTid, 1024, OnCrowding.throwException); //FIXME
1159 // wait for "i'm ready" signal
1160 receive(
1161 (int ok) {
1162 if (ok != 42) assert(0, "wtf?!");
1165 conwriteln("rendering thread started");
1169 // ////////////////////////////////////////////////////////////////////////// //
1170 public void closeWindow () {
1171 if (atomicLoad(diedie) != 2) {
1172 atomicStore(diedie, 1);
1173 while (atomicLoad(diedie) != 2) {}
1175 if (!sdwindow.closed) {
1176 flushGui();
1177 sdwindow.close();
1182 // ////////////////////////////////////////////////////////////////////////// //
1183 // thread messages
1186 // ////////////////////////////////////////////////////////////////////////// //
1187 struct TMsgTestLightMove {
1188 int x, y;
1191 public void postTestLightMove (int x, int y) {
1192 if (!atomicLoad(renderThreadStarted)) return;
1193 auto msg = TMsgTestLightMove(x, y);
1194 send(renderTid, msg);
1198 // ////////////////////////////////////////////////////////////////////////// //
1199 struct TMsgMessage {
1200 char[256] text;
1201 uint textlen;
1202 int pauseMsecs;
1203 bool noreplace;
1206 public void postAddMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
1207 if (!atomicLoad(renderThreadStarted)) return;
1208 if (msgtext.length > TMsgMessage.text.length) msgtext = msgtext[0..TMsgMessage.text.length];
1209 TMsgMessage msg;
1210 msg.textlen = cast(uint)msgtext.length;
1211 if (msg.textlen) msg.text[0..msg.textlen] = msgtext[0..msg.textlen];
1212 msg.pauseMsecs = pauseMsecs;
1213 msg.noreplace = noreplace;
1214 send(renderTid, msg);
1218 // ////////////////////////////////////////////////////////////////////////// //
1219 struct TMsgLoadLevel {
1220 char[1024] mapfile;
1221 uint textlen;
1224 public void postLoadLevel (const(char)[] mapfile) {
1225 if (!atomicLoad(renderThreadStarted)) return;
1226 if (mapfile.length > TMsgLoadLevel.mapfile.length) mapfile = mapfile[0..TMsgLoadLevel.mapfile.length];
1227 TMsgLoadLevel msg;
1228 msg.textlen = cast(uint)mapfile.length;
1229 if (msg.textlen) msg.mapfile[0..msg.textlen] = mapfile[0..msg.textlen];
1230 send(renderTid, msg);
1234 // ////////////////////////////////////////////////////////////////////////// //
1235 struct TMsgSkipLevel {
1238 public void postSkipLevel () {
1239 if (!atomicLoad(renderThreadStarted)) return;
1240 TMsgSkipLevel msg;
1241 send(renderTid, msg);
1245 // ////////////////////////////////////////////////////////////////////////// //
1246 struct TMsgIntOption {
1247 enum Type {
1248 Scale,
1250 Type type;
1251 int value;
1252 bool showMessage;
1256 struct TMsgBoolOption {
1257 enum Type {
1258 Interpolation,
1259 Lighting,
1260 Pause,
1261 CheatNoDoors,
1262 CheatNoWallClip,
1264 Type type;
1265 bool value;
1266 bool toggle;
1267 bool showMessage;
1271 bool strCaseEqu (const(char)[] s0, const(char)[] s1) {
1272 if (s0.length != s1.length) return false;
1273 foreach (immutable idx, char ch; s0) {
1274 if (ch >= 'A' && ch <= 'Z') ch += 32;
1275 char c1 = s1.ptr[idx];
1276 if (c1 >= 'A' && c1 <= 'Z') c1 += 32;
1277 if (ch != c1) return false;
1279 return true;
1283 public bool postToggleOption (const(char)[] name, bool showMessage=false) { pragma(inline, true); return postSetOption(name, true, showMessage:showMessage, toggle:true); }
1285 public bool postSetOption(T) (const(char)[] name, T value, bool showMessage=false, bool toggle=false) if ((is(T == int) || is(T == bool))) {
1286 if (!atomicLoad(renderThreadStarted)) return false;
1287 if (name.length == 0 || name.length > 127) return false;
1288 static if (is(T == int)) {
1289 TMsgIntOption msg;
1290 foreach (string oname; __traits(allMembers, TMsgIntOption.Type)) {
1291 if (strCaseEqu(oname, name)) {
1292 msg.type = __traits(getMember, TMsgIntOption.Type, oname);
1293 msg.value = value;
1294 msg.showMessage = showMessage;
1295 send(renderTid, msg);
1296 return true;
1299 } else static if (is(T == bool)) {
1300 TMsgBoolOption msg;
1301 foreach (string oname; __traits(allMembers, TMsgBoolOption.Type)) {
1302 if (strCaseEqu(oname, name)) {
1303 msg.type = __traits(getMember, TMsgBoolOption.Type, oname);
1304 msg.value = value;
1305 msg.toggle = toggle;
1306 msg.showMessage = showMessage;
1307 send(renderTid, msg);
1308 return true;
1311 } else {
1312 static assert(0, "invalid option type '"~T.stringof~"'");
1314 return false;