more console commands
[dd2d.git] / render.d
blobd8779e3fa0a47d64901c934e6357020d37da34d5
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;
22 private:
23 import core.atomic;
24 import core.thread;
25 import core.time;
27 import std.concurrency;
29 import iv.glbinds;
30 import glutils;
31 import console;
32 import wadarc;
34 import iv.vfs.augs;
36 import d2dmap;
37 import d2dadefs;
38 import d2dimage;
39 import d2dfont;
40 import dacs;
42 import d2dunigrid;
44 // `map` is there
45 import dengapi;
47 import d2dparts;
50 // ////////////////////////////////////////////////////////////////////////// //
51 import arsd.color;
52 import arsd.png;
55 // ////////////////////////////////////////////////////////////////////////// //
56 public __gshared bool cheatNoDoors = false;
57 public __gshared bool cheatNoWallClip = false;
60 // ////////////////////////////////////////////////////////////////////////// //
61 public __gshared SimpleWindow sdwindow;
64 public enum vlWidth = 800;
65 public enum vlHeight = 800;
66 __gshared int scale = 2;
68 public int getScale () nothrow @trusted @nogc { pragma(inline, true); return scale; }
71 // ////////////////////////////////////////////////////////////////////////// //
72 __gshared bool levelLoaded = false;
75 // ////////////////////////////////////////////////////////////////////////// //
76 __gshared bool scanlines = false;
77 __gshared bool doLighting = true;
78 __gshared bool gamePaused = false;
79 shared bool editMode = false;
80 shared bool vquitRequested = false;
82 public @property bool inEditMode () nothrow @trusted @nogc { import core.atomic; return atomicLoad(editMode); }
83 @property void inEditMode (bool v) nothrow @trusted @nogc { import core.atomic; atomicStore(editMode, v); }
85 public @property bool quitRequested () nothrow @trusted @nogc { import core.atomic; return atomicLoad(vquitRequested); }
88 // ////////////////////////////////////////////////////////////////////////// //
89 __gshared char[32768] concmdbuf;
90 __gshared uint concmdbufpos;
91 private import core.sync.mutex : Mutex;
92 __gshared Mutex concmdbufLock;
93 shared static this () { concmdbufLock = new Mutex(); }
96 void concmdAdd (const(char)[] s) {
97 for (;;) {
98 auto cmd = conGetCommand(s);
99 if (cmd is null) break;
100 if (concmdbuf.length-concmdbufpos < cmd.length+1) return; // alas
101 concmdbuf[concmdbufpos..concmdbufpos+cmd.length] = cmd[];
102 concmdbufpos += cmd.length;
103 concmdbuf.ptr[concmdbufpos++] = '\x00';
107 // `null`: no more
108 const(char)[] concmdGet (ref uint pos) {
109 if (pos >= concmdbufpos) return null;
110 auto st = pos;
111 while (pos < concmdbufpos && concmdbuf.ptr[pos]) ++pos;
112 auto res = concmdbuf[st..pos];
113 ++pos;
114 return res;
118 // ////////////////////////////////////////////////////////////////////////// //
119 shared static this () {
120 conRegVar!doLighting("r_lighting", "dynamic lighting");
121 conRegVar!gamePaused("g_pause", "pause game");
122 conRegVar!frameInterpolation("r_interpolation", "interpolate camera and sprite movement");
123 conRegVar!scale(1, 2, "r_scale");
124 conRegFunc!({
125 cheatNoDoors = !cheatNoDoors;
126 if (cheatNoDoors) conwriteln("player ignores doors"); else conwriteln("player respects doors");
127 })("nodoorclip", "ignore doors");
128 conRegFunc!({
129 cheatNoWallClip = !cheatNoWallClip;
130 if (cheatNoWallClip) conwriteln("player ignores walls"); else conwriteln("player respects walls");
131 })("nowallclip", "ignore walls");
132 conRegFunc!({
133 import core.atomic;
134 atomicStore(vquitRequested, true);
135 })("quit", "quit game");
136 conRegFunc!((const(char)[] msg, int pauseMsecs=3000, bool noreplace=false) {
137 char[256] buf;
138 auto s = buf.conFormatStr(msg);
139 if (s.length) postAddMessage(s, pauseMsecs, noreplace);
140 })("hudmsg", "show hud message; hudmsg msg [pausemsecs [noreplace]]");
144 // ////////////////////////////////////////////////////////////////////////// //
145 // interpolation
146 __gshared ubyte[] prevFrameActorsData;
147 __gshared uint[65536] prevFrameActorOfs; // uint.max-1: dead; uint.max: last
148 __gshared MonoTime lastthink = MonoTime.zero; // for interpolator
149 __gshared MonoTime nextthink = MonoTime.zero;
150 __gshared bool frameInterpolation = true;
153 // ////////////////////////////////////////////////////////////////////////// //
154 // attached lights
155 struct AttachedLightInfo {
156 enum Type {
157 Point,
158 Ambient,
160 Type type;
161 int x, y;
162 int w, h; // for ambient lights
163 float r, g, b;
164 bool uncolored;
165 int radius;
168 __gshared AttachedLightInfo[65536] attachedLights;
169 __gshared uint attachedLightCount = 0;
172 // ////////////////////////////////////////////////////////////////////////// //
173 enum MaxLightRadius = 255;
176 // ////////////////////////////////////////////////////////////////////////// //
177 // for light
178 __gshared FBO[MaxLightRadius+1] fboDistMap;
179 __gshared FBO fboOccluders;
180 __gshared Shader shadToPolar, shadBlur, shadBlurOcc, shadAmbient;
181 __gshared TrueColorImage editorImg;
182 __gshared FBO fboEditor;
184 __gshared FBO fboLevel, fboLevelLight, fboOrigBack, fboLMaskSmall;
185 __gshared Shader shadScanlines;
186 __gshared Shader shadLiquidDistort;
189 // ////////////////////////////////////////////////////////////////////////// //
190 // call once!
191 public void initOpenGL () {
192 gloStackClear();
194 glEnable(GL_TEXTURE_2D);
195 glDisable(GL_LIGHTING);
196 glDisable(GL_DITHER);
197 glDisable(GL_BLEND);
198 glDisable(GL_DEPTH_TEST);
200 // create shaders
201 shadScanlines = new Shader("scanlines", loadTextFile("shaders/srscanlines.frag"));
203 shadLiquidDistort = new Shader("liquid_distort", loadTextFile("shaders/srliquid_distort.frag"));
204 shadLiquidDistort.exec((Shader shad) {
205 shad["texLqMap"] = 0;
208 // lights
209 shadToPolar = new Shader("light_trace", loadTextFile("shaders/srlight_trace.frag"));
210 shadToPolar.exec((Shader shad) {
211 shad["texOcc"] = 0;
212 shad["texOccFull"] = 2;
213 shad["texOccSmall"] = 3;
216 shadBlur = new Shader("light_blur", loadTextFile("shaders/srlight_blur.frag"));
217 shadBlur.exec((Shader shad) {
218 shad["texDist"] = 0;
219 shad["texBg"] = 1;
220 shad["texOcc"] = 2;
221 shad["texOccSmall"] = 3;
224 shadBlurOcc = new Shader("light_blur_occ", loadTextFile("shaders/srlight_blur_occ.frag"));
225 shadBlurOcc.exec((Shader shad) {
226 shad["texLMap"] = 0;
227 shad["texBg"] = 1;
228 shad["texOcc"] = 2;
229 shad["texOccSmall"] = 3;
232 shadAmbient = new Shader("light_ambient", loadTextFile("shaders/srlight_ambient.frag"));
233 shadAmbient.exec((Shader shad) {
234 shad["texBg"] = 1;
235 shad["texOcc"] = 2;
236 shad["texOccSmall"] = 3;
239 fboOccluders = new FBO(MaxLightRadius*2, MaxLightRadius*2, Texture.Option.Clamp, Texture.Option.Linear);
240 //TODO: this sux!
241 foreach (int sz; 2..MaxLightRadius+1) {
242 fboDistMap[sz] = new FBO(sz*2, 1, Texture.Option.Clamp, Texture.Option.Linear); // create 1d distance map FBO
245 editorImg = new TrueColorImage(vlWidth, vlHeight);
246 editorImg.imageData.colors[] = Color(0, 0, 0, 0);
247 fboEditor = new FBO(vlWidth, vlHeight, Texture.Option.Nearest);
249 // setup matrices
250 glMatrixMode(GL_MODELVIEW);
251 glLoadIdentity();
253 loadSmFont();
254 loadBfFont();
255 loadAllMonsterGraphics();
259 // ////////////////////////////////////////////////////////////////////////// //
260 // should be called when OpenGL is initialized
261 void loadMap (string mapname) {
262 mapscripts.runUnloading(); // "map unloading" script
263 clearMapScripts();
265 if (map !is null) map.clear();
266 map = new LevelMap(mapname);
267 curmapname = mapname;
269 ugInit(map.width*TileSize, map.height*TileSize);
271 map.oglBuildMega();
272 mapTilesChanged = 0;
274 if (fboLevel !is null) fboLevel.clear();
275 if (fboLevelLight !is null) fboLevelLight.clear();
276 if (fboOrigBack !is null) fboOrigBack.clear();
277 if (fboLMaskSmall !is null) fboLMaskSmall.clear();
279 fboLevel = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // final level render will be here
280 fboLevelLight = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // level lights will be rendered here
281 fboOrigBack = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest/*, Texture.Option.Depth*/); // background+foreground
282 fboLMaskSmall = new FBO(map.width, map.height, Texture.Option.Nearest); // small lightmask
284 shadToPolar.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize-1, map.height*TileSize-1); });
285 shadBlur.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
286 shadBlurOcc.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
287 shadAmbient.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
289 glActiveTexture(GL_TEXTURE0+0);
290 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
291 orthoCamera(vlWidth, vlHeight);
293 Actor.resetStorage();
295 setupMapScripts();
296 mapscripts.runInit();
297 loadMapMonsters();
298 dotInit();
299 mapscripts.runLoaded();
301 // save first snapshot
302 if (prevFrameActorsData.length == 0) prevFrameActorsData = new ubyte[](Actor.actorSize*65536); // ~15-20 megabytes
303 prevFrameActorOfs[] = uint.max; // just for fun
304 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
306 levelLoaded = true;
308 { import core.memory : GC; GC.collect(); }
312 // ////////////////////////////////////////////////////////////////////////// //
313 //FIXME: optimize!
314 __gshared uint mapTilesChanged = 0;
317 //WARNING! this can be called only from DACS, so we don't have to sync it!
318 public void mapDirty (uint layermask) { mapTilesChanged |= layermask; }
321 void rebuildMapMegaTextures () {
322 //fbo.replaceTexture
323 //mapTilesChanged = false;
324 //map.clearMegaTextures();
325 map.oglBuildMega(mapTilesChanged);
326 mapTilesChanged = 0;
327 dotsAwake(); // let dormant dots fall
331 // ////////////////////////////////////////////////////////////////////////// //
332 // messages
333 struct Message {
334 enum Phase { FadeIn, Stay, FadeOut }
335 Phase phase;
336 int alpha;
337 int pauseMsecs;
338 MonoTime removeTime;
339 char[256] text;
340 usize textlen;
343 //private import core.sync.mutex : Mutex;
345 __gshared Message[128] messages;
346 __gshared uint messagesUsed = 0;
348 //__gshared Mutex messageLock;
349 //shared static this () { messageLock = new Mutex(); }
352 void addMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
353 //messageLock.lock();
354 //scope(exit) messageLock.unlock();
355 if (msgtext.length == 0) return;
356 conwriteln(msgtext);
357 if (pauseMsecs <= 50) return;
358 if (messagesUsed == messages.length) {
359 // remove top message
360 foreach (immutable cidx; 1..messagesUsed) messages.ptr[cidx-1] = messages.ptr[cidx];
361 messages.ptr[0].alpha = 255;
362 --messagesUsed;
364 // quick replace
365 if (!noreplace && messagesUsed == 1) {
366 switch (messages.ptr[0].phase) {
367 case Message.Phase.FadeIn:
368 messages.ptr[0].phase = Message.Phase.FadeOut;
369 break;
370 case Message.Phase.Stay:
371 messages.ptr[0].phase = Message.Phase.FadeOut;
372 messages.ptr[0].alpha = 255;
373 break;
374 default:
377 auto msg = messages.ptr+messagesUsed;
378 ++messagesUsed;
379 msg.phase = Message.Phase.FadeIn;
380 msg.alpha = 0;
381 msg.pauseMsecs = pauseMsecs;
382 // copy text
383 if (msgtext.length > msg.text.length) {
384 msg.text = msgtext[0..msg.text.length];
385 msg.textlen = msg.text.length;
386 } else {
387 msg.text[0..msgtext.length] = msgtext[];
388 msg.textlen = msgtext.length;
393 void doMessages (MonoTime curtime) {
394 //messageLock.lock();
395 //scope(exit) messageLock.unlock();
397 if (messagesUsed == 0) return;
398 glEnable(GL_BLEND);
399 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
401 Message* msg;
403 again:
404 msg = messages.ptr;
405 final switch (msg.phase) {
406 case Message.Phase.FadeIn:
407 if ((msg.alpha += 10) >= 255) {
408 msg.phase = Message.Phase.Stay;
409 msg.removeTime = curtime+dur!"msecs"(msg.pauseMsecs);
410 goto case; // to stay
412 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
413 break;
414 case Message.Phase.Stay:
415 if (msg.removeTime <= curtime) {
416 msg.alpha = 255;
417 msg.phase = Message.Phase.FadeOut;
418 goto case; // to fade
420 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
421 break;
422 case Message.Phase.FadeOut:
423 if ((msg.alpha -= 10) <= 0) {
424 if (--messagesUsed == 0) return;
425 // remove this message
426 foreach (immutable cidx; 1..messagesUsed+1) messages.ptr[cidx-1] = messages.ptr[cidx];
427 goto again;
429 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
430 break;
433 smDrawText(10, 10, msg.text[0..msg.textlen]);
437 // ////////////////////////////////////////////////////////////////////////// //
438 //mixin(Actor.FieldPropMixin!("0drawlistpos", uint));
440 mixin(Actor.FieldGetMixin!("classtype", StrId)); // fget_classtype
441 mixin(Actor.FieldGetMixin!("classname", StrId)); // fget_classname
442 mixin(Actor.FieldGetMixin!("x", int));
443 mixin(Actor.FieldGetMixin!("y", int));
444 mixin(Actor.FieldGetMixin!("s", int));
445 mixin(Actor.FieldGetMixin!("radius", int));
446 mixin(Actor.FieldGetMixin!("height", int));
447 mixin(Actor.FieldGetMixin!("flags", uint));
448 mixin(Actor.FieldGetMixin!("zAnimstate", StrId));
449 mixin(Actor.FieldGetMixin!("zAnimidx", int));
450 mixin(Actor.FieldGetMixin!("dir", uint));
451 mixin(Actor.FieldGetMixin!("attLightXOfs", int));
452 mixin(Actor.FieldGetMixin!("attLightYOfs", int));
453 mixin(Actor.FieldGetMixin!("attLightRGBX", uint));
455 //mixin(Actor.FieldGetPtrMixin!("classtype", StrId)); // fget_classtype
456 //mixin(Actor.FieldGetPtrMixin!("classname", StrId)); // fget_classname
457 mixin(Actor.FieldGetPtrMixin!("x", int));
458 mixin(Actor.FieldGetPtrMixin!("y", int));
459 //mixin(Actor.FieldGetPtrMixin!("flags", uint));
460 //mixin(Actor.FieldGetPtrMixin!("zAnimstate", StrId));
461 //mixin(Actor.FieldGetPtrMixin!("zAnimidx", int));
462 //mixin(Actor.FieldGetPtrMixin!("dir", uint));
463 //mixin(Actor.FieldGetPtrMixin!("attLightXOfs", int));
464 //mixin(Actor.FieldGetPtrMixin!("attLightYOfs", int));
465 //mixin(Actor.FieldGetPtrMixin!("attLightRGBX", uint));
468 // ////////////////////////////////////////////////////////////////////////// //
469 __gshared int vportX0, vportY0, vportX1, vportY1;
472 // ////////////////////////////////////////////////////////////////////////// //
473 void renderLightAmbient() (int lightX, int lightY, int lightW, int lightH, in auto ref SVec4F lcol) {
474 //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));
475 if (lightW < 1 || lightH < 1) return;
476 int lightX1 = lightX+lightW-1;
477 int lightY1 = lightY+lightH-1;
478 // clip light to viewport
479 if (lightX < vportX0) lightX = vportX0;
480 if (lightY < vportY0) lightY = vportY0;
481 if (lightX1 > vportX1) lightX1 = vportX1;
482 if (lightY1 > vportY1) lightY1 = vportY1;
483 // is this light visible?
484 //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));
485 if (lightX1 < lightX || lightY1 < lightY || lightX > vportX1 || lightY > vportY1 || lightX1 < vportX0 || lightY1 < vportY0) return;
486 //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));
488 fboLevelLight.exec({
489 glEnable(GL_BLEND);
490 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
491 //glDisable(GL_BLEND);
492 orthoCamera(map.width*TileSize, map.height*TileSize);
493 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
494 shadAmbient.exec((Shader shad) {
495 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
496 //shad["lightPos"] = SVec2F(lightX, lightY);
497 glRectf(lightX, lightY, lightX1, lightY1);
503 // ////////////////////////////////////////////////////////////////////////// //
504 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
505 if (lightRadius < 2) return;
506 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
507 int lightSize = lightRadius*2;
508 // is this light visible?
509 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*TileSize || lightY-lightRadius >= map.height*TileSize) return;
511 // out of viewport -- do nothing
512 if (lightX+lightRadius < vportX0 || lightY+lightRadius < vportY0) return;
513 if (lightX-lightRadius > vportX1 || lightY-lightRadius > vportY1) return;
515 if (lightX >= 0 && lightY >= 0 && lightX < map.width*TileSize && lightY < map.height*TileSize &&
516 map.teximgs[map.LightMask].imageData.colors.ptr[lightY*(map.width*TileSize)+lightX].a > 190) return;
518 // common color for all the following
519 glDisable(GL_BLEND);
520 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
522 // build 1d distance map to fboShadowMapId
523 fboDistMap.ptr[lightRadius].exec({
524 // no need to clear it, shader will take care of that
525 shadToPolar.exec((Shader shad) {
526 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
527 shad["lightPos"] = SVec2F(lightX, lightY);
528 orthoCamera(lightSize, 1);
529 // it doesn't matter what we will draw here, so just draw filled rect
530 glRectf(0, 0, lightSize, 1);
534 // build light texture for blending
535 fboOccluders.exec({
536 // no need to clear it, shader will take care of that
537 // debug
538 //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
539 //glClear(GL_COLOR_BUFFER_BIT);
540 shadBlur.exec((Shader shad) {
541 shad["lightTexSize"] = SVec2F(lightSize, MaxLightRadius*2); // x: size of distmap; y: size of this texture
542 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
543 shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
544 orthoCamera(fboOccluders.tex.width, fboOccluders.tex.height);
545 //drawAtXY(fboDistMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
546 bindTexture(fboDistMap.ptr[lightRadius].tex.tid);
547 glBegin(GL_QUADS);
548 glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0); // top-left
549 glTexCoord2f(1.0f, 0.0f); glVertex2i(lightSize, 0); // top-right
550 glTexCoord2f(1.0f, 1.0f); glVertex2i(lightSize, lightSize); // bottom-right
551 glTexCoord2f(0.0f, 1.0f); glVertex2i(0, lightSize); // bottom-left
552 glEnd();
556 // blend light texture
557 fboLevelLight.exec({
558 glEnable(GL_BLEND);
559 //glDisable(GL_BLEND);
560 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
561 orthoCamera(fboLevelLight.tex.width, fboLevelLight.tex.height);
562 //drawAtXY(fboOccluders.tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
563 float occe = 1.0f*lightSize/(MaxLightRadius*2);
564 float occs = 1.0f-occe;
565 int x0 = lightX-lightRadius+0;
566 int y1 = lightY-lightRadius+0;
567 int x1 = lightX+lightRadius-1+1;
568 int y0 = lightY+lightRadius-1+1;
569 bindTexture(fboOccluders.tex.tid);
570 glBegin(GL_QUADS);
572 glTexCoord2f(0.0f, 0.0f); glVertex2i(x0, y0); // top-left
573 glTexCoord2f(occe, 0.0f); glVertex2i(x1, y0); // top-right
574 glTexCoord2f(occe, occe); glVertex2i(x1, y1); // bottom-right
575 glTexCoord2f(0.0f, occe); glVertex2i(x0, y1); // bottom-left
577 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
578 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
579 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
580 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
581 glEnd();
583 bindTexture(0);
584 glRectf(x0, y0, x1, y1);
586 // and blend it again, with the shader that will touch only occluders
587 shadBlurOcc.exec((Shader shad) {
588 //shad["lightTexSize"] = SVec2F(lightSize, lightSize);
589 shad["lightTexSize"] = SVec2F(lightSize, fboOccluders.tex.height);
590 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
591 shad["lightPos"] = SVec2F(lightX, lightY);
592 //shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
593 bindTexture(fboOccluders.tex.tid);
594 glBegin(GL_QUADS);
595 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
596 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
597 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
598 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
599 glEnd();
600 //drawAtXY(fboOccluders.tex.tid, lightX-lightRadius, lightY-lightRadius, lightSize, lightSize, mirrorY:true);
606 // ////////////////////////////////////////////////////////////////////////// //
607 __gshared int testLightX = vlWidth/2, testLightY = vlHeight/2;
608 __gshared bool testLightMoved = false;
609 //__gshared int mapOfsX, mapOfsY;
610 //__gshared bool movement = false;
611 __gshared float iLiquidTime = 0.0;
612 //__gshared bool altMove = false;
615 void renderScene (MonoTime curtime) {
616 //enum BackIntens = 0.05f;
617 enum BackIntens = 0.0f;
619 gloStackClear();
620 float atob = (curtime > lastthink ? cast(float)((curtime-lastthink).total!"msecs")/cast(float)((nextthink-lastthink).total!"msecs") : 1.0f);
621 if (gamePaused || inEditMode) atob = 1.0f;
622 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
625 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
626 int curfp = cast(int)((curtime-lastthink).total!"msecs");
627 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
631 int mofsx, mofsy; // camera offset, will be set in background layer builder
633 if (mapTilesChanged != 0) rebuildMapMegaTextures();
635 // build background layer
636 fboOrigBack.exec({
637 //glDisable(GL_BLEND);
638 //glClearDepth(1.0f);
639 //glDepthFunc(GL_LESS);
640 //glDepthFunc(GL_NEVER);
641 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
642 glClear(GL_COLOR_BUFFER_BIT/*|GL_DEPTH_BUFFER_BIT*/);
643 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
644 orthoCamera(map.width*TileSize, map.height*TileSize);
646 glEnable(GL_BLEND);
647 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
648 // draw sky
650 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
651 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
652 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
653 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
655 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*TileSize, map.MapSize*TileSize);
656 // draw background
657 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
658 // draw distorted liquid areas
659 shadLiquidDistort.exec((Shader shad) {
660 shad["iDistortTime"] = iLiquidTime;
661 drawAtXY(map.texgl.ptr[map.AllLiquids], 0, 0);
663 // monsters, items; we'll do linear interpolation here
664 glColor3f(1.0f, 1.0f, 1.0f);
665 //glEnable(GL_DEPTH_TEST);
666 attachedLightCount = 0;
668 // who cares about memory?!
669 // draw order: players, items, monsters, other
670 static struct DrawInfo {
671 ActorDef adef;
672 ActorId aid;
673 int actorX, actorY;
674 @disable this (this); // no copies
676 enum { Pixels, Players, Items, Monsters, Other }
677 __gshared DrawInfo[65536][4] drawlists;
678 __gshared uint[4] dlpos;
679 DrawInfo camchickdi;
681 dlpos[] = 0;
683 Actor.forEach((ActorId me) {
684 //me.fprop_0drawlistpos = 0;
685 if (auto adef = findActorDef(me)) {
686 uint dlnum = Other;
687 switch (adef.classtype.get) {
688 case "monster": dlnum = (adef.classname.get != "Player" ? Monsters : Players); break;
689 case "item": dlnum = Items; break;
690 default: dlnum = Other; break;
692 if (me.fget_flags&AF_PIXEL) dlnum = Pixels;
693 int actorX, actorY; // current actor position
695 auto ofs = prevFrameActorOfs.ptr[me.id&0xffff];
696 if (frameInterpolation && ofs < uint.max-1 && (me.fget_flags&AF_TELEPORT) == 0 && Actor.isSameSavedActor(me.id, prevFrameActorsData.ptr, ofs)) {
697 import core.stdc.math : roundf;
698 auto xptr = prevFrameActorsData.ptr+ofs;
699 int ox = xptr.fgetp_x;
700 int nx = me.fget_x;
701 int oy = xptr.fgetp_y;
702 int ny = me.fget_y;
703 actorX = cast(int)(ox+roundf((nx-ox)*atob));
704 actorY = cast(int)(oy+roundf((ny-oy)*atob));
705 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
706 } else {
707 actorX = me.fget_x;
708 actorY = me.fget_y;
711 if (me.id == cameraChick.id) {
712 camchickdi.adef = adef;
713 camchickdi.aid = me;
714 camchickdi.actorX = actorX;
715 camchickdi.actorY = actorY;
717 // draw sprite
718 if ((me.fget_flags&AF_NODRAW) == 0) {
719 //auto dl = &drawlists[dlnum][dlpos.ptr[dlnum]];
720 //me.fprop_0drawlistpos = (dlpos.ptr[dlnum]&0xffff)|((dlnum&0xff)<<16);
721 auto dl = drawlists.ptr[dlnum].ptr+dlpos.ptr[dlnum];
722 ++dlpos.ptr[dlnum];
723 dl.adef = adef;
724 dl.aid = me;
725 dl.actorX = actorX;
726 dl.actorY = actorY;
728 // process attached lights
729 if ((me.fget_flags&AF_NOLIGHT) == 0) {
730 uint alr = me.fget_attLightRGBX;
731 bool isambient = (me.fget_classtype.get == "light" && me.fget_classname.get == "Ambient");
732 if ((alr&0xff) >= 4 || (isambient && me.fget_radius >= 1 && me.fget_height >= 1)) {
733 //if (isambient) conwriteln("isambient: ", isambient, "; x=", me.fget_x, "; y=", me.fget_y, "; w=", me.fget_radius, "; h=", me.fget_height);
734 // yep, add it
735 auto li = attachedLights.ptr+attachedLightCount;
736 ++attachedLightCount;
737 li.type = (!isambient ? AttachedLightInfo.Type.Point : AttachedLightInfo.Type.Ambient);
738 li.x = actorX+me.fget_attLightXOfs;
739 li.y = actorY+me.fget_attLightYOfs;
740 li.r = ((alr>>24)&0xff)/255.0f; // red or intensity
741 if ((alr&0x00_ff_ff_00U) == 0x00_00_01_00U) {
742 li.uncolored = true;
743 } else {
744 li.g = ((alr>>16)&0xff)/255.0f;
745 li.b = ((alr>>8)&0xff)/255.0f;
746 li.uncolored = false;
748 li.radius = (alr&0xff);
749 if (li.radius > MaxLightRadius) li.radius = MaxLightRadius;
750 if (isambient) {
751 li.w = me.fget_radius;
752 li.h = me.fget_height;
756 } else {
757 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
761 // draw actor lists
762 foreach_reverse (uint dlnum; 0..drawlists.length) {
763 if (dlnum == Pixels) continue;
764 auto dl = drawlists.ptr[dlnum].ptr;
765 if (dlnum == Players) dl += dlpos.ptr[dlnum]-1;
766 foreach (uint idx; 0..dlpos.ptr[dlnum]) {
767 auto me = dl.aid;
768 if (auto isp = dl.adef.animSpr(me.fget_zAnimstate, me.fget_dir, me.fget_zAnimidx)) {
769 //drawAtXY(isp.tex, dl.actorX-isp.vga.sx, dl.actorY-isp.vga.sy);
770 isp.drawAtXY(dl.actorX, dl.actorY);
772 if (dlnum != Players) ++dl; else --dl;
775 // draw pixels
776 if (dlpos[Pixels]) {
777 bindTexture(0);
778 bool pointsStarted = false;
779 Color lastColor = Color(0, 0, 0, 0);
780 auto dl = drawlists.ptr[Pixels].ptr;
781 foreach (uint idx; 0..dlpos.ptr[Pixels]) {
782 auto me = dl.aid;
783 auto s = me.fget_s;
784 if (s < 0 || s > 255) continue; //FIXME
785 Color clr = d2dpal.ptr[s&0xff];
786 if (clr.a == 0) continue;
787 if (clr != lastColor) {
788 if (pointsStarted) glEnd();
789 glColor4f(clr.r/255.0f, clr.g/255.0f, clr.b/255.0f, clr.a/255.0f);
790 lastColor = clr;
791 pointsStarted = false;
793 if (!pointsStarted) {
794 glBegin(GL_POINTS);
795 pointsStarted = true;
797 glVertex2i(dl.actorX, dl.actorY);
798 ++dl;
800 if (pointsStarted) {
801 glEnd();
802 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
806 // camera movement
807 if (/*altMove || movement ||*/ scale == 1 || !cameraChick.valid) {
808 mofsx = 0;
809 mofsy = 0;
810 vportX0 = 0;
811 vportY0 = 0;
812 vportX1 = map.width*TileSize;
813 vportY1 = map.height*TileSize;
814 } else {
815 int tiltHeight = /*getMapViewHeight()*/(vlHeight/scale)/4;
816 int vy = cameraChick.looky!int;
817 if (vy < -tiltHeight) vy = -tiltHeight; else if (vy > tiltHeight) vy = tiltHeight;
818 int swdt = vlWidth/scale;
819 int shgt = vlHeight/scale;
820 int x = camchickdi.actorX-swdt/2;
821 int y = (camchickdi.actorY+vy)-shgt/2;
822 if (x < 0) x = 0; else if (x >= map.width*TileSize-swdt) x = map.width*TileSize-swdt-1;
823 if (y < 0) y = 0; else if (y >= map.height*TileSize-shgt) y = map.height*TileSize-shgt-1;
824 mofsx = x*2;
825 mofsy = y*2;
826 vportX0 = mofsx/scale;
827 vportY0 = mofsy/scale;
828 vportX1 = vportX0+vlWidth/scale;
829 vportY1 = vportY0+vlHeight/scale;
832 //glDisable(GL_DEPTH_TEST);
833 // draw dots
834 dotDraw(atob);
835 // do liquid coloring
836 drawAtXY(map.texgl.ptr[map.LiquidMask], 0, 0);
837 // foreground -- hide secrets, draw lifts and such
838 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
841 enum r = 255;
842 enum g = 0;
843 enum b = 0;
844 enum a = 255;
845 bindTexture(0);
846 glColor4f(r/255.0f, g/255.0f, b/255.0f, a/255.0f);
847 glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
848 if (cameraChick.valid) {
849 glBegin(GL_POINTS);
850 glVertex2i(camchickdi.actorX, camchickdi.actorY-70);
851 glEnd();
852 //glRectf(camchickdi.actorX, camchickdi.actorY-70, camchickdi.actorX+4, camchickdi.actorY-70+4);
854 //glRectf(0, 0, 300, 300);
855 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
861 if (doLighting) {
862 glDisable(GL_BLEND);
863 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
865 // make smaller occluder texture, so we can trace faster
866 //assert(fboLMaskSmall.tex.width == map.width);
867 //assert(fboLMaskSmall.tex.height == map.height);
868 fboLMaskSmall.exec({
869 orthoCamera(map.width, map.height);
870 drawAtXY(map.texgl.ptr[map.LightMask].tid, 0, 0, map.width, map.height, mirrorY:true);
873 // clear light layer
874 fboLevelLight.exec({
875 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
876 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
877 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
878 glClear(GL_COLOR_BUFFER_BIT);
881 // texture 1 is background
882 glActiveTexture(GL_TEXTURE0+1);
883 glBindTexture(GL_TEXTURE_2D, fboOrigBack.tex.tid);
884 // texture 2 is occluders
885 glActiveTexture(GL_TEXTURE0+2);
886 glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.LightMask].tid);
887 // texture 3 is small occluder map
888 glActiveTexture(GL_TEXTURE0+3);
889 glBindTexture(GL_TEXTURE_2D, fboLMaskSmall.tex.tid);
890 // done texture assign
891 glActiveTexture(GL_TEXTURE0+0);
894 enum LYOfs = 1;
896 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
897 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
898 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
899 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
900 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
901 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
902 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
903 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
904 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
905 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
907 renderLight(24*TileSize+4, (24+18)*TileSize-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
909 renderLight(280, 330, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
912 foreach (; 0..1) {
913 // attached lights
914 foreach (ref li; attachedLights[0..attachedLightCount]) {
915 if (li.type == AttachedLightInfo.Type.Ambient) {
916 //conwriteln("ambient: x=", li.x, "; y=", li.y, "; w=", li.w, "; h=", li.h);
917 // ambient light
918 if (li.uncolored) {
919 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(0.0f, 0.0f, 0.0f, li.r));
920 } else {
921 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(li.r, li.g, li.b, 1.0f));
923 } else if (li.type == AttachedLightInfo.Type.Point) {
924 // point light
925 if (li.uncolored) {
926 renderLight(li.x, li.y, SVec4F(0.0f, 0.0f, 0.0f, li.r), li.radius);
927 } else {
928 renderLight(li.x, li.y, SVec4F(li.r, li.g, li.b, 1.0f), li.radius);
935 if (testLightMoved) {
936 testLightX = testLightX/scale+mofsx/scale;
937 testLightY = testLightY/scale+mofsy/scale;
938 testLightMoved = false;
940 foreach (immutable _; 0..1) {
941 renderLight(testLightX, testLightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
945 glActiveTexture(GL_TEXTURE0+1);
946 glBindTexture(GL_TEXTURE_2D, 0);
947 glActiveTexture(GL_TEXTURE0+2);
948 glBindTexture(GL_TEXTURE_2D, 0);
949 glActiveTexture(GL_TEXTURE0+3);
950 glBindTexture(GL_TEXTURE_2D, 0);
951 glActiveTexture(GL_TEXTURE0+0);
954 // draw scaled level
956 shadScanlines.exec((Shader shad) {
957 shad["scanlines"] = scanlines;
958 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
959 glClear(GL_COLOR_BUFFER_BIT);
960 orthoCamera(vlWidth, vlHeight);
961 //orthoCamera(map.width*TileSize*scale, map.height*TileSize*scale);
962 //glMatrixMode(GL_MODELVIEW);
963 //glLoadIdentity();
964 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
965 // somehow, FBO objects are mirrored; wtf?!
966 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
967 //glLoadIdentity();
972 fboLevelLight.exec({
973 smDrawText(map.width*TileSize/2, map.height*TileSize/2, "Testing...");
978 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
980 glDisable(GL_BLEND);
983 fboOrigBack.exec({
984 //auto img = smfont.ptr[0x39];
985 auto img = fftest;
986 if (img !is null) {
987 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
988 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
995 { // http://stackoverflow.com/questions/7207422/setting-up-opengl-multiple-render-targets
996 GLenum[1] buffers = [ GL_BACK_LEFT, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
997 //GLenum[1] buffers = [ GL_NONE, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
998 glDrawBuffers(1, buffers.ptr);
1003 orthoCamera(vlWidth, vlHeight);
1004 auto tex = (doLighting ? fboLevelLight.tex.tid : fboOrigBack.tex.tid);
1005 drawAtXY(tex, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1007 if (levelLoaded) {
1008 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1009 //orthoCamera(map.width*TileSize, map.height*TileSize);
1010 glEnable(GL_BLEND);
1011 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1012 hudScripts.runDraw();
1015 //drawAtXY(map.texgl.ptr[map.LightMask].tid, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1016 //drawAtXY(fboLMaskSmall.tex.tid, 0, 0, map.width*TileSize, map.height*TileSize);
1018 if (inEditMode) {
1019 glEnable(GL_BLEND);
1020 //glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1021 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1022 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1023 editorUpdateImage();
1024 fboEditor.tex.setFromImage(editorImg);
1025 drawAtXY(fboEditor.tex, 0, 0);
1026 glDisable(GL_BLEND);
1029 doMessages(curtime);
1033 // ////////////////////////////////////////////////////////////////////////// //
1034 // returns time slept
1035 int sleepAtMaxMsecs (int msecs) {
1036 if (msecs > 0) {
1037 import core.sys.posix.signal : timespec;
1038 import core.sys.posix.time : nanosleep;
1039 timespec ts = void, tpassed = void;
1040 ts.tv_sec = 0;
1041 ts.tv_nsec = msecs*1000*1000+(500*1000); // milli to nano
1042 nanosleep(&ts, &tpassed);
1043 return (ts.tv_nsec-tpassed.tv_nsec)/(1000*1000);
1044 } else {
1045 return 0;
1050 // ////////////////////////////////////////////////////////////////////////// //
1051 mixin(import("editor.d"));
1054 // ////////////////////////////////////////////////////////////////////////// //
1055 // rendering thread
1056 shared int diedie = 0;
1058 enum D2DFrameTime = 55; // milliseconds
1059 enum MinFrameTime = 1000/60; // ~60 FPS
1061 public void renderThread (Tid starterTid) {
1062 enum BoolOptVarMsgMixin(string varname) =
1063 "if (msg.toggle) msg.value = !"~varname~";\n"~
1064 "if ("~varname~" != msg.value) "~varname~" = msg.value; else msg.showMessage = false;\n";
1066 send(starterTid, 42);
1067 try {
1068 MonoTime curtime = MonoTime.currTime;
1070 lastthink = curtime; // for interpolator
1071 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1072 MonoTime nextvframe = curtime;
1074 enum MaxFPSFrames = 16;
1075 float frtimes = 0.0f;
1076 int framenum = 0;
1077 int prevFPS = -1;
1078 int hushFrames = 6; // ignore first `hushFrames` frames overtime
1079 MonoTime prevFrameStartTime = curtime;
1081 bool vframeWasLost = false;
1083 void resetFrameTimers () {
1084 MonoTime curtime = MonoTime.currTime;
1085 lastthink = curtime; // for interpolator
1086 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1087 nextvframe = curtime;
1090 void loadNewLevel (string name) {
1092 if (levelLoaded) {
1093 conwriteln("ERROR: can't load new levels yet");
1094 return;
1097 if (name.length == 0) {
1098 conwriteln("ERROR: can't load empty level!");
1100 conwriteln("loading map '", name, "'");
1101 loadMap(name);
1102 resetFrameTimers();
1105 conRegFunc!({
1106 string mn = genNextMapName(0);
1107 if (mn.length) {
1108 nextmapname = null; // clear "exit" flag
1109 loadNewLevel(mn);
1110 } else {
1111 conwriteln("can't skip level");
1113 })("skiplevel", "skip current level");
1115 conRegFunc!({
1116 inEditMode = !inEditMode;
1117 if (inEditMode) sdwindow.hideCursor(); else sdwindow.showCursor();
1118 })("ed_toggle", "toggle editor");
1120 conRegFunc!({
1121 if (inEditMode) {
1122 inEditMode = false;
1123 sdwindow.showCursor();
1125 })("ed_exit", "exit from editor");
1127 conRegFunc!((string mapname) {
1128 nextmapname = null; // clear "exit" flag
1129 loadNewLevel(mapname);
1130 })("map", "load map");
1132 void receiveMessages () {
1133 for (;;) {
1134 import core.time : Duration;
1135 //conwriteln("rendering thread: waiting for messages...");
1136 auto got = receiveTimeout(
1137 Duration.zero, // don't wait
1138 (TMsgMessage msg) {
1139 addMessage(msg.text[0..msg.textlen], msg.pauseMsecs, msg.noreplace);
1141 (TMsgTestLightMove msg) {
1142 testLightX = msg.x;
1143 testLightY = msg.y;
1144 testLightMoved = true;
1146 (TMsgMouseEvent msg) { editorMouseEvent(msg); },
1147 (TMsgKeyEvent msg) { editorKeyEvent(msg); },
1148 (Variant v) {
1149 conwriteln("WARNING: unknown thread message received and ignored");
1152 if (!got) {
1153 // no more messages
1154 //conwriteln("rendering thread: no more messages");
1155 break;
1158 if (nextmapname.length) {
1159 string mn = nextmapname;
1160 nextmapname = null; // clear "exit" flag
1161 loadNewLevel(mn);
1165 void processConsoleCommands () {
1166 concmdbufLock.lock();
1167 scope(exit) { concmdbufpos = 0; concmdbufLock.unlock(); }
1168 uint pos = 0;
1169 for (;;) {
1170 auto cmd = concmdGet(pos);
1171 if (cmd is null) break;
1172 conExecute(cmd);
1176 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1177 bool doThinkFrame () {
1178 if (curtime >= nextthink) {
1179 lastthink = curtime;
1180 while (nextthink <= curtime) nextthink += dur!"msecs"(D2DFrameTime);
1181 if (levelLoaded) {
1182 // save snapshot and other data for interpolator
1183 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
1184 if (!gamePaused && !inEditMode) {
1185 // process actors
1186 doActorsThink();
1187 dotThink();
1190 // some timing
1191 auto tm = MonoTime.currTime;
1192 int thinkTime = cast(int)((tm-curtime).total!"msecs");
1193 if (thinkTime > 9) { import core.stdc.stdio; printf("spent on thinking: %d msecs\n", thinkTime); }
1194 curtime = tm;
1195 return true;
1196 } else {
1197 return false;
1201 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1202 bool doVFrame () {
1203 version(dont_use_vsync) {
1204 // timer
1205 enum doCheckTime = true;
1206 } else {
1207 // vsync
1208 __gshared bool prevLost = false;
1209 bool doCheckTime = vframeWasLost;
1210 if (vframeWasLost) {
1211 if (!prevLost) {
1212 { import core.stdc.stdio; printf("frame was lost!\n"); }
1214 prevLost = true;
1215 } else {
1216 prevLost = false;
1219 if (doCheckTime) {
1220 if (curtime < nextvframe) return false;
1221 version(dont_use_vsync) {
1222 if (curtime > nextvframe) {
1223 auto overtime = cast(int)((curtime-nextvframe).total!"msecs");
1224 if (overtime > 2500) {
1225 if (hushFrames) {
1226 --hushFrames;
1227 } else {
1228 { import core.stdc.stdio; printf(" spent whole %d msecs\n", overtime); }
1234 while (nextvframe <= curtime) nextvframe += dur!"msecs"(MinFrameTime);
1235 bool ctset = false;
1237 sdwindow.mtLock();
1238 scope(exit) sdwindow.mtUnlock();
1239 ctset = sdwindow.setAsCurrentOpenGlContextNT;
1241 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
1242 if (ctset) {
1243 // render scene
1244 iLiquidTime = cast(float)((curtime-MonoTime.zero).total!"msecs"%10000000)/18.0f*0.04f;
1245 receiveMessages(); // here, 'cause we need active OpenGL context for some messages
1246 processConsoleCommands();
1247 if (levelLoaded) {
1248 renderScene(curtime);
1249 } else {
1250 //renderLoading(curtime);
1252 sdwindow.mtLock();
1253 scope(exit) sdwindow.mtUnlock();
1254 sdwindow.swapOpenGlBuffers();
1255 glFinish();
1256 sdwindow.releaseCurrentOpenGlContext();
1257 vframeWasLost = false;
1258 } else {
1259 vframeWasLost = true;
1260 { import core.stdc.stdio; printf("xframe was lost!\n"); }
1262 curtime = MonoTime.currTime;
1263 return true;
1266 for (;;) {
1267 if (sdwindow.closed) break;
1268 if (atomicLoad(diedie) > 0) break;
1270 curtime = MonoTime.currTime;
1271 auto fstime = curtime;
1273 doThinkFrame(); // this will fix curtime if necessary
1274 if (doVFrame()) {
1275 if (!vframeWasLost) {
1276 // fps
1277 auto frameTime = cast(float)(curtime-prevFrameStartTime).total!"msecs"/1000.0f;
1278 prevFrameStartTime = curtime;
1279 frtimes += frameTime;
1280 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
1281 import std.string : format;
1282 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
1283 if (newFPS != prevFPS) {
1284 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
1285 prevFPS = newFPS;
1287 framenum = 0;
1288 frtimes = 0.0f;
1293 curtime = MonoTime.currTime;
1295 // now sleep until next "video" or "think" frame
1296 if (nextthink > curtime && nextvframe > curtime) {
1297 // let's decide
1298 immutable nextVideoFrameSleep = cast(int)((nextvframe-curtime).total!"msecs");
1299 immutable nextThinkFrameSleep = cast(int)((nextthink-curtime).total!"msecs");
1300 immutable sleepTime = (nextVideoFrameSleep < nextThinkFrameSleep ? nextVideoFrameSleep : nextThinkFrameSleep);
1301 sleepAtMaxMsecs(sleepTime);
1302 //curtime = MonoTime.currTime;
1305 } catch (Throwable e) {
1306 // here, we are dead and fucked (the exact order doesn't matter)
1307 import core.stdc.stdlib : abort;
1308 import core.stdc.stdio : fprintf, stderr;
1309 import core.memory : GC;
1310 GC.disable(); // yeah
1311 thread_suspendAll(); // stop right here, you criminal scum!
1312 auto s = e.toString();
1313 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
1314 abort(); // die, you bitch!
1316 import core.stdc.stdio;
1317 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
1318 import std.stdio : stderr;
1319 writeln(
1320 for (;;) {
1321 if (sdwindow.closed) break;
1322 if (atomicLoad(diedie) > 0) break;
1323 sleepAtMaxMsecs(100);
1327 atomicStore(diedie, 2);
1331 // ////////////////////////////////////////////////////////////////////////// //
1332 __gshared Tid renderTid;
1333 shared bool renderThreadStarted = false;
1336 public void startRenderThread () {
1337 if (!cas(&renderThreadStarted, false, true)) {
1338 assert(0, "render thread already started!");
1340 renderTid = spawn(&renderThread, thisTid);
1341 setMaxMailboxSize(renderTid, 1024, OnCrowding.throwException); //FIXME
1342 // wait for "i'm ready" signal
1343 receive(
1344 (int ok) {
1345 if (ok != 42) assert(0, "wtf?!");
1348 conwriteln("rendering thread started");
1352 // ////////////////////////////////////////////////////////////////////////// //
1353 public void closeWindow () {
1354 if (atomicLoad(diedie) != 2) {
1355 atomicStore(diedie, 1);
1356 while (atomicLoad(diedie) != 2) {}
1358 if (!sdwindow.closed) {
1359 flushGui();
1360 sdwindow.close();
1365 // ////////////////////////////////////////////////////////////////////////// //
1366 // thread messages
1369 // ////////////////////////////////////////////////////////////////////////// //
1370 struct TMsgMouseEvent {
1371 MouseEventType type;
1372 int x, y;
1373 int dx, dy;
1374 MouseButton button; /// See $(LREF MouseButton)
1375 int modifierState; /// See $(LREF ModifierState)
1378 public void postMouseEvent() (in auto ref MouseEvent evt) {
1379 if (!atomicLoad(renderThreadStarted)) return;
1380 TMsgMouseEvent msg;
1381 msg.type = evt.type;
1382 msg.x = evt.x;
1383 msg.y = evt.y;
1384 msg.dx = evt.dx;
1385 msg.dy = evt.dy;
1386 msg.button = evt.button;
1387 msg.modifierState = evt.modifierState;
1388 send(renderTid, msg);
1392 // ////////////////////////////////////////////////////////////////////////// //
1393 struct TMsgKeyEvent {
1394 Key key;
1395 uint hardwareCode;
1396 bool pressed;
1397 dchar character;
1398 uint modifierState;
1401 public void postKeyEvent() (in auto ref KeyEvent evt) {
1402 if (!atomicLoad(renderThreadStarted)) return;
1403 TMsgKeyEvent msg;
1404 msg.key = evt.key;
1405 msg.pressed = evt.pressed;
1406 msg.character = evt.character;
1407 msg.modifierState = evt.modifierState;
1408 send(renderTid, msg);
1412 // ////////////////////////////////////////////////////////////////////////// //
1413 struct TMsgTestLightMove {
1414 int x, y;
1417 public void postTestLightMove (int x, int y) {
1418 if (!atomicLoad(renderThreadStarted)) return;
1419 auto msg = TMsgTestLightMove(x, y);
1420 send(renderTid, msg);
1424 // ////////////////////////////////////////////////////////////////////////// //
1425 struct TMsgMessage {
1426 char[256] text;
1427 uint textlen;
1428 int pauseMsecs;
1429 bool noreplace;
1432 public void postAddMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
1433 if (!atomicLoad(renderThreadStarted)) return;
1434 if (msgtext.length > TMsgMessage.text.length) msgtext = msgtext[0..TMsgMessage.text.length];
1435 TMsgMessage msg;
1436 msg.textlen = cast(uint)msgtext.length;
1437 if (msg.textlen) msg.text[0..msg.textlen] = msgtext[0..msg.textlen];
1438 msg.pauseMsecs = pauseMsecs;
1439 msg.noreplace = noreplace;
1440 send(renderTid, msg);
1444 // ////////////////////////////////////////////////////////////////////////// //
1445 public void concmd (const(char)[] cmd) {
1446 //if (!atomicLoad(renderThreadStarted)) return;
1447 concmdbufLock.lock();
1448 scope(exit) concmdbufLock.unlock();
1449 concmdAdd(cmd);