script renamings
[dd2d.git] / xmain_d2d.d
blobb71deee64bbc8e09866ddd75ebb5234518df523f
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 __gshared int scale = 1;
38 __gshared bool scanlines = false;
41 // ////////////////////////////////////////////////////////////////////////// //
42 // interpolation
43 __gshared ubyte[] prevFrameActorsData;
44 __gshared uint[65536] prevFrameActorOfs; // uint.max-1: dead; uint.max: last
45 __gshared MonoTime lastthink = MonoTime.zero; // for interpolator
46 __gshared MonoTime nextthink = MonoTime.zero;
47 __gshared bool frameInterpolation = true;
48 __gshared bool doLighting = true;
51 // ////////////////////////////////////////////////////////////////////////// //
52 enum MaxLightRadius = 512;
55 // ////////////////////////////////////////////////////////////////////////// //
56 // for light
57 __gshared FBO[MaxLightRadius+1] fboOccluders, fboShadowMap;
58 __gshared Shader shadToPolar, shadBlur, shadBlurOcc;
60 __gshared FBO fboLevel, fboLevelLight, fboOrigBack;
61 __gshared Shader shadScanlines;
62 __gshared Shader shadLiquidDistort;
65 // ////////////////////////////////////////////////////////////////////////// //
66 void initOpenGL () {
67 glEnable(GL_TEXTURE_2D);
68 glDisable(GL_LIGHTING);
69 glDisable(GL_DITHER);
70 glDisable(GL_BLEND);
71 glDisable(GL_DEPTH_TEST);
73 // create shaders
74 shadScanlines = new Shader("scanlines", loadTextFile("data/shaders/srscanlines.frag"));
76 shadLiquidDistort = new Shader("liquid_distort", loadTextFile("data/shaders/srliquid_distort.frag"));
77 shadLiquidDistort.exec((Shader shad) { shad["tex0"] = 0; });
79 // lights
80 shadToPolar = new Shader("light_topolar", loadTextFile("data/shaders/srlight_topolar.frag"));
81 shadBlur = new Shader("light_blur", loadTextFile("data/shaders/srlight_blur.frag"));
82 shadBlur.exec((Shader shad) {
83 shad["tex0"] = 0;
84 shad["tex1"] = 1;
85 shad["tex2"] = 2;
86 });
87 shadBlurOcc = new Shader("light_blur_occ", loadTextFile("data/shaders/srlight_blur_occ.frag"));
88 shadBlurOcc.exec((Shader shad) {
89 shad["tex0"] = 0;
90 shad["tex1"] = 1;
91 shad["tex2"] = 2;
92 });
93 //TODO: this sux!
94 foreach (int sz; 2..MaxLightRadius+1) {
95 fboOccluders[sz] = new FBO(sz*2, sz*2, Texture.Option.Clamp, Texture.Option.Linear); // create occluders FBO
96 fboShadowMap[sz] = new FBO(sz*2, 1, Texture.Option.Clamp); // create 1d shadowmap FBO
99 // setup matrices
100 glMatrixMode(GL_MODELVIEW);
101 glLoadIdentity();
103 map.oglBuildMega();
105 fboLevel = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // final level render will be here
106 fboLevelLight = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // level lights will be rendered here
107 //fboForeground = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // level foreground
108 fboOrigBack = new FBO(map.width*8, map.height*8, Texture.Option.Nearest); // background+foreground
110 shadBlur.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*8, map.height*8); });
111 shadBlurOcc.exec((Shader shad) { shad["mapPixSize"] = SVec2F(map.width*8, map.height*8); });
113 glActiveTexture(GL_TEXTURE0+0);
114 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
115 orthoCamera(vlWidth, vlHeight);
117 loadSmFont();
119 Actor.resetStorage();
120 loadMapMonsters();
122 // save first snapshot
123 prevFrameActorsData = new ubyte[](Actor.actorSize*65536); // ~15-20 megabytes
124 prevFrameActorOfs[] = uint.max; // just for fun
125 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
127 { import core.memory : GC; GC.collect(); }
131 // ////////////////////////////////////////////////////////////////////////// //
132 void renderLight() (int lightX, int lightY, in auto ref SVec4F lcol, int lightRadius) {
133 if (lightRadius < 2) return;
134 if (lightRadius > MaxLightRadius) lightRadius = MaxLightRadius;
135 int lightSize = lightRadius*2;
136 // is this light visible?
137 if (lightX <= -lightRadius || lightY <= -lightRadius || lightX-lightRadius >= map.width*8 || lightY-lightRadius >= map.height*8) return;
139 // draw shadow casters to fboOccludersId, light should be in the center
140 glUseProgram(0);
141 glDisable(GL_BLEND);
142 fboOccluders[lightRadius].exec({
143 //glDisable(GL_BLEND);
144 glColor3f(0.0f, 0.0f, 0.0f);
145 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
146 glClear(GL_COLOR_BUFFER_BIT);
147 orthoCamera(lightSize, lightSize);
148 drawAtXY(map.texgl.ptr[map.LightMask], lightRadius-lightX, lightRadius-lightY);
151 // build 1d shadow map to fboShadowMapId
152 fboShadowMap[lightRadius].exec({
153 shadToPolar.exec((Shader shad) {
154 //glDisable(GL_BLEND);
155 glColor3f(0.0f, 0.0f, 0.0f);
156 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
157 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
158 glClear(GL_COLOR_BUFFER_BIT);
159 orthoCamera(lightSize, 1);
160 drawAtXY(fboOccluders[lightRadius].tex.tid, 0, 0, lightSize, 1);
164 // build light texture for blending
165 fboOccluders[lightRadius].exec({
166 // no need to clear it, shader will take care of that
167 //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
168 //glClear(GL_COLOR_BUFFER_BIT);
169 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
170 //glDisable(GL_BLEND);
171 shadBlur.exec((Shader shad) {
172 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
173 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
174 shad["lightPos"] = SVec2F(lightX, lightY);
175 orthoCamera(lightSize, lightSize);
176 drawAtXY(fboShadowMap[lightRadius].tex.tid, 0, 0, lightSize, lightSize);
180 // blend light texture
181 fboLevelLight.exec({
182 glEnable(GL_BLEND);
183 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
184 orthoCamera(map.width*8, map.height*8);
185 drawAtXY(fboOccluders[lightRadius].tex, lightX-lightRadius, lightY-lightRadius, mirrorY:true);
186 // and blend it again, somewhat bigger, with the shader that will touch only occluders
187 enum szmore = 0;
188 shadBlurOcc.exec((Shader shad) {
189 shad["lightTexSize"] = SVec2F(lightSize, lightSize);
190 shad["lightColor"] = SVec4F(lcol.x, lcol.y, lcol.z, lcol.w);
191 shad["lightPos"] = SVec2F(lightX, lightY);
192 drawAtXY(fboOccluders[lightRadius].tex.tid, lightX-lightRadius-szmore, lightY-lightRadius-szmore, lightSize+szmore*2, lightSize+szmore*2, mirrorY:true);
198 // ////////////////////////////////////////////////////////////////////////// //
199 // messages
200 struct Message {
201 enum Phase { FadeIn, Stay, FadeOut }
202 Phase phase;
203 int alpha;
204 int pauseMsecs;
205 MonoTime removeTime;
206 char[256] text;
207 usize textlen;
210 private import core.sync.mutex : Mutex;
212 __gshared Message[128] messages;
213 __gshared uint messagesUsed = 0;
214 __gshared Mutex messageLock;
216 shared static this () { messageLock = new Mutex(); }
219 void addMessage (const(char)[] msgtext, int pauseMsecs=3000, bool noreplace=false) {
220 messageLock.lock();
221 scope(exit) messageLock.unlock();
222 if (msgtext.length == 0) return;
223 conwriteln(msgtext);
224 if (pauseMsecs <= 50) return;
225 if (messagesUsed == messages.length) {
226 // remove top message
227 foreach (immutable cidx; 1..messagesUsed) messages.ptr[cidx-1] = messages.ptr[cidx];
228 messages.ptr[0].alpha = 255;
229 --messagesUsed;
231 // quick replace
232 if (!noreplace && messagesUsed == 1) {
233 switch (messages.ptr[0].phase) {
234 case Message.Phase.FadeIn:
235 messages.ptr[0].phase = Message.Phase.FadeOut;
236 break;
237 case Message.Phase.Stay:
238 messages.ptr[0].phase = Message.Phase.FadeOut;
239 messages.ptr[0].alpha = 255;
240 break;
241 default:
244 auto msg = messages.ptr+messagesUsed;
245 ++messagesUsed;
246 msg.phase = Message.Phase.FadeIn;
247 msg.alpha = 0;
248 msg.pauseMsecs = pauseMsecs;
249 // copy text
250 if (msgtext.length > msg.text.length) {
251 msg.text = msgtext[0..msg.text.length];
252 msg.textlen = msg.text.length;
253 } else {
254 msg.text[0..msgtext.length] = msgtext[];
255 msg.textlen = msgtext.length;
260 void doMessages (MonoTime curtime) {
261 messageLock.lock();
262 scope(exit) messageLock.unlock();
264 if (messagesUsed == 0) return;
265 glEnable(GL_BLEND);
266 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
268 Message* msg;
270 again:
271 msg = messages.ptr;
272 final switch (msg.phase) {
273 case Message.Phase.FadeIn:
274 if ((msg.alpha += 10) >= 255) {
275 msg.phase = Message.Phase.Stay;
276 msg.removeTime = curtime+dur!"msecs"(msg.pauseMsecs);
277 goto case; // to stay
279 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
280 break;
281 case Message.Phase.Stay:
282 if (msg.removeTime <= curtime) {
283 msg.alpha = 255;
284 msg.phase = Message.Phase.FadeOut;
285 goto case; // to fade
287 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
288 break;
289 case Message.Phase.FadeOut:
290 if ((msg.alpha -= 10) <= 0) {
291 if (--messagesUsed == 0) return;
292 // remove this message
293 foreach (immutable cidx; 1..messagesUsed+1) messages.ptr[cidx-1] = messages.ptr[cidx];
294 goto again;
296 glColor4f(1.0f, 1.0f, 1.0f, msg.alpha/255.0f);
297 break;
300 smDrawText(10, 10, msg.text[0..msg.textlen]);
304 // ////////////////////////////////////////////////////////////////////////// //
305 __gshared int lightX = vlWidth/2, lightY = vlHeight/2;
306 __gshared int mapOfsX, mapOfsY;
307 __gshared bool movement = false;
308 __gshared float iLiquidTime = 0.0;
311 void renderScene (MonoTime curtime) {
312 //enum BackIntens = 0.05f;
313 enum BackIntens = 0.0f;
315 float atob = (curtime > lastthink ? cast(float)((curtime-lastthink).total!"msecs")/cast(float)((nextthink-lastthink).total!"msecs") : 1.0f);
316 //{ import core.stdc.stdio; printf("atob=%f\n", cast(double)atob); }
319 int framelen = cast(int)((nextthink-lastthink).total!"msecs");
320 int curfp = cast(int)((curtime-lastthink).total!"msecs");
321 { import core.stdc.stdio; printf("framelen=%d; curfp=%d\n", framelen, curfp); }
325 glUseProgram(0);
327 // build background layer
328 fboOrigBack.exec({
329 //glDisable(GL_BLEND);
330 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
331 glClear(GL_COLOR_BUFFER_BIT);
332 glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
333 orthoCamera(map.width*8, map.height*8);
334 glEnable(GL_BLEND);
335 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
336 // draw sky
338 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
339 drawAtXY(map.skytexgl.tid, mapOfsX/2-map.MapSize*8, mapOfsY/2, map.MapSize*8, map.MapSize*8);
340 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2-map.MapSize*8, map.MapSize*8, map.MapSize*8);
341 drawAtXY(map.skytexgl.tid, mapOfsX/2, mapOfsY/2, map.MapSize*8, map.MapSize*8);
343 drawAtXY(map.skytexgl.tid, 0, 0, map.MapSize*8, map.MapSize*8);
344 // draw background
345 drawAtXY(map.texgl.ptr[map.Back], 0, 0);
346 // draw distorted liquid areas
347 shadLiquidDistort.exec((Shader shad) {
348 shad["iDistortTime"] = iLiquidTime;
349 drawAtXY(map.texgl.ptr[map.AllLiquids], 0, 0);
351 // monsters, items; we'll do linear interpolation here
352 // we need this for saved frame anyway, so let's play dirty and speed up the things
353 uint fxofs = Actor.fields["x"].ofs;
354 uint fyofs = Actor.fields["y"].ofs;
355 uint fzAnimstateofs = Actor.fields["zAnimstate"].ofs;
356 uint fdirofs = Actor.fields["dir"].ofs;
357 uint fzAnimidxofs = Actor.fields["zAnimidx"].ofs;
358 uint fclasstypeofs = Actor.fields["classtype"].ofs;
359 uint fclassnameofs = Actor.fields["classname"].ofs;
360 glColor3f(1.0f, 1.0f, 1.0f);
361 Actor.forEach((ActorId me) {
362 // `act` is always valid here
363 auto aptr = me.data.ptr;
364 auto ctstr = StrId(*cast(uint*)(aptr+fclasstypeofs));
365 auto cnstr = StrId(*cast(uint*)(aptr+fclassnameofs));
366 if (auto adef = findActorDef(ctstr, cnstr)) {
367 ctstr = StrId(*cast(uint*)(aptr+fzAnimstateofs));
368 if (auto isp = adef.animSpr(ctstr, *cast(uint*)(aptr+fdirofs), *cast(int*)(aptr+fzAnimidxofs))) {
369 auto ofs = prevFrameActorOfs.ptr[me.id&0xffff];
370 if (frameInterpolation && ofs < uint.max-1 && Actor.isSameSavedActor(me.id, prevFrameActorsData.ptr, ofs)) {
371 import core.stdc.math : roundf;
372 auto xptr = prevFrameActorsData.ptr+ofs;
373 int ox = *cast(int*)(xptr+fxofs);
374 int nx = *cast(int*)(aptr+fxofs);
375 int oy = *cast(int*)(xptr+fyofs);
376 int ny = *cast(int*)(aptr+fyofs);
377 int x = cast(int)(ox+roundf((nx-ox)*atob));
378 int y = cast(int)(oy+roundf((ny-oy)*atob));
379 //conwriteln("actor ", me.id, "; o=(", ox, ",", oy, "); n=(", nx, ",", ny, "); p=(", x, ",", y, ")");
380 drawAtXY(isp.tex, x-isp.vga.sx, y-isp.vga.sy);
381 } else {
382 int nx = *cast(int*)(aptr+fxofs)-isp.vga.sx;
383 int ny = *cast(int*)(aptr+fyofs)-isp.vga.sy;
384 //conwriteln("actor ", me.id, "; nx=", nx, "; ny=", ny);
385 drawAtXY(isp.tex, nx, ny);
387 } else {
388 conwriteln("no animation for actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
390 } else {
391 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
394 if (auto adef = findActorDef(me.classtype!StrId, me.classname!StrId)) {
395 if (auto isp = adef.animSpr(me.zAnimstate!StrId, me.dir!uint, me.zAnimidx!int)) {
396 int y = me.y!int-isp.vga.sy;
397 drawAtXY(isp.tex, me.x!int-isp.vga.sx, y);
398 //conwriteln("found animation for actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")", " (", adef.fullname, ")");
399 } else {
400 conwriteln("no animation for actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
402 } else {
403 conwriteln("not found actor ", me.id, " (", me.classtype!string, ":", me.classname!string, ")");
407 // do liquid coloring
408 drawAtXY(map.texgl.ptr[map.LiquidMask], 0, 0);
409 // foreground -- hide secrets, draw lifts and such
410 drawAtXY(map.texgl.ptr[map.Front], 0, 0);
412 glDisable(GL_BLEND);
413 if (auto img = fftest) {
414 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
415 drawAtXY(img.tex, 50-img.sx, 50-img.sy);
421 if (doLighting) {
422 // clear light layer
423 fboLevelLight.exec({
424 glDisable(GL_BLEND);
425 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
426 //glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
427 ////glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
428 glClear(GL_COLOR_BUFFER_BIT);
431 // texture 1 is background
432 glActiveTexture(GL_TEXTURE0+1);
433 glBindTexture(GL_TEXTURE_2D, fboOrigBack.tex.tid);
434 // texture 2 is occluders
435 glActiveTexture(GL_TEXTURE0+2);
436 glBindTexture(GL_TEXTURE_2D, map.texgl.ptr[map.LightMask].tid);
437 // done texture assign
438 glActiveTexture(GL_TEXTURE0+0);
440 enum LYOfs = 1;
442 renderLight( 27, 391-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 100);
443 renderLight(542, 424-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 100);
444 renderLight(377, 368-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 32);
445 renderLight(147, 288-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 64);
446 renderLight( 71, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 128);
447 renderLight(249, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 128);
448 renderLight(426, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 128);
449 renderLight(624, 200-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 128);
450 renderLight(549, 298-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 64);
451 renderLight( 74, 304-0+LYOfs, SVec4F(0.0f, 0.0f, 0.0f, 0.0f), 32);
453 renderLight(24*8+4, (24+18)*8-2+LYOfs, SVec4F(0.6f, 0.0f, 0.0f, 1.0f), 128);
455 foreach (immutable _; 0..1) {
456 renderLight(lightX, lightY, SVec4F(0.3f, 0.3f, 0.0f, 1.0f), 96);
459 glActiveTexture(GL_TEXTURE0+1);
460 glBindTexture(GL_TEXTURE_2D, 0);
461 glActiveTexture(GL_TEXTURE0+2);
462 glBindTexture(GL_TEXTURE_2D, 0);
463 glActiveTexture(GL_TEXTURE0+0);
466 // draw scaled level
468 shadScanlines.exec((Shader shad) {
469 shad["scanlines"] = scanlines;
470 glClearColor(BackIntens, BackIntens, BackIntens, 1.0f);
471 glClear(GL_COLOR_BUFFER_BIT);
472 orthoCamera(vlWidth, vlHeight);
473 //orthoCamera(map.width*8*scale, map.height*8*scale);
474 //glMatrixMode(GL_MODELVIEW);
475 //glLoadIdentity();
476 //glTranslatef(0.375, 0.375, 0); // to be pixel-perfect
477 // somehow, FBO objects are mirrored; wtf?!
478 drawAtXY(fboLevel.tex.tid, -mapOfsX, -mapOfsY, map.width*8*scale, map.height*8*scale, mirrorY:true);
479 //glLoadIdentity();
484 fboLevelLight.exec({
485 smDrawText(map.width*8/2, map.height*8/2, "Testing...");
490 glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
491 glDisable(GL_BLEND);
494 fboOrigBack.exec({
495 //auto img = smfont.ptr[0x39];
496 auto img = fftest;
497 if (img !is null) {
498 //conwriteln("img.sx=", img.sx, "; img.sy=", img.sy, "; ", img.width, "x", img.height);
499 drawAtXY(img.tex, 10-img.sx, 10-img.sy);
504 orthoCamera(vlWidth, vlHeight);
505 if (doLighting) {
506 drawAtXY(fboLevelLight.tex.tid, -mapOfsX, -mapOfsY, map.width*8*scale, map.height*8*scale, mirrorY:true);
507 } else {
508 drawAtXY(fboOrigBack.tex.tid, -mapOfsX, -mapOfsY, map.width*8*scale, map.height*8*scale, mirrorY:true);
511 doMessages(curtime);
515 // ////////////////////////////////////////////////////////////////////////// //
516 // returns time slept
517 int sleepAtMaxMsecs (int msecs) {
518 if (msecs > 0) {
519 import core.sys.posix.signal : timespec;
520 import core.sys.posix.time : nanosleep;
521 timespec ts = void, tpassed = void;
522 ts.tv_sec = 0;
523 ts.tv_nsec = msecs*1000*1000+(500*1000); // milli to nano
524 nanosleep(&ts, &tpassed);
525 return (ts.tv_nsec-tpassed.tv_nsec)/(1000*1000);
526 } else {
527 return 0;
532 // ////////////////////////////////////////////////////////////////////////// //
533 // rendering thread
534 shared int diedie = 0;
536 enum D2DFrameTime = 55; // milliseconds
537 enum MinFrameTime = 1000/60; // ~60 FPS
539 void renderThread () {
540 try {
541 version(use_vsync) {} else MonoTime ltt = MonoTime.currTime;
543 MonoTime curtime = MonoTime.currTime;
545 lastthink = curtime; // for interpolator
546 nextthink = curtime+dur!"msecs"(D2DFrameTime);
547 MonoTime nextvframe = curtime;
549 enum MaxFPSFrames = 16;
550 float frtimes = 0.0f;
551 int framenum = 0;
552 int prevFPS = -1;
553 int hushFrames = 6; // ignore first `hushFrames` frames overtime
554 MonoTime prevFrameStartTime = curtime;
556 bool vframeWasLost = false;
558 // "D2D frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
559 bool doThinkFrame () {
560 if (curtime >= nextthink) {
561 lastthink = curtime;
562 while (nextthink <= curtime) nextthink += dur!"msecs"(D2DFrameTime);
563 // save snapshot for interpolator
564 Actor.saveSnapshot(prevFrameActorsData[], prevFrameActorOfs.ptr);
565 // process actors
566 doActorsThink();
567 // some timing
568 auto tm = MonoTime.currTime;
569 int thinkTime = cast(int)((tm-curtime).total!"msecs");
570 if (thinkTime > 9) { import core.stdc.stdio; printf("spent on thinking: %d msecs\n", thinkTime); }
571 curtime = tm;
572 return true;
573 } else {
574 return false;
578 // "video frames"; curtime should be set; return `true` if frame was processed; will fix `curtime`
579 bool doVFrame () {
580 version(use_vsync) {
581 __gshared bool prevLost = false;
582 bool doCheckTime = vframeWasLost;
583 if (vframeWasLost) {
584 if (!prevLost) {
585 { import core.stdc.stdio; printf("frame was lost!\n"); }
587 prevLost = true;
588 } else {
589 prevLost = false;
591 } else {
592 enum doCheckTime = true;
594 if (doCheckTime) {
595 if (curtime < nextvframe) return false;
596 version(use_vsync) {} else {
597 if (curtime > nextvframe) {
598 auto overtime = cast(int)((curtime-nextvframe).total!"msecs");
599 if (overtime > 2500) {
600 if (hushFrames) {
601 --hushFrames;
602 } else {
603 { import core.stdc.stdio; printf(" spent whole %d msecs\n", overtime); }
609 while (nextvframe <= curtime) nextvframe += dur!"msecs"(MinFrameTime);
610 bool ctset = false;
612 sdwindow.mtLock();
613 scope(exit) sdwindow.mtUnlock();
614 ctset = sdwindow.setAsCurrentOpenGlContextNT;
616 // if we can't set context, pretend that videoframe was processed; this should solve problem with vsync and invisible window
617 if (ctset) {
618 // render scene
619 iLiquidTime = cast(float)((curtime-MonoTime.zero).total!"msecs"%10000000)/18.0f*0.04f;
620 renderScene(curtime);
621 sdwindow.mtLock();
622 scope(exit) sdwindow.mtUnlock();
623 sdwindow.swapOpenGlBuffers();
624 glFinish();
625 sdwindow.releaseCurrentOpenGlContext();
626 vframeWasLost = false;
627 } else {
628 vframeWasLost = true;
629 { import core.stdc.stdio; printf("xframe was lost!\n"); }
631 curtime = MonoTime.currTime;
632 return true;
635 for (;;) {
636 if (sdwindow.closed) break;
637 if (atomicLoad(diedie) > 0) break;
639 curtime = MonoTime.currTime;
640 auto fstime = curtime;
642 doThinkFrame(); // this will fix curtime if necessary
643 if (doVFrame()) {
644 if (!vframeWasLost) {
645 // fps
646 auto frameTime = cast(float)(curtime-prevFrameStartTime).total!"msecs"/1000.0f;
647 prevFrameStartTime = curtime;
648 frtimes += frameTime;
649 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
650 import std.string : format;
651 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
652 if (newFPS != prevFPS) {
653 sdwindow.title = "%s / FPS:%s".format("D2D", newFPS);
654 prevFPS = newFPS;
656 framenum = 0;
657 frtimes = 0.0f;
662 curtime = MonoTime.currTime;
664 // now sleep until next "video" or "think" frame
665 if (nextthink > curtime && nextvframe > curtime) {
666 // let's decide
667 immutable nextVideoFrameSleep = cast(int)((nextvframe-curtime).total!"msecs");
668 immutable nextThinkFrameSleep = cast(int)((nextthink-curtime).total!"msecs");
669 immutable sleepTime = (nextVideoFrameSleep < nextThinkFrameSleep ? nextVideoFrameSleep : nextThinkFrameSleep);
670 sleepAtMaxMsecs(sleepTime);
671 //curtime = MonoTime.currTime;
674 } catch (Exception e) {
675 import core.stdc.stdio;
676 fprintf(stderr, "FUUUUUUUUUUUUUUUUUUUUUUUUUU\n");
677 for (;;) {
678 if (sdwindow.closed) break;
679 if (atomicLoad(diedie) > 0) break;
680 //{ import core.stdc.stdio; printf(" spent only %d msecs\n", cast(int)((time-ltt).total!"msecs")); }
681 sleepAtMaxMsecs(100);
684 atomicStore(diedie, 2);
688 // ////////////////////////////////////////////////////////////////////////// //
689 void closeWindow () {
690 if (atomicLoad(diedie) != 2) {
691 atomicStore(diedie, 1);
692 while (atomicLoad(diedie) != 2) {}
694 if (!sdwindow.closed) {
695 flushGui();
696 sdwindow.close();
701 // ////////////////////////////////////////////////////////////////////////// //
702 __gshared Thread renderTid;
705 void main (string[] args) {
706 FuncPool.dumpCode = false;
707 FuncPool.dumpCodeSize = false;
708 dacsDumpSemantic = false;
709 dacsOptimize = 9;
710 //version(rdmd) { dacsOptimize = 0; }
711 bool compileOnly = false;
713 for (usize idx = 1; idx < args.length; ++idx) {
714 bool remove = true;
715 if (args[idx] == "--dump-code") FuncPool.dumpCode = true;
716 else if (args[idx] == "--dump-code-size") FuncPool.dumpCodeSize = true;
717 else if (args[idx] == "--dump-semantic") dacsDumpSemantic = true;
718 else if (args[idx] == "--dump-all") { FuncPool.dumpCode = true; FuncPool.dumpCodeSize = true; dacsDumpSemantic = true; }
719 else if (args[idx] == "--compile") compileOnly = true;
720 else if (args[idx] == "--compile-only") compileOnly = true;
721 else if (args[idx] == "--messages") ++dacsMessages;
722 else if (args[idx] == "--no-copro") dacsOptimizeNoCoPro = true;
723 else if (args[idx] == "--no-deadass") dacsOptimizeNoDeadAss = true;
724 else if (args[idx] == "--no-purekill") dacsOptimizeNoPureKill = true;
725 else if (args[idx].length > 2 && args[idx][0..2] == "-O") {
726 import std.conv : to;
727 ubyte olevel = to!ubyte(args[idx][2..$]);
728 dacsOptimize = olevel;
730 else remove = false;
731 if (remove) {
732 foreach (immutable c; idx+1..args.length) args.ptr[c-1] = args.ptr[c];
733 args.length -= 1;
734 --idx; //hack
738 static void setDP () {
739 version(rdmd) {
740 setDataPath("data");
741 } else {
742 import std.file : thisExePath;
743 import std.path : dirName;
744 setDataPath(thisExePath.dirName);
746 addPK3(getDataPath~"base.pk3"); loadWadScripts();
747 //addWad("/home/ketmar/k8prj/doom2d-tl/data/doom2d.wad"); loadWadScripts();
748 //addWad("/home/ketmar/k8prj/doom2d-tl/data/meat.wad"); loadWadScripts();
749 //addWad("/home/ketmar/k8prj/doom2d-tl/data/megadm.wad"); loadWadScripts();
750 //addWad("/home/ketmar/k8prj/doom2d-tl/data/megadm1.wad"); loadWadScripts();
751 //addWad("/home/ketmar/k8prj/doom2d-tl/data/superdm.wad"); loadWadScripts();
752 //addWad("/home/ketmar/k8prj/doom2d-tl/data/zadoomka.wad"); loadWadScripts();
753 scriptLoadingComplete();
756 setDP();
758 if (compileOnly) return;
760 try {
761 registerAPI();
762 loadPalette();
764 setOpenGLContextVersion(3, 2); // up to GLSL 150
765 //openGLContextCompatible = false;
767 map = new LevelMap("maps/map01.d2m");
769 //mapOfsX = 8*26;
770 //mapOfsY = 8*56;
771 map.getThingPos(1/*ThingId.Player1*/, &mapOfsX, &mapOfsY);
772 // fix viewport
773 mapOfsX = (mapOfsX*2)-vlWidth/2;
774 if (mapOfsX+vlWidth > map.width*16) mapOfsX = map.width*16-vlWidth;
775 if (mapOfsX < 0) mapOfsX = 0;
776 mapOfsY = (mapOfsY*2)-vlHeight/2;
777 if (mapOfsY+vlHeight > map.height*16) mapOfsY = map.height*16-vlHeight;
778 if (mapOfsY < 0) mapOfsY = 0;
779 scale = 2;
781 sdwindow = new SimpleWindow(vlWidth, vlHeight, "D2D", OpenGlOptions.yes, Resizablity.fixedSize);
783 sdwindow.visibleForTheFirstTime = delegate () {
784 sdwindow.setAsCurrentOpenGlContext(); // make this window active
785 glbindLoadFunctions();
788 import core.stdc.stdio;
789 printf("GL version: %s\n", glGetString(GL_VERSION));
790 GLint l, h;
791 glGetIntegerv(GL_MAJOR_VERSION, &h);
792 glGetIntegerv(GL_MINOR_VERSION, &l);
793 printf("version: %d.%d\n", h, l);
794 printf("shader version: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
795 GLint svcount;
796 glGetIntegerv(GL_NUM_SHADING_LANGUAGE_VERSIONS, &svcount);
797 if (svcount > 0) {
798 printf("%d shader versions supported:\n", svcount);
799 foreach (GLuint n; 0..svcount) printf(" %d: %s\n", n, glGetStringi(GL_SHADING_LANGUAGE_VERSION, n));
802 GLint ecount;
803 glGetIntegerv(GL_NUM_EXTENSIONS, &ecount);
804 if (ecount > 0) {
805 printf("%d extensions supported:\n", ecount);
806 foreach (GLuint n; 0..ecount) printf(" %d: %s\n", n, glGetStringi(GL_EXTENSIONS, n));
811 // check if we have sufficient shader version here
813 bool found = false;
814 GLint svcount;
815 glGetIntegerv(GL_NUM_SHADING_LANGUAGE_VERSIONS, &svcount);
816 if (svcount > 0) {
817 foreach (GLuint n; 0..svcount) {
818 import core.stdc.string : strncmp;
819 auto v = glGetStringi(GL_SHADING_LANGUAGE_VERSION, n);
820 if (v is null) continue;
821 if (strncmp(v, "120", 3) != 0) continue;
822 if (v[3] > ' ') continue;
823 found = true;
824 break;
827 if (!found) assert(0, "can't find OpenGL GLSL 120");
829 auto adr = glGetProcAddress("glTexParameterf");
830 if (adr is null) assert(0);
833 version(use_vsync) {
834 sdwindow.vsync = true;
835 } else {
836 sdwindow.vsync = false;
838 //sdwindow.useGLFinish = false;
839 initOpenGL();
840 //sdwindow.redrawOpenGlScene();
841 if (!sdwindow.releaseCurrentOpenGlContext()) { import core.stdc.stdio; printf("can't release OpenGL context(1)\n"); }
842 if (!renderTid) {
843 renderTid = new Thread(&renderThread);
844 renderTid.start();
848 //sdwindow.redrawOpenGlScene = delegate () { renderScene(); };
850 enum MSecsPerFrame = 1000/30; /* 30 is FPS */
852 uint[8] frameTimes = 1000;
853 enum { Left, Right, Up, Down }
854 bool[4] pressed = false;
855 bool altMove = false;
857 sdwindow.eventLoop(MSecsPerFrame,
858 delegate () {
859 if (sdwindow.closed) return;
860 if (pressed[Left]) mapOfsX -= 8;
861 if (pressed[Right]) mapOfsX += 8;
862 if (pressed[Up]) mapOfsY -= 8;
863 if (pressed[Down]) mapOfsY += 8;
864 import std.math : cos, sin;
865 __gshared float itime = 0.0;
866 itime += 0.02;
867 if (movement) {
868 mapOfsX = cast(int)(800.0/2.0+cos(itime)*220.0);
869 mapOfsY = cast(int)(800.0/2.0+120.0+sin(itime)*160.0);
871 if (scale == 1) mapOfsX = mapOfsY = 0;
872 //sdwindow.redrawOpenGlSceneNow();
874 delegate (KeyEvent event) {
875 if (sdwindow.closed) return;
876 if (event.pressed && event.key == Key.Escape) { closeWindow(); return; }
877 switch (event.key) {
878 case Key.X: if (event.pressed) altMove = !altMove; break;
879 case Key.Left: if (altMove) pressed[Left] = event.pressed; break;
880 case Key.Right: if (altMove) pressed[Right] = event.pressed; break;
881 case Key.Up: if (altMove) pressed[Up] = event.pressed; break;
882 case Key.Down: if (altMove) pressed[Down] = event.pressed; break;
883 default:
885 if (!altMove) {
886 switch (event.key) {
887 case Key.Left: case Key.Pad4: plrKeyUpDown(0, PLK_LEFT, event.pressed); break;
888 case Key.Right: case Key.Pad6: plrKeyUpDown(0, PLK_RIGHT, event.pressed); break;
889 case Key.Up: case Key.Pad8: plrKeyUpDown(0, PLK_UP, event.pressed); break;
890 case Key.Down: case Key.Pad2: plrKeyUpDown(0, PLK_DOWN, event.pressed); break;
891 case Key.Alt: plrKeyUpDown(0, PLK_JUMP, event.pressed); break;
892 case Key.Ctrl: plrKeyUpDown(0, PLK_FIRE, event.pressed); break;
893 case Key.Shift: plrKeyUpDown(0, PLK_USE, event.pressed); break;
894 default:
898 delegate (MouseEvent event) {
899 lightX = event.x/scale;
900 lightY = event.y/scale;
901 lightX += mapOfsX/scale;
902 lightY += mapOfsY/scale;
904 delegate (dchar ch) {
905 if (ch == 'q') { closeWindow(); return; }
906 if (ch == 's') scanlines = !scanlines;
907 if (ch == '1') scale = 1;
908 if (ch == '2') scale = 2;
909 if (ch == 'i') {
910 frameInterpolation = !frameInterpolation;
911 if (frameInterpolation) addMessage("Interpolation: ON"); else addMessage("Interpolation: OFF");
913 if (ch == 'l') {
914 doLighting = !doLighting;
915 if (doLighting) addMessage("Lighting: ON"); else addMessage("Lighting: OFF");
917 if (ch == ' ') movement = !movement;
920 } catch (Exception e) {
921 import std.stdio : stderr;
922 stderr.writeln("FUUUUUUUUUUUU\n", e.toString);
924 closeWindow();
925 flushGui();