cosmetix
[dd2d.git] / xmain_d2d.d
bloba33e2af49c1e4ea587cd8bba9aa5f4b618c7868c
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);
162 mapViewPosX[0] = mapViewPosX[1];
163 mapViewPosY[0] = mapViewPosY[1];
165 { import core.memory : GC; GC.collect(); }
169 // ////////////////////////////////////////////////////////////////////////// //
170 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
171 if (lightRadius < 2) return;
172 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
173 int lightSize = lightRadius*2;
174 // is this light visible?
175 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*8 || lightY-lightRadius >= map.height*8) return;
177 // draw shadow casters to fboOccludersId, light should be in the center
178 glUseProgram(0);
179 glDisable(GL_BLEND);
180 fboOccluders[lightRadius].exec({
181 //glDisable(GL_BLEND);
182 glColor3f(0.0f, 0.0f, 0.0f);
183 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
184 glClear(GL_COLOR_BUFFER_BIT);
185 orthoCamera(lightSize, lightSize);
186 drawAtXY(map.texgl.ptr[map.LightMask], lightRadius-lightX, lightRadius-lightY);
189 // build 1d shadow map to fboShadowMapId
190 fboShadowMap[lightRadius].exec({
191 shadToPolar.exec((Shader shad) {
192 //glDisable(GL_BLEND);
193 glColor3f(0.0f, 0.0f, 0.0f);
194 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
195 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
196 glClear(GL_COLOR_BUFFER_BIT);
197 orthoCamera(lightSize, 1);
198 drawAtXY(fboOccluders[lightRadius].tex.tid, 0, 0, lightSize, 1);
202 // build light texture for blending
203 fboOccluders[lightRadius].exec({
204 // no need to clear it, shader will take care of that
205 //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
206 //glClear(GL_COLOR_BUFFER_BIT);
207 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
208 //glDisable(GL_BLEND);
209 shadBlur.exec((Shader shad) {
210 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
211 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
212 shad["lightPos"] = SVec2F(lightX, lightY);
213 orthoCamera(lightSize, lightSize);
214 drawAtXY(fboShadowMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
218 // blend light texture
219 fboLevelLight.exec({
220 glEnable(GL_BLEND);
221 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
222 orthoCamera(map.width*8, map.height*8);
223 drawAtXY(fboOccluders[lightRadius].tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
224 // and blend it again, somewhat bigger, with the shader that will touch only occluders
225 enum szmore = 0;
226 shadBlurOcc.exec((Shader shad) {
227 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
228 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
229 shad["lightPos"] = SVec2F(lightX, lightY);
230 drawAtXY(fboOccluders[lightRadius].tex.tid, lightX-lightRadius-szmore, lightY-lightRadius-szmore, lightSize+szmore*2, lightSize+szmore*2, mirrorY:true);
236 // ////////////////////////////////////////////////////////////////////////// //
237 // messages
238 struct Message {
239 enum Phase { FadeIn, Stay, FadeOut }
240 Phase phase;
241 int alpha;
242 int pauseMsecs;
243 MonoTime removeTime;
244 char[256] text;
245 usize textlen;
248 private import core.sync.mutex : Mutex;
250 __gshared Message[128] messages;
251 __gshared uint messagesUsed = 0;
252 __gshared Mutex messageLock;
254 shared static this () { messageLock = new Mutex(); }
257 void addMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
258 messageLock.lock();
259 scope(exit) messageLock.unlock();
260 if (msgtext.length == 0) return;
261 conwriteln(msgtext);
262 if (pauseMsecs <= 50) return;
263 if (messagesUsed == messages.length) {
264 // remove top message
265 foreach (immutable cidx; 1..messagesUsed) messages.ptr[cidx-1] = messages.ptr[cidx];
266 messages.ptr[0].alpha = 255;
267 --messagesUsed;
269 // quick replace
270 if (!noreplace && messagesUsed == 1) {
271 switch (messages.ptr[0].phase) {
272 case Message.Phase.FadeIn:
273 messages.ptr[0].phase = Message.Phase.FadeOut;
274 break;
275 case Message.Phase.Stay:
276 messages.ptr[0].phase = Message.Phase.FadeOut;
277 messages.ptr[0].alpha = 255;
278 break;
279 default:
282 auto msg = messages.ptr+messagesUsed;
283 ++messagesUsed;
284 msg.phase = Message.Phase.FadeIn;
285 msg.alpha = 0;
286 msg.pauseMsecs = pauseMsecs;
287 // copy text
288 if (msgtext.length > msg.text.length) {
289 msg.text = msgtext[0..msg.text.length];
290 msg.textlen = msg.text.length;
291 } else {
292 msg.text[0..msgtext.length] = msgtext[];
293 msg.textlen = msgtext.length;
298 void doMessages (MonoTime curtime) {
299 messageLock.lock();
300 scope(exit) messageLock.unlock();
302 if (messagesUsed == 0) return;
303 glEnable(GL_BLEND);
304 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
306 Message* msg;
308 again:
309 msg = messages.ptr;
310 final switch (msg.phase) {
311 case Message.Phase.FadeIn:
312 if ((msg.alpha += 10) >= 255) {
313 msg.phase = Message.Phase.Stay;
314 msg.removeTime = curtime+dur!"msecs"(msg.pauseMsecs);
315 goto case; // to stay
317 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
318 break;
319 case Message.Phase.Stay:
320 if (msg.removeTime <= curtime) {
321 msg.alpha = 255;
322 msg.phase = Message.Phase.FadeOut;
323 goto case; // to fade
325 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
326 break;
327 case Message.Phase.FadeOut:
328 if ((msg.alpha -= 10) <= 0) {
329 if (--messagesUsed == 0) return;
330 // remove this message
331 foreach (immutable cidx; 1..messagesUsed+1) messages.ptr[cidx-1] = messages.ptr[cidx];
332 goto again;
334 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
335 break;
338 smDrawText(10, 10, msg.text[0..msg.textlen]);
342 // ////////////////////////////////////////////////////////////////////////// //
343 __gshared int lightX = vlWidth/2, lightY = vlHeight/2;
344 __gshared int mapOfsX, mapOfsY;
345 __gshared bool movement = false;
346 __gshared float iLiquidTime = 0.0;
347 __gshared bool altMove = false;
350 void renderScene (MonoTime curtime) {
351 //enum BackIntens = 0.05f;
352 enum BackIntens = 0.0f;
354 float atob = (curtime > lastthink ? cast(float)((curtime-lastthink).total!"msecs")/cast(float)((nextthink-lastthink).total!"msecs") : 1.0f);
355 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
358 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
359 int curfp = cast(int)((curtime-lastthink).total!"msecs");
360 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
364 glUseProgram(0);
366 // build background layer
367 fboOrigBack.exec({
368 //glDisable(GL_BLEND);
369 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
370 glClear(GL_COLOR_BUFFER_BIT);
371 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
372 orthoCamera(map.width*8, map.height*8);
373 glEnable(GL_BLEND);
374 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
375 // draw sky
377 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
378 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2, map.MapSize*8, map.MapSize*8);
379 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
380 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*8, map.MapSize*8);
382 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*8, map.MapSize*8);
383 // draw background
384 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
385 // draw distorted liquid areas
386 shadLiquidDistort.exec((Shader shad) {
387 shad["iDistortTime"] = iLiquidTime;
388 drawAtXY(map.texgl.ptr[map.AllLiquids], 0, 0);
390 // monsters, items; we'll do linear interpolation here
391 // we need this for saved frame anyway, so let's play dirty and speed up the things
392 uint fxofs = Actor.fields["x"].ofs;
393 uint fyofs = Actor.fields["y"].ofs;
394 uint fzAnimstateofs = Actor.fields["zAnimstate"].ofs;
395 uint fdirofs = Actor.fields["dir"].ofs;
396 uint fzAnimidxofs = Actor.fields["zAnimidx"].ofs;
397 uint fclasstypeofs = Actor.fields["classtype"].ofs;
398 uint fclassnameofs = Actor.fields["classname"].ofs;
399 uint fattLightXOfs = Actor.fields["attLightXOfs"].ofs;
400 uint fattLightYOfs = Actor.fields["attLightYOfs"].ofs;
401 uint fattLightRGBX = Actor.fields["attLightRGBX"].ofs;
402 glColor3f(1.0f, 1.0f, 1.0f);
403 attachedLightCount = 0;
404 Actor.forEach((ActorId me) {
405 // `act` is always valid here
406 auto aptr = me.data.ptr;
407 auto ctstr = StrId(*cast(uint*)(aptr+fclasstypeofs));
408 auto cnstr = StrId(*cast(uint*)(aptr+fclassnameofs));
409 if (auto adef = findActorDef(ctstr, cnstr)) {
410 ctstr = StrId(*cast(uint*)(aptr+fzAnimstateofs));
411 int actorX, actorY; // current actor position
413 auto ofs = prevFrameActorOfs.ptr[me.id&0xffff];
414 if (frameInterpolation && ofs < uint.max-1 && Actor.isSameSavedActor(me.id, prevFrameActorsData.ptr, ofs)) {
415 import core.stdc.math : roundf;
416 auto xptr = prevFrameActorsData.ptr+ofs;
417 int ox = *cast(int*)(xptr+fxofs);
418 int nx = *cast(int*)(aptr+fxofs);
419 int oy = *cast(int*)(xptr+fyofs);
420 int ny = *cast(int*)(aptr+fyofs);
421 actorX = cast(int)(ox+roundf((nx-ox)*atob));
422 actorY = cast(int)(oy+roundf((ny-oy)*atob));
423 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
424 } else {
425 actorX = *cast(int*)(aptr+fxofs);
426 actorY = *cast(int*)(aptr+fyofs);
430 if (auto isp = adef.animSpr(ctstr, *cast(uint*)(aptr+fdirofs), *cast(int*)(aptr+fzAnimidxofs))) {
431 drawAtXY(isp.tex, actorX-isp.vga.sx, actorY-isp.vga.sy);
432 } else {
433 //conwriteln("no animation for actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
435 // process attached lights
437 uint alr = *cast(uint*)(aptr+fattLightRGBX);
438 if ((alr&0xff) >= 4) {
439 // yep, add it
440 auto li = attachedLights.ptr+attachedLightCount;
441 ++attachedLightCount;
442 li.x = actorX+(*cast(int*)(aptr+fattLightXOfs));
443 li.y = actorY+(*cast(int*)(aptr+fattLightYOfs));
444 li.r = ((alr>>24)&0xff)/255.0f; // red or intensity
445 if ((alr&0x00_ff_ff_00U) == 0x00_00_01_00U) {
446 li.uncolored = true;
447 } else {
448 li.g = ((alr>>16)&0xff)/255.0f;
449 li.b = ((alr>>8)&0xff)/255.0f;
450 li.uncolored = false;
452 li.radius = (alr&0xff);
453 if (li.radius > MaxLightRadius) li.radius = MaxLightRadius;
456 } else {
457 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
460 // do liquid coloring
461 drawAtXY(map.texgl.ptr[map.LiquidMask], 0, 0);
462 // foreground -- hide secrets, draw lifts and such
463 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
467 if (doLighting) {
468 // clear light layer
469 fboLevelLight.exec({
470 glDisable(GL_BLEND);
471 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
472 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
473 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
474 glClear(GL_COLOR_BUFFER_BIT);
477 // texture 1 is background
478 glActiveTexture(GL_TEXTURE0+1);
479 glBindTexture(GL_TEXTURE_2D, fboOrigBack.tex.tid);
480 // texture 2 is occluders
481 glActiveTexture(GL_TEXTURE0+2);
482 glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.LightMask].tid);
483 // done texture assign
484 glActiveTexture(GL_TEXTURE0+0);
486 enum LYOfs = 1;
488 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
489 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 100);
490 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
491 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
492 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
493 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
494 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
495 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 128);
496 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 64);
497 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 1.0f), 32);
499 renderLight(24*8+4, (24+18)*8-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
501 // attached lights
502 foreach (ref li; attachedLights[0..attachedLightCount]) {
503 if (li.uncolored) {
504 renderLight(li.x, li.y, SVec4F(0.0f, 0.0f, 0.0f, li.r), li.radius);
505 } else {
506 renderLight(li.x, li.y, SVec4F(li.r, li.g, li.b, 1.0f), li.radius);
510 foreach (immutable _; 0..1) {
511 renderLight(lightX, lightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
514 glActiveTexture(GL_TEXTURE0+1);
515 glBindTexture(GL_TEXTURE_2D, 0);
516 glActiveTexture(GL_TEXTURE0+2);
517 glBindTexture(GL_TEXTURE_2D, 0);
518 glActiveTexture(GL_TEXTURE0+0);
521 // draw scaled level
523 shadScanlines.exec((Shader shad) {
524 shad["scanlines"] = scanlines;
525 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
526 glClear(GL_COLOR_BUFFER_BIT);
527 orthoCamera(vlWidth, vlHeight);
528 //orthoCamera(map.width*8*scale, map.height*8*scale);
529 //glMatrixMode(GL_MODELVIEW);
530 //glLoadIdentity();
531 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
532 // somehow, FBO objects are mirrored; wtf?!
533 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*8*scale, map.height*8*scale, mirrorY:true);
534 //glLoadIdentity();
539 fboLevelLight.exec({
540 smDrawText(map.width*8/2, map.height*8/2, "Testing...");
545 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
546 glDisable(GL_BLEND);
549 fboOrigBack.exec({
550 //auto img = smfont.ptr[0x39];
551 auto img = fftest;
552 if (img !is null) {
553 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
554 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
559 int mofsx, mofsy;
561 if (altMove || movement || scale == 1) {
562 mofsx = mapOfsX;
563 mofsy = mapOfsY;
564 } else {
565 if (frameInterpolation) {
566 import core.stdc.math : roundf;
567 mofsx = cast(int)(mapViewPosX[0]+roundf((mapViewPosX[1]-mapViewPosX[0])*atob));
568 mofsy = cast(int)(mapViewPosY[0]+roundf((mapViewPosY[1]-mapViewPosY[0])*atob));
569 } else {
570 mofsx = mapViewPosX[1];
571 mofsy = mapViewPosY[1];
575 orthoCamera(vlWidth, vlHeight);
576 auto tex = (doLighting ? fboLevelLight.tex.tid : fboOrigBack.tex.tid);
577 drawAtXY(fboLevelLight.tex.tid, -mofsx, -mofsy, map.width*8*scale, map.height*8*scale, mirrorY:true);
579 doMessages(curtime);
583 // ////////////////////////////////////////////////////////////////////////// //
584 // returns time slept
585 int sleepAtMaxMsecs (int msecs) {
586 if (msecs > 0) {
587 import core.sys.posix.signal : timespec;
588 import core.sys.posix.time : nanosleep;
589 timespec ts = void, tpassed = void;
590 ts.tv_sec = 0;
591 ts.tv_nsec = msecs*1000*1000+(500*1000); // milli to nano
592 nanosleep(&ts, &tpassed);
593 return (ts.tv_nsec-tpassed.tv_nsec)/(1000*1000);
594 } else {
595 return 0;
600 // ////////////////////////////////////////////////////////////////////////// //
601 // rendering thread
602 shared int diedie = 0;
604 enum D2DFrameTime = 55; // milliseconds
605 enum MinFrameTime = 1000/60; // ~60 FPS
607 void renderThread () {
608 try {
609 version(use_vsync) {} else MonoTime ltt = MonoTime.currTime;
611 MonoTime curtime = MonoTime.currTime;
613 lastthink = curtime; // for interpolator
614 nextthink = curtime+dur!"msecs"(D2DFrameTime);
615 MonoTime nextvframe = curtime;
617 enum MaxFPSFrames = 16;
618 float frtimes = 0.0f;
619 int framenum = 0;
620 int prevFPS = -1;
621 int hushFrames = 6; // ignore first `hushFrames` frames overtime
622 MonoTime prevFrameStartTime = curtime;
624 bool vframeWasLost = false;
626 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
627 bool doThinkFrame () {
628 if (curtime >= nextthink) {
629 lastthink = curtime;
630 while (nextthink <= curtime) nextthink += dur!"msecs"(D2DFrameTime);
631 // save snapshot and other datafor interpolator
632 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
633 mapViewPosX[0] = mapViewPosX[1];
634 mapViewPosY[0] = mapViewPosY[1];
635 // process actors
636 doActorsThink();
637 // some timing
638 auto tm = MonoTime.currTime;
639 int thinkTime = cast(int)((tm-curtime).total!"msecs");
640 if (thinkTime > 9) { import core.stdc.stdio; printf("spent on thinking: %d msecs\n", thinkTime); }
641 curtime = tm;
642 return true;
643 } else {
644 return false;
648 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
649 bool doVFrame () {
650 version(use_vsync) {
651 __gshared bool prevLost = false;
652 bool doCheckTime = vframeWasLost;
653 if (vframeWasLost) {
654 if (!prevLost) {
655 { import core.stdc.stdio; printf("frame was lost!\n"); }
657 prevLost = true;
658 } else {
659 prevLost = false;
661 } else {
662 enum doCheckTime = true;
664 if (doCheckTime) {
665 if (curtime < nextvframe) return false;
666 version(use_vsync) {} else {
667 if (curtime > nextvframe) {
668 auto overtime = cast(int)((curtime-nextvframe).total!"msecs");
669 if (overtime > 2500) {
670 if (hushFrames) {
671 --hushFrames;
672 } else {
673 { import core.stdc.stdio; printf(" spent whole %d msecs\n", overtime); }
679 while (nextvframe <= curtime) nextvframe += dur!"msecs"(MinFrameTime);
680 bool ctset = false;
682 sdwindow.mtLock();
683 scope(exit) sdwindow.mtUnlock();
684 ctset = sdwindow.setAsCurrentOpenGlContextNT;
686 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
687 if (ctset) {
688 // render scene
689 iLiquidTime = cast(float)((curtime-MonoTime.zero).total!"msecs"%10000000)/18.0f*0.04f;
690 renderScene(curtime);
691 sdwindow.mtLock();
692 scope(exit) sdwindow.mtUnlock();
693 sdwindow.swapOpenGlBuffers();
694 glFinish();
695 sdwindow.releaseCurrentOpenGlContext();
696 vframeWasLost = false;
697 } else {
698 vframeWasLost = true;
699 { import core.stdc.stdio; printf("xframe was lost!\n"); }
701 curtime = MonoTime.currTime;
702 return true;
705 for (;;) {
706 if (sdwindow.closed) break;
707 if (atomicLoad(diedie) > 0) break;
709 curtime = MonoTime.currTime;
710 auto fstime = curtime;
712 doThinkFrame(); // this will fix curtime if necessary
713 if (doVFrame()) {
714 if (!vframeWasLost) {
715 // fps
716 auto frameTime = cast(float)(curtime-prevFrameStartTime).total!"msecs"/1000.0f;
717 prevFrameStartTime = curtime;
718 frtimes += frameTime;
719 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
720 import std.string : format;
721 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
722 if (newFPS != prevFPS) {
723 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
724 prevFPS = newFPS;
726 framenum = 0;
727 frtimes = 0.0f;
732 curtime = MonoTime.currTime;
734 // now sleep until next "video" or "think" frame
735 if (nextthink > curtime && nextvframe > curtime) {
736 // let's decide
737 immutable nextVideoFrameSleep = cast(int)((nextvframe-curtime).total!"msecs");
738 immutable nextThinkFrameSleep = cast(int)((nextthink-curtime).total!"msecs");
739 immutable sleepTime = (nextVideoFrameSleep < nextThinkFrameSleep ? nextVideoFrameSleep : nextThinkFrameSleep);
740 sleepAtMaxMsecs(sleepTime);
741 //curtime = MonoTime.currTime;
744 } catch (Exception e) {
745 import core.stdc.stdio;
746 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
747 for (;;) {
748 if (sdwindow.closed) break;
749 if (atomicLoad(diedie) > 0) break;
750 //{ import core.stdc.stdio; printf(" spent only %d msecs\n", cast(int)((time-ltt).total!"msecs")); }
751 sleepAtMaxMsecs(100);
754 atomicStore(diedie, 2);
758 // ////////////////////////////////////////////////////////////////////////// //
759 void closeWindow () {
760 if (atomicLoad(diedie) != 2) {
761 atomicStore(diedie, 1);
762 while (atomicLoad(diedie) != 2) {}
764 if (!sdwindow.closed) {
765 flushGui();
766 sdwindow.close();
771 // ////////////////////////////////////////////////////////////////////////// //
772 __gshared Thread renderTid;
775 void main (string[] args) {
776 FuncPool.dumpCode = false;
777 FuncPool.dumpCodeSize = false;
778 dacsDumpSemantic = false;
779 dacsOptimize = 9;
780 //version(rdmd) { dacsOptimize = 0; }
781 bool compileOnly = false;
783 for (usize idx = 1; idx < args.length; ++idx) {
784 bool remove = true;
785 if (args[idx] == "--dump-code") FuncPool.dumpCode = true;
786 else if (args[idx] == "--dump-code-size") FuncPool.dumpCodeSize = true;
787 else if (args[idx] == "--dump-semantic") dacsDumpSemantic = true;
788 else if (args[idx] == "--dump-all") { FuncPool.dumpCode = true; FuncPool.dumpCodeSize = true; dacsDumpSemantic = true; }
789 else if (args[idx] == "--compile") compileOnly = true;
790 else if (args[idx] == "--compile-only") compileOnly = true;
791 else if (args[idx] == "--messages") ++dacsMessages;
792 else if (args[idx] == "--no-copro") dacsOptimizeNoCoPro = true;
793 else if (args[idx] == "--no-deadass") dacsOptimizeNoDeadAss = true;
794 else if (args[idx] == "--no-purekill") dacsOptimizeNoPureKill = true;
795 else if (args[idx].length > 2 && args[idx][0..2] == "-O") {
796 import std.conv : to;
797 ubyte olevel = to!ubyte(args[idx][2..$]);
798 dacsOptimize = olevel;
800 else remove = false;
801 if (remove) {
802 foreach (immutable c; idx+1..args.length) args.ptr[c-1] = args.ptr[c];
803 args.length -= 1;
804 --idx; //hack
808 static void setDP () {
809 version(rdmd) {
810 setDataPath("data");
811 } else {
812 import std.file : thisExePath;
813 import std.path : dirName;
814 setDataPath(thisExePath.dirName);
816 addPK3(getDataPath~"base.pk3"); loadWadScripts();
817 //addWad("/home/ketmar/k8prj/doom2d-tl/data/doom2d.wad"); loadWadScripts();
818 //addWad("/home/ketmar/k8prj/doom2d-tl/data/meat.wad"); loadWadScripts();
819 //addWad("/home/ketmar/k8prj/doom2d-tl/data/megadm.wad"); loadWadScripts();
820 //addWad("/home/ketmar/k8prj/doom2d-tl/data/megadm1.wad"); loadWadScripts();
821 //addWad("/home/ketmar/k8prj/doom2d-tl/data/superdm.wad"); loadWadScripts();
822 //addWad("/home/ketmar/k8prj/doom2d-tl/data/zadoomka.wad"); loadWadScripts();
823 scriptLoadingComplete();
826 setDP();
828 if (compileOnly) return;
830 try {
831 registerAPI();
832 loadPalette();
834 setOpenGLContextVersion(3, 2); // up to GLSL 150
835 //openGLContextCompatible = false;
837 map = new LevelMap("maps/map01.d2m");
839 //mapOfsX = 8*26;
840 //mapOfsY = 8*56;
841 map.getThingPos(1/*ThingId.Player1*/, &mapOfsX, &mapOfsY);
842 // fix viewport
843 mapOfsX = (mapOfsX*2)-vlWidth/2;
844 if (mapOfsX+vlWidth > map.width*16) mapOfsX = map.width*16-vlWidth;
845 if (mapOfsX < 0) mapOfsX = 0;
846 mapOfsY = (mapOfsY*2)-vlHeight/2;
847 if (mapOfsY+vlHeight > map.height*16) mapOfsY = map.height*16-vlHeight;
848 if (mapOfsY < 0) mapOfsY = 0;
849 scale = 2;
851 sdwindow = new SimpleWindow(vlWidth, vlHeight, "D2D", OpenGlOptions.yes, Resizablity.fixedSize);
853 sdwindow.visibleForTheFirstTime = delegate () {
854 sdwindow.setAsCurrentOpenGlContext(); // make this window active
855 glbindLoadFunctions();
858 import core.stdc.stdio;
859 printf("GL version: %s\n", glGetString(GL_VERSION));
860 GLint l, h;
861 glGetIntegerv(GL_MAJOR_VERSION, &h);
862 glGetIntegerv(GL_MINOR_VERSION, &l);
863 printf("version: %d.%d\n", h, l);
864 printf("shader version: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
865 GLint svcount;
866 glGetIntegerv(GL_NUM_SHADING_LANGUAGE_VERSIONS, &svcount);
867 if (svcount > 0) {
868 printf("%d shader versions supported:\n", svcount);
869 foreach (GLuint n; 0..svcount) printf(" %d: %s\n", n, glGetStringi(GL_SHADING_LANGUAGE_VERSION, n));
872 GLint ecount;
873 glGetIntegerv(GL_NUM_EXTENSIONS, &ecount);
874 if (ecount > 0) {
875 printf("%d extensions supported:\n", ecount);
876 foreach (GLuint n; 0..ecount) printf(" %d: %s\n", n, glGetStringi(GL_EXTENSIONS, n));
881 // check if we have sufficient shader version here
883 bool found = false;
884 GLint svcount;
885 glGetIntegerv(GL_NUM_SHADING_LANGUAGE_VERSIONS, &svcount);
886 if (svcount > 0) {
887 foreach (GLuint n; 0..svcount) {
888 import core.stdc.string : strncmp;
889 auto v = glGetStringi(GL_SHADING_LANGUAGE_VERSION, n);
890 if (v is null) continue;
891 if (strncmp(v, "120", 3) != 0) continue;
892 if (v[3] > ' ') continue;
893 found = true;
894 break;
897 if (!found) assert(0, "can't find OpenGL GLSL 120");
899 auto adr = glGetProcAddress("glTexParameterf");
900 if (adr is null) assert(0);
903 version(use_vsync) {
904 sdwindow.vsync = true;
905 } else {
906 sdwindow.vsync = false;
908 //sdwindow.useGLFinish = false;
909 initOpenGL();
910 //sdwindow.redrawOpenGlScene();
911 if (!sdwindow.releaseCurrentOpenGlContext()) { import core.stdc.stdio; printf("can't release OpenGL context(1)\n"); }
912 if (!renderTid) {
913 renderTid = new Thread(&renderThread);
914 renderTid.start();
918 //sdwindow.redrawOpenGlScene = delegate () { renderScene(); };
920 enum MSecsPerFrame = 1000/30; /* 30 is FPS */
922 uint[8] frameTimes = 1000;
923 enum { Left, Right, Up, Down }
924 bool[4] pressed = false;
926 sdwindow.eventLoop(MSecsPerFrame,
927 delegate () {
928 if (sdwindow.closed) return;
929 if (pressed[Left]) mapOfsX -= 8;
930 if (pressed[Right]) mapOfsX += 8;
931 if (pressed[Up]) mapOfsY -= 8;
932 if (pressed[Down]) mapOfsY += 8;
933 import std.math : cos, sin;
934 __gshared float itime = 0.0;
935 itime += 0.02;
936 if (movement) {
937 mapOfsX = cast(int)(800.0/2.0+cos(itime)*220.0);
938 mapOfsY = cast(int)(800.0/2.0+120.0+sin(itime)*160.0);
940 if (scale == 1) mapOfsX = mapOfsY = 0;
941 //sdwindow.redrawOpenGlSceneNow();
943 delegate (KeyEvent event) {
944 if (sdwindow.closed) return;
945 if (event.pressed && event.key == Key.Escape) { closeWindow(); return; }
946 switch (event.key) {
947 case Key.X: if (event.pressed) altMove = !altMove; break;
948 case Key.Left: if (altMove) pressed[Left] = event.pressed; break;
949 case Key.Right: if (altMove) pressed[Right] = event.pressed; break;
950 case Key.Up: if (altMove) pressed[Up] = event.pressed; break;
951 case Key.Down: if (altMove) pressed[Down] = event.pressed; break;
952 default:
954 if (!altMove) {
955 switch (event.key) {
956 case Key.Left: case Key.Pad4: plrKeyUpDown(0, PLK_LEFT, event.pressed); break;
957 case Key.Right: case Key.Pad6: plrKeyUpDown(0, PLK_RIGHT, event.pressed); break;
958 case Key.Up: case Key.Pad8: plrKeyUpDown(0, PLK_UP, event.pressed); break;
959 case Key.Down: case Key.Pad2: plrKeyUpDown(0, PLK_DOWN, event.pressed); break;
960 case Key.Alt: plrKeyUpDown(0, PLK_JUMP, event.pressed); break;
961 case Key.Ctrl: plrKeyUpDown(0, PLK_FIRE, event.pressed); break;
962 case Key.Shift: plrKeyUpDown(0, PLK_USE, event.pressed); break;
963 default:
967 delegate (MouseEvent event) {
968 lightX = event.x/scale;
969 lightY = event.y/scale;
970 lightX += mapOfsX/scale;
971 lightY += mapOfsY/scale;
973 delegate (dchar ch) {
974 if (ch == 'q') { closeWindow(); return; }
975 if (ch == 's') scanlines = !scanlines;
976 if (ch == '1') scale = 1;
977 if (ch == '2') scale = 2;
978 if (ch == 'i') {
979 frameInterpolation = !frameInterpolation;
980 if (frameInterpolation) addMessage("Interpolation: ON"); else addMessage("Interpolation: OFF");
982 if (ch == 'l') {
983 doLighting = !doLighting;
984 if (doLighting) addMessage("Lighting: ON"); else addMessage("Lighting: OFF");
986 if (ch == ' ') movement = !movement;
989 } catch (Exception e) {
990 import std.stdio : stderr;
991 stderr.writeln("FUUUUUUUUUUUU\n", e.toString);
993 closeWindow();
994 flushGui();