added storage buffer for console output
[dd2d.git] / render.d
blob33dacb30f989e007a61467a532b530563ca175f6
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[] concmdbuf;
90 __gshared uint concmdbufpos;
91 private import core.sync.mutex : Mutex;
92 __gshared Mutex concmdbufLock;
93 shared static this () { concmdbuf.length = 65536; concmdbufLock = new Mutex(); }
96 void concmdAdd (const(char)[] s) {
97 if (s.length) {
98 if (concmdbuf.length-concmdbufpos < s.length+1) {
99 concmdbuf.assumeSafeAppend.length += s.length-(concmdbuf.length-concmdbufpos)+512;
101 if (concmdbufpos > 0 && concmdbuf[concmdbufpos-1] != '\n') concmdbuf.ptr[concmdbufpos++] = '\n';
102 concmdbuf[concmdbufpos..concmdbufpos+s.length] = s[];
103 concmdbufpos += s.length;
107 // `null`: no more
108 void concmdDoAll () {
109 if (concmdbufpos == 0) return;
110 scope(exit) concmdbufpos = 0;
111 auto ebuf = concmdbufpos;
112 const(char)[] s = concmdbuf[0..concmdbufpos];
113 for (;;) {
114 while (s.length) {
115 auto cmd = conGetCommand(s);
116 if (cmd is null) break;
117 try {
118 conExecute(cmd);
119 } catch (Exception e) {
120 conwriteln("***ERROR: ", e.msg);
123 if (concmdbufpos <= ebuf) break;
124 s = concmdbuf[ebuf..concmdbufpos];
125 ebuf = concmdbufpos;
130 // ////////////////////////////////////////////////////////////////////////// //
131 shared static this () {
132 conRegVar!doLighting("r_lighting", "dynamic lighting");
133 conRegVar!gamePaused("g_pause", "pause game");
134 conRegVar!frameInterpolation("r_interpolation", "interpolate camera and sprite movement");
135 conRegVar!scale(1, 2, "r_scale");
136 conRegFunc!({
137 cheatNoDoors = !cheatNoDoors;
138 if (cheatNoDoors) conwriteln("player ignores doors"); else conwriteln("player respects doors");
139 })("nodoorclip", "ignore doors");
140 conRegFunc!({
141 cheatNoWallClip = !cheatNoWallClip;
142 if (cheatNoWallClip) conwriteln("player ignores walls"); else conwriteln("player respects walls");
143 })("nowallclip", "ignore walls");
144 conRegFunc!({
145 import core.atomic;
146 atomicStore(vquitRequested, true);
147 })("quit", "quit game");
148 conRegFunc!((const(char)[] msg, int pauseMsecs=3000, bool noreplace=false) {
149 char[256] buf;
150 auto s = buf.conFormatStr(msg);
151 if (s.length) postAddMessage(s, pauseMsecs, noreplace);
152 })("hudmsg", "show hud message; hudmsg msg [pausemsecs [noreplace]]");
156 // ////////////////////////////////////////////////////////////////////////// //
157 // interpolation
158 __gshared ubyte[] prevFrameActorsData;
159 __gshared uint[65536] prevFrameActorOfs; // uint.max-1: dead; uint.max: last
160 __gshared MonoTime lastthink = MonoTime.zero; // for interpolator
161 __gshared MonoTime nextthink = MonoTime.zero;
162 __gshared bool frameInterpolation = true;
165 // ////////////////////////////////////////////////////////////////////////// //
166 // attached lights
167 struct AttachedLightInfo {
168 enum Type {
169 Point,
170 Ambient,
172 Type type;
173 int x, y;
174 int w, h; // for ambient lights
175 float r, g, b;
176 bool uncolored;
177 int radius;
180 __gshared AttachedLightInfo[65536] attachedLights;
181 __gshared uint attachedLightCount = 0;
184 // ////////////////////////////////////////////////////////////////////////// //
185 enum MaxLightRadius = 255;
188 // ////////////////////////////////////////////////////////////////////////// //
189 // for light
190 __gshared FBO[MaxLightRadius+1] fboDistMap;
191 __gshared FBO fboOccluders;
192 __gshared Shader shadToPolar, shadBlur, shadBlurOcc, shadAmbient;
193 __gshared TrueColorImage editorImg;
194 __gshared FBO fboEditor;
196 __gshared FBO fboLevel, fboLevelLight, fboOrigBack, fboLMaskSmall;
197 __gshared Shader shadScanlines;
198 __gshared Shader shadLiquidDistort;
201 // ////////////////////////////////////////////////////////////////////////// //
202 // call once!
203 public void initOpenGL () {
204 gloStackClear();
206 glEnable(GL_TEXTURE_2D);
207 glDisable(GL_LIGHTING);
208 glDisable(GL_DITHER);
209 glDisable(GL_BLEND);
210 glDisable(GL_DEPTH_TEST);
212 // create shaders
213 shadScanlines = new Shader("scanlines", loadTextFile("shaders/srscanlines.frag"));
215 shadLiquidDistort = new Shader("liquid_distort", loadTextFile("shaders/srliquid_distort.frag"));
216 shadLiquidDistort.exec((Shader shad) {
217 shad["texLqMap"] = 0;
220 // lights
221 shadToPolar = new Shader("light_trace", loadTextFile("shaders/srlight_trace.frag"));
222 shadToPolar.exec((Shader shad) {
223 shad["texOcc"] = 0;
224 shad["texOccFull"] = 2;
225 shad["texOccSmall"] = 3;
228 shadBlur = new Shader("light_blur", loadTextFile("shaders/srlight_blur.frag"));
229 shadBlur.exec((Shader shad) {
230 shad["texDist"] = 0;
231 shad["texBg"] = 1;
232 shad["texOcc"] = 2;
233 shad["texOccSmall"] = 3;
236 shadBlurOcc = new Shader("light_blur_occ", loadTextFile("shaders/srlight_blur_occ.frag"));
237 shadBlurOcc.exec((Shader shad) {
238 shad["texLMap"] = 0;
239 shad["texBg"] = 1;
240 shad["texOcc"] = 2;
241 shad["texOccSmall"] = 3;
244 shadAmbient = new Shader("light_ambient", loadTextFile("shaders/srlight_ambient.frag"));
245 shadAmbient.exec((Shader shad) {
246 shad["texBg"] = 1;
247 shad["texOcc"] = 2;
248 shad["texOccSmall"] = 3;
251 fboOccluders = new FBO(MaxLightRadius*2, MaxLightRadius*2, Texture.Option.Clamp, Texture.Option.Linear);
252 //TODO: this sux!
253 foreach (int sz; 2..MaxLightRadius+1) {
254 fboDistMap[sz] = new FBO(sz*2, 1, Texture.Option.Clamp, Texture.Option.Linear); // create 1d distance map FBO
257 editorImg = new TrueColorImage(vlWidth, vlHeight);
258 editorImg.imageData.colors[] = Color(0, 0, 0, 0);
259 fboEditor = new FBO(vlWidth, vlHeight, Texture.Option.Nearest);
261 // setup matrices
262 glMatrixMode(GL_MODELVIEW);
263 glLoadIdentity();
265 loadSmFont();
266 loadBfFont();
267 loadAllMonsterGraphics();
271 // ////////////////////////////////////////////////////////////////////////// //
272 // should be called when OpenGL is initialized
273 void loadMap (string mapname) {
274 mapscripts.runUnloading(); // "map unloading" script
275 clearMapScripts();
277 if (map !is null) map.clear();
278 map = new LevelMap(mapname);
279 curmapname = mapname;
281 ugInit(map.width*TileSize, map.height*TileSize);
283 map.oglBuildMega();
284 mapTilesChanged = 0;
286 if (fboLevel !is null) fboLevel.clear();
287 if (fboLevelLight !is null) fboLevelLight.clear();
288 if (fboOrigBack !is null) fboOrigBack.clear();
289 if (fboLMaskSmall !is null) fboLMaskSmall.clear();
291 fboLevel = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // final level render will be here
292 fboLevelLight = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest); // level lights will be rendered here
293 fboOrigBack = new FBO(map.width*TileSize, map.height*TileSize, Texture.Option.Nearest/*, Texture.Option.Depth*/); // background+foreground
294 fboLMaskSmall = new FBO(map.width, map.height, Texture.Option.Nearest); // small lightmask
296 shadToPolar.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize-1, map.height*TileSize-1); });
297 shadBlur.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
298 shadBlurOcc.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
299 shadAmbient.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*TileSize, map.height*TileSize); });
301 glActiveTexture(GL_TEXTURE0+0);
302 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
303 orthoCamera(vlWidth, vlHeight);
305 Actor.resetStorage();
307 setupMapScripts();
308 mapscripts.runInit();
309 loadMapMonsters();
310 dotInit();
311 mapscripts.runLoaded();
313 // save first snapshot
314 if (prevFrameActorsData.length == 0) prevFrameActorsData = new ubyte[](Actor.actorSize*65536); // ~15-20 megabytes
315 prevFrameActorOfs[] = uint.max; // just for fun
316 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
318 levelLoaded = true;
320 { import core.memory : GC; GC.collect(); }
324 // ////////////////////////////////////////////////////////////////////////// //
325 //FIXME: optimize!
326 __gshared uint mapTilesChanged = 0;
329 //WARNING! this can be called only from DACS, so we don't have to sync it!
330 public void mapDirty (uint layermask) { mapTilesChanged |= layermask; }
333 void rebuildMapMegaTextures () {
334 //fbo.replaceTexture
335 //mapTilesChanged = false;
336 //map.clearMegaTextures();
337 map.oglBuildMega(mapTilesChanged);
338 mapTilesChanged = 0;
339 dotsAwake(); // let dormant dots fall
343 // ////////////////////////////////////////////////////////////////////////// //
344 // messages
345 struct Message {
346 enum Phase { FadeIn, Stay, FadeOut }
347 Phase phase;
348 int alpha;
349 int pauseMsecs;
350 MonoTime removeTime;
351 char[256] text;
352 usize textlen;
355 //private import core.sync.mutex : Mutex;
357 __gshared Message[128] messages;
358 __gshared uint messagesUsed = 0;
360 //__gshared Mutex messageLock;
361 //shared static this () { messageLock = new Mutex(); }
364 void addMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
365 //messageLock.lock();
366 //scope(exit) messageLock.unlock();
367 if (msgtext.length == 0) return;
368 conwriteln(msgtext);
369 if (pauseMsecs <= 50) return;
370 if (messagesUsed == messages.length) {
371 // remove top message
372 foreach (immutable cidx; 1..messagesUsed) messages.ptr[cidx-1] = messages.ptr[cidx];
373 messages.ptr[0].alpha = 255;
374 --messagesUsed;
376 // quick replace
377 if (!noreplace && messagesUsed == 1) {
378 switch (messages.ptr[0].phase) {
379 case Message.Phase.FadeIn:
380 messages.ptr[0].phase = Message.Phase.FadeOut;
381 break;
382 case Message.Phase.Stay:
383 messages.ptr[0].phase = Message.Phase.FadeOut;
384 messages.ptr[0].alpha = 255;
385 break;
386 default:
389 auto msg = messages.ptr+messagesUsed;
390 ++messagesUsed;
391 msg.phase = Message.Phase.FadeIn;
392 msg.alpha = 0;
393 msg.pauseMsecs = pauseMsecs;
394 // copy text
395 if (msgtext.length > msg.text.length) {
396 msg.text = msgtext[0..msg.text.length];
397 msg.textlen = msg.text.length;
398 } else {
399 msg.text[0..msgtext.length] = msgtext[];
400 msg.textlen = msgtext.length;
405 void doMessages (MonoTime curtime) {
406 //messageLock.lock();
407 //scope(exit) messageLock.unlock();
409 if (messagesUsed == 0) return;
410 glEnable(GL_BLEND);
411 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
413 Message* msg;
415 again:
416 msg = messages.ptr;
417 final switch (msg.phase) {
418 case Message.Phase.FadeIn:
419 if ((msg.alpha += 10) >= 255) {
420 msg.phase = Message.Phase.Stay;
421 msg.removeTime = curtime+dur!"msecs"(msg.pauseMsecs);
422 goto case; // to stay
424 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
425 break;
426 case Message.Phase.Stay:
427 if (msg.removeTime <= curtime) {
428 msg.alpha = 255;
429 msg.phase = Message.Phase.FadeOut;
430 goto case; // to fade
432 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
433 break;
434 case Message.Phase.FadeOut:
435 if ((msg.alpha -= 10) <= 0) {
436 if (--messagesUsed == 0) return;
437 // remove this message
438 foreach (immutable cidx; 1..messagesUsed+1) messages.ptr[cidx-1] = messages.ptr[cidx];
439 goto again;
441 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
442 break;
445 smDrawText(10, 10, msg.text[0..msg.textlen]);
449 // ////////////////////////////////////////////////////////////////////////// //
450 //mixin(Actor.FieldPropMixin!("0drawlistpos", uint));
452 mixin(Actor.FieldGetMixin!("classtype", StrId)); // fget_classtype
453 mixin(Actor.FieldGetMixin!("classname", StrId)); // fget_classname
454 mixin(Actor.FieldGetMixin!("x", int));
455 mixin(Actor.FieldGetMixin!("y", int));
456 mixin(Actor.FieldGetMixin!("s", int));
457 mixin(Actor.FieldGetMixin!("radius", int));
458 mixin(Actor.FieldGetMixin!("height", int));
459 mixin(Actor.FieldGetMixin!("flags", uint));
460 mixin(Actor.FieldGetMixin!("zAnimstate", StrId));
461 mixin(Actor.FieldGetMixin!("zAnimidx", int));
462 mixin(Actor.FieldGetMixin!("dir", uint));
463 mixin(Actor.FieldGetMixin!("attLightXOfs", int));
464 mixin(Actor.FieldGetMixin!("attLightYOfs", int));
465 mixin(Actor.FieldGetMixin!("attLightRGBX", uint));
467 //mixin(Actor.FieldGetPtrMixin!("classtype", StrId)); // fget_classtype
468 //mixin(Actor.FieldGetPtrMixin!("classname", StrId)); // fget_classname
469 mixin(Actor.FieldGetPtrMixin!("x", int));
470 mixin(Actor.FieldGetPtrMixin!("y", int));
471 //mixin(Actor.FieldGetPtrMixin!("flags", uint));
472 //mixin(Actor.FieldGetPtrMixin!("zAnimstate", StrId));
473 //mixin(Actor.FieldGetPtrMixin!("zAnimidx", int));
474 //mixin(Actor.FieldGetPtrMixin!("dir", uint));
475 //mixin(Actor.FieldGetPtrMixin!("attLightXOfs", int));
476 //mixin(Actor.FieldGetPtrMixin!("attLightYOfs", int));
477 //mixin(Actor.FieldGetPtrMixin!("attLightRGBX", uint));
480 // ////////////////////////////////////////////////////////////////////////// //
481 __gshared int vportX0, vportY0, vportX1, vportY1;
484 // ////////////////////////////////////////////////////////////////////////// //
485 void renderLightAmbient() (int lightX, int lightY, int lightW, int lightH, in auto ref SVec4F lcol) {
486 //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));
487 if (lightW < 1 || lightH < 1) return;
488 int lightX1 = lightX+lightW-1;
489 int lightY1 = lightY+lightH-1;
490 // clip light to viewport
491 if (lightX < vportX0) lightX = vportX0;
492 if (lightY < vportY0) lightY = vportY0;
493 if (lightX1 > vportX1) lightX1 = vportX1;
494 if (lightY1 > vportY1) lightY1 = vportY1;
495 // is this light visible?
496 //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));
497 if (lightX1 < lightX || lightY1 < lightY || lightX > vportX1 || lightY > vportY1 || lightX1 < vportX0 || lightY1 < vportY0) return;
498 //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));
500 fboLevelLight.exec({
501 glEnable(GL_BLEND);
502 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
503 //glDisable(GL_BLEND);
504 orthoCamera(map.width*TileSize, map.height*TileSize);
505 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
506 shadAmbient.exec((Shader shad) {
507 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
508 //shad["lightPos"] = SVec2F(lightX, lightY);
509 glRectf(lightX, lightY, lightX1, lightY1);
515 // ////////////////////////////////////////////////////////////////////////// //
516 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
517 if (lightRadius < 2) return;
518 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
519 int lightSize = lightRadius*2;
520 // is this light visible?
521 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*TileSize || lightY-lightRadius >= map.height*TileSize) return;
523 // out of viewport -- do nothing
524 if (lightX+lightRadius < vportX0 || lightY+lightRadius < vportY0) return;
525 if (lightX-lightRadius > vportX1 || lightY-lightRadius > vportY1) return;
527 if (lightX >= 0 && lightY >= 0 && lightX < map.width*TileSize && lightY < map.height*TileSize &&
528 map.teximgs[map.LightMask].imageData.colors.ptr[lightY*(map.width*TileSize)+lightX].a > 190) return;
530 // common color for all the following
531 glDisable(GL_BLEND);
532 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
534 // build 1d distance map to fboShadowMapId
535 fboDistMap.ptr[lightRadius].exec({
536 // no need to clear it, shader will take care of that
537 shadToPolar.exec((Shader shad) {
538 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
539 shad["lightPos"] = SVec2F(lightX, lightY);
540 orthoCamera(lightSize, 1);
541 // it doesn't matter what we will draw here, so just draw filled rect
542 glRectf(0, 0, lightSize, 1);
546 // build light texture for blending
547 fboOccluders.exec({
548 // no need to clear it, shader will take care of that
549 // debug
550 //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
551 //glClear(GL_COLOR_BUFFER_BIT);
552 shadBlur.exec((Shader shad) {
553 shad["lightTexSize"] = SVec2F(lightSize, MaxLightRadius*2); // x: size of distmap; y: size of this texture
554 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
555 shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
556 orthoCamera(fboOccluders.tex.width, fboOccluders.tex.height);
557 //drawAtXY(fboDistMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
558 bindTexture(fboDistMap.ptr[lightRadius].tex.tid);
559 glBegin(GL_QUADS);
560 glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0); // top-left
561 glTexCoord2f(1.0f, 0.0f); glVertex2i(lightSize, 0); // top-right
562 glTexCoord2f(1.0f, 1.0f); glVertex2i(lightSize, lightSize); // bottom-right
563 glTexCoord2f(0.0f, 1.0f); glVertex2i(0, lightSize); // bottom-left
564 glEnd();
568 // blend light texture
569 fboLevelLight.exec({
570 glEnable(GL_BLEND);
571 //glDisable(GL_BLEND);
572 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
573 orthoCamera(fboLevelLight.tex.width, fboLevelLight.tex.height);
574 //drawAtXY(fboOccluders.tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
575 float occe = 1.0f*lightSize/(MaxLightRadius*2);
576 float occs = 1.0f-occe;
577 int x0 = lightX-lightRadius+0;
578 int y1 = lightY-lightRadius+0;
579 int x1 = lightX+lightRadius-1+1;
580 int y0 = lightY+lightRadius-1+1;
581 bindTexture(fboOccluders.tex.tid);
582 glBegin(GL_QUADS);
584 glTexCoord2f(0.0f, 0.0f); glVertex2i(x0, y0); // top-left
585 glTexCoord2f(occe, 0.0f); glVertex2i(x1, y0); // top-right
586 glTexCoord2f(occe, occe); glVertex2i(x1, y1); // bottom-right
587 glTexCoord2f(0.0f, occe); glVertex2i(x0, y1); // bottom-left
589 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
590 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
591 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
592 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
593 glEnd();
595 bindTexture(0);
596 glRectf(x0, y0, x1, y1);
598 // and blend it again, with the shader that will touch only occluders
599 shadBlurOcc.exec((Shader shad) {
600 //shad["lightTexSize"] = SVec2F(lightSize, lightSize);
601 shad["lightTexSize"] = SVec2F(lightSize, fboOccluders.tex.height);
602 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
603 shad["lightPos"] = SVec2F(lightX, lightY);
604 //shad["lightPos"] = SVec2F(lightX-lightRadius, lightY-lightRadius);
605 bindTexture(fboOccluders.tex.tid);
606 glBegin(GL_QUADS);
607 glTexCoord2f(0.0f, occs); glVertex2i(x0, y0); // top-left
608 glTexCoord2f(occe, occs); glVertex2i(x1, y0); // top-right
609 glTexCoord2f(occe, 1.0f); glVertex2i(x1, y1); // bottom-right
610 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
611 glEnd();
612 //drawAtXY(fboOccluders.tex.tid, lightX-lightRadius, lightY-lightRadius, lightSize, lightSize, mirrorY:true);
618 // ////////////////////////////////////////////////////////////////////////// //
619 __gshared int testLightX = vlWidth/2, testLightY = vlHeight/2;
620 __gshared bool testLightMoved = false;
621 //__gshared int mapOfsX, mapOfsY;
622 //__gshared bool movement = false;
623 __gshared float iLiquidTime = 0.0;
624 //__gshared bool altMove = false;
627 void renderScene (MonoTime curtime) {
628 //enum BackIntens = 0.05f;
629 enum BackIntens = 0.0f;
631 gloStackClear();
632 float atob = (curtime > lastthink ? cast(float)((curtime-lastthink).total!"msecs")/cast(float)((nextthink-lastthink).total!"msecs") : 1.0f);
633 if (gamePaused || inEditMode) atob = 1.0f;
634 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
637 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
638 int curfp = cast(int)((curtime-lastthink).total!"msecs");
639 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
643 int mofsx, mofsy; // camera offset, will be set in background layer builder
645 if (mapTilesChanged != 0) rebuildMapMegaTextures();
647 // build background layer
648 fboOrigBack.exec({
649 //glDisable(GL_BLEND);
650 //glClearDepth(1.0f);
651 //glDepthFunc(GL_LESS);
652 //glDepthFunc(GL_NEVER);
653 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
654 glClear(GL_COLOR_BUFFER_BIT/*|GL_DEPTH_BUFFER_BIT*/);
655 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
656 orthoCamera(map.width*TileSize, map.height*TileSize);
658 glEnable(GL_BLEND);
659 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
660 // draw sky
662 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
663 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*TileSize, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
664 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*TileSize, map.MapSize*TileSize, map.MapSize*TileSize);
665 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*TileSize, map.MapSize*TileSize);
667 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*TileSize, map.MapSize*TileSize);
668 // draw background
669 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
670 // draw distorted liquid areas
671 shadLiquidDistort.exec((Shader shad) {
672 shad["iDistortTime"] = iLiquidTime;
673 drawAtXY(map.texgl.ptr[map.AllLiquids], 0, 0);
675 // monsters, items; we'll do linear interpolation here
676 glColor3f(1.0f, 1.0f, 1.0f);
677 //glEnable(GL_DEPTH_TEST);
678 attachedLightCount = 0;
680 // who cares about memory?!
681 // draw order: players, items, monsters, other
682 static struct DrawInfo {
683 ActorDef adef;
684 ActorId aid;
685 int actorX, actorY;
686 @disable this (this); // no copies
688 enum { Pixels, Players, Items, Monsters, Other }
689 __gshared DrawInfo[65536][4] drawlists;
690 __gshared uint[4] dlpos;
691 DrawInfo camchickdi;
693 dlpos[] = 0;
695 Actor.forEach((ActorId me) {
696 //me.fprop_0drawlistpos = 0;
697 if (auto adef = findActorDef(me)) {
698 uint dlnum = Other;
699 switch (adef.classtype.get) {
700 case "monster": dlnum = (adef.classname.get != "Player" ? Monsters : Players); break;
701 case "item": dlnum = Items; break;
702 default: dlnum = Other; break;
704 if (me.fget_flags&AF_PIXEL) dlnum = Pixels;
705 int actorX, actorY; // current actor position
707 auto ofs = prevFrameActorOfs.ptr[me.id&0xffff];
708 if (frameInterpolation && ofs < uint.max-1 && (me.fget_flags&AF_TELEPORT) == 0 && Actor.isSameSavedActor(me.id, prevFrameActorsData.ptr, ofs)) {
709 import core.stdc.math : roundf;
710 auto xptr = prevFrameActorsData.ptr+ofs;
711 int ox = xptr.fgetp_x;
712 int nx = me.fget_x;
713 int oy = xptr.fgetp_y;
714 int ny = me.fget_y;
715 actorX = cast(int)(ox+roundf((nx-ox)*atob));
716 actorY = cast(int)(oy+roundf((ny-oy)*atob));
717 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
718 } else {
719 actorX = me.fget_x;
720 actorY = me.fget_y;
723 if (me.id == cameraChick.id) {
724 camchickdi.adef = adef;
725 camchickdi.aid = me;
726 camchickdi.actorX = actorX;
727 camchickdi.actorY = actorY;
729 // draw sprite
730 if ((me.fget_flags&AF_NODRAW) == 0) {
731 //auto dl = &drawlists[dlnum][dlpos.ptr[dlnum]];
732 //me.fprop_0drawlistpos = (dlpos.ptr[dlnum]&0xffff)|((dlnum&0xff)<<16);
733 auto dl = drawlists.ptr[dlnum].ptr+dlpos.ptr[dlnum];
734 ++dlpos.ptr[dlnum];
735 dl.adef = adef;
736 dl.aid = me;
737 dl.actorX = actorX;
738 dl.actorY = actorY;
740 // process attached lights
741 if ((me.fget_flags&AF_NOLIGHT) == 0) {
742 uint alr = me.fget_attLightRGBX;
743 bool isambient = (me.fget_classtype.get == "light" && me.fget_classname.get == "Ambient");
744 if ((alr&0xff) >= 4 || (isambient && me.fget_radius >= 1 && me.fget_height >= 1)) {
745 //if (isambient) conwriteln("isambient: ", isambient, "; x=", me.fget_x, "; y=", me.fget_y, "; w=", me.fget_radius, "; h=", me.fget_height);
746 // yep, add it
747 auto li = attachedLights.ptr+attachedLightCount;
748 ++attachedLightCount;
749 li.type = (!isambient ? AttachedLightInfo.Type.Point : AttachedLightInfo.Type.Ambient);
750 li.x = actorX+me.fget_attLightXOfs;
751 li.y = actorY+me.fget_attLightYOfs;
752 li.r = ((alr>>24)&0xff)/255.0f; // red or intensity
753 if ((alr&0x00_ff_ff_00U) == 0x00_00_01_00U) {
754 li.uncolored = true;
755 } else {
756 li.g = ((alr>>16)&0xff)/255.0f;
757 li.b = ((alr>>8)&0xff)/255.0f;
758 li.uncolored = false;
760 li.radius = (alr&0xff);
761 if (li.radius > MaxLightRadius) li.radius = MaxLightRadius;
762 if (isambient) {
763 li.w = me.fget_radius;
764 li.h = me.fget_height;
768 } else {
769 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
773 // draw actor lists
774 foreach_reverse (uint dlnum; 0..drawlists.length) {
775 if (dlnum == Pixels) continue;
776 auto dl = drawlists.ptr[dlnum].ptr;
777 if (dlnum == Players) dl += dlpos.ptr[dlnum]-1;
778 foreach (uint idx; 0..dlpos.ptr[dlnum]) {
779 auto me = dl.aid;
780 if (auto isp = dl.adef.animSpr(me.fget_zAnimstate, me.fget_dir, me.fget_zAnimidx)) {
781 //drawAtXY(isp.tex, dl.actorX-isp.vga.sx, dl.actorY-isp.vga.sy);
782 isp.drawAtXY(dl.actorX, dl.actorY);
784 if (dlnum != Players) ++dl; else --dl;
787 // draw pixels
788 if (dlpos[Pixels]) {
789 bindTexture(0);
790 bool pointsStarted = false;
791 Color lastColor = Color(0, 0, 0, 0);
792 auto dl = drawlists.ptr[Pixels].ptr;
793 foreach (uint idx; 0..dlpos.ptr[Pixels]) {
794 auto me = dl.aid;
795 auto s = me.fget_s;
796 if (s < 0 || s > 255) continue; //FIXME
797 Color clr = d2dpal.ptr[s&0xff];
798 if (clr.a == 0) continue;
799 if (clr != lastColor) {
800 if (pointsStarted) glEnd();
801 glColor4f(clr.r/255.0f, clr.g/255.0f, clr.b/255.0f, clr.a/255.0f);
802 lastColor = clr;
803 pointsStarted = false;
805 if (!pointsStarted) {
806 glBegin(GL_POINTS);
807 pointsStarted = true;
809 glVertex2i(dl.actorX, dl.actorY);
810 ++dl;
812 if (pointsStarted) {
813 glEnd();
814 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
818 // camera movement
819 if (/*altMove || movement ||*/ scale == 1 || !cameraChick.valid) {
820 mofsx = 0;
821 mofsy = 0;
822 vportX0 = 0;
823 vportY0 = 0;
824 vportX1 = map.width*TileSize;
825 vportY1 = map.height*TileSize;
826 } else {
827 int tiltHeight = /*getMapViewHeight()*/(vlHeight/scale)/4;
828 int vy = cameraChick.looky!int;
829 if (vy < -tiltHeight) vy = -tiltHeight; else if (vy > tiltHeight) vy = tiltHeight;
830 int swdt = vlWidth/scale;
831 int shgt = vlHeight/scale;
832 int x = camchickdi.actorX-swdt/2;
833 int y = (camchickdi.actorY+vy)-shgt/2;
834 if (x < 0) x = 0; else if (x >= map.width*TileSize-swdt) x = map.width*TileSize-swdt-1;
835 if (y < 0) y = 0; else if (y >= map.height*TileSize-shgt) y = map.height*TileSize-shgt-1;
836 mofsx = x*2;
837 mofsy = y*2;
838 vportX0 = mofsx/scale;
839 vportY0 = mofsy/scale;
840 vportX1 = vportX0+vlWidth/scale;
841 vportY1 = vportY0+vlHeight/scale;
844 //glDisable(GL_DEPTH_TEST);
845 // draw dots
846 dotDraw(atob);
847 // do liquid coloring
848 drawAtXY(map.texgl.ptr[map.LiquidMask], 0, 0);
849 // foreground -- hide secrets, draw lifts and such
850 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
853 enum r = 255;
854 enum g = 0;
855 enum b = 0;
856 enum a = 255;
857 bindTexture(0);
858 glColor4f(r/255.0f, g/255.0f, b/255.0f, a/255.0f);
859 glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
860 if (cameraChick.valid) {
861 glBegin(GL_POINTS);
862 glVertex2i(camchickdi.actorX, camchickdi.actorY-70);
863 glEnd();
864 //glRectf(camchickdi.actorX, camchickdi.actorY-70, camchickdi.actorX+4, camchickdi.actorY-70+4);
866 //glRectf(0, 0, 300, 300);
867 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
873 if (doLighting) {
874 glDisable(GL_BLEND);
875 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
877 // make smaller occluder texture, so we can trace faster
878 //assert(fboLMaskSmall.tex.width == map.width);
879 //assert(fboLMaskSmall.tex.height == map.height);
880 fboLMaskSmall.exec({
881 orthoCamera(map.width, map.height);
882 drawAtXY(map.texgl.ptr[map.LightMask].tid, 0, 0, map.width, map.height, mirrorY:true);
885 // clear light layer
886 fboLevelLight.exec({
887 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
888 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
889 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
890 glClear(GL_COLOR_BUFFER_BIT);
893 // texture 1 is background
894 glActiveTexture(GL_TEXTURE0+1);
895 glBindTexture(GL_TEXTURE_2D, fboOrigBack.tex.tid);
896 // texture 2 is occluders
897 glActiveTexture(GL_TEXTURE0+2);
898 glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.LightMask].tid);
899 // texture 3 is small occluder map
900 glActiveTexture(GL_TEXTURE0+3);
901 glBindTexture(GL_TEXTURE_2D, fboLMaskSmall.tex.tid);
902 // done texture assign
903 glActiveTexture(GL_TEXTURE0+0);
906 enum LYOfs = 1;
908 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
909 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
910 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
911 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
912 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
913 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
914 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
915 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
916 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
917 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
919 renderLight(24*TileSize+4, (24+18)*TileSize-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
921 renderLight(280, 330, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
924 foreach (; 0..1) {
925 // attached lights
926 foreach (ref li; attachedLights[0..attachedLightCount]) {
927 if (li.type == AttachedLightInfo.Type.Ambient) {
928 //conwriteln("ambient: x=", li.x, "; y=", li.y, "; w=", li.w, "; h=", li.h);
929 // ambient light
930 if (li.uncolored) {
931 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(0.0f, 0.0f, 0.0f, li.r));
932 } else {
933 renderLightAmbient(li.x, li.y, li.w, li.h, SVec4F(li.r, li.g, li.b, 1.0f));
935 } else if (li.type == AttachedLightInfo.Type.Point) {
936 // point light
937 if (li.uncolored) {
938 renderLight(li.x, li.y, SVec4F(0.0f, 0.0f, 0.0f, li.r), li.radius);
939 } else {
940 renderLight(li.x, li.y, SVec4F(li.r, li.g, li.b, 1.0f), li.radius);
947 if (testLightMoved) {
948 testLightX = testLightX/scale+mofsx/scale;
949 testLightY = testLightY/scale+mofsy/scale;
950 testLightMoved = false;
952 foreach (immutable _; 0..1) {
953 renderLight(testLightX, testLightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
957 glActiveTexture(GL_TEXTURE0+1);
958 glBindTexture(GL_TEXTURE_2D, 0);
959 glActiveTexture(GL_TEXTURE0+2);
960 glBindTexture(GL_TEXTURE_2D, 0);
961 glActiveTexture(GL_TEXTURE0+3);
962 glBindTexture(GL_TEXTURE_2D, 0);
963 glActiveTexture(GL_TEXTURE0+0);
966 // draw scaled level
968 shadScanlines.exec((Shader shad) {
969 shad["scanlines"] = scanlines;
970 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
971 glClear(GL_COLOR_BUFFER_BIT);
972 orthoCamera(vlWidth, vlHeight);
973 //orthoCamera(map.width*TileSize*scale, map.height*TileSize*scale);
974 //glMatrixMode(GL_MODELVIEW);
975 //glLoadIdentity();
976 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
977 // somehow, FBO objects are mirrored; wtf?!
978 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
979 //glLoadIdentity();
984 fboLevelLight.exec({
985 smDrawText(map.width*TileSize/2, map.height*TileSize/2, "Testing...");
990 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
992 glDisable(GL_BLEND);
995 fboOrigBack.exec({
996 //auto img = smfont.ptr[0x39];
997 auto img = fftest;
998 if (img !is null) {
999 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
1000 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
1007 { // http://stackoverflow.com/questions/7207422/setting-up-opengl-multiple-render-targets
1008 GLenum[1] buffers = [ GL_BACK_LEFT, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
1009 //GLenum[1] buffers = [ GL_NONE, /*GL_COLOR_ATTACHMENT0_EXT*/ ];
1010 glDrawBuffers(1, buffers.ptr);
1015 orthoCamera(vlWidth, vlHeight);
1016 auto tex = (doLighting ? fboLevelLight.tex.tid : fboOrigBack.tex.tid);
1017 drawAtXY(tex, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1019 if (levelLoaded) {
1020 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1021 //orthoCamera(map.width*TileSize, map.height*TileSize);
1022 glEnable(GL_BLEND);
1023 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1024 hudScripts.runDraw();
1027 //drawAtXY(map.texgl.ptr[map.LightMask].tid, -mofsx, -mofsy, map.width*TileSize*scale, map.height*TileSize*scale, mirrorY:true);
1028 //drawAtXY(fboLMaskSmall.tex.tid, 0, 0, map.width*TileSize, map.height*TileSize);
1030 if (inEditMode) {
1031 glEnable(GL_BLEND);
1032 //glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1033 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1034 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
1035 editorUpdateImage();
1036 fboEditor.tex.setFromImage(editorImg);
1037 drawAtXY(fboEditor.tex, 0, 0);
1038 glDisable(GL_BLEND);
1041 doMessages(curtime);
1045 // ////////////////////////////////////////////////////////////////////////// //
1046 // returns time slept
1047 int sleepAtMaxMsecs (int msecs) {
1048 if (msecs > 0) {
1049 import core.sys.posix.signal : timespec;
1050 import core.sys.posix.time : nanosleep;
1051 timespec ts = void, tpassed = void;
1052 ts.tv_sec = 0;
1053 ts.tv_nsec = msecs*1000*1000+(500*1000); // milli to nano
1054 nanosleep(&ts, &tpassed);
1055 return (ts.tv_nsec-tpassed.tv_nsec)/(1000*1000);
1056 } else {
1057 return 0;
1062 // ////////////////////////////////////////////////////////////////////////// //
1063 mixin(import("editor.d"));
1066 // ////////////////////////////////////////////////////////////////////////// //
1067 // rendering thread
1068 shared int diedie = 0;
1070 enum D2DFrameTime = 55; // milliseconds
1071 enum MinFrameTime = 1000/60; // ~60 FPS
1073 public void renderThread (Tid starterTid) {
1074 enum BoolOptVarMsgMixin(string varname) =
1075 "if (msg.toggle) msg.value = !"~varname~";\n"~
1076 "if ("~varname~" != msg.value) "~varname~" = msg.value; else msg.showMessage = false;\n";
1078 send(starterTid, 42);
1079 try {
1080 MonoTime curtime = MonoTime.currTime;
1082 lastthink = curtime; // for interpolator
1083 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1084 MonoTime nextvframe = curtime;
1086 enum MaxFPSFrames = 16;
1087 float frtimes = 0.0f;
1088 int framenum = 0;
1089 int prevFPS = -1;
1090 int hushFrames = 6; // ignore first `hushFrames` frames overtime
1091 MonoTime prevFrameStartTime = curtime;
1093 bool vframeWasLost = false;
1095 void resetFrameTimers () {
1096 MonoTime curtime = MonoTime.currTime;
1097 lastthink = curtime; // for interpolator
1098 nextthink = curtime+dur!"msecs"(D2DFrameTime);
1099 nextvframe = curtime;
1102 void loadNewLevel (string name) {
1104 if (levelLoaded) {
1105 conwriteln("ERROR: can't load new levels yet");
1106 return;
1109 if (name.length == 0) {
1110 conwriteln("ERROR: can't load empty level!");
1112 conwriteln("loading map '", name, "'");
1113 loadMap(name);
1114 resetFrameTimers();
1117 conRegFunc!({
1118 string mn = genNextMapName(0);
1119 if (mn.length) {
1120 nextmapname = null; // clear "exit" flag
1121 loadNewLevel(mn);
1122 } else {
1123 conwriteln("can't skip level");
1125 })("skiplevel", "skip current level");
1127 conRegFunc!({
1128 inEditMode = !inEditMode;
1129 if (inEditMode) sdwindow.hideCursor(); else sdwindow.showCursor();
1130 })("ed_toggle", "toggle editor");
1132 conRegFunc!({
1133 if (inEditMode) {
1134 inEditMode = false;
1135 sdwindow.showCursor();
1137 })("ed_exit", "exit from editor");
1139 conRegFunc!((string mapname) {
1140 nextmapname = null; // clear "exit" flag
1141 loadNewLevel(mapname);
1142 })("map", "load map");
1144 void receiveMessages () {
1145 for (;;) {
1146 import core.time : Duration;
1147 //conwriteln("rendering thread: waiting for messages...");
1148 auto got = receiveTimeout(
1149 Duration.zero, // don't wait
1150 (TMsgMessage msg) {
1151 addMessage(msg.text[0..msg.textlen], msg.pauseMsecs, msg.noreplace);
1153 (TMsgTestLightMove msg) {
1154 testLightX = msg.x;
1155 testLightY = msg.y;
1156 testLightMoved = true;
1158 (TMsgMouseEvent msg) { editorMouseEvent(msg); },
1159 (TMsgKeyEvent msg) { editorKeyEvent(msg); },
1160 (Variant v) {
1161 conwriteln("WARNING: unknown thread message received and ignored");
1164 if (!got) {
1165 // no more messages
1166 //conwriteln("rendering thread: no more messages");
1167 break;
1170 if (nextmapname.length) {
1171 string mn = nextmapname;
1172 nextmapname = null; // clear "exit" flag
1173 loadNewLevel(mn);
1177 void processConsoleCommands () {
1178 concmdbufLock.lock();
1179 scope(exit) concmdbufLock.unlock();
1180 concmdDoAll();
1183 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1184 bool doThinkFrame () {
1185 if (curtime >= nextthink) {
1186 lastthink = curtime;
1187 while (nextthink <= curtime) nextthink += dur!"msecs"(D2DFrameTime);
1188 if (levelLoaded) {
1189 // save snapshot and other data for interpolator
1190 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
1191 if (!gamePaused && !inEditMode) {
1192 // process actors
1193 doActorsThink();
1194 dotThink();
1197 // some timing
1198 auto tm = MonoTime.currTime;
1199 int thinkTime = cast(int)((tm-curtime).total!"msecs");
1200 if (thinkTime > 9) { import core.stdc.stdio; printf("spent on thinking: %d msecs\n", thinkTime); }
1201 curtime = tm;
1202 return true;
1203 } else {
1204 return false;
1208 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
1209 bool doVFrame () {
1210 version(dont_use_vsync) {
1211 // timer
1212 enum doCheckTime = true;
1213 } else {
1214 // vsync
1215 __gshared bool prevLost = false;
1216 bool doCheckTime = vframeWasLost;
1217 if (vframeWasLost) {
1218 if (!prevLost) {
1219 { import core.stdc.stdio; printf("frame was lost!\n"); }
1221 prevLost = true;
1222 } else {
1223 prevLost = false;
1226 if (doCheckTime) {
1227 if (curtime < nextvframe) return false;
1228 version(dont_use_vsync) {
1229 if (curtime > nextvframe) {
1230 auto overtime = cast(int)((curtime-nextvframe).total!"msecs");
1231 if (overtime > 2500) {
1232 if (hushFrames) {
1233 --hushFrames;
1234 } else {
1235 { import core.stdc.stdio; printf(" spent whole %d msecs\n", overtime); }
1241 while (nextvframe <= curtime) nextvframe += dur!"msecs"(MinFrameTime);
1242 bool ctset = false;
1244 sdwindow.mtLock();
1245 scope(exit) sdwindow.mtUnlock();
1246 ctset = sdwindow.setAsCurrentOpenGlContextNT;
1248 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
1249 if (ctset) {
1250 // render scene
1251 iLiquidTime = cast(float)((curtime-MonoTime.zero).total!"msecs"%10000000)/18.0f*0.04f;
1252 receiveMessages(); // here, 'cause we need active OpenGL context for some messages
1253 processConsoleCommands();
1254 if (levelLoaded) {
1255 renderScene(curtime);
1256 } else {
1257 //renderLoading(curtime);
1259 sdwindow.mtLock();
1260 scope(exit) sdwindow.mtUnlock();
1261 sdwindow.swapOpenGlBuffers();
1262 glFinish();
1263 sdwindow.releaseCurrentOpenGlContext();
1264 vframeWasLost = false;
1265 } else {
1266 vframeWasLost = true;
1267 { import core.stdc.stdio; printf("xframe was lost!\n"); }
1269 curtime = MonoTime.currTime;
1270 return true;
1273 for (;;) {
1274 if (sdwindow.closed) break;
1275 if (atomicLoad(diedie) > 0) break;
1277 curtime = MonoTime.currTime;
1278 auto fstime = curtime;
1280 doThinkFrame(); // this will fix curtime if necessary
1281 if (doVFrame()) {
1282 if (!vframeWasLost) {
1283 // fps
1284 auto frameTime = cast(float)(curtime-prevFrameStartTime).total!"msecs"/1000.0f;
1285 prevFrameStartTime = curtime;
1286 frtimes += frameTime;
1287 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
1288 import std.string : format;
1289 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
1290 if (newFPS != prevFPS) {
1291 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
1292 prevFPS = newFPS;
1294 framenum = 0;
1295 frtimes = 0.0f;
1300 curtime = MonoTime.currTime;
1302 // now sleep until next "video" or "think" frame
1303 if (nextthink > curtime && nextvframe > curtime) {
1304 // let's decide
1305 immutable nextVideoFrameSleep = cast(int)((nextvframe-curtime).total!"msecs");
1306 immutable nextThinkFrameSleep = cast(int)((nextthink-curtime).total!"msecs");
1307 immutable sleepTime = (nextVideoFrameSleep < nextThinkFrameSleep ? nextVideoFrameSleep : nextThinkFrameSleep);
1308 sleepAtMaxMsecs(sleepTime);
1309 //curtime = MonoTime.currTime;
1312 } catch (Throwable e) {
1313 // here, we are dead and fucked (the exact order doesn't matter)
1314 import core.stdc.stdlib : abort;
1315 import core.stdc.stdio : fprintf, stderr;
1316 import core.memory : GC;
1317 GC.disable(); // yeah
1318 thread_suspendAll(); // stop right here, you criminal scum!
1319 auto s = e.toString();
1320 fprintf(stderr, "\n=== FATAL ===\n%.*s\n", cast(uint)s.length, s.ptr);
1321 abort(); // die, you bitch!
1323 import core.stdc.stdio;
1324 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
1325 import std.stdio : stderr;
1326 writeln(
1327 for (;;) {
1328 if (sdwindow.closed) break;
1329 if (atomicLoad(diedie) > 0) break;
1330 sleepAtMaxMsecs(100);
1334 atomicStore(diedie, 2);
1338 // ////////////////////////////////////////////////////////////////////////// //
1339 __gshared Tid renderTid;
1340 shared bool renderThreadStarted = false;
1343 public void startRenderThread () {
1344 if (!cas(&renderThreadStarted, false, true)) {
1345 assert(0, "render thread already started!");
1347 renderTid = spawn(&renderThread, thisTid);
1348 setMaxMailboxSize(renderTid, 1024, OnCrowding.throwException); //FIXME
1349 // wait for "i'm ready" signal
1350 receive(
1351 (int ok) {
1352 if (ok != 42) assert(0, "wtf?!");
1355 conwriteln("rendering thread started");
1359 // ////////////////////////////////////////////////////////////////////////// //
1360 public void closeWindow () {
1361 if (atomicLoad(diedie) != 2) {
1362 atomicStore(diedie, 1);
1363 while (atomicLoad(diedie) != 2) {}
1365 if (!sdwindow.closed) {
1366 flushGui();
1367 sdwindow.close();
1372 // ////////////////////////////////////////////////////////////////////////// //
1373 // thread messages
1376 // ////////////////////////////////////////////////////////////////////////// //
1377 struct TMsgMouseEvent {
1378 MouseEventType type;
1379 int x, y;
1380 int dx, dy;
1381 MouseButton button; /// See $(LREF MouseButton)
1382 int modifierState; /// See $(LREF ModifierState)
1385 public void postMouseEvent() (in auto ref MouseEvent evt) {
1386 if (!atomicLoad(renderThreadStarted)) return;
1387 TMsgMouseEvent msg;
1388 msg.type = evt.type;
1389 msg.x = evt.x;
1390 msg.y = evt.y;
1391 msg.dx = evt.dx;
1392 msg.dy = evt.dy;
1393 msg.button = evt.button;
1394 msg.modifierState = evt.modifierState;
1395 send(renderTid, msg);
1399 // ////////////////////////////////////////////////////////////////////////// //
1400 struct TMsgKeyEvent {
1401 Key key;
1402 uint hardwareCode;
1403 bool pressed;
1404 dchar character;
1405 uint modifierState;
1408 public void postKeyEvent() (in auto ref KeyEvent evt) {
1409 if (!atomicLoad(renderThreadStarted)) return;
1410 TMsgKeyEvent msg;
1411 msg.key = evt.key;
1412 msg.pressed = evt.pressed;
1413 msg.character = evt.character;
1414 msg.modifierState = evt.modifierState;
1415 send(renderTid, msg);
1419 // ////////////////////////////////////////////////////////////////////////// //
1420 struct TMsgTestLightMove {
1421 int x, y;
1424 public void postTestLightMove (int x, int y) {
1425 if (!atomicLoad(renderThreadStarted)) return;
1426 auto msg = TMsgTestLightMove(x, y);
1427 send(renderTid, msg);
1431 // ////////////////////////////////////////////////////////////////////////// //
1432 struct TMsgMessage {
1433 char[256] text;
1434 uint textlen;
1435 int pauseMsecs;
1436 bool noreplace;
1439 public void postAddMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
1440 if (!atomicLoad(renderThreadStarted)) return;
1441 if (msgtext.length > TMsgMessage.text.length) msgtext = msgtext[0..TMsgMessage.text.length];
1442 TMsgMessage msg;
1443 msg.textlen = cast(uint)msgtext.length;
1444 if (msg.textlen) msg.text[0..msg.textlen] = msgtext[0..msg.textlen];
1445 msg.pauseMsecs = pauseMsecs;
1446 msg.noreplace = noreplace;
1447 send(renderTid, msg);
1451 // ////////////////////////////////////////////////////////////////////////// //
1452 public void concmd (const(char)[] cmd) {
1453 //if (!atomicLoad(renderThreadStarted)) return;
1454 concmdbufLock.lock();
1455 scope(exit) concmdbufLock.unlock();
1456 concmdAdd(cmd);