level scrolling
[dd2d.git] / xmain_d2d.d
blob58925b6763af17f692b505a7954f5f1cc7646486
1 module xmain_d2d is aliced;
3 private:
4 import core.atomic;
5 import core.thread;
6 import core.time;
8 import iv.glbinds;
9 import glutils;
10 import console;
11 import wadarc;
13 import iv.stream;
15 import d2dmap;
16 import d2dadefs;
17 //import d2dactors;
18 import d2dgfx;
19 import d2dfont;
20 import dacs;
22 // `map` is there
23 import dengapi;
26 // ////////////////////////////////////////////////////////////////////////// //
27 import arsd.color;
28 import arsd.png;
31 // ////////////////////////////////////////////////////////////////////////// //
32 __gshared SimpleWindow sdwindow;
35 public enum vlWidth = 800;
36 public enum vlHeight = 800;
37 public __gshared int scale = 1;
40 // ////////////////////////////////////////////////////////////////////////// //
41 __gshared bool scanlines = false;
42 __gshared bool doLighting = true;
45 // ////////////////////////////////////////////////////////////////////////// //
46 // interpolation
47 __gshared ubyte[] prevFrameActorsData;
48 __gshared uint[65536] prevFrameActorOfs; // uint.max-1: dead; uint.max: last
49 __gshared MonoTime lastthink = MonoTime.zero; // for interpolator
50 __gshared MonoTime nextthink = MonoTime.zero;
51 __gshared bool frameInterpolation = true;
54 __gshared int[2] mapViewPosX, mapViewPosY; // [0]: previous frame -- for interpolator
56 // this should be screen center
57 public void setMapViewPos (int x, int y) {
58 if (map is null) {
59 mapViewPosX[] = 0;
60 mapViewPosY[] = 0;
61 return;
63 int swdt = vlWidth/scale;
64 int shgt = vlHeight/scale;
65 x -= swdt/2;
66 y -= shgt/2;
67 if (x < 0) x = 0; else if (x >= map.width*8-swdt) x = map.width*8-swdt-1;
68 if (y < 0) y = 0; else if (y >= map.height*8-shgt) y = map.height*8-shgt-1;
69 mapViewPosX[1] = x*scale;
70 mapViewPosY[1] = y*scale;
74 // ////////////////////////////////////////////////////////////////////////// //
75 // attached lights
76 struct AttachedLightInfo {
77 int x, y;
78 float r, g, b;
79 bool uncolored;
80 int radius;
83 __gshared AttachedLightInfo[65536] attachedLights;
84 __gshared uint attachedLightCount = 0;
87 // ////////////////////////////////////////////////////////////////////////// //
88 enum MaxLightRadius = 512;
91 // ////////////////////////////////////////////////////////////////////////// //
92 // for light
93 __gshared FBO[MaxLightRadius+1] fboOccluders, fboShadowMap;
94 __gshared Shader shadToPolar, shadBlur, shadBlurOcc;
96 __gshared FBO fboLevel, fboLevelLight, fboOrigBack;
97 __gshared Shader shadScanlines;
98 __gshared Shader shadLiquidDistort;
101 // ////////////////////////////////////////////////////////////////////////// //
102 void initOpenGL () {
103 glEnable(GL_TEXTURE_2D);
104 glDisable(GL_LIGHTING);
105 glDisable(GL_DITHER);
106 glDisable(GL_BLEND);
107 glDisable(GL_DEPTH_TEST);
109 // create shaders
110 shadScanlines = new Shader("scanlines", loadTextFile("data/shaders/srscanlines.frag"));
112 shadLiquidDistort = new Shader("liquid_distort", loadTextFile("data/shaders/srliquid_distort.frag"));
113 shadLiquidDistort.exec((Shader shad) { shad["tex0"] = 0; });
115 // lights
116 shadToPolar = new Shader("light_topolar", loadTextFile("data/shaders/srlight_topolar.frag"));
117 shadBlur = new Shader("light_blur", loadTextFile("data/shaders/srlight_blur.frag"));
118 shadBlur.exec((Shader shad) {
119 shad["tex0"] = 0;
120 shad["tex1"] = 1;
121 shad["tex2"] = 2;
123 shadBlurOcc = new Shader("light_blur_occ", loadTextFile("data/shaders/srlight_blur_occ.frag"));
124 shadBlurOcc.exec((Shader shad) {
125 shad["tex0"] = 0;
126 shad["tex1"] = 1;
127 shad["tex2"] = 2;
129 //TODO: this sux!
130 foreach (int sz; 2..MaxLightRadius+1) {
131 fboOccluders[sz] = new FBO(sz*2, sz*2, Texture.Option.Clamp, Texture.Option.Linear); // create occluders FBO
132 fboShadowMap[sz] = new FBO(sz*2, 1, Texture.Option.Clamp); // create 1d shadowmap FBO
135 // setup matrices
136 glMatrixMode(GL_MODELVIEW);
137 glLoadIdentity();
139 map.oglBuildMega();
141 fboLevel = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // final level render will be here
142 fboLevelLight = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // level lights will be rendered here
143 //fboForeground = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // level foreground
144 fboOrigBack = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // background+foreground
146 shadBlur.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*8, map.height*8); });
147 shadBlurOcc.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*8, map.height*8); });
149 glActiveTexture(GL_TEXTURE0+0);
150 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
151 orthoCamera(vlWidth, vlHeight);
153 loadSmFont();
155 Actor.resetStorage();
156 loadMapMonsters();
158 // save first snapshot
159 prevFrameActorsData = new ubyte[](Actor.actorSize*65536); // ~15-20 megabytes
160 prevFrameActorOfs[] = uint.max; // just for fun
161 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
163 { import core.memory : GC; GC.collect(); }
167 // ////////////////////////////////////////////////////////////////////////// //
168 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
169 if (lightRadius < 2) return;
170 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
171 int lightSize = lightRadius*2;
172 // is this light visible?
173 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*8 || lightY-lightRadius >= map.height*8) return;
175 // draw shadow casters to fboOccludersId, light should be in the center
176 glUseProgram(0);
177 glDisable(GL_BLEND);
178 fboOccluders[lightRadius].exec({
179 //glDisable(GL_BLEND);
180 glColor3f(0.0f, 0.0f, 0.0f);
181 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
182 glClear(GL_COLOR_BUFFER_BIT);
183 orthoCamera(lightSize, lightSize);
184 drawAtXY(map.texgl.ptr[map.LightMask], lightRadius-lightX, lightRadius-lightY);
187 // build 1d shadow map to fboShadowMapId
188 fboShadowMap[lightRadius].exec({
189 shadToPolar.exec((Shader shad) {
190 //glDisable(GL_BLEND);
191 glColor3f(0.0f, 0.0f, 0.0f);
192 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
193 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
194 glClear(GL_COLOR_BUFFER_BIT);
195 orthoCamera(lightSize, 1);
196 drawAtXY(fboOccluders[lightRadius].tex.tid, 0, 0, lightSize, 1);
200 // build light texture for blending
201 fboOccluders[lightRadius].exec({
202 // no need to clear it, shader will take care of that
203 //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
204 //glClear(GL_COLOR_BUFFER_BIT);
205 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
206 //glDisable(GL_BLEND);
207 shadBlur.exec((Shader shad) {
208 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
209 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
210 shad["lightPos"] = SVec2F(lightX, lightY);
211 orthoCamera(lightSize, lightSize);
212 drawAtXY(fboShadowMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
216 // blend light texture
217 fboLevelLight.exec({
218 glEnable(GL_BLEND);
219 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
220 orthoCamera(map.width*8, map.height*8);
221 drawAtXY(fboOccluders[lightRadius].tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
222 // and blend it again, somewhat bigger, with the shader that will touch only occluders
223 enum szmore = 0;
224 shadBlurOcc.exec((Shader shad) {
225 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
226 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
227 shad["lightPos"] = SVec2F(lightX, lightY);
228 drawAtXY(fboOccluders[lightRadius].tex.tid, lightX-lightRadius-szmore, lightY-lightRadius-szmore, lightSize+szmore*2, lightSize+szmore*2, mirrorY:true);
234 // ////////////////////////////////////////////////////////////////////////// //
235 // messages
236 struct Message {
237 enum Phase { FadeIn, Stay, FadeOut }
238 Phase phase;
239 int alpha;
240 int pauseMsecs;
241 MonoTime removeTime;
242 char[256] text;
243 usize textlen;
246 private import core.sync.mutex : Mutex;
248 __gshared Message[128] messages;
249 __gshared uint messagesUsed = 0;
250 __gshared Mutex messageLock;
252 shared static this () { messageLock = new Mutex(); }
255 void addMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
256 messageLock.lock();
257 scope(exit) messageLock.unlock();
258 if (msgtext.length == 0) return;
259 conwriteln(msgtext);
260 if (pauseMsecs <= 50) return;
261 if (messagesUsed == messages.length) {
262 // remove top message
263 foreach (immutable cidx; 1..messagesUsed) messages.ptr[cidx-1] = messages.ptr[cidx];
264 messages.ptr[0].alpha = 255;
265 --messagesUsed;
267 // quick replace
268 if (!noreplace && messagesUsed == 1) {
269 switch (messages.ptr[0].phase) {
270 case Message.Phase.FadeIn:
271 messages.ptr[0].phase = Message.Phase.FadeOut;
272 break;
273 case Message.Phase.Stay:
274 messages.ptr[0].phase = Message.Phase.FadeOut;
275 messages.ptr[0].alpha = 255;
276 break;
277 default:
280 auto msg = messages.ptr+messagesUsed;
281 ++messagesUsed;
282 msg.phase = Message.Phase.FadeIn;
283 msg.alpha = 0;
284 msg.pauseMsecs = pauseMsecs;
285 // copy text
286 if (msgtext.length > msg.text.length) {
287 msg.text = msgtext[0..msg.text.length];
288 msg.textlen = msg.text.length;
289 } else {
290 msg.text[0..msgtext.length] = msgtext[];
291 msg.textlen = msgtext.length;
296 void doMessages (MonoTime curtime) {
297 messageLock.lock();
298 scope(exit) messageLock.unlock();
300 if (messagesUsed == 0) return;
301 glEnable(GL_BLEND);
302 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
304 Message* msg;
306 again:
307 msg = messages.ptr;
308 final switch (msg.phase) {
309 case Message.Phase.FadeIn:
310 if ((msg.alpha += 10) >= 255) {
311 msg.phase = Message.Phase.Stay;
312 msg.removeTime = curtime+dur!"msecs"(msg.pauseMsecs);
313 goto case; // to stay
315 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
316 break;
317 case Message.Phase.Stay:
318 if (msg.removeTime <= curtime) {
319 msg.alpha = 255;
320 msg.phase = Message.Phase.FadeOut;
321 goto case; // to fade
323 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
324 break;
325 case Message.Phase.FadeOut:
326 if ((msg.alpha -= 10) <= 0) {
327 if (--messagesUsed == 0) return;
328 // remove this message
329 foreach (immutable cidx; 1..messagesUsed+1) messages.ptr[cidx-1] = messages.ptr[cidx];
330 goto again;
332 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
333 break;
336 smDrawText(10, 10, msg.text[0..msg.textlen]);
340 // ////////////////////////////////////////////////////////////////////////// //
341 __gshared int lightX = vlWidth/2, lightY = vlHeight/2;
342 __gshared int mapOfsX, mapOfsY;
343 __gshared bool movement = false;
344 __gshared float iLiquidTime = 0.0;
345 __gshared bool altMove = false;
348 void renderScene (MonoTime curtime) {
349 //enum BackIntens = 0.05f;
350 enum BackIntens = 0.0f;
352 float atob = (curtime > lastthink ? cast(float)((curtime-lastthink).total!"msecs")/cast(float)((nextthink-lastthink).total!"msecs") : 1.0f);
353 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
356 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
357 int curfp = cast(int)((curtime-lastthink).total!"msecs");
358 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
362 glUseProgram(0);
364 // build background layer
365 fboOrigBack.exec({
366 //glDisable(GL_BLEND);
367 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
368 glClear(GL_COLOR_BUFFER_BIT);
369 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
370 orthoCamera(map.width*8, map.height*8);
371 glEnable(GL_BLEND);
372 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
373 // draw sky
375 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
376 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2, map.MapSize*8, map.MapSize*8);
377 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
378 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*8, map.MapSize*8);
380 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*8, map.MapSize*8);
381 // draw background
382 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
383 // draw distorted liquid areas
384 shadLiquidDistort.exec((Shader shad) {
385 shad["iDistortTime"] = iLiquidTime;
386 drawAtXY(map.texgl.ptr[map.AllLiquids], 0, 0);
388 // monsters, items; we'll do linear interpolation here
389 // we need this for saved frame anyway, so let's play dirty and speed up the things
390 uint fxofs = Actor.fields["x"].ofs;
391 uint fyofs = Actor.fields["y"].ofs;
392 uint fzAnimstateofs = Actor.fields["zAnimstate"].ofs;
393 uint fdirofs = Actor.fields["dir"].ofs;
394 uint fzAnimidxofs = Actor.fields["zAnimidx"].ofs;
395 uint fclasstypeofs = Actor.fields["classtype"].ofs;
396 uint fclassnameofs = Actor.fields["classname"].ofs;
397 uint fattLightXOfs = Actor.fields["attLightXOfs"].ofs;
398 uint fattLightYOfs = Actor.fields["attLightYOfs"].ofs;
399 uint fattLightRGBX = Actor.fields["attLightRGBX"].ofs;
400 glColor3f(1.0f, 1.0f, 1.0f);
401 attachedLightCount = 0;
402 Actor.forEach((ActorId me) {
403 // `act` is always valid here
404 auto aptr = me.data.ptr;
405 auto ctstr = StrId(*cast(uint*)(aptr+fclasstypeofs));
406 auto cnstr = StrId(*cast(uint*)(aptr+fclassnameofs));
407 if (auto adef = findActorDef(ctstr, cnstr)) {
408 ctstr = StrId(*cast(uint*)(aptr+fzAnimstateofs));
409 int actorX, actorY; // current actor position
411 auto ofs = prevFrameActorOfs.ptr[me.id&0xffff];
412 if (frameInterpolation && ofs < uint.max-1 && Actor.isSameSavedActor(me.id, prevFrameActorsData.ptr, ofs)) {
413 import core.stdc.math : roundf;
414 auto xptr = prevFrameActorsData.ptr+ofs;
415 int ox = *cast(int*)(xptr+fxofs);
416 int nx = *cast(int*)(aptr+fxofs);
417 int oy = *cast(int*)(xptr+fyofs);
418 int ny = *cast(int*)(aptr+fyofs);
419 actorX = cast(int)(ox+roundf((nx-ox)*atob));
420 actorY = cast(int)(oy+roundf((ny-oy)*atob));
421 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
422 } else {
423 actorX = *cast(int*)(aptr+fxofs);
424 actorY = *cast(int*)(aptr+fyofs);
428 if (auto isp = adef.animSpr(ctstr, *cast(uint*)(aptr+fdirofs), *cast(int*)(aptr+fzAnimidxofs))) {
429 drawAtXY(isp.tex, actorX-isp.vga.sx, actorY-isp.vga.sy);
430 } else {
431 //conwriteln("no animation for actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
433 // process attached lights
435 uint alr = *cast(uint*)(aptr+fattLightRGBX);
436 if ((alr&0xff) >= 4) {
437 // yep, add it
438 auto li = attachedLights.ptr+attachedLightCount;
439 ++attachedLightCount;
440 li.x = actorX+(*cast(int*)(aptr+fattLightXOfs));
441 li.y = actorY+(*cast(int*)(aptr+fattLightYOfs));
442 li.r = ((alr>>24)&0xff)/255.0f; // red or intensity
443 if ((alr&0x00_ff_ff_00U) == 0x00_00_01_00U) {
444 li.uncolored = true;
445 } else {
446 li.g = ((alr>>16)&0xff)/255.0f;
447 li.b = ((alr>>8)&0xff)/255.0f;
448 li.uncolored = false;
450 li.radius = (alr&0xff);
451 if (li.radius > MaxLightRadius) li.radius = MaxLightRadius;
454 } else {
455 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
458 // do liquid coloring
459 drawAtXY(map.texgl.ptr[map.LiquidMask], 0, 0);
460 // foreground -- hide secrets, draw lifts and such
461 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
465 if (doLighting) {
466 // clear light layer
467 fboLevelLight.exec({
468 glDisable(GL_BLEND);
469 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
470 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
471 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
472 glClear(GL_COLOR_BUFFER_BIT);
475 // texture 1 is background
476 glActiveTexture(GL_TEXTURE0+1);
477 glBindTexture(GL_TEXTURE_2D, fboOrigBack.tex.tid);
478 // texture 2 is occluders
479 glActiveTexture(GL_TEXTURE0+2);
480 glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.LightMask].tid);
481 // done texture assign
482 glActiveTexture(GL_TEXTURE0+0);
484 enum LYOfs = 1;
486 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
487 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
488 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
489 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
490 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
491 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
492 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
493 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
494 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
495 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
497 renderLight(24*8+4, (24+18)*8-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
499 // attached lights
500 foreach (ref li; attachedLights[0..attachedLightCount]) {
501 if (li.uncolored) {
502 renderLight(li.x, li.y, SVec4F(0.0f, 0.0f, 0.0f, li.r), li.radius);
503 } else {
504 renderLight(li.x, li.y, SVec4F(li.r, li.g, li.b, 1.0f), li.radius);
508 foreach (immutable _; 0..1) {
509 renderLight(lightX, lightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
512 glActiveTexture(GL_TEXTURE0+1);
513 glBindTexture(GL_TEXTURE_2D, 0);
514 glActiveTexture(GL_TEXTURE0+2);
515 glBindTexture(GL_TEXTURE_2D, 0);
516 glActiveTexture(GL_TEXTURE0+0);
519 // draw scaled level
521 shadScanlines.exec((Shader shad) {
522 shad["scanlines"] = scanlines;
523 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
524 glClear(GL_COLOR_BUFFER_BIT);
525 orthoCamera(vlWidth, vlHeight);
526 //orthoCamera(map.width*8*scale, map.height*8*scale);
527 //glMatrixMode(GL_MODELVIEW);
528 //glLoadIdentity();
529 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
530 // somehow, FBO objects are mirrored; wtf?!
531 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*8*scale, map.height*8*scale, mirrorY:true);
532 //glLoadIdentity();
537 fboLevelLight.exec({
538 smDrawText(map.width*8/2, map.height*8/2, "Testing...");
543 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
544 glDisable(GL_BLEND);
547 fboOrigBack.exec({
548 //auto img = smfont.ptr[0x39];
549 auto img = fftest;
550 if (img !is null) {
551 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
552 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
557 int mofsx, mofsy;
559 if (altMove || movement || scale == 1) {
560 mofsx = mapOfsX;
561 mofsy = mapOfsY;
562 } else {
563 if (frameInterpolation) {
564 import core.stdc.math : roundf;
565 mofsx = cast(int)(mapViewPosX[0]+roundf((mapViewPosX[1]-mapViewPosX[0])*atob));
566 mofsy = cast(int)(mapViewPosY[0]+roundf((mapViewPosY[1]-mapViewPosY[0])*atob));
567 } else {
568 mofsx = mapViewPosX[1];
569 mofsy = mapViewPosY[1];
573 orthoCamera(vlWidth, vlHeight);
574 auto tex = (doLighting ? fboLevelLight.tex.tid : fboOrigBack.tex.tid);
575 drawAtXY(fboLevelLight.tex.tid, -mofsx, -mofsy, map.width*8*scale, map.height*8*scale, mirrorY:true);
577 doMessages(curtime);
581 // ////////////////////////////////////////////////////////////////////////// //
582 // returns time slept
583 int sleepAtMaxMsecs (int msecs) {
584 if (msecs > 0) {
585 import core.sys.posix.signal : timespec;
586 import core.sys.posix.time : nanosleep;
587 timespec ts = void, tpassed = void;
588 ts.tv_sec = 0;
589 ts.tv_nsec = msecs*1000*1000+(500*1000); // milli to nano
590 nanosleep(&ts, &tpassed);
591 return (ts.tv_nsec-tpassed.tv_nsec)/(1000*1000);
592 } else {
593 return 0;
598 // ////////////////////////////////////////////////////////////////////////// //
599 // rendering thread
600 shared int diedie = 0;
602 enum D2DFrameTime = 55; // milliseconds
603 enum MinFrameTime = 1000/60; // ~60 FPS
605 void renderThread () {
606 try {
607 version(use_vsync) {} else MonoTime ltt = MonoTime.currTime;
609 MonoTime curtime = MonoTime.currTime;
611 lastthink = curtime; // for interpolator
612 nextthink = curtime+dur!"msecs"(D2DFrameTime);
613 MonoTime nextvframe = curtime;
615 enum MaxFPSFrames = 16;
616 float frtimes = 0.0f;
617 int framenum = 0;
618 int prevFPS = -1;
619 int hushFrames = 6; // ignore first `hushFrames` frames overtime
620 MonoTime prevFrameStartTime = curtime;
622 bool vframeWasLost = false;
624 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
625 bool doThinkFrame () {
626 if (curtime >= nextthink) {
627 lastthink = curtime;
628 while (nextthink <= curtime) nextthink += dur!"msecs"(D2DFrameTime);
629 // save snapshot and other datafor interpolator
630 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
631 mapViewPosX[0] = mapViewPosX[1];
632 mapViewPosY[0] = mapViewPosY[1];
633 // process actors
634 doActorsThink();
635 // some timing
636 auto tm = MonoTime.currTime;
637 int thinkTime = cast(int)((tm-curtime).total!"msecs");
638 if (thinkTime > 9) { import core.stdc.stdio; printf("spent on thinking: %d msecs\n", thinkTime); }
639 curtime = tm;
640 return true;
641 } else {
642 return false;
646 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
647 bool doVFrame () {
648 version(use_vsync) {
649 __gshared bool prevLost = false;
650 bool doCheckTime = vframeWasLost;
651 if (vframeWasLost) {
652 if (!prevLost) {
653 { import core.stdc.stdio; printf("frame was lost!\n"); }
655 prevLost = true;
656 } else {
657 prevLost = false;
659 } else {
660 enum doCheckTime = true;
662 if (doCheckTime) {
663 if (curtime < nextvframe) return false;
664 version(use_vsync) {} else {
665 if (curtime > nextvframe) {
666 auto overtime = cast(int)((curtime-nextvframe).total!"msecs");
667 if (overtime > 2500) {
668 if (hushFrames) {
669 --hushFrames;
670 } else {
671 { import core.stdc.stdio; printf(" spent whole %d msecs\n", overtime); }
677 while (nextvframe <= curtime) nextvframe += dur!"msecs"(MinFrameTime);
678 bool ctset = false;
680 sdwindow.mtLock();
681 scope(exit) sdwindow.mtUnlock();
682 ctset = sdwindow.setAsCurrentOpenGlContextNT;
684 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
685 if (ctset) {
686 // render scene
687 iLiquidTime = cast(float)((curtime-MonoTime.zero).total!"msecs"%10000000)/18.0f*0.04f;
688 renderScene(curtime);
689 sdwindow.mtLock();
690 scope(exit) sdwindow.mtUnlock();
691 sdwindow.swapOpenGlBuffers();
692 glFinish();
693 sdwindow.releaseCurrentOpenGlContext();
694 vframeWasLost = false;
695 } else {
696 vframeWasLost = true;
697 { import core.stdc.stdio; printf("xframe was lost!\n"); }
699 curtime = MonoTime.currTime;
700 return true;
703 for (;;) {
704 if (sdwindow.closed) break;
705 if (atomicLoad(diedie) > 0) break;
707 curtime = MonoTime.currTime;
708 auto fstime = curtime;
710 doThinkFrame(); // this will fix curtime if necessary
711 if (doVFrame()) {
712 if (!vframeWasLost) {
713 // fps
714 auto frameTime = cast(float)(curtime-prevFrameStartTime).total!"msecs"/1000.0f;
715 prevFrameStartTime = curtime;
716 frtimes += frameTime;
717 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
718 import std.string : format;
719 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
720 if (newFPS != prevFPS) {
721 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
722 prevFPS = newFPS;
724 framenum = 0;
725 frtimes = 0.0f;
730 curtime = MonoTime.currTime;
732 // now sleep until next "video" or "think" frame
733 if (nextthink > curtime && nextvframe > curtime) {
734 // let's decide
735 immutable nextVideoFrameSleep = cast(int)((nextvframe-curtime).total!"msecs");
736 immutable nextThinkFrameSleep = cast(int)((nextthink-curtime).total!"msecs");
737 immutable sleepTime = (nextVideoFrameSleep < nextThinkFrameSleep ? nextVideoFrameSleep : nextThinkFrameSleep);
738 sleepAtMaxMsecs(sleepTime);
739 //curtime = MonoTime.currTime;
742 } catch (Exception e) {
743 import core.stdc.stdio;
744 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
745 for (;;) {
746 if (sdwindow.closed) break;
747 if (atomicLoad(diedie) > 0) break;
748 //{ import core.stdc.stdio; printf(" spent only %d msecs\n", cast(int)((time-ltt).total!"msecs")); }
749 sleepAtMaxMsecs(100);
752 atomicStore(diedie, 2);
756 // ////////////////////////////////////////////////////////////////////////// //
757 void closeWindow () {
758 if (atomicLoad(diedie) != 2) {
759 atomicStore(diedie, 1);
760 while (atomicLoad(diedie) != 2) {}
762 if (!sdwindow.closed) {
763 flushGui();
764 sdwindow.close();
769 // ////////////////////////////////////////////////////////////////////////// //
770 __gshared Thread renderTid;
773 void main (string[] args) {
774 FuncPool.dumpCode = false;
775 FuncPool.dumpCodeSize = false;
776 dacsDumpSemantic = false;
777 dacsOptimize = 9;
778 //version(rdmd) { dacsOptimize = 0; }
779 bool compileOnly = false;
781 for (usize idx = 1; idx < args.length; ++idx) {
782 bool remove = true;
783 if (args[idx] == "--dump-code") FuncPool.dumpCode = true;
784 else if (args[idx] == "--dump-code-size") FuncPool.dumpCodeSize = true;
785 else if (args[idx] == "--dump-semantic") dacsDumpSemantic = true;
786 else if (args[idx] == "--dump-all") { FuncPool.dumpCode = true; FuncPool.dumpCodeSize = true; dacsDumpSemantic = true; }
787 else if (args[idx] == "--compile") compileOnly = true;
788 else if (args[idx] == "--compile-only") compileOnly = true;
789 else if (args[idx] == "--messages") ++dacsMessages;
790 else if (args[idx] == "--no-copro") dacsOptimizeNoCoPro = true;
791 else if (args[idx] == "--no-deadass") dacsOptimizeNoDeadAss = true;
792 else if (args[idx] == "--no-purekill") dacsOptimizeNoPureKill = true;
793 else if (args[idx].length > 2 && args[idx][0..2] == "-O") {
794 import std.conv : to;
795 ubyte olevel = to!ubyte(args[idx][2..$]);
796 dacsOptimize = olevel;
798 else remove = false;
799 if (remove) {
800 foreach (immutable c; idx+1..args.length) args.ptr[c-1] = args.ptr[c];
801 args.length -= 1;
802 --idx; //hack
806 static void setDP () {
807 version(rdmd) {
808 setDataPath("data");
809 } else {
810 import std.file : thisExePath;
811 import std.path : dirName;
812 setDataPath(thisExePath.dirName);
814 addPK3(getDataPath~"base.pk3"); loadWadScripts();
815 //addWad("/home/ketmar/k8prj/doom2d-tl/data/doom2d.wad"); loadWadScripts();
816 //addWad("/home/ketmar/k8prj/doom2d-tl/data/meat.wad"); loadWadScripts();
817 //addWad("/home/ketmar/k8prj/doom2d-tl/data/megadm.wad"); loadWadScripts();
818 //addWad("/home/ketmar/k8prj/doom2d-tl/data/megadm1.wad"); loadWadScripts();
819 //addWad("/home/ketmar/k8prj/doom2d-tl/data/superdm.wad"); loadWadScripts();
820 //addWad("/home/ketmar/k8prj/doom2d-tl/data/zadoomka.wad"); loadWadScripts();
821 scriptLoadingComplete();
824 setDP();
826 if (compileOnly) return;
828 try {
829 registerAPI();
830 loadPalette();
832 setOpenGLContextVersion(3, 2); // up to GLSL 150
833 //openGLContextCompatible = false;
835 map = new LevelMap("maps/map01.d2m");
837 //mapOfsX = 8*26;
838 //mapOfsY = 8*56;
839 map.getThingPos(1/*ThingId.Player1*/, &mapOfsX, &mapOfsY);
840 // fix viewport
841 mapOfsX = (mapOfsX*2)-vlWidth/2;
842 if (mapOfsX+vlWidth > map.width*16) mapOfsX = map.width*16-vlWidth;
843 if (mapOfsX < 0) mapOfsX = 0;
844 mapOfsY = (mapOfsY*2)-vlHeight/2;
845 if (mapOfsY+vlHeight > map.height*16) mapOfsY = map.height*16-vlHeight;
846 if (mapOfsY < 0) mapOfsY = 0;
847 scale = 2;
849 sdwindow = new SimpleWindow(vlWidth, vlHeight, "D2D", OpenGlOptions.yes, Resizablity.fixedSize);
851 sdwindow.visibleForTheFirstTime = delegate () {
852 sdwindow.setAsCurrentOpenGlContext(); // make this window active
853 glbindLoadFunctions();
856 import core.stdc.stdio;
857 printf("GL version: %s\n", glGetString(GL_VERSION));
858 GLint l, h;
859 glGetIntegerv(GL_MAJOR_VERSION, &h);
860 glGetIntegerv(GL_MINOR_VERSION, &l);
861 printf("version: %d.%d\n", h, l);
862 printf("shader version: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
863 GLint svcount;
864 glGetIntegerv(GL_NUM_SHADING_LANGUAGE_VERSIONS, &svcount);
865 if (svcount > 0) {
866 printf("%d shader versions supported:\n", svcount);
867 foreach (GLuint n; 0..svcount) printf(" %d: %s\n", n, glGetStringi(GL_SHADING_LANGUAGE_VERSION, n));
870 GLint ecount;
871 glGetIntegerv(GL_NUM_EXTENSIONS, &ecount);
872 if (ecount > 0) {
873 printf("%d extensions supported:\n", ecount);
874 foreach (GLuint n; 0..ecount) printf(" %d: %s\n", n, glGetStringi(GL_EXTENSIONS, n));
879 // check if we have sufficient shader version here
881 bool found = false;
882 GLint svcount;
883 glGetIntegerv(GL_NUM_SHADING_LANGUAGE_VERSIONS, &svcount);
884 if (svcount > 0) {
885 foreach (GLuint n; 0..svcount) {
886 import core.stdc.string : strncmp;
887 auto v = glGetStringi(GL_SHADING_LANGUAGE_VERSION, n);
888 if (v is null) continue;
889 if (strncmp(v, "120", 3) != 0) continue;
890 if (v[3] > ' ') continue;
891 found = true;
892 break;
895 if (!found) assert(0, "can't find OpenGL GLSL 120");
897 auto adr = glGetProcAddress("glTexParameterf");
898 if (adr is null) assert(0);
901 version(use_vsync) {
902 sdwindow.vsync = true;
903 } else {
904 sdwindow.vsync = false;
906 //sdwindow.useGLFinish = false;
907 initOpenGL();
908 //sdwindow.redrawOpenGlScene();
909 if (!sdwindow.releaseCurrentOpenGlContext()) { import core.stdc.stdio; printf("can't release OpenGL context(1)\n"); }
910 if (!renderTid) {
911 renderTid = new Thread(&renderThread);
912 renderTid.start();
916 //sdwindow.redrawOpenGlScene = delegate () { renderScene(); };
918 enum MSecsPerFrame = 1000/30; /* 30 is FPS */
920 uint[8] frameTimes = 1000;
921 enum { Left, Right, Up, Down }
922 bool[4] pressed = false;
924 sdwindow.eventLoop(MSecsPerFrame,
925 delegate () {
926 if (sdwindow.closed) return;
927 if (pressed[Left]) mapOfsX -= 8;
928 if (pressed[Right]) mapOfsX += 8;
929 if (pressed[Up]) mapOfsY -= 8;
930 if (pressed[Down]) mapOfsY += 8;
931 import std.math : cos, sin;
932 __gshared float itime = 0.0;
933 itime += 0.02;
934 if (movement) {
935 mapOfsX = cast(int)(800.0/2.0+cos(itime)*220.0);
936 mapOfsY = cast(int)(800.0/2.0+120.0+sin(itime)*160.0);
938 if (scale == 1) mapOfsX = mapOfsY = 0;
939 //sdwindow.redrawOpenGlSceneNow();
941 delegate (KeyEvent event) {
942 if (sdwindow.closed) return;
943 if (event.pressed && event.key == Key.Escape) { closeWindow(); return; }
944 switch (event.key) {
945 case Key.X: if (event.pressed) altMove = !altMove; break;
946 case Key.Left: if (altMove) pressed[Left] = event.pressed; break;
947 case Key.Right: if (altMove) pressed[Right] = event.pressed; break;
948 case Key.Up: if (altMove) pressed[Up] = event.pressed; break;
949 case Key.Down: if (altMove) pressed[Down] = event.pressed; break;
950 default:
952 if (!altMove) {
953 switch (event.key) {
954 case Key.Left: case Key.Pad4: plrKeyUpDown(0, PLK_LEFT, event.pressed); break;
955 case Key.Right: case Key.Pad6: plrKeyUpDown(0, PLK_RIGHT, event.pressed); break;
956 case Key.Up: case Key.Pad8: plrKeyUpDown(0, PLK_UP, event.pressed); break;
957 case Key.Down: case Key.Pad2: plrKeyUpDown(0, PLK_DOWN, event.pressed); break;
958 case Key.Alt: plrKeyUpDown(0, PLK_JUMP, event.pressed); break;
959 case Key.Ctrl: plrKeyUpDown(0, PLK_FIRE, event.pressed); break;
960 case Key.Shift: plrKeyUpDown(0, PLK_USE, event.pressed); break;
961 default:
965 delegate (MouseEvent event) {
966 lightX = event.x/scale;
967 lightY = event.y/scale;
968 lightX += mapOfsX/scale;
969 lightY += mapOfsY/scale;
971 delegate (dchar ch) {
972 if (ch == 'q') { closeWindow(); return; }
973 if (ch == 's') scanlines = !scanlines;
974 if (ch == '1') scale = 1;
975 if (ch == '2') scale = 2;
976 if (ch == 'i') {
977 frameInterpolation = !frameInterpolation;
978 if (frameInterpolation) addMessage("Interpolation: ON"); else addMessage("Interpolation: OFF");
980 if (ch == 'l') {
981 doLighting = !doLighting;
982 if (doLighting) addMessage("Lighting: ON"); else addMessage("Lighting: OFF");
984 if (ch == ' ') movement = !movement;
987 } catch (Exception e) {
988 import std.stdio : stderr;
989 stderr.writeln("FUUUUUUUUUUUU\n", e.toString);
991 closeWindow();
992 flushGui();