fixes to scroll
[dd2d.git] / xmain_d2d.d
blob327d3dcad4881775b0cf8e4e888ca6ae47ef8ae1
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;
46 import d2dparts;
49 // ////////////////////////////////////////////////////////////////////////// //
50 import arsd.color;
51 import arsd.png;
54 // ////////////////////////////////////////////////////////////////////////// //
55 public __gshared bool cheatNoDoors = false;
58 // ////////////////////////////////////////////////////////////////////////// //
59 __gshared SimpleWindow sdwindow;
62 public enum vlWidth = 800;
63 public enum vlHeight = 800;
64 public __gshared int scale = 1;
67 // ////////////////////////////////////////////////////////////////////////// //
68 __gshared bool scanlines = false;
69 __gshared bool doLighting = true;
72 // ////////////////////////////////////////////////////////////////////////// //
73 // interpolation
74 __gshared ubyte[] prevFrameActorsData;
75 __gshared uint[65536] prevFrameActorOfs; // uint.max-1: dead; uint.max: last
76 __gshared MonoTime lastthink = MonoTime.zero; // for interpolator
77 __gshared MonoTime nextthink = MonoTime.zero;
78 __gshared bool frameInterpolation = true;
81 __gshared int[2] mapViewPosX, mapViewPosY; // [0]: previous frame -- for interpolator
84 // this should be screen center
85 public void setMapViewPos (int x, int y) {
86 if (map is null) {
87 mapViewPosX[] = 0;
88 mapViewPosY[] = 0;
89 return;
91 int swdt = vlWidth/scale;
92 int shgt = vlHeight/scale;
93 x -= swdt/2;
94 y -= shgt/2;
95 if (x < 0) x = 0; else if (x >= map.width*8-swdt) x = map.width*8-swdt-1;
96 if (y < 0) y = 0; else if (y >= map.height*8-shgt) y = map.height*8-shgt-1;
97 mapViewPosX[1] = x*scale;
98 mapViewPosY[1] = y*scale;
102 // ////////////////////////////////////////////////////////////////////////// //
103 // attached lights
104 struct AttachedLightInfo {
105 int x, y;
106 float r, g, b;
107 bool uncolored;
108 int radius;
111 __gshared AttachedLightInfo[65536] attachedLights;
112 __gshared uint attachedLightCount = 0;
115 // ////////////////////////////////////////////////////////////////////////// //
116 enum MaxLightRadius = 512;
119 // ////////////////////////////////////////////////////////////////////////// //
120 // for light
121 __gshared FBO[MaxLightRadius+1] fboOccluders, fboShadowMap;
122 __gshared Shader shadToPolar, shadBlur, shadBlurOcc;
124 __gshared FBO fboLevel, fboLevelLight, fboOrigBack;
125 __gshared Shader shadScanlines;
126 __gshared Shader shadLiquidDistort;
129 // ////////////////////////////////////////////////////////////////////////// //
130 void initOpenGL () {
131 gloStackClear();
133 glEnable(GL_TEXTURE_2D);
134 glDisable(GL_LIGHTING);
135 glDisable(GL_DITHER);
136 glDisable(GL_BLEND);
137 glDisable(GL_DEPTH_TEST);
139 // create shaders
140 shadScanlines = new Shader("scanlines", loadTextFile("data/shaders/srscanlines.frag"));
142 shadLiquidDistort = new Shader("liquid_distort", loadTextFile("data/shaders/srliquid_distort.frag"));
143 shadLiquidDistort.exec((Shader shad) { shad["tex0"] = 0; });
145 // lights
146 shadToPolar = new Shader("light_topolar", loadTextFile("data/shaders/srlight_topolar.frag"));
148 shadBlur = new Shader("light_blur", loadTextFile("data/shaders/srlight_blur.frag"));
149 shadBlur.exec((Shader shad) {
150 shad["tex0"] = 0;
151 shad["tex1"] = 1;
152 shad["tex2"] = 2;
155 shadBlurOcc = new Shader("light_blur_occ", loadTextFile("data/shaders/srlight_blur_occ.frag"));
156 shadBlurOcc.exec((Shader shad) {
157 shad["tex0"] = 0;
158 shad["tex1"] = 1;
159 shad["tex2"] = 2;
162 //TODO: this sux!
163 foreach (int sz; 2..MaxLightRadius+1) {
164 fboOccluders[sz] = new FBO(sz*2, sz*2, Texture.Option.Clamp, Texture.Option.Linear); // create occluders FBO
165 fboShadowMap[sz] = new FBO(sz*2, 1, Texture.Option.Clamp); // create 1d shadowmap FBO
168 // setup matrices
169 glMatrixMode(GL_MODELVIEW);
170 glLoadIdentity();
172 map.oglBuildMega();
173 mapTilesChanged = 0;
175 fboLevel = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // final level render will be here
176 fboLevelLight = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // level lights will be rendered here
177 //fboForeground = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // level foreground
178 fboOrigBack = new FBO(map.width*8, map.height*8, Texture.Option.Nearest, Texture.Option.Depth); // background+foreground
180 shadBlur.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*8, map.height*8); });
181 shadBlurOcc.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*8, map.height*8); });
183 glActiveTexture(GL_TEXTURE0+0);
184 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
185 orthoCamera(vlWidth, vlHeight);
187 loadSmFont();
189 Actor.resetStorage();
190 loadMapMonsters();
192 dotInit();
194 // save first snapshot
195 prevFrameActorsData = new ubyte[](Actor.actorSize*65536); // ~15-20 megabytes
196 prevFrameActorOfs[] = uint.max; // just for fun
197 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
198 mapViewPosX[0] = mapViewPosX[1];
199 mapViewPosY[0] = mapViewPosY[1];
201 { import core.memory : GC; GC.collect(); }
205 // ////////////////////////////////////////////////////////////////////////// //
206 //FIXME: optimize!
207 __gshared uint mapTilesChanged = 0;
210 public void mapDirty (uint layermask) { mapTilesChanged |= layermask; }
212 void rebuildMapMegaTextures () {
213 //fbo.replaceTexture
214 //mapTilesChanged = false;
215 //map.clearMegaTextures();
216 map.oglBuildMega(mapTilesChanged);
217 mapTilesChanged = 0;
221 // ////////////////////////////////////////////////////////////////////////// //
222 __gshared int vportX0, vportY0, vportX1, vportY1;
225 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
226 if (lightRadius < 2) return;
227 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
228 int lightSize = lightRadius*2;
229 // is this light visible?
230 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*8 || lightY-lightRadius >= map.height*8) return;
232 // out of viewport -- do nothing
233 if (lightX+lightRadius < vportX0 || lightY+lightRadius < vportY0) return;
234 if (lightX-lightRadius > vportX1 || lightY-lightRadius > vportY1) return;
236 // draw shadow casters to fboOccludersId, light should be in the center
237 glUseProgram(0);
238 glDisable(GL_BLEND);
239 fboOccluders[lightRadius].exec({
240 //glDisable(GL_BLEND);
241 glColor3f(0.0f, 0.0f, 0.0f);
242 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
243 glClear(GL_COLOR_BUFFER_BIT);
244 orthoCamera(lightSize, lightSize);
245 drawAtXY(map.texgl.ptr[map.LightMask], lightRadius-lightX, lightRadius-lightY);
248 // build 1d shadow map to fboShadowMapId
249 fboShadowMap[lightRadius].exec({
250 shadToPolar.exec((Shader shad) {
251 //glDisable(GL_BLEND);
252 glColor3f(0.0f, 0.0f, 0.0f);
253 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
254 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
255 glClear(GL_COLOR_BUFFER_BIT);
256 orthoCamera(lightSize, 1);
257 drawAtXY(fboOccluders[lightRadius].tex.tid, 0, 0, lightSize, 1);
261 // build light texture for blending
262 fboOccluders[lightRadius].exec({
263 // no need to clear it, shader will take care of that
264 //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
265 //glClear(GL_COLOR_BUFFER_BIT);
266 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
267 //glDisable(GL_BLEND);
268 shadBlur.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 orthoCamera(lightSize, lightSize);
273 drawAtXY(fboShadowMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
277 // blend light texture
278 fboLevelLight.exec({
279 glEnable(GL_BLEND);
280 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
281 orthoCamera(map.width*8, map.height*8);
282 drawAtXY(fboOccluders[lightRadius].tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
283 // and blend it again, somewhat bigger, with the shader that will touch only occluders
284 enum szmore = 0;
285 shadBlurOcc.exec((Shader shad) {
286 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
287 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
288 shad["lightPos"] = SVec2F(lightX, lightY);
289 drawAtXY(fboOccluders[lightRadius].tex.tid, lightX-lightRadius-szmore, lightY-lightRadius-szmore, lightSize+szmore*2, lightSize+szmore*2, mirrorY:true);
295 // ////////////////////////////////////////////////////////////////////////// //
296 // messages
297 struct Message {
298 enum Phase { FadeIn, Stay, FadeOut }
299 Phase phase;
300 int alpha;
301 int pauseMsecs;
302 MonoTime removeTime;
303 char[256] text;
304 usize textlen;
307 private import core.sync.mutex : Mutex;
309 __gshared Message[128] messages;
310 __gshared uint messagesUsed = 0;
311 __gshared Mutex messageLock;
313 shared static this () { messageLock = new Mutex(); }
316 public void addMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
317 messageLock.lock();
318 scope(exit) messageLock.unlock();
319 if (msgtext.length == 0) return;
320 conwriteln(msgtext);
321 if (pauseMsecs <= 50) return;
322 if (messagesUsed == messages.length) {
323 // remove top message
324 foreach (immutable cidx; 1..messagesUsed) messages.ptr[cidx-1] = messages.ptr[cidx];
325 messages.ptr[0].alpha = 255;
326 --messagesUsed;
328 // quick replace
329 if (!noreplace && messagesUsed == 1) {
330 switch (messages.ptr[0].phase) {
331 case Message.Phase.FadeIn:
332 messages.ptr[0].phase = Message.Phase.FadeOut;
333 break;
334 case Message.Phase.Stay:
335 messages.ptr[0].phase = Message.Phase.FadeOut;
336 messages.ptr[0].alpha = 255;
337 break;
338 default:
341 auto msg = messages.ptr+messagesUsed;
342 ++messagesUsed;
343 msg.phase = Message.Phase.FadeIn;
344 msg.alpha = 0;
345 msg.pauseMsecs = pauseMsecs;
346 // copy text
347 if (msgtext.length > msg.text.length) {
348 msg.text = msgtext[0..msg.text.length];
349 msg.textlen = msg.text.length;
350 } else {
351 msg.text[0..msgtext.length] = msgtext[];
352 msg.textlen = msgtext.length;
357 void doMessages (MonoTime curtime) {
358 messageLock.lock();
359 scope(exit) messageLock.unlock();
361 if (messagesUsed == 0) return;
362 glEnable(GL_BLEND);
363 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
365 Message* msg;
367 again:
368 msg = messages.ptr;
369 final switch (msg.phase) {
370 case Message.Phase.FadeIn:
371 if ((msg.alpha += 10) >= 255) {
372 msg.phase = Message.Phase.Stay;
373 msg.removeTime = curtime+dur!"msecs"(msg.pauseMsecs);
374 goto case; // to stay
376 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
377 break;
378 case Message.Phase.Stay:
379 if (msg.removeTime <= curtime) {
380 msg.alpha = 255;
381 msg.phase = Message.Phase.FadeOut;
382 goto case; // to fade
384 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
385 break;
386 case Message.Phase.FadeOut:
387 if ((msg.alpha -= 10) <= 0) {
388 if (--messagesUsed == 0) return;
389 // remove this message
390 foreach (immutable cidx; 1..messagesUsed+1) messages.ptr[cidx-1] = messages.ptr[cidx];
391 goto again;
393 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
394 break;
397 smDrawText(10, 10, msg.text[0..msg.textlen]);
401 // ////////////////////////////////////////////////////////////////////////// //
402 mixin(Actor.FieldGetMixin!("classtype", StrId)); // fget_classtype
403 mixin(Actor.FieldGetMixin!("classname", StrId)); // fget_classname
404 mixin(Actor.FieldGetMixin!("x", int));
405 mixin(Actor.FieldGetMixin!("y", int));
406 mixin(Actor.FieldGetMixin!("flags", uint));
407 mixin(Actor.FieldGetMixin!("zAnimstate", StrId));
408 mixin(Actor.FieldGetMixin!("zAnimidx", int));
409 mixin(Actor.FieldGetMixin!("dir", uint));
410 mixin(Actor.FieldGetMixin!("attLightXOfs", int));
411 mixin(Actor.FieldGetMixin!("attLightYOfs", int));
412 mixin(Actor.FieldGetMixin!("attLightRGBX", uint));
414 mixin(Actor.FieldGetPtrMixin!("classtype", StrId)); // fget_classtype
415 mixin(Actor.FieldGetPtrMixin!("classname", StrId)); // fget_classname
416 mixin(Actor.FieldGetPtrMixin!("x", int));
417 mixin(Actor.FieldGetPtrMixin!("y", int));
418 mixin(Actor.FieldGetPtrMixin!("flags", uint));
419 mixin(Actor.FieldGetPtrMixin!("zAnimstate", StrId));
420 mixin(Actor.FieldGetPtrMixin!("zAnimidx", int));
421 mixin(Actor.FieldGetPtrMixin!("dir", uint));
422 mixin(Actor.FieldGetPtrMixin!("attLightXOfs", int));
423 mixin(Actor.FieldGetPtrMixin!("attLightYOfs", int));
424 mixin(Actor.FieldGetPtrMixin!("attLightRGBX", uint));
427 // ////////////////////////////////////////////////////////////////////////// //
428 __gshared int lightX = vlWidth/2, lightY = vlHeight/2;
429 __gshared int mapOfsX, mapOfsY;
430 __gshared bool movement = false;
431 __gshared float iLiquidTime = 0.0;
432 __gshared bool altMove = false;
435 void renderScene (MonoTime curtime) {
436 //enum BackIntens = 0.05f;
437 enum BackIntens = 0.0f;
439 gloStackClear();
441 float atob = (curtime > lastthink ? cast(float)((curtime-lastthink).total!"msecs")/cast(float)((nextthink-lastthink).total!"msecs") : 1.0f);
442 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
445 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
446 int curfp = cast(int)((curtime-lastthink).total!"msecs");
447 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
451 int mofsx, mofsy;
453 bool camCenter = true;
454 if (altMove || movement || scale == 1) {
455 mofsx = mapOfsX;
456 mofsy = mapOfsY;
457 vportX0 = 0;
458 vportY0 = 0;
459 vportX1 = map.width*8;
460 vportY1 = map.height*8;
461 camCenter = false;
462 } else {
463 if (frameInterpolation) {
464 import core.stdc.math : roundf;
465 mofsx = cast(int)(mapViewPosX[0]+roundf((mapViewPosX[1]-mapViewPosX[0])*atob));
466 mofsy = cast(int)(mapViewPosY[0]+roundf((mapViewPosY[1]-mapViewPosY[0])*atob));
467 } else {
468 mofsx = mapViewPosX[1];
469 mofsy = mapViewPosY[1];
471 vportX0 = mofsx/scale;
472 vportY0 = mofsy/scale;
473 vportX1 = vportX0+vlWidth/scale;
474 vportY1 = vportY0+vlHeight/scale;
477 if (mapTilesChanged != 0) rebuildMapMegaTextures();
479 dotDraw(atob);
481 // build background layer
482 fboOrigBack.exec({
483 //glDisable(GL_BLEND);
484 //glClearDepth(1.0f);
485 //glDepthFunc(GL_LESS);
486 //glDepthFunc(GL_NEVER);
487 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
488 glClear(GL_COLOR_BUFFER_BIT/*|GL_DEPTH_BUFFER_BIT*/);
489 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
490 orthoCamera(map.width*8, map.height*8);
492 glEnable(GL_BLEND);
493 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
494 // draw sky
496 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
497 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2, map.MapSize*8, map.MapSize*8);
498 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
499 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*8, map.MapSize*8);
501 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*8, map.MapSize*8);
502 // draw background
503 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
504 // draw distorted liquid areas
505 shadLiquidDistort.exec((Shader shad) {
506 shad["iDistortTime"] = iLiquidTime;
507 drawAtXY(map.texgl.ptr[map.AllLiquids], 0, 0);
509 // monsters, items; we'll do linear interpolation here
510 glColor3f(1.0f, 1.0f, 1.0f);
511 //glEnable(GL_DEPTH_TEST);
512 attachedLightCount = 0;
514 // who cares about memory?!
515 // draw order: players, items, monsters, other
516 static struct DrawInfo {
517 ActorDef adef;
518 ActorId aid;
519 int actorX, actorY;
520 @disable this (this); // no copies
522 enum { Players, Items, Monsters, Other }
523 __gshared DrawInfo[65536][4] drawlists;
524 __gshared uint[4] dlpos;
526 dlpos[] = 0;
528 Actor.forEach((ActorId me) {
529 if (auto adef = findActorDef(me)) {
530 uint dlnum = Other;
531 switch (adef.classtype.get) {
532 case "monster": dlnum = (adef.classname.get != "Player" ? Monsters : Players); break;
533 case "item": dlnum = Items; break;
534 default: dlnum = Other; break;
536 int actorX, actorY; // current actor position
538 auto ofs = prevFrameActorOfs.ptr[me.id&0xffff];
539 if (frameInterpolation && ofs < uint.max-1 && Actor.isSameSavedActor(me.id, prevFrameActorsData.ptr, ofs)) {
540 import core.stdc.math : roundf;
541 auto xptr = prevFrameActorsData.ptr+ofs;
542 int ox = xptr.fgetp_x;
543 int nx = me.fget_x;
544 int oy = xptr.fgetp_y;
545 int ny = me.fget_y;
546 actorX = cast(int)(ox+roundf((nx-ox)*atob));
547 actorY = cast(int)(oy+roundf((ny-oy)*atob));
548 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
549 } else {
550 actorX = me.fget_x;
551 actorY = me.fget_y;
554 // draw sprite
555 if ((me.fget_flags&AF_NODRAW) == 0) {
556 //auto dl = &drawlists[dlnum][dlpos.ptr[dlnum]];
557 auto dl = drawlists.ptr[dlnum].ptr+dlpos.ptr[dlnum];
558 ++dlpos.ptr[dlnum];
559 dl.adef = adef;
560 dl.aid = me;
561 dl.actorX = actorX;
562 dl.actorY = actorY;
564 if (auto isp = adef.animSpr(me.fget_zAnimstate, me.fget_dir, me.fget_zAnimidx)) {
565 drawAtXY(isp.tex, actorX-isp.vga.sx, actorY-isp.vga.sy, adef.zz);
566 } else {
567 //conwriteln("no animation for actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
571 // process attached lights
572 if ((me.fget_flags&AF_NOLIGHT) == 0) {
573 uint alr = me.fget_attLightRGBX;
574 if ((alr&0xff) >= 4) {
575 // yep, add it
576 auto li = attachedLights.ptr+attachedLightCount;
577 ++attachedLightCount;
578 li.x = actorX+me.fget_attLightXOfs;
579 li.y = actorY+me.fget_attLightYOfs;
580 li.r = ((alr>>24)&0xff)/255.0f; // red or intensity
581 if ((alr&0x00_ff_ff_00U) == 0x00_00_01_00U) {
582 li.uncolored = true;
583 } else {
584 li.g = ((alr>>16)&0xff)/255.0f;
585 li.b = ((alr>>8)&0xff)/255.0f;
586 li.uncolored = false;
588 li.radius = (alr&0xff);
589 if (li.radius > MaxLightRadius) li.radius = MaxLightRadius;
592 } else {
593 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
596 // draw actor lists
597 foreach_reverse (uint dlnum; 0..drawlists.length) {
598 auto dl = drawlists.ptr[dlnum].ptr;
599 if (dlnum == Players) dl += dlpos.ptr[dlnum]-1;
600 foreach (uint idx; 0..dlpos.ptr[dlnum]) {
601 auto me = dl.aid;
602 if (auto isp = dl.adef.animSpr(me.fget_zAnimstate, me.fget_dir, me.fget_zAnimidx)) {
603 drawAtXY(isp.tex, dl.actorX-isp.vga.sx, dl.actorY-isp.vga.sy);
605 if (dlnum != Players) ++dl; else --dl;
608 if (camCenter && dlpos.ptr[Players]) {
609 int tiltHeight = /*getMapViewHeight()*/(vlHeight/scale)/4;
610 int vy = players.ptr[0].looky!int;
611 if (vy < -tiltHeight) vy = -tiltHeight; else if (vy > tiltHeight) vy = tiltHeight;
612 //setMapViewPos(me.x, me.y+vy);
613 auto dl = drawlists.ptr[Players].ptr;
614 //mofsx = dl.actorX*2;
615 //mofsy = dl.actorY*2;
616 int swdt = vlWidth/scale;
617 int shgt = vlHeight/scale;
618 int x = dl.actorX-swdt/2;
619 int y = (dl.actorY+vy)-shgt/2;
620 if (x < 0) x = 0; else if (x >= map.width*8-swdt) x = map.width*8-swdt-1;
621 if (y < 0) y = 0; else if (y >= map.height*8-shgt) y = map.height*8-shgt-1;
622 mofsx = x*2;
623 mofsy = y*2;
624 vportX0 = mofsx/scale;
625 vportY0 = mofsy/scale;
626 vportX1 = vportX0+vlWidth/scale;
627 vportY1 = vportY0+vlHeight/scale;
629 //glDisable(GL_DEPTH_TEST);
630 // draw dots
631 drawAtXY(texParts, 0, 0);
632 // do liquid coloring
633 drawAtXY(map.texgl.ptr[map.LiquidMask], 0, 0);
634 // foreground -- hide secrets, draw lifts and such
635 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
639 if (doLighting) {
640 // clear light layer
641 fboLevelLight.exec({
642 glDisable(GL_BLEND);
643 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
644 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
645 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
646 glClear(GL_COLOR_BUFFER_BIT);
649 // texture 1 is background
650 glActiveTexture(GL_TEXTURE0+1);
651 glBindTexture(GL_TEXTURE_2D, fboOrigBack.tex.tid);
652 // texture 2 is occluders
653 glActiveTexture(GL_TEXTURE0+2);
654 glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.LightMask].tid);
655 // done texture assign
656 glActiveTexture(GL_TEXTURE0+0);
658 enum LYOfs = 1;
660 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
661 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
662 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
663 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
664 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
665 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
666 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
667 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
668 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
669 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
671 renderLight(24*8+4, (24+18)*8-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
673 renderLight(280, 330, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
675 // attached lights
676 foreach (ref li; attachedLights[0..attachedLightCount]) {
677 if (li.uncolored) {
678 renderLight(li.x, li.y, SVec4F(0.0f, 0.0f, 0.0f, li.r), li.radius);
679 } else {
680 renderLight(li.x, li.y, SVec4F(li.r, li.g, li.b, 1.0f), li.radius);
684 foreach (immutable _; 0..1) {
685 renderLight(lightX, lightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
688 glActiveTexture(GL_TEXTURE0+1);
689 glBindTexture(GL_TEXTURE_2D, 0);
690 glActiveTexture(GL_TEXTURE0+2);
691 glBindTexture(GL_TEXTURE_2D, 0);
692 glActiveTexture(GL_TEXTURE0+0);
695 // draw scaled level
697 shadScanlines.exec((Shader shad) {
698 shad["scanlines"] = scanlines;
699 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
700 glClear(GL_COLOR_BUFFER_BIT);
701 orthoCamera(vlWidth, vlHeight);
702 //orthoCamera(map.width*8*scale, map.height*8*scale);
703 //glMatrixMode(GL_MODELVIEW);
704 //glLoadIdentity();
705 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
706 // somehow, FBO objects are mirrored; wtf?!
707 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*8*scale, map.height*8*scale, mirrorY:true);
708 //glLoadIdentity();
713 fboLevelLight.exec({
714 smDrawText(map.width*8/2, map.height*8/2, "Testing...");
719 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
720 glDisable(GL_BLEND);
723 fboOrigBack.exec({
724 //auto img = smfont.ptr[0x39];
725 auto img = fftest;
726 if (img !is null) {
727 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
728 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
733 orthoCamera(vlWidth, vlHeight);
734 auto tex = (doLighting ? fboLevelLight.tex.tid : fboOrigBack.tex.tid);
735 //mofsx &= ~1;
736 //mofsy &= ~1;
737 drawAtXY(tex, -mofsx, -mofsy, map.width*8*scale, map.height*8*scale, mirrorY:true);
739 doMessages(curtime);
743 // ////////////////////////////////////////////////////////////////////////// //
744 // returns time slept
745 int sleepAtMaxMsecs (int msecs) {
746 if (msecs > 0) {
747 import core.sys.posix.signal : timespec;
748 import core.sys.posix.time : nanosleep;
749 timespec ts = void, tpassed = void;
750 ts.tv_sec = 0;
751 ts.tv_nsec = msecs*1000*1000+(500*1000); // milli to nano
752 nanosleep(&ts, &tpassed);
753 return (ts.tv_nsec-tpassed.tv_nsec)/(1000*1000);
754 } else {
755 return 0;
760 // ////////////////////////////////////////////////////////////////////////// //
761 // rendering thread
762 shared int diedie = 0;
764 enum D2DFrameTime = 55; // milliseconds
765 enum MinFrameTime = 1000/60; // ~60 FPS
767 void renderThread () {
768 try {
769 MonoTime curtime = MonoTime.currTime;
771 lastthink = curtime; // for interpolator
772 nextthink = curtime+dur!"msecs"(D2DFrameTime);
773 MonoTime nextvframe = curtime;
775 enum MaxFPSFrames = 16;
776 float frtimes = 0.0f;
777 int framenum = 0;
778 int prevFPS = -1;
779 int hushFrames = 6; // ignore first `hushFrames` frames overtime
780 MonoTime prevFrameStartTime = curtime;
782 bool vframeWasLost = false;
784 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
785 bool doThinkFrame () {
786 if (curtime >= nextthink) {
787 lastthink = curtime;
788 while (nextthink <= curtime) nextthink += dur!"msecs"(D2DFrameTime);
789 // save snapshot and other data for interpolator
790 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
791 mapViewPosX[0] = mapViewPosX[1];
792 mapViewPosY[0] = mapViewPosY[1];
793 // process actors
794 doActorsThink();
795 dotThink();
796 // some timing
797 auto tm = MonoTime.currTime;
798 int thinkTime = cast(int)((tm-curtime).total!"msecs");
799 if (thinkTime > 9) { import core.stdc.stdio; printf("spent on thinking: %d msecs\n", thinkTime); }
800 curtime = tm;
801 return true;
802 } else {
803 return false;
807 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
808 bool doVFrame () {
809 version(dont_use_vsync) {
810 // timer
811 enum doCheckTime = true;
812 } else {
813 // vsync
814 __gshared bool prevLost = false;
815 bool doCheckTime = vframeWasLost;
816 if (vframeWasLost) {
817 if (!prevLost) {
818 { import core.stdc.stdio; printf("frame was lost!\n"); }
820 prevLost = true;
821 } else {
822 prevLost = false;
825 if (doCheckTime) {
826 if (curtime < nextvframe) return false;
827 version(dont_use_vsync) {
828 if (curtime > nextvframe) {
829 auto overtime = cast(int)((curtime-nextvframe).total!"msecs");
830 if (overtime > 2500) {
831 if (hushFrames) {
832 --hushFrames;
833 } else {
834 { import core.stdc.stdio; printf(" spent whole %d msecs\n", overtime); }
840 while (nextvframe <= curtime) nextvframe += dur!"msecs"(MinFrameTime);
841 bool ctset = false;
843 sdwindow.mtLock();
844 scope(exit) sdwindow.mtUnlock();
845 ctset = sdwindow.setAsCurrentOpenGlContextNT;
847 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
848 if (ctset) {
849 // render scene
850 iLiquidTime = cast(float)((curtime-MonoTime.zero).total!"msecs"%10000000)/18.0f*0.04f;
851 renderScene(curtime);
852 sdwindow.mtLock();
853 scope(exit) sdwindow.mtUnlock();
854 sdwindow.swapOpenGlBuffers();
855 glFinish();
856 sdwindow.releaseCurrentOpenGlContext();
857 vframeWasLost = false;
858 } else {
859 vframeWasLost = true;
860 { import core.stdc.stdio; printf("xframe was lost!\n"); }
862 curtime = MonoTime.currTime;
863 return true;
866 for (;;) {
867 if (sdwindow.closed) break;
868 if (atomicLoad(diedie) > 0) break;
870 curtime = MonoTime.currTime;
871 auto fstime = curtime;
873 doThinkFrame(); // this will fix curtime if necessary
874 if (doVFrame()) {
875 if (!vframeWasLost) {
876 // fps
877 auto frameTime = cast(float)(curtime-prevFrameStartTime).total!"msecs"/1000.0f;
878 prevFrameStartTime = curtime;
879 frtimes += frameTime;
880 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
881 import std.string : format;
882 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
883 if (newFPS != prevFPS) {
884 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
885 prevFPS = newFPS;
887 framenum = 0;
888 frtimes = 0.0f;
893 curtime = MonoTime.currTime;
895 // now sleep until next "video" or "think" frame
896 if (nextthink > curtime && nextvframe > curtime) {
897 // let's decide
898 immutable nextVideoFrameSleep = cast(int)((nextvframe-curtime).total!"msecs");
899 immutable nextThinkFrameSleep = cast(int)((nextthink-curtime).total!"msecs");
900 immutable sleepTime = (nextVideoFrameSleep < nextThinkFrameSleep ? nextVideoFrameSleep : nextThinkFrameSleep);
901 sleepAtMaxMsecs(sleepTime);
902 //curtime = MonoTime.currTime;
905 } catch (Exception e) {
906 import core.stdc.stdio;
907 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
908 for (;;) {
909 if (sdwindow.closed) break;
910 if (atomicLoad(diedie) > 0) break;
911 sleepAtMaxMsecs(100);
914 atomicStore(diedie, 2);
918 // ////////////////////////////////////////////////////////////////////////// //
919 void closeWindow () {
920 if (atomicLoad(diedie) != 2) {
921 atomicStore(diedie, 1);
922 while (atomicLoad(diedie) != 2) {}
924 if (!sdwindow.closed) {
925 flushGui();
926 sdwindow.close();
931 // ////////////////////////////////////////////////////////////////////////// //
932 __gshared Thread renderTid;
935 void main (string[] args) {
936 FuncPool.dumpCode = false;
937 FuncPool.dumpCodeSize = false;
938 dacsDumpSemantic = false;
939 dacsOptimize = 9;
940 //version(rdmd) { dacsOptimize = 0; }
941 bool compileOnly = false;
943 for (usize idx = 1; idx < args.length; ++idx) {
944 bool remove = true;
945 if (args[idx] == "--dump-code") FuncPool.dumpCode = true;
946 else if (args[idx] == "--dump-code-size") FuncPool.dumpCodeSize = true;
947 else if (args[idx] == "--dump-semantic") dacsDumpSemantic = true;
948 else if (args[idx] == "--dump-all") { FuncPool.dumpCode = true; FuncPool.dumpCodeSize = true; dacsDumpSemantic = true; }
949 else if (args[idx] == "--compile") compileOnly = true;
950 else if (args[idx] == "--compile-only") compileOnly = true;
951 else if (args[idx] == "--messages") ++dacsMessages;
952 else if (args[idx] == "--no-copro") dacsOptimizeNoCoPro = true;
953 else if (args[idx] == "--no-deadass") dacsOptimizeNoDeadAss = true;
954 else if (args[idx] == "--no-purekill") dacsOptimizeNoPureKill = true;
955 else if (args[idx].length > 2 && args[idx][0..2] == "-O") {
956 import std.conv : to;
957 ubyte olevel = to!ubyte(args[idx][2..$]);
958 dacsOptimize = olevel;
960 else remove = false;
961 if (remove) {
962 foreach (immutable c; idx+1..args.length) args.ptr[c-1] = args.ptr[c];
963 args.length -= 1;
964 --idx; //hack
968 static void setDP () {
969 version(rdmd) {
970 setDataPath("data");
971 } else {
972 import std.file : thisExePath;
973 import std.path : dirName;
974 setDataPath(thisExePath.dirName);
976 addPK3(getDataPath~"base.pk3"); loadWadScripts();
977 //addWad("/home/ketmar/k8prj/doom2d-tl/data/doom2d.wad"); loadWadScripts();
978 //addWad("/home/ketmar/k8prj/doom2d-tl/data/meat.wad"); loadWadScripts();
979 //addWad("/home/ketmar/k8prj/doom2d-tl/data/megadm.wad"); loadWadScripts();
980 //addWad("/home/ketmar/k8prj/doom2d-tl/data/megadm1.wad"); loadWadScripts();
981 //addWad("/home/ketmar/k8prj/doom2d-tl/data/superdm.wad"); loadWadScripts();
982 //addWad("/home/ketmar/k8prj/doom2d-tl/data/zadoomka.wad"); loadWadScripts();
983 scriptLoadingComplete();
986 setDP();
988 if (compileOnly) return;
990 try {
991 registerAPI();
992 loadPalette();
994 setOpenGLContextVersion(3, 2); // up to GLSL 150
995 //openGLContextCompatible = false;
997 map = new LevelMap("maps/map01.d2m");
998 ugInit(map.width*8, map.height*8);
1000 scale = 2;
1002 //mapOfsX = 8*26;
1003 //mapOfsY = 8*56;
1004 map.getThingPos(1/*ThingId.Player1*/, &mapOfsX, &mapOfsY);
1005 // fix viewport
1007 mapOfsX = (mapOfsX*2)-vlWidth/2;
1008 if (mapOfsX+vlWidth > map.width*16) mapOfsX = map.width*16-vlWidth;
1009 if (mapOfsX < 0) mapOfsX = 0;
1010 mapOfsY = (mapOfsY*2)-vlHeight/2;
1011 if (mapOfsY+vlHeight > map.height*16) mapOfsY = map.height*16-vlHeight;
1012 if (mapOfsY < 0) mapOfsY = 0;
1014 setMapViewPos(mapOfsX, mapOfsY);
1015 mapOfsX = mapViewPosX[1];
1016 mapOfsY = mapViewPosY[1];
1018 sdwindow = new SimpleWindow(vlWidth, vlHeight, "D2D", OpenGlOptions.yes, Resizablity.fixedSize);
1020 sdwindow.visibleForTheFirstTime = delegate () {
1021 sdwindow.setAsCurrentOpenGlContext(); // make this window active
1022 glbindLoadFunctions();
1025 import core.stdc.stdio;
1026 printf("GL version: %s\n", glGetString(GL_VERSION));
1027 GLint l, h;
1028 glGetIntegerv(GL_MAJOR_VERSION, &h);
1029 glGetIntegerv(GL_MINOR_VERSION, &l);
1030 printf("version: %d.%d\n", h, l);
1031 printf("shader version: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
1032 GLint svcount;
1033 glGetIntegerv(GL_NUM_SHADING_LANGUAGE_VERSIONS, &svcount);
1034 if (svcount > 0) {
1035 printf("%d shader versions supported:\n", svcount);
1036 foreach (GLuint n; 0..svcount) printf(" %d: %s\n", n, glGetStringi(GL_SHADING_LANGUAGE_VERSION, n));
1039 GLint ecount;
1040 glGetIntegerv(GL_NUM_EXTENSIONS, &ecount);
1041 if (ecount > 0) {
1042 printf("%d extensions supported:\n", ecount);
1043 foreach (GLuint n; 0..ecount) printf(" %d: %s\n", n, glGetStringi(GL_EXTENSIONS, n));
1048 // check if we have sufficient shader version here
1050 bool found = false;
1051 GLint svcount;
1052 glGetIntegerv(GL_NUM_SHADING_LANGUAGE_VERSIONS, &svcount);
1053 if (svcount > 0) {
1054 foreach (GLuint n; 0..svcount) {
1055 import core.stdc.string : strncmp;
1056 auto v = glGetStringi(GL_SHADING_LANGUAGE_VERSION, n);
1057 if (v is null) continue;
1058 if (strncmp(v, "120", 3) != 0) continue;
1059 if (v[3] > ' ') continue;
1060 found = true;
1061 break;
1064 if (!found) assert(0, "can't find OpenGL GLSL 120");
1066 auto adr = glGetProcAddress("glTexParameterf");
1067 if (adr is null) assert(0);
1070 version(dont_use_vsync) {
1071 sdwindow.vsync = false;
1072 } else {
1073 sdwindow.vsync = true;
1075 //sdwindow.useGLFinish = false;
1076 initOpenGL();
1077 //sdwindow.redrawOpenGlScene();
1078 if (!sdwindow.releaseCurrentOpenGlContext()) { import core.stdc.stdio; printf("can't release OpenGL context(1)\n"); }
1079 if (!renderTid) {
1080 renderTid = new Thread(&renderThread);
1081 renderTid.start();
1085 //sdwindow.redrawOpenGlScene = delegate () { renderScene(); };
1087 enum MSecsPerFrame = 1000/30; /* 30 is FPS */
1089 uint[8] frameTimes = 1000;
1090 enum { Left, Right, Up, Down }
1091 bool[4] pressed = false;
1093 sdwindow.eventLoop(MSecsPerFrame,
1094 delegate () {
1095 if (sdwindow.closed) return;
1096 if (pressed[Left]) mapOfsX -= 8;
1097 if (pressed[Right]) mapOfsX += 8;
1098 if (pressed[Up]) mapOfsY -= 8;
1099 if (pressed[Down]) mapOfsY += 8;
1100 import std.math : cos, sin;
1101 __gshared float itime = 0.0;
1102 itime += 0.02;
1103 if (movement) {
1104 mapOfsX = cast(int)(800.0/2.0+cos(itime)*220.0);
1105 mapOfsY = cast(int)(800.0/2.0+120.0+sin(itime)*160.0);
1107 if (scale == 1) mapOfsX = mapOfsY = 0;
1108 //sdwindow.redrawOpenGlSceneNow();
1110 delegate (KeyEvent event) {
1111 if (sdwindow.closed) return;
1112 if (event.pressed && event.key == Key.Escape) { closeWindow(); return; }
1113 switch (event.key) {
1114 case Key.X: if (event.pressed) altMove = !altMove; break;
1115 case Key.Left: if (altMove) pressed[Left] = event.pressed; break;
1116 case Key.Right: if (altMove) pressed[Right] = event.pressed; break;
1117 case Key.Up: if (altMove) pressed[Up] = event.pressed; break;
1118 case Key.Down: if (altMove) pressed[Down] = event.pressed; break;
1119 default:
1121 if (!altMove) {
1122 switch (event.key) {
1123 case Key.Left: case Key.Pad4: plrKeyUpDown(0, PLK_LEFT, event.pressed); break;
1124 case Key.Right: case Key.Pad6: plrKeyUpDown(0, PLK_RIGHT, event.pressed); break;
1125 case Key.Up: case Key.Pad8: plrKeyUpDown(0, PLK_UP, event.pressed); break;
1126 case Key.Down: case Key.Pad2: plrKeyUpDown(0, PLK_DOWN, event.pressed); break;
1127 case Key.Alt: plrKeyUpDown(0, PLK_JUMP, event.pressed); break;
1128 case Key.Ctrl: plrKeyUpDown(0, PLK_FIRE, event.pressed); break;
1129 case Key.Shift: plrKeyUpDown(0, PLK_USE, event.pressed); break;
1130 default:
1134 delegate (MouseEvent event) {
1135 lightX = event.x/scale;
1136 lightY = event.y/scale;
1137 lightX += mapOfsX/scale;
1138 lightY += mapOfsY/scale;
1140 delegate (dchar ch) {
1141 if (ch == 'q') { closeWindow(); return; }
1142 if (ch == 's') scanlines = !scanlines;
1143 if (ch == '1') scale = 1;
1144 if (ch == '2') scale = 2;
1145 if (ch == 'D') cheatNoDoors = !cheatNoDoors;
1146 if (ch == 'i') {
1147 frameInterpolation = !frameInterpolation;
1148 if (frameInterpolation) addMessage("Interpolation: ON"); else addMessage("Interpolation: OFF");
1150 if (ch == 'l') {
1151 doLighting = !doLighting;
1152 if (doLighting) addMessage("Lighting: ON"); else addMessage("Lighting: OFF");
1154 if (ch == ' ') movement = !movement;
1157 } catch (Exception e) {
1158 import std.stdio : stderr;
1159 stderr.writeln("FUUUUUUUUUUUU\n", e.toString);
1161 closeWindow();
1162 flushGui();