"no doors" cheat
[dd2d.git] / xmain_d2d.d
blob81693a1cfb4cdac0d55f05d01bc486167cb21d8e
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 xmain_d2d is aliced;
20 private:
21 import core.atomic;
22 import core.thread;
23 import core.time;
25 import iv.glbinds;
26 import glutils;
27 import console;
28 import wadarc;
30 import iv.stream;
32 import d2dmap;
33 import d2dadefs;
34 //import d2dactors;
35 import d2dgfx;
36 import d2dfont;
37 import dacs;
39 import d2dunigrid;
41 // `map` is there
42 import dengapi;
45 // ////////////////////////////////////////////////////////////////////////// //
46 import arsd.color;
47 import arsd.png;
50 // ////////////////////////////////////////////////////////////////////////// //
51 public __gshared bool cheatNoDoors = false;
54 // ////////////////////////////////////////////////////////////////////////// //
55 __gshared SimpleWindow sdwindow;
58 public enum vlWidth = 800;
59 public enum vlHeight = 800;
60 public __gshared int scale = 1;
63 // ////////////////////////////////////////////////////////////////////////// //
64 __gshared bool scanlines = false;
65 __gshared bool doLighting = true;
68 // ////////////////////////////////////////////////////////////////////////// //
69 // interpolation
70 __gshared ubyte[] prevFrameActorsData;
71 __gshared uint[65536] prevFrameActorOfs; // uint.max-1: dead; uint.max: last
72 __gshared MonoTime lastthink = MonoTime.zero; // for interpolator
73 __gshared MonoTime nextthink = MonoTime.zero;
74 __gshared bool frameInterpolation = true;
77 __gshared int[2] mapViewPosX, mapViewPosY; // [0]: previous frame -- for interpolator
80 // this should be screen center
81 public void setMapViewPos (int x, int y) {
82 if (map is null) {
83 mapViewPosX[] = 0;
84 mapViewPosY[] = 0;
85 return;
87 int swdt = vlWidth/scale;
88 int shgt = vlHeight/scale;
89 x -= swdt/2;
90 y -= shgt/2;
91 if (x < 0) x = 0; else if (x >= map.width*8-swdt) x = map.width*8-swdt-1;
92 if (y < 0) y = 0; else if (y >= map.height*8-shgt) y = map.height*8-shgt-1;
93 mapViewPosX[1] = x*scale;
94 mapViewPosY[1] = y*scale;
98 // ////////////////////////////////////////////////////////////////////////// //
99 // attached lights
100 struct AttachedLightInfo {
101 int x, y;
102 float r, g, b;
103 bool uncolored;
104 int radius;
107 __gshared AttachedLightInfo[65536] attachedLights;
108 __gshared uint attachedLightCount = 0;
111 // ////////////////////////////////////////////////////////////////////////// //
112 enum MaxLightRadius = 512;
115 // ////////////////////////////////////////////////////////////////////////// //
116 // for light
117 __gshared FBO[MaxLightRadius+1] fboOccluders, fboShadowMap;
118 __gshared Shader shadToPolar, shadBlur, shadBlurOcc;
120 __gshared FBO fboLevel, fboLevelLight, fboOrigBack;
121 __gshared Shader shadScanlines;
122 __gshared Shader shadLiquidDistort;
125 // ////////////////////////////////////////////////////////////////////////// //
126 void initOpenGL () {
127 glEnable(GL_TEXTURE_2D);
128 glDisable(GL_LIGHTING);
129 glDisable(GL_DITHER);
130 glDisable(GL_BLEND);
131 glDisable(GL_DEPTH_TEST);
133 // create shaders
134 shadScanlines = new Shader("scanlines", loadTextFile("data/shaders/srscanlines.frag"));
136 shadLiquidDistort = new Shader("liquid_distort", loadTextFile("data/shaders/srliquid_distort.frag"));
137 shadLiquidDistort.exec((Shader shad) { shad["tex0"] = 0; });
139 // lights
140 shadToPolar = new Shader("light_topolar", loadTextFile("data/shaders/srlight_topolar.frag"));
141 shadBlur = new Shader("light_blur", loadTextFile("data/shaders/srlight_blur.frag"));
142 shadBlur.exec((Shader shad) {
143 shad["tex0"] = 0;
144 shad["tex1"] = 1;
145 shad["tex2"] = 2;
147 shadBlurOcc = new Shader("light_blur_occ", loadTextFile("data/shaders/srlight_blur_occ.frag"));
148 shadBlurOcc.exec((Shader shad) {
149 shad["tex0"] = 0;
150 shad["tex1"] = 1;
151 shad["tex2"] = 2;
153 //TODO: this sux!
154 foreach (int sz; 2..MaxLightRadius+1) {
155 fboOccluders[sz] = new FBO(sz*2, sz*2, Texture.Option.Clamp, Texture.Option.Linear); // create occluders FBO
156 fboShadowMap[sz] = new FBO(sz*2, 1, Texture.Option.Clamp); // create 1d shadowmap FBO
159 // setup matrices
160 glMatrixMode(GL_MODELVIEW);
161 glLoadIdentity();
163 map.oglBuildMega();
165 fboLevel = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // final level render will be here
166 fboLevelLight = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // level lights will be rendered here
167 //fboForeground = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // level foreground
168 fboOrigBack = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // background+foreground
170 shadBlur.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*8, map.height*8); });
171 shadBlurOcc.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*8, map.height*8); });
173 glActiveTexture(GL_TEXTURE0+0);
174 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
175 orthoCamera(vlWidth, vlHeight);
177 loadSmFont();
179 Actor.resetStorage();
180 loadMapMonsters();
182 // save first snapshot
183 prevFrameActorsData = new ubyte[](Actor.actorSize*65536); // ~15-20 megabytes
184 prevFrameActorOfs[] = uint.max; // just for fun
185 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
186 mapViewPosX[0] = mapViewPosX[1];
187 mapViewPosY[0] = mapViewPosY[1];
189 { import core.memory : GC; GC.collect(); }
193 // ////////////////////////////////////////////////////////////////////////// //
194 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
195 if (lightRadius < 2) return;
196 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
197 int lightSize = lightRadius*2;
198 // is this light visible?
199 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*8 || lightY-lightRadius >= map.height*8) return;
201 // draw shadow casters to fboOccludersId, light should be in the center
202 glUseProgram(0);
203 glDisable(GL_BLEND);
204 fboOccluders[lightRadius].exec({
205 //glDisable(GL_BLEND);
206 glColor3f(0.0f, 0.0f, 0.0f);
207 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
208 glClear(GL_COLOR_BUFFER_BIT);
209 orthoCamera(lightSize, lightSize);
210 drawAtXY(map.texgl.ptr[map.LightMask], lightRadius-lightX, lightRadius-lightY);
213 // build 1d shadow map to fboShadowMapId
214 fboShadowMap[lightRadius].exec({
215 shadToPolar.exec((Shader shad) {
216 //glDisable(GL_BLEND);
217 glColor3f(0.0f, 0.0f, 0.0f);
218 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
219 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
220 glClear(GL_COLOR_BUFFER_BIT);
221 orthoCamera(lightSize, 1);
222 drawAtXY(fboOccluders[lightRadius].tex.tid, 0, 0, lightSize, 1);
226 // build light texture for blending
227 fboOccluders[lightRadius].exec({
228 // no need to clear it, shader will take care of that
229 //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
230 //glClear(GL_COLOR_BUFFER_BIT);
231 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
232 //glDisable(GL_BLEND);
233 shadBlur.exec((Shader shad) {
234 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
235 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
236 shad["lightPos"] = SVec2F(lightX, lightY);
237 orthoCamera(lightSize, lightSize);
238 drawAtXY(fboShadowMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
242 // blend light texture
243 fboLevelLight.exec({
244 glEnable(GL_BLEND);
245 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
246 orthoCamera(map.width*8, map.height*8);
247 drawAtXY(fboOccluders[lightRadius].tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
248 // and blend it again, somewhat bigger, with the shader that will touch only occluders
249 enum szmore = 0;
250 shadBlurOcc.exec((Shader shad) {
251 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
252 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
253 shad["lightPos"] = SVec2F(lightX, lightY);
254 drawAtXY(fboOccluders[lightRadius].tex.tid, lightX-lightRadius-szmore, lightY-lightRadius-szmore, lightSize+szmore*2, lightSize+szmore*2, mirrorY:true);
260 // ////////////////////////////////////////////////////////////////////////// //
261 // messages
262 struct Message {
263 enum Phase { FadeIn, Stay, FadeOut }
264 Phase phase;
265 int alpha;
266 int pauseMsecs;
267 MonoTime removeTime;
268 char[256] text;
269 usize textlen;
272 private import core.sync.mutex : Mutex;
274 __gshared Message[128] messages;
275 __gshared uint messagesUsed = 0;
276 __gshared Mutex messageLock;
278 shared static this () { messageLock = new Mutex(); }
281 void addMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
282 messageLock.lock();
283 scope(exit) messageLock.unlock();
284 if (msgtext.length == 0) return;
285 conwriteln(msgtext);
286 if (pauseMsecs <= 50) return;
287 if (messagesUsed == messages.length) {
288 // remove top message
289 foreach (immutable cidx; 1..messagesUsed) messages.ptr[cidx-1] = messages.ptr[cidx];
290 messages.ptr[0].alpha = 255;
291 --messagesUsed;
293 // quick replace
294 if (!noreplace && messagesUsed == 1) {
295 switch (messages.ptr[0].phase) {
296 case Message.Phase.FadeIn:
297 messages.ptr[0].phase = Message.Phase.FadeOut;
298 break;
299 case Message.Phase.Stay:
300 messages.ptr[0].phase = Message.Phase.FadeOut;
301 messages.ptr[0].alpha = 255;
302 break;
303 default:
306 auto msg = messages.ptr+messagesUsed;
307 ++messagesUsed;
308 msg.phase = Message.Phase.FadeIn;
309 msg.alpha = 0;
310 msg.pauseMsecs = pauseMsecs;
311 // copy text
312 if (msgtext.length > msg.text.length) {
313 msg.text = msgtext[0..msg.text.length];
314 msg.textlen = msg.text.length;
315 } else {
316 msg.text[0..msgtext.length] = msgtext[];
317 msg.textlen = msgtext.length;
322 void doMessages (MonoTime curtime) {
323 messageLock.lock();
324 scope(exit) messageLock.unlock();
326 if (messagesUsed == 0) return;
327 glEnable(GL_BLEND);
328 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
330 Message* msg;
332 again:
333 msg = messages.ptr;
334 final switch (msg.phase) {
335 case Message.Phase.FadeIn:
336 if ((msg.alpha += 10) >= 255) {
337 msg.phase = Message.Phase.Stay;
338 msg.removeTime = curtime+dur!"msecs"(msg.pauseMsecs);
339 goto case; // to stay
341 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
342 break;
343 case Message.Phase.Stay:
344 if (msg.removeTime <= curtime) {
345 msg.alpha = 255;
346 msg.phase = Message.Phase.FadeOut;
347 goto case; // to fade
349 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
350 break;
351 case Message.Phase.FadeOut:
352 if ((msg.alpha -= 10) <= 0) {
353 if (--messagesUsed == 0) return;
354 // remove this message
355 foreach (immutable cidx; 1..messagesUsed+1) messages.ptr[cidx-1] = messages.ptr[cidx];
356 goto again;
358 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
359 break;
362 smDrawText(10, 10, msg.text[0..msg.textlen]);
366 // ////////////////////////////////////////////////////////////////////////// //
367 mixin(Actor.FieldGetMixin!("classtype", StrId)); // fget_classtype
368 mixin(Actor.FieldGetMixin!("classname", StrId)); // fget_classname
369 mixin(Actor.FieldGetMixin!("x", int));
370 mixin(Actor.FieldGetMixin!("y", int));
371 mixin(Actor.FieldGetMixin!("flags", uint));
372 mixin(Actor.FieldGetMixin!("zAnimstate", StrId));
373 mixin(Actor.FieldGetMixin!("zAnimidx", int));
374 mixin(Actor.FieldGetMixin!("dir", uint));
375 mixin(Actor.FieldGetMixin!("attLightXOfs", int));
376 mixin(Actor.FieldGetMixin!("attLightYOfs", int));
377 mixin(Actor.FieldGetMixin!("attLightRGBX", uint));
379 mixin(Actor.FieldGetPtrMixin!("classtype", StrId)); // fget_classtype
380 mixin(Actor.FieldGetPtrMixin!("classname", StrId)); // fget_classname
381 mixin(Actor.FieldGetPtrMixin!("x", int));
382 mixin(Actor.FieldGetPtrMixin!("y", int));
383 mixin(Actor.FieldGetPtrMixin!("flags", uint));
384 mixin(Actor.FieldGetPtrMixin!("zAnimstate", StrId));
385 mixin(Actor.FieldGetPtrMixin!("zAnimidx", int));
386 mixin(Actor.FieldGetPtrMixin!("dir", uint));
387 mixin(Actor.FieldGetPtrMixin!("attLightXOfs", int));
388 mixin(Actor.FieldGetPtrMixin!("attLightYOfs", int));
389 mixin(Actor.FieldGetPtrMixin!("attLightRGBX", uint));
392 // ////////////////////////////////////////////////////////////////////////// //
393 __gshared int lightX = vlWidth/2, lightY = vlHeight/2;
394 __gshared int mapOfsX, mapOfsY;
395 __gshared bool movement = false;
396 __gshared float iLiquidTime = 0.0;
397 __gshared bool altMove = false;
400 void renderScene (MonoTime curtime) {
401 //enum BackIntens = 0.05f;
402 enum BackIntens = 0.0f;
404 float atob = (curtime > lastthink ? cast(float)((curtime-lastthink).total!"msecs")/cast(float)((nextthink-lastthink).total!"msecs") : 1.0f);
405 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
408 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
409 int curfp = cast(int)((curtime-lastthink).total!"msecs");
410 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
414 glUseProgram(0);
416 // build background layer
417 fboOrigBack.exec({
418 //glDisable(GL_BLEND);
419 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
420 glClear(GL_COLOR_BUFFER_BIT);
421 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
422 orthoCamera(map.width*8, map.height*8);
423 glEnable(GL_BLEND);
424 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
425 // draw sky
427 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
428 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2, map.MapSize*8, map.MapSize*8);
429 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
430 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*8, map.MapSize*8);
432 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*8, map.MapSize*8);
433 // draw background
434 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
435 // draw distorted liquid areas
436 shadLiquidDistort.exec((Shader shad) {
437 shad["iDistortTime"] = iLiquidTime;
438 drawAtXY(map.texgl.ptr[map.AllLiquids], 0, 0);
440 // monsters, items; we'll do linear interpolation here
441 // we need this for saved frame anyway, so let's play dirty and speed up the things
442 glColor3f(1.0f, 1.0f, 1.0f);
443 attachedLightCount = 0;
444 Actor.forEach((ActorId me) {
445 if (auto adef = findActorDef(me)) {
446 int actorX, actorY; // current actor position
448 auto ofs = prevFrameActorOfs.ptr[me.id&0xffff];
449 if (frameInterpolation && ofs < uint.max-1 && Actor.isSameSavedActor(me.id, prevFrameActorsData.ptr, ofs)) {
450 import core.stdc.math : roundf;
451 auto xptr = prevFrameActorsData.ptr+ofs;
452 int ox = xptr.fgetp_x;
453 int nx = me.fget_x;
454 int oy = xptr.fgetp_y;
455 int ny = me.fget_y;
456 actorX = cast(int)(ox+roundf((nx-ox)*atob));
457 actorY = cast(int)(oy+roundf((ny-oy)*atob));
458 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
459 } else {
460 actorX = me.fget_x;
461 actorY = me.fget_y;
464 // draw sprite
465 if ((me.fget_flags&AF_NODRAW) == 0) {
466 if (auto isp = adef.animSpr(me.fget_zAnimstate, me.fget_dir, me.fget_zAnimidx)) {
467 drawAtXY(isp.tex, actorX-isp.vga.sx, actorY-isp.vga.sy);
468 } else {
469 //conwriteln("no animation for actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
472 // process attached lights
473 if ((me.fget_flags&AF_NOLIGHT) == 0) {
474 uint alr = me.fget_attLightRGBX;
475 if ((alr&0xff) >= 4) {
476 // yep, add it
477 auto li = attachedLights.ptr+attachedLightCount;
478 ++attachedLightCount;
479 li.x = actorX+me.fget_attLightXOfs;
480 li.y = actorY+me.fget_attLightYOfs;
481 li.r = ((alr>>24)&0xff)/255.0f; // red or intensity
482 if ((alr&0x00_ff_ff_00U) == 0x00_00_01_00U) {
483 li.uncolored = true;
484 } else {
485 li.g = ((alr>>16)&0xff)/255.0f;
486 li.b = ((alr>>8)&0xff)/255.0f;
487 li.uncolored = false;
489 li.radius = (alr&0xff);
490 if (li.radius > MaxLightRadius) li.radius = MaxLightRadius;
493 } else {
494 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
497 // do liquid coloring
498 drawAtXY(map.texgl.ptr[map.LiquidMask], 0, 0);
499 // foreground -- hide secrets, draw lifts and such
500 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
504 if (doLighting) {
505 // clear light layer
506 fboLevelLight.exec({
507 glDisable(GL_BLEND);
508 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
509 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
510 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
511 glClear(GL_COLOR_BUFFER_BIT);
514 // texture 1 is background
515 glActiveTexture(GL_TEXTURE0+1);
516 glBindTexture(GL_TEXTURE_2D, fboOrigBack.tex.tid);
517 // texture 2 is occluders
518 glActiveTexture(GL_TEXTURE0+2);
519 glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.LightMask].tid);
520 // done texture assign
521 glActiveTexture(GL_TEXTURE0+0);
523 enum LYOfs = 1;
525 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
526 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
527 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
528 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
529 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
530 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
531 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
532 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
533 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
534 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
536 renderLight(24*8+4, (24+18)*8-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
538 // attached lights
539 foreach (ref li; attachedLights[0..attachedLightCount]) {
540 if (li.uncolored) {
541 renderLight(li.x, li.y, SVec4F(0.0f, 0.0f, 0.0f, li.r), li.radius);
542 } else {
543 renderLight(li.x, li.y, SVec4F(li.r, li.g, li.b, 1.0f), li.radius);
547 foreach (immutable _; 0..1) {
548 renderLight(lightX, lightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
551 glActiveTexture(GL_TEXTURE0+1);
552 glBindTexture(GL_TEXTURE_2D, 0);
553 glActiveTexture(GL_TEXTURE0+2);
554 glBindTexture(GL_TEXTURE_2D, 0);
555 glActiveTexture(GL_TEXTURE0+0);
558 // draw scaled level
560 shadScanlines.exec((Shader shad) {
561 shad["scanlines"] = scanlines;
562 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
563 glClear(GL_COLOR_BUFFER_BIT);
564 orthoCamera(vlWidth, vlHeight);
565 //orthoCamera(map.width*8*scale, map.height*8*scale);
566 //glMatrixMode(GL_MODELVIEW);
567 //glLoadIdentity();
568 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
569 // somehow, FBO objects are mirrored; wtf?!
570 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*8*scale, map.height*8*scale, mirrorY:true);
571 //glLoadIdentity();
576 fboLevelLight.exec({
577 smDrawText(map.width*8/2, map.height*8/2, "Testing...");
582 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
583 glDisable(GL_BLEND);
586 fboOrigBack.exec({
587 //auto img = smfont.ptr[0x39];
588 auto img = fftest;
589 if (img !is null) {
590 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
591 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
596 int mofsx, mofsy;
598 if (altMove || movement || scale == 1) {
599 mofsx = mapOfsX;
600 mofsy = mapOfsY;
601 } else {
602 if (frameInterpolation) {
603 import core.stdc.math : roundf;
604 mofsx = cast(int)(mapViewPosX[0]+roundf((mapViewPosX[1]-mapViewPosX[0])*atob));
605 mofsy = cast(int)(mapViewPosY[0]+roundf((mapViewPosY[1]-mapViewPosY[0])*atob));
606 } else {
607 mofsx = mapViewPosX[1];
608 mofsy = mapViewPosY[1];
612 orthoCamera(vlWidth, vlHeight);
613 auto tex = (doLighting ? fboLevelLight.tex.tid : fboOrigBack.tex.tid);
614 drawAtXY(tex, -mofsx, -mofsy, map.width*8*scale, map.height*8*scale, mirrorY:true);
616 doMessages(curtime);
620 // ////////////////////////////////////////////////////////////////////////// //
621 // returns time slept
622 int sleepAtMaxMsecs (int msecs) {
623 if (msecs > 0) {
624 import core.sys.posix.signal : timespec;
625 import core.sys.posix.time : nanosleep;
626 timespec ts = void, tpassed = void;
627 ts.tv_sec = 0;
628 ts.tv_nsec = msecs*1000*1000+(500*1000); // milli to nano
629 nanosleep(&ts, &tpassed);
630 return (ts.tv_nsec-tpassed.tv_nsec)/(1000*1000);
631 } else {
632 return 0;
637 // ////////////////////////////////////////////////////////////////////////// //
638 // rendering thread
639 shared int diedie = 0;
641 enum D2DFrameTime = 55; // milliseconds
642 enum MinFrameTime = 1000/60; // ~60 FPS
644 void renderThread () {
645 try {
646 version(use_vsync) {} else MonoTime ltt = MonoTime.currTime;
648 MonoTime curtime = MonoTime.currTime;
650 lastthink = curtime; // for interpolator
651 nextthink = curtime+dur!"msecs"(D2DFrameTime);
652 MonoTime nextvframe = curtime;
654 enum MaxFPSFrames = 16;
655 float frtimes = 0.0f;
656 int framenum = 0;
657 int prevFPS = -1;
658 int hushFrames = 6; // ignore first `hushFrames` frames overtime
659 MonoTime prevFrameStartTime = curtime;
661 bool vframeWasLost = false;
663 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
664 bool doThinkFrame () {
665 if (curtime >= nextthink) {
666 lastthink = curtime;
667 while (nextthink <= curtime) nextthink += dur!"msecs"(D2DFrameTime);
668 // save snapshot and other datafor interpolator
669 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
670 mapViewPosX[0] = mapViewPosX[1];
671 mapViewPosY[0] = mapViewPosY[1];
672 // process actors
673 doActorsThink();
674 // some timing
675 auto tm = MonoTime.currTime;
676 int thinkTime = cast(int)((tm-curtime).total!"msecs");
677 if (thinkTime > 9) { import core.stdc.stdio; printf("spent on thinking: %d msecs\n", thinkTime); }
678 curtime = tm;
679 return true;
680 } else {
681 return false;
685 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
686 bool doVFrame () {
687 version(use_vsync) {
688 __gshared bool prevLost = false;
689 bool doCheckTime = vframeWasLost;
690 if (vframeWasLost) {
691 if (!prevLost) {
692 { import core.stdc.stdio; printf("frame was lost!\n"); }
694 prevLost = true;
695 } else {
696 prevLost = false;
698 } else {
699 enum doCheckTime = true;
701 if (doCheckTime) {
702 if (curtime < nextvframe) return false;
703 version(use_vsync) {} else {
704 if (curtime > nextvframe) {
705 auto overtime = cast(int)((curtime-nextvframe).total!"msecs");
706 if (overtime > 2500) {
707 if (hushFrames) {
708 --hushFrames;
709 } else {
710 { import core.stdc.stdio; printf(" spent whole %d msecs\n", overtime); }
716 while (nextvframe <= curtime) nextvframe += dur!"msecs"(MinFrameTime);
717 bool ctset = false;
719 sdwindow.mtLock();
720 scope(exit) sdwindow.mtUnlock();
721 ctset = sdwindow.setAsCurrentOpenGlContextNT;
723 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
724 if (ctset) {
725 // render scene
726 iLiquidTime = cast(float)((curtime-MonoTime.zero).total!"msecs"%10000000)/18.0f*0.04f;
727 renderScene(curtime);
728 sdwindow.mtLock();
729 scope(exit) sdwindow.mtUnlock();
730 sdwindow.swapOpenGlBuffers();
731 glFinish();
732 sdwindow.releaseCurrentOpenGlContext();
733 vframeWasLost = false;
734 } else {
735 vframeWasLost = true;
736 { import core.stdc.stdio; printf("xframe was lost!\n"); }
738 curtime = MonoTime.currTime;
739 return true;
742 for (;;) {
743 if (sdwindow.closed) break;
744 if (atomicLoad(diedie) > 0) break;
746 curtime = MonoTime.currTime;
747 auto fstime = curtime;
749 doThinkFrame(); // this will fix curtime if necessary
750 if (doVFrame()) {
751 if (!vframeWasLost) {
752 // fps
753 auto frameTime = cast(float)(curtime-prevFrameStartTime).total!"msecs"/1000.0f;
754 prevFrameStartTime = curtime;
755 frtimes += frameTime;
756 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
757 import std.string : format;
758 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
759 if (newFPS != prevFPS) {
760 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
761 prevFPS = newFPS;
763 framenum = 0;
764 frtimes = 0.0f;
769 curtime = MonoTime.currTime;
771 // now sleep until next "video" or "think" frame
772 if (nextthink > curtime && nextvframe > curtime) {
773 // let's decide
774 immutable nextVideoFrameSleep = cast(int)((nextvframe-curtime).total!"msecs");
775 immutable nextThinkFrameSleep = cast(int)((nextthink-curtime).total!"msecs");
776 immutable sleepTime = (nextVideoFrameSleep < nextThinkFrameSleep ? nextVideoFrameSleep : nextThinkFrameSleep);
777 sleepAtMaxMsecs(sleepTime);
778 //curtime = MonoTime.currTime;
781 } catch (Exception e) {
782 import core.stdc.stdio;
783 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
784 for (;;) {
785 if (sdwindow.closed) break;
786 if (atomicLoad(diedie) > 0) break;
787 //{ import core.stdc.stdio; printf(" spent only %d msecs\n", cast(int)((time-ltt).total!"msecs")); }
788 sleepAtMaxMsecs(100);
791 atomicStore(diedie, 2);
795 // ////////////////////////////////////////////////////////////////////////// //
796 void closeWindow () {
797 if (atomicLoad(diedie) != 2) {
798 atomicStore(diedie, 1);
799 while (atomicLoad(diedie) != 2) {}
801 if (!sdwindow.closed) {
802 flushGui();
803 sdwindow.close();
808 // ////////////////////////////////////////////////////////////////////////// //
809 __gshared Thread renderTid;
812 void main (string[] args) {
813 FuncPool.dumpCode = false;
814 FuncPool.dumpCodeSize = false;
815 dacsDumpSemantic = false;
816 dacsOptimize = 9;
817 //version(rdmd) { dacsOptimize = 0; }
818 bool compileOnly = false;
820 for (usize idx = 1; idx < args.length; ++idx) {
821 bool remove = true;
822 if (args[idx] == "--dump-code") FuncPool.dumpCode = true;
823 else if (args[idx] == "--dump-code-size") FuncPool.dumpCodeSize = true;
824 else if (args[idx] == "--dump-semantic") dacsDumpSemantic = true;
825 else if (args[idx] == "--dump-all") { FuncPool.dumpCode = true; FuncPool.dumpCodeSize = true; dacsDumpSemantic = true; }
826 else if (args[idx] == "--compile") compileOnly = true;
827 else if (args[idx] == "--compile-only") compileOnly = true;
828 else if (args[idx] == "--messages") ++dacsMessages;
829 else if (args[idx] == "--no-copro") dacsOptimizeNoCoPro = true;
830 else if (args[idx] == "--no-deadass") dacsOptimizeNoDeadAss = true;
831 else if (args[idx] == "--no-purekill") dacsOptimizeNoPureKill = true;
832 else if (args[idx].length > 2 && args[idx][0..2] == "-O") {
833 import std.conv : to;
834 ubyte olevel = to!ubyte(args[idx][2..$]);
835 dacsOptimize = olevel;
837 else remove = false;
838 if (remove) {
839 foreach (immutable c; idx+1..args.length) args.ptr[c-1] = args.ptr[c];
840 args.length -= 1;
841 --idx; //hack
845 static void setDP () {
846 version(rdmd) {
847 setDataPath("data");
848 } else {
849 import std.file : thisExePath;
850 import std.path : dirName;
851 setDataPath(thisExePath.dirName);
853 addPK3(getDataPath~"base.pk3"); loadWadScripts();
854 //addWad("/home/ketmar/k8prj/doom2d-tl/data/doom2d.wad"); loadWadScripts();
855 //addWad("/home/ketmar/k8prj/doom2d-tl/data/meat.wad"); loadWadScripts();
856 //addWad("/home/ketmar/k8prj/doom2d-tl/data/megadm.wad"); loadWadScripts();
857 //addWad("/home/ketmar/k8prj/doom2d-tl/data/megadm1.wad"); loadWadScripts();
858 //addWad("/home/ketmar/k8prj/doom2d-tl/data/superdm.wad"); loadWadScripts();
859 //addWad("/home/ketmar/k8prj/doom2d-tl/data/zadoomka.wad"); loadWadScripts();
860 scriptLoadingComplete();
863 setDP();
865 if (compileOnly) return;
867 try {
868 registerAPI();
869 loadPalette();
871 setOpenGLContextVersion(3, 2); // up to GLSL 150
872 //openGLContextCompatible = false;
874 map = new LevelMap("maps/map01.d2m");
875 ugInit(map.width*8, map.height*8);
877 scale = 2;
879 //mapOfsX = 8*26;
880 //mapOfsY = 8*56;
881 map.getThingPos(1/*ThingId.Player1*/, &mapOfsX, &mapOfsY);
882 // fix viewport
884 mapOfsX = (mapOfsX*2)-vlWidth/2;
885 if (mapOfsX+vlWidth > map.width*16) mapOfsX = map.width*16-vlWidth;
886 if (mapOfsX < 0) mapOfsX = 0;
887 mapOfsY = (mapOfsY*2)-vlHeight/2;
888 if (mapOfsY+vlHeight > map.height*16) mapOfsY = map.height*16-vlHeight;
889 if (mapOfsY < 0) mapOfsY = 0;
891 setMapViewPos(mapOfsX, mapOfsY);
892 mapOfsX = mapViewPosX[1];
893 mapOfsY = mapViewPosY[1];
895 sdwindow = new SimpleWindow(vlWidth, vlHeight, "D2D", OpenGlOptions.yes, Resizablity.fixedSize);
897 sdwindow.visibleForTheFirstTime = delegate () {
898 sdwindow.setAsCurrentOpenGlContext(); // make this window active
899 glbindLoadFunctions();
902 import core.stdc.stdio;
903 printf("GL version: %s\n", glGetString(GL_VERSION));
904 GLint l, h;
905 glGetIntegerv(GL_MAJOR_VERSION, &h);
906 glGetIntegerv(GL_MINOR_VERSION, &l);
907 printf("version: %d.%d\n", h, l);
908 printf("shader version: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
909 GLint svcount;
910 glGetIntegerv(GL_NUM_SHADING_LANGUAGE_VERSIONS, &svcount);
911 if (svcount > 0) {
912 printf("%d shader versions supported:\n", svcount);
913 foreach (GLuint n; 0..svcount) printf(" %d: %s\n", n, glGetStringi(GL_SHADING_LANGUAGE_VERSION, n));
916 GLint ecount;
917 glGetIntegerv(GL_NUM_EXTENSIONS, &ecount);
918 if (ecount > 0) {
919 printf("%d extensions supported:\n", ecount);
920 foreach (GLuint n; 0..ecount) printf(" %d: %s\n", n, glGetStringi(GL_EXTENSIONS, n));
925 // check if we have sufficient shader version here
927 bool found = false;
928 GLint svcount;
929 glGetIntegerv(GL_NUM_SHADING_LANGUAGE_VERSIONS, &svcount);
930 if (svcount > 0) {
931 foreach (GLuint n; 0..svcount) {
932 import core.stdc.string : strncmp;
933 auto v = glGetStringi(GL_SHADING_LANGUAGE_VERSION, n);
934 if (v is null) continue;
935 if (strncmp(v, "120", 3) != 0) continue;
936 if (v[3] > ' ') continue;
937 found = true;
938 break;
941 if (!found) assert(0, "can't find OpenGL GLSL 120");
943 auto adr = glGetProcAddress("glTexParameterf");
944 if (adr is null) assert(0);
947 version(use_vsync) {
948 sdwindow.vsync = true;
949 } else {
950 sdwindow.vsync = false;
952 //sdwindow.useGLFinish = false;
953 initOpenGL();
954 //sdwindow.redrawOpenGlScene();
955 if (!sdwindow.releaseCurrentOpenGlContext()) { import core.stdc.stdio; printf("can't release OpenGL context(1)\n"); }
956 if (!renderTid) {
957 renderTid = new Thread(&renderThread);
958 renderTid.start();
962 //sdwindow.redrawOpenGlScene = delegate () { renderScene(); };
964 enum MSecsPerFrame = 1000/30; /* 30 is FPS */
966 uint[8] frameTimes = 1000;
967 enum { Left, Right, Up, Down }
968 bool[4] pressed = false;
970 sdwindow.eventLoop(MSecsPerFrame,
971 delegate () {
972 if (sdwindow.closed) return;
973 if (pressed[Left]) mapOfsX -= 8;
974 if (pressed[Right]) mapOfsX += 8;
975 if (pressed[Up]) mapOfsY -= 8;
976 if (pressed[Down]) mapOfsY += 8;
977 import std.math : cos, sin;
978 __gshared float itime = 0.0;
979 itime += 0.02;
980 if (movement) {
981 mapOfsX = cast(int)(800.0/2.0+cos(itime)*220.0);
982 mapOfsY = cast(int)(800.0/2.0+120.0+sin(itime)*160.0);
984 if (scale == 1) mapOfsX = mapOfsY = 0;
985 //sdwindow.redrawOpenGlSceneNow();
987 delegate (KeyEvent event) {
988 if (sdwindow.closed) return;
989 if (event.pressed && event.key == Key.Escape) { closeWindow(); return; }
990 switch (event.key) {
991 case Key.X: if (event.pressed) altMove = !altMove; break;
992 case Key.Left: if (altMove) pressed[Left] = event.pressed; break;
993 case Key.Right: if (altMove) pressed[Right] = event.pressed; break;
994 case Key.Up: if (altMove) pressed[Up] = event.pressed; break;
995 case Key.Down: if (altMove) pressed[Down] = event.pressed; break;
996 default:
998 if (!altMove) {
999 switch (event.key) {
1000 case Key.Left: case Key.Pad4: plrKeyUpDown(0, PLK_LEFT, event.pressed); break;
1001 case Key.Right: case Key.Pad6: plrKeyUpDown(0, PLK_RIGHT, event.pressed); break;
1002 case Key.Up: case Key.Pad8: plrKeyUpDown(0, PLK_UP, event.pressed); break;
1003 case Key.Down: case Key.Pad2: plrKeyUpDown(0, PLK_DOWN, event.pressed); break;
1004 case Key.Alt: plrKeyUpDown(0, PLK_JUMP, event.pressed); break;
1005 case Key.Ctrl: plrKeyUpDown(0, PLK_FIRE, event.pressed); break;
1006 case Key.Shift: plrKeyUpDown(0, PLK_USE, event.pressed); break;
1007 default:
1011 delegate (MouseEvent event) {
1012 lightX = event.x/scale;
1013 lightY = event.y/scale;
1014 lightX += mapOfsX/scale;
1015 lightY += mapOfsY/scale;
1017 delegate (dchar ch) {
1018 if (ch == 'q') { closeWindow(); return; }
1019 if (ch == 's') scanlines = !scanlines;
1020 if (ch == '1') scale = 1;
1021 if (ch == '2') scale = 2;
1022 if (ch == 'D') cheatNoDoors = !cheatNoDoors;
1023 if (ch == 'i') {
1024 frameInterpolation = !frameInterpolation;
1025 if (frameInterpolation) addMessage("Interpolation: ON"); else addMessage("Interpolation: OFF");
1027 if (ch == 'l') {
1028 doLighting = !doLighting;
1029 if (doLighting) addMessage("Lighting: ON"); else addMessage("Lighting: OFF");
1031 if (ch == ' ') movement = !movement;
1034 } catch (Exception e) {
1035 import std.stdio : stderr;
1036 stderr.writeln("FUUUUUUUUUUUU\n", e.toString);
1038 closeWindow();
1039 flushGui();