initial work for switch actors
[dd2d.git] / xmain_d2d.d
blob9954254a5b15e8ecf973aa81aea6569d904056cf
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 //version = dont_use_vsync;
22 private:
23 import core.atomic;
24 import core.thread;
25 import core.time;
27 import iv.glbinds;
28 import glutils;
29 import console;
30 import wadarc;
32 import iv.stream;
34 import d2dmap;
35 import d2dadefs;
36 //import d2dactors;
37 import d2dgfx;
38 import d2dfont;
39 import dacs;
41 import d2dunigrid;
43 // `map` is there
44 import dengapi;
47 // ////////////////////////////////////////////////////////////////////////// //
48 import arsd.color;
49 import arsd.png;
52 // ////////////////////////////////////////////////////////////////////////// //
53 public __gshared bool cheatNoDoors = false;
56 // ////////////////////////////////////////////////////////////////////////// //
57 __gshared SimpleWindow sdwindow;
60 public enum vlWidth = 800;
61 public enum vlHeight = 800;
62 public __gshared int scale = 1;
65 // ////////////////////////////////////////////////////////////////////////// //
66 __gshared bool scanlines = false;
67 __gshared bool doLighting = true;
70 // ////////////////////////////////////////////////////////////////////////// //
71 // interpolation
72 __gshared ubyte[] prevFrameActorsData;
73 __gshared uint[65536] prevFrameActorOfs; // uint.max-1: dead; uint.max: last
74 __gshared MonoTime lastthink = MonoTime.zero; // for interpolator
75 __gshared MonoTime nextthink = MonoTime.zero;
76 __gshared bool frameInterpolation = true;
79 __gshared int[2] mapViewPosX, mapViewPosY; // [0]: previous frame -- for interpolator
82 // this should be screen center
83 public void setMapViewPos (int x, int y) {
84 if (map is null) {
85 mapViewPosX[] = 0;
86 mapViewPosY[] = 0;
87 return;
89 int swdt = vlWidth/scale;
90 int shgt = vlHeight/scale;
91 x -= swdt/2;
92 y -= shgt/2;
93 if (x < 0) x = 0; else if (x >= map.width*8-swdt) x = map.width*8-swdt-1;
94 if (y < 0) y = 0; else if (y >= map.height*8-shgt) y = map.height*8-shgt-1;
95 mapViewPosX[1] = x*scale;
96 mapViewPosY[1] = y*scale;
100 // ////////////////////////////////////////////////////////////////////////// //
101 // attached lights
102 struct AttachedLightInfo {
103 int x, y;
104 float r, g, b;
105 bool uncolored;
106 int radius;
109 __gshared AttachedLightInfo[65536] attachedLights;
110 __gshared uint attachedLightCount = 0;
113 // ////////////////////////////////////////////////////////////////////////// //
114 enum MaxLightRadius = 512;
117 // ////////////////////////////////////////////////////////////////////////// //
118 // for light
119 __gshared FBO[MaxLightRadius+1] fboOccluders, fboShadowMap;
120 __gshared Shader shadToPolar, shadBlur, shadBlurOcc;
122 __gshared FBO fboLevel, fboLevelLight, fboOrigBack;
123 __gshared Shader shadScanlines;
124 __gshared Shader shadLiquidDistort;
127 // ////////////////////////////////////////////////////////////////////////// //
128 void initOpenGL () {
129 glEnable(GL_TEXTURE_2D);
130 glDisable(GL_LIGHTING);
131 glDisable(GL_DITHER);
132 glDisable(GL_BLEND);
133 glDisable(GL_DEPTH_TEST);
135 // create shaders
136 shadScanlines = new Shader("scanlines", loadTextFile("data/shaders/srscanlines.frag"));
138 shadLiquidDistort = new Shader("liquid_distort", loadTextFile("data/shaders/srliquid_distort.frag"));
139 shadLiquidDistort.exec((Shader shad) { shad["tex0"] = 0; });
141 // lights
142 shadToPolar = new Shader("light_topolar", loadTextFile("data/shaders/srlight_topolar.frag"));
143 shadBlur = new Shader("light_blur", loadTextFile("data/shaders/srlight_blur.frag"));
144 shadBlur.exec((Shader shad) {
145 shad["tex0"] = 0;
146 shad["tex1"] = 1;
147 shad["tex2"] = 2;
149 shadBlurOcc = new Shader("light_blur_occ", loadTextFile("data/shaders/srlight_blur_occ.frag"));
150 shadBlurOcc.exec((Shader shad) {
151 shad["tex0"] = 0;
152 shad["tex1"] = 1;
153 shad["tex2"] = 2;
155 //TODO: this sux!
156 foreach (int sz; 2..MaxLightRadius+1) {
157 fboOccluders[sz] = new FBO(sz*2, sz*2, Texture.Option.Clamp, Texture.Option.Linear); // create occluders FBO
158 fboShadowMap[sz] = new FBO(sz*2, 1, Texture.Option.Clamp); // create 1d shadowmap FBO
161 // setup matrices
162 glMatrixMode(GL_MODELVIEW);
163 glLoadIdentity();
165 map.oglBuildMega();
166 mapTilesChanged = false;
168 fboLevel = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // final level render will be here
169 fboLevelLight = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // level lights will be rendered here
170 //fboForeground = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // level foreground
171 fboOrigBack = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // background+foreground
173 shadBlur.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*8, map.height*8); });
174 shadBlurOcc.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*8, map.height*8); });
176 glActiveTexture(GL_TEXTURE0+0);
177 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
178 orthoCamera(vlWidth, vlHeight);
180 loadSmFont();
182 Actor.resetStorage();
183 loadMapMonsters();
185 // save first snapshot
186 prevFrameActorsData = new ubyte[](Actor.actorSize*65536); // ~15-20 megabytes
187 prevFrameActorOfs[] = uint.max; // just for fun
188 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
189 mapViewPosX[0] = mapViewPosX[1];
190 mapViewPosY[0] = mapViewPosY[1];
192 { import core.memory : GC; GC.collect(); }
196 // ////////////////////////////////////////////////////////////////////////// //
197 //FIXME: optimize!
198 __gshared bool mapTilesChanged = false;
201 public void mapDirty () { mapTilesChanged = true; }
203 void rebuildMapMegaTextures () {
204 //fbo.replaceTexture
205 mapTilesChanged = false;
206 map.clearMegaTextures();
207 map.oglBuildMega();
211 // ////////////////////////////////////////////////////////////////////////// //
212 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
213 if (lightRadius < 2) return;
214 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
215 int lightSize = lightRadius*2;
216 // is this light visible?
217 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*8 || lightY-lightRadius >= map.height*8) return;
219 // draw shadow casters to fboOccludersId, light should be in the center
220 glUseProgram(0);
221 glDisable(GL_BLEND);
222 fboOccluders[lightRadius].exec({
223 //glDisable(GL_BLEND);
224 glColor3f(0.0f, 0.0f, 0.0f);
225 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
226 glClear(GL_COLOR_BUFFER_BIT);
227 orthoCamera(lightSize, lightSize);
228 drawAtXY(map.texgl.ptr[map.LightMask], lightRadius-lightX, lightRadius-lightY);
231 // build 1d shadow map to fboShadowMapId
232 fboShadowMap[lightRadius].exec({
233 shadToPolar.exec((Shader shad) {
234 //glDisable(GL_BLEND);
235 glColor3f(0.0f, 0.0f, 0.0f);
236 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
237 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
238 glClear(GL_COLOR_BUFFER_BIT);
239 orthoCamera(lightSize, 1);
240 drawAtXY(fboOccluders[lightRadius].tex.tid, 0, 0, lightSize, 1);
244 // build light texture for blending
245 fboOccluders[lightRadius].exec({
246 // no need to clear it, shader will take care of that
247 //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
248 //glClear(GL_COLOR_BUFFER_BIT);
249 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
250 //glDisable(GL_BLEND);
251 shadBlur.exec((Shader shad) {
252 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
253 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
254 shad["lightPos"] = SVec2F(lightX, lightY);
255 orthoCamera(lightSize, lightSize);
256 drawAtXY(fboShadowMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
260 // blend light texture
261 fboLevelLight.exec({
262 glEnable(GL_BLEND);
263 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
264 orthoCamera(map.width*8, map.height*8);
265 drawAtXY(fboOccluders[lightRadius].tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
266 // and blend it again, somewhat bigger, with the shader that will touch only occluders
267 enum szmore = 0;
268 shadBlurOcc.exec((Shader shad) {
269 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
270 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
271 shad["lightPos"] = SVec2F(lightX, lightY);
272 drawAtXY(fboOccluders[lightRadius].tex.tid, lightX-lightRadius-szmore, lightY-lightRadius-szmore, lightSize+szmore*2, lightSize+szmore*2, mirrorY:true);
278 // ////////////////////////////////////////////////////////////////////////// //
279 // messages
280 struct Message {
281 enum Phase { FadeIn, Stay, FadeOut }
282 Phase phase;
283 int alpha;
284 int pauseMsecs;
285 MonoTime removeTime;
286 char[256] text;
287 usize textlen;
290 private import core.sync.mutex : Mutex;
292 __gshared Message[128] messages;
293 __gshared uint messagesUsed = 0;
294 __gshared Mutex messageLock;
296 shared static this () { messageLock = new Mutex(); }
299 void addMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
300 messageLock.lock();
301 scope(exit) messageLock.unlock();
302 if (msgtext.length == 0) return;
303 conwriteln(msgtext);
304 if (pauseMsecs <= 50) return;
305 if (messagesUsed == messages.length) {
306 // remove top message
307 foreach (immutable cidx; 1..messagesUsed) messages.ptr[cidx-1] = messages.ptr[cidx];
308 messages.ptr[0].alpha = 255;
309 --messagesUsed;
311 // quick replace
312 if (!noreplace && messagesUsed == 1) {
313 switch (messages.ptr[0].phase) {
314 case Message.Phase.FadeIn:
315 messages.ptr[0].phase = Message.Phase.FadeOut;
316 break;
317 case Message.Phase.Stay:
318 messages.ptr[0].phase = Message.Phase.FadeOut;
319 messages.ptr[0].alpha = 255;
320 break;
321 default:
324 auto msg = messages.ptr+messagesUsed;
325 ++messagesUsed;
326 msg.phase = Message.Phase.FadeIn;
327 msg.alpha = 0;
328 msg.pauseMsecs = pauseMsecs;
329 // copy text
330 if (msgtext.length > msg.text.length) {
331 msg.text = msgtext[0..msg.text.length];
332 msg.textlen = msg.text.length;
333 } else {
334 msg.text[0..msgtext.length] = msgtext[];
335 msg.textlen = msgtext.length;
340 void doMessages (MonoTime curtime) {
341 messageLock.lock();
342 scope(exit) messageLock.unlock();
344 if (messagesUsed == 0) return;
345 glEnable(GL_BLEND);
346 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
348 Message* msg;
350 again:
351 msg = messages.ptr;
352 final switch (msg.phase) {
353 case Message.Phase.FadeIn:
354 if ((msg.alpha += 10) >= 255) {
355 msg.phase = Message.Phase.Stay;
356 msg.removeTime = curtime+dur!"msecs"(msg.pauseMsecs);
357 goto case; // to stay
359 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
360 break;
361 case Message.Phase.Stay:
362 if (msg.removeTime <= curtime) {
363 msg.alpha = 255;
364 msg.phase = Message.Phase.FadeOut;
365 goto case; // to fade
367 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
368 break;
369 case Message.Phase.FadeOut:
370 if ((msg.alpha -= 10) <= 0) {
371 if (--messagesUsed == 0) return;
372 // remove this message
373 foreach (immutable cidx; 1..messagesUsed+1) messages.ptr[cidx-1] = messages.ptr[cidx];
374 goto again;
376 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
377 break;
380 smDrawText(10, 10, msg.text[0..msg.textlen]);
384 // ////////////////////////////////////////////////////////////////////////// //
385 mixin(Actor.FieldGetMixin!("classtype", StrId)); // fget_classtype
386 mixin(Actor.FieldGetMixin!("classname", StrId)); // fget_classname
387 mixin(Actor.FieldGetMixin!("x", int));
388 mixin(Actor.FieldGetMixin!("y", int));
389 mixin(Actor.FieldGetMixin!("flags", uint));
390 mixin(Actor.FieldGetMixin!("zAnimstate", StrId));
391 mixin(Actor.FieldGetMixin!("zAnimidx", int));
392 mixin(Actor.FieldGetMixin!("dir", uint));
393 mixin(Actor.FieldGetMixin!("attLightXOfs", int));
394 mixin(Actor.FieldGetMixin!("attLightYOfs", int));
395 mixin(Actor.FieldGetMixin!("attLightRGBX", uint));
397 mixin(Actor.FieldGetPtrMixin!("classtype", StrId)); // fget_classtype
398 mixin(Actor.FieldGetPtrMixin!("classname", StrId)); // fget_classname
399 mixin(Actor.FieldGetPtrMixin!("x", int));
400 mixin(Actor.FieldGetPtrMixin!("y", int));
401 mixin(Actor.FieldGetPtrMixin!("flags", uint));
402 mixin(Actor.FieldGetPtrMixin!("zAnimstate", StrId));
403 mixin(Actor.FieldGetPtrMixin!("zAnimidx", int));
404 mixin(Actor.FieldGetPtrMixin!("dir", uint));
405 mixin(Actor.FieldGetPtrMixin!("attLightXOfs", int));
406 mixin(Actor.FieldGetPtrMixin!("attLightYOfs", int));
407 mixin(Actor.FieldGetPtrMixin!("attLightRGBX", uint));
410 // ////////////////////////////////////////////////////////////////////////// //
411 __gshared int lightX = vlWidth/2, lightY = vlHeight/2;
412 __gshared int mapOfsX, mapOfsY;
413 __gshared bool movement = false;
414 __gshared float iLiquidTime = 0.0;
415 __gshared bool altMove = false;
418 void renderScene (MonoTime curtime) {
419 //enum BackIntens = 0.05f;
420 enum BackIntens = 0.0f;
422 float atob = (curtime > lastthink ? cast(float)((curtime-lastthink).total!"msecs")/cast(float)((nextthink-lastthink).total!"msecs") : 1.0f);
423 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
426 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
427 int curfp = cast(int)((curtime-lastthink).total!"msecs");
428 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
432 glUseProgram(0);
434 if (mapTilesChanged) rebuildMapMegaTextures();
436 // build background layer
437 fboOrigBack.exec({
438 //glDisable(GL_BLEND);
439 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
440 glClear(GL_COLOR_BUFFER_BIT);
441 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
442 orthoCamera(map.width*8, map.height*8);
443 glEnable(GL_BLEND);
444 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
445 // draw sky
447 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
448 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2, map.MapSize*8, map.MapSize*8);
449 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
450 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*8, map.MapSize*8);
452 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*8, map.MapSize*8);
453 // draw background
454 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
455 // draw distorted liquid areas
456 shadLiquidDistort.exec((Shader shad) {
457 shad["iDistortTime"] = iLiquidTime;
458 drawAtXY(map.texgl.ptr[map.AllLiquids], 0, 0);
460 // monsters, items; we'll do linear interpolation here
461 // we need this for saved frame anyway, so let's play dirty and speed up the things
462 glColor3f(1.0f, 1.0f, 1.0f);
463 attachedLightCount = 0;
464 Actor.forEach((ActorId me) {
465 if (auto adef = findActorDef(me)) {
466 int actorX, actorY; // current actor position
468 auto ofs = prevFrameActorOfs.ptr[me.id&0xffff];
469 if (frameInterpolation && ofs < uint.max-1 && Actor.isSameSavedActor(me.id, prevFrameActorsData.ptr, ofs)) {
470 import core.stdc.math : roundf;
471 auto xptr = prevFrameActorsData.ptr+ofs;
472 int ox = xptr.fgetp_x;
473 int nx = me.fget_x;
474 int oy = xptr.fgetp_y;
475 int ny = me.fget_y;
476 actorX = cast(int)(ox+roundf((nx-ox)*atob));
477 actorY = cast(int)(oy+roundf((ny-oy)*atob));
478 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
479 } else {
480 actorX = me.fget_x;
481 actorY = me.fget_y;
484 // draw sprite
485 if ((me.fget_flags&AF_NODRAW) == 0) {
486 if (auto isp = adef.animSpr(me.fget_zAnimstate, me.fget_dir, me.fget_zAnimidx)) {
487 drawAtXY(isp.tex, actorX-isp.vga.sx, actorY-isp.vga.sy);
488 } else {
489 //conwriteln("no animation for actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
492 // process attached lights
493 if ((me.fget_flags&AF_NOLIGHT) == 0) {
494 uint alr = me.fget_attLightRGBX;
495 if ((alr&0xff) >= 4) {
496 // yep, add it
497 auto li = attachedLights.ptr+attachedLightCount;
498 ++attachedLightCount;
499 li.x = actorX+me.fget_attLightXOfs;
500 li.y = actorY+me.fget_attLightYOfs;
501 li.r = ((alr>>24)&0xff)/255.0f; // red or intensity
502 if ((alr&0x00_ff_ff_00U) == 0x00_00_01_00U) {
503 li.uncolored = true;
504 } else {
505 li.g = ((alr>>16)&0xff)/255.0f;
506 li.b = ((alr>>8)&0xff)/255.0f;
507 li.uncolored = false;
509 li.radius = (alr&0xff);
510 if (li.radius > MaxLightRadius) li.radius = MaxLightRadius;
513 } else {
514 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
517 // do liquid coloring
518 drawAtXY(map.texgl.ptr[map.LiquidMask], 0, 0);
519 // foreground -- hide secrets, draw lifts and such
520 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
524 if (doLighting) {
525 // clear light layer
526 fboLevelLight.exec({
527 glDisable(GL_BLEND);
528 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
529 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
530 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
531 glClear(GL_COLOR_BUFFER_BIT);
534 // texture 1 is background
535 glActiveTexture(GL_TEXTURE0+1);
536 glBindTexture(GL_TEXTURE_2D, fboOrigBack.tex.tid);
537 // texture 2 is occluders
538 glActiveTexture(GL_TEXTURE0+2);
539 glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.LightMask].tid);
540 // done texture assign
541 glActiveTexture(GL_TEXTURE0+0);
543 enum LYOfs = 1;
545 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
546 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
547 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
548 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
549 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
550 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
551 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
552 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
553 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
554 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
556 renderLight(24*8+4, (24+18)*8-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
558 // attached lights
559 foreach (ref li; attachedLights[0..attachedLightCount]) {
560 if (li.uncolored) {
561 renderLight(li.x, li.y, SVec4F(0.0f, 0.0f, 0.0f, li.r), li.radius);
562 } else {
563 renderLight(li.x, li.y, SVec4F(li.r, li.g, li.b, 1.0f), li.radius);
567 foreach (immutable _; 0..1) {
568 renderLight(lightX, lightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
571 glActiveTexture(GL_TEXTURE0+1);
572 glBindTexture(GL_TEXTURE_2D, 0);
573 glActiveTexture(GL_TEXTURE0+2);
574 glBindTexture(GL_TEXTURE_2D, 0);
575 glActiveTexture(GL_TEXTURE0+0);
578 // draw scaled level
580 shadScanlines.exec((Shader shad) {
581 shad["scanlines"] = scanlines;
582 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
583 glClear(GL_COLOR_BUFFER_BIT);
584 orthoCamera(vlWidth, vlHeight);
585 //orthoCamera(map.width*8*scale, map.height*8*scale);
586 //glMatrixMode(GL_MODELVIEW);
587 //glLoadIdentity();
588 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
589 // somehow, FBO objects are mirrored; wtf?!
590 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*8*scale, map.height*8*scale, mirrorY:true);
591 //glLoadIdentity();
596 fboLevelLight.exec({
597 smDrawText(map.width*8/2, map.height*8/2, "Testing...");
602 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
603 glDisable(GL_BLEND);
606 fboOrigBack.exec({
607 //auto img = smfont.ptr[0x39];
608 auto img = fftest;
609 if (img !is null) {
610 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
611 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
616 int mofsx, mofsy;
618 if (altMove || movement || scale == 1) {
619 mofsx = mapOfsX;
620 mofsy = mapOfsY;
621 } else {
622 if (frameInterpolation) {
623 import core.stdc.math : roundf;
624 mofsx = cast(int)(mapViewPosX[0]+roundf((mapViewPosX[1]-mapViewPosX[0])*atob));
625 mofsy = cast(int)(mapViewPosY[0]+roundf((mapViewPosY[1]-mapViewPosY[0])*atob));
626 } else {
627 mofsx = mapViewPosX[1];
628 mofsy = mapViewPosY[1];
632 orthoCamera(vlWidth, vlHeight);
633 auto tex = (doLighting ? fboLevelLight.tex.tid : fboOrigBack.tex.tid);
634 drawAtXY(tex, -mofsx, -mofsy, map.width*8*scale, map.height*8*scale, mirrorY:true);
636 doMessages(curtime);
640 // ////////////////////////////////////////////////////////////////////////// //
641 // returns time slept
642 int sleepAtMaxMsecs (int msecs) {
643 if (msecs > 0) {
644 import core.sys.posix.signal : timespec;
645 import core.sys.posix.time : nanosleep;
646 timespec ts = void, tpassed = void;
647 ts.tv_sec = 0;
648 ts.tv_nsec = msecs*1000*1000+(500*1000); // milli to nano
649 nanosleep(&ts, &tpassed);
650 return (ts.tv_nsec-tpassed.tv_nsec)/(1000*1000);
651 } else {
652 return 0;
657 // ////////////////////////////////////////////////////////////////////////// //
658 // rendering thread
659 shared int diedie = 0;
661 enum D2DFrameTime = 55; // milliseconds
662 enum MinFrameTime = 1000/60; // ~60 FPS
664 void renderThread () {
665 try {
666 MonoTime curtime = MonoTime.currTime;
668 lastthink = curtime; // for interpolator
669 nextthink = curtime+dur!"msecs"(D2DFrameTime);
670 MonoTime nextvframe = curtime;
672 enum MaxFPSFrames = 16;
673 float frtimes = 0.0f;
674 int framenum = 0;
675 int prevFPS = -1;
676 int hushFrames = 6; // ignore first `hushFrames` frames overtime
677 MonoTime prevFrameStartTime = curtime;
679 bool vframeWasLost = false;
681 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
682 bool doThinkFrame () {
683 if (curtime >= nextthink) {
684 lastthink = curtime;
685 while (nextthink <= curtime) nextthink += dur!"msecs"(D2DFrameTime);
686 // save snapshot and other datafor interpolator
687 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
688 mapViewPosX[0] = mapViewPosX[1];
689 mapViewPosY[0] = mapViewPosY[1];
690 // process actors
691 doActorsThink();
692 // some timing
693 auto tm = MonoTime.currTime;
694 int thinkTime = cast(int)((tm-curtime).total!"msecs");
695 if (thinkTime > 9) { import core.stdc.stdio; printf("spent on thinking: %d msecs\n", thinkTime); }
696 curtime = tm;
697 return true;
698 } else {
699 return false;
703 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
704 bool doVFrame () {
705 version(dont_use_vsync) {
706 // timer
707 enum doCheckTime = true;
708 } else {
709 // vsync
710 __gshared bool prevLost = false;
711 bool doCheckTime = vframeWasLost;
712 if (vframeWasLost) {
713 if (!prevLost) {
714 { import core.stdc.stdio; printf("frame was lost!\n"); }
716 prevLost = true;
717 } else {
718 prevLost = false;
721 if (doCheckTime) {
722 if (curtime < nextvframe) return false;
723 version(dont_use_vsync) {
724 if (curtime > nextvframe) {
725 auto overtime = cast(int)((curtime-nextvframe).total!"msecs");
726 if (overtime > 2500) {
727 if (hushFrames) {
728 --hushFrames;
729 } else {
730 { import core.stdc.stdio; printf(" spent whole %d msecs\n", overtime); }
736 while (nextvframe <= curtime) nextvframe += dur!"msecs"(MinFrameTime);
737 bool ctset = false;
739 sdwindow.mtLock();
740 scope(exit) sdwindow.mtUnlock();
741 ctset = sdwindow.setAsCurrentOpenGlContextNT;
743 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
744 if (ctset) {
745 // render scene
746 iLiquidTime = cast(float)((curtime-MonoTime.zero).total!"msecs"%10000000)/18.0f*0.04f;
747 renderScene(curtime);
748 sdwindow.mtLock();
749 scope(exit) sdwindow.mtUnlock();
750 sdwindow.swapOpenGlBuffers();
751 glFinish();
752 sdwindow.releaseCurrentOpenGlContext();
753 vframeWasLost = false;
754 } else {
755 vframeWasLost = true;
756 { import core.stdc.stdio; printf("xframe was lost!\n"); }
758 curtime = MonoTime.currTime;
759 return true;
762 for (;;) {
763 if (sdwindow.closed) break;
764 if (atomicLoad(diedie) > 0) break;
766 curtime = MonoTime.currTime;
767 auto fstime = curtime;
769 doThinkFrame(); // this will fix curtime if necessary
770 if (doVFrame()) {
771 if (!vframeWasLost) {
772 // fps
773 auto frameTime = cast(float)(curtime-prevFrameStartTime).total!"msecs"/1000.0f;
774 prevFrameStartTime = curtime;
775 frtimes += frameTime;
776 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
777 import std.string : format;
778 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
779 if (newFPS != prevFPS) {
780 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
781 prevFPS = newFPS;
783 framenum = 0;
784 frtimes = 0.0f;
789 curtime = MonoTime.currTime;
791 // now sleep until next "video" or "think" frame
792 if (nextthink > curtime && nextvframe > curtime) {
793 // let's decide
794 immutable nextVideoFrameSleep = cast(int)((nextvframe-curtime).total!"msecs");
795 immutable nextThinkFrameSleep = cast(int)((nextthink-curtime).total!"msecs");
796 immutable sleepTime = (nextVideoFrameSleep < nextThinkFrameSleep ? nextVideoFrameSleep : nextThinkFrameSleep);
797 sleepAtMaxMsecs(sleepTime);
798 //curtime = MonoTime.currTime;
801 } catch (Exception e) {
802 import core.stdc.stdio;
803 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
804 for (;;) {
805 if (sdwindow.closed) break;
806 if (atomicLoad(diedie) > 0) break;
807 sleepAtMaxMsecs(100);
810 atomicStore(diedie, 2);
814 // ////////////////////////////////////////////////////////////////////////// //
815 void closeWindow () {
816 if (atomicLoad(diedie) != 2) {
817 atomicStore(diedie, 1);
818 while (atomicLoad(diedie) != 2) {}
820 if (!sdwindow.closed) {
821 flushGui();
822 sdwindow.close();
827 // ////////////////////////////////////////////////////////////////////////// //
828 __gshared Thread renderTid;
831 void main (string[] args) {
832 FuncPool.dumpCode = false;
833 FuncPool.dumpCodeSize = false;
834 dacsDumpSemantic = false;
835 dacsOptimize = 9;
836 //version(rdmd) { dacsOptimize = 0; }
837 bool compileOnly = false;
839 for (usize idx = 1; idx < args.length; ++idx) {
840 bool remove = true;
841 if (args[idx] == "--dump-code") FuncPool.dumpCode = true;
842 else if (args[idx] == "--dump-code-size") FuncPool.dumpCodeSize = true;
843 else if (args[idx] == "--dump-semantic") dacsDumpSemantic = true;
844 else if (args[idx] == "--dump-all") { FuncPool.dumpCode = true; FuncPool.dumpCodeSize = true; dacsDumpSemantic = true; }
845 else if (args[idx] == "--compile") compileOnly = true;
846 else if (args[idx] == "--compile-only") compileOnly = true;
847 else if (args[idx] == "--messages") ++dacsMessages;
848 else if (args[idx] == "--no-copro") dacsOptimizeNoCoPro = true;
849 else if (args[idx] == "--no-deadass") dacsOptimizeNoDeadAss = true;
850 else if (args[idx] == "--no-purekill") dacsOptimizeNoPureKill = true;
851 else if (args[idx].length > 2 && args[idx][0..2] == "-O") {
852 import std.conv : to;
853 ubyte olevel = to!ubyte(args[idx][2..$]);
854 dacsOptimize = olevel;
856 else remove = false;
857 if (remove) {
858 foreach (immutable c; idx+1..args.length) args.ptr[c-1] = args.ptr[c];
859 args.length -= 1;
860 --idx; //hack
864 static void setDP () {
865 version(rdmd) {
866 setDataPath("data");
867 } else {
868 import std.file : thisExePath;
869 import std.path : dirName;
870 setDataPath(thisExePath.dirName);
872 addPK3(getDataPath~"base.pk3"); loadWadScripts();
873 //addWad("/home/ketmar/k8prj/doom2d-tl/data/doom2d.wad"); loadWadScripts();
874 //addWad("/home/ketmar/k8prj/doom2d-tl/data/meat.wad"); loadWadScripts();
875 //addWad("/home/ketmar/k8prj/doom2d-tl/data/megadm.wad"); loadWadScripts();
876 //addWad("/home/ketmar/k8prj/doom2d-tl/data/megadm1.wad"); loadWadScripts();
877 //addWad("/home/ketmar/k8prj/doom2d-tl/data/superdm.wad"); loadWadScripts();
878 //addWad("/home/ketmar/k8prj/doom2d-tl/data/zadoomka.wad"); loadWadScripts();
879 scriptLoadingComplete();
882 setDP();
884 if (compileOnly) return;
886 try {
887 registerAPI();
888 loadPalette();
890 setOpenGLContextVersion(3, 2); // up to GLSL 150
891 //openGLContextCompatible = false;
893 map = new LevelMap("maps/map01.d2m");
894 ugInit(map.width*8, map.height*8);
896 scale = 2;
898 //mapOfsX = 8*26;
899 //mapOfsY = 8*56;
900 map.getThingPos(1/*ThingId.Player1*/, &mapOfsX, &mapOfsY);
901 // fix viewport
903 mapOfsX = (mapOfsX*2)-vlWidth/2;
904 if (mapOfsX+vlWidth > map.width*16) mapOfsX = map.width*16-vlWidth;
905 if (mapOfsX < 0) mapOfsX = 0;
906 mapOfsY = (mapOfsY*2)-vlHeight/2;
907 if (mapOfsY+vlHeight > map.height*16) mapOfsY = map.height*16-vlHeight;
908 if (mapOfsY < 0) mapOfsY = 0;
910 setMapViewPos(mapOfsX, mapOfsY);
911 mapOfsX = mapViewPosX[1];
912 mapOfsY = mapViewPosY[1];
914 sdwindow = new SimpleWindow(vlWidth, vlHeight, "D2D", OpenGlOptions.yes, Resizablity.fixedSize);
916 sdwindow.visibleForTheFirstTime = delegate () {
917 sdwindow.setAsCurrentOpenGlContext(); // make this window active
918 glbindLoadFunctions();
921 import core.stdc.stdio;
922 printf("GL version: %s\n", glGetString(GL_VERSION));
923 GLint l, h;
924 glGetIntegerv(GL_MAJOR_VERSION, &h);
925 glGetIntegerv(GL_MINOR_VERSION, &l);
926 printf("version: %d.%d\n", h, l);
927 printf("shader version: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
928 GLint svcount;
929 glGetIntegerv(GL_NUM_SHADING_LANGUAGE_VERSIONS, &svcount);
930 if (svcount > 0) {
931 printf("%d shader versions supported:\n", svcount);
932 foreach (GLuint n; 0..svcount) printf(" %d: %s\n", n, glGetStringi(GL_SHADING_LANGUAGE_VERSION, n));
935 GLint ecount;
936 glGetIntegerv(GL_NUM_EXTENSIONS, &ecount);
937 if (ecount > 0) {
938 printf("%d extensions supported:\n", ecount);
939 foreach (GLuint n; 0..ecount) printf(" %d: %s\n", n, glGetStringi(GL_EXTENSIONS, n));
944 // check if we have sufficient shader version here
946 bool found = false;
947 GLint svcount;
948 glGetIntegerv(GL_NUM_SHADING_LANGUAGE_VERSIONS, &svcount);
949 if (svcount > 0) {
950 foreach (GLuint n; 0..svcount) {
951 import core.stdc.string : strncmp;
952 auto v = glGetStringi(GL_SHADING_LANGUAGE_VERSION, n);
953 if (v is null) continue;
954 if (strncmp(v, "120", 3) != 0) continue;
955 if (v[3] > ' ') continue;
956 found = true;
957 break;
960 if (!found) assert(0, "can't find OpenGL GLSL 120");
962 auto adr = glGetProcAddress("glTexParameterf");
963 if (adr is null) assert(0);
966 version(dont_use_vsync) {
967 sdwindow.vsync = false;
968 } else {
969 sdwindow.vsync = true;
971 //sdwindow.useGLFinish = false;
972 initOpenGL();
973 //sdwindow.redrawOpenGlScene();
974 if (!sdwindow.releaseCurrentOpenGlContext()) { import core.stdc.stdio; printf("can't release OpenGL context(1)\n"); }
975 if (!renderTid) {
976 renderTid = new Thread(&renderThread);
977 renderTid.start();
981 //sdwindow.redrawOpenGlScene = delegate () { renderScene(); };
983 enum MSecsPerFrame = 1000/30; /* 30 is FPS */
985 uint[8] frameTimes = 1000;
986 enum { Left, Right, Up, Down }
987 bool[4] pressed = false;
989 sdwindow.eventLoop(MSecsPerFrame,
990 delegate () {
991 if (sdwindow.closed) return;
992 if (pressed[Left]) mapOfsX -= 8;
993 if (pressed[Right]) mapOfsX += 8;
994 if (pressed[Up]) mapOfsY -= 8;
995 if (pressed[Down]) mapOfsY += 8;
996 import std.math : cos, sin;
997 __gshared float itime = 0.0;
998 itime += 0.02;
999 if (movement) {
1000 mapOfsX = cast(int)(800.0/2.0+cos(itime)*220.0);
1001 mapOfsY = cast(int)(800.0/2.0+120.0+sin(itime)*160.0);
1003 if (scale == 1) mapOfsX = mapOfsY = 0;
1004 //sdwindow.redrawOpenGlSceneNow();
1006 delegate (KeyEvent event) {
1007 if (sdwindow.closed) return;
1008 if (event.pressed && event.key == Key.Escape) { closeWindow(); return; }
1009 switch (event.key) {
1010 case Key.X: if (event.pressed) altMove = !altMove; break;
1011 case Key.Left: if (altMove) pressed[Left] = event.pressed; break;
1012 case Key.Right: if (altMove) pressed[Right] = event.pressed; break;
1013 case Key.Up: if (altMove) pressed[Up] = event.pressed; break;
1014 case Key.Down: if (altMove) pressed[Down] = event.pressed; break;
1015 default:
1017 if (!altMove) {
1018 switch (event.key) {
1019 case Key.Left: case Key.Pad4: plrKeyUpDown(0, PLK_LEFT, event.pressed); break;
1020 case Key.Right: case Key.Pad6: plrKeyUpDown(0, PLK_RIGHT, event.pressed); break;
1021 case Key.Up: case Key.Pad8: plrKeyUpDown(0, PLK_UP, event.pressed); break;
1022 case Key.Down: case Key.Pad2: plrKeyUpDown(0, PLK_DOWN, event.pressed); break;
1023 case Key.Alt: plrKeyUpDown(0, PLK_JUMP, event.pressed); break;
1024 case Key.Ctrl: plrKeyUpDown(0, PLK_FIRE, event.pressed); break;
1025 case Key.Shift: plrKeyUpDown(0, PLK_USE, event.pressed); break;
1026 default:
1030 delegate (MouseEvent event) {
1031 lightX = event.x/scale;
1032 lightY = event.y/scale;
1033 lightX += mapOfsX/scale;
1034 lightY += mapOfsY/scale;
1036 delegate (dchar ch) {
1037 if (ch == 'q') { closeWindow(); return; }
1038 if (ch == 's') scanlines = !scanlines;
1039 if (ch == '1') scale = 1;
1040 if (ch == '2') scale = 2;
1041 if (ch == 'D') cheatNoDoors = !cheatNoDoors;
1042 if (ch == 'i') {
1043 frameInterpolation = !frameInterpolation;
1044 if (frameInterpolation) addMessage("Interpolation: ON"); else addMessage("Interpolation: OFF");
1046 if (ch == 'l') {
1047 doLighting = !doLighting;
1048 if (doLighting) addMessage("Lighting: ON"); else addMessage("Lighting: OFF");
1050 if (ch == ' ') movement = !movement;
1053 } catch (Exception e) {
1054 import std.stdio : stderr;
1055 stderr.writeln("FUUUUUUUUUUUU\n", e.toString);
1057 closeWindow();
1058 flushGui();