don't do vsync: slow shaders are somewhat faster this way
[stoyd.git] / xmain.d
blob4c8a493a1f9eb0bd31b963a35a11cd41a8c15596
1 // main driver
2 module xmain_mt/* is aliced*/;
4 private:
5 import core.atomic;
6 import core.thread;
7 import core.time;
9 import glbinds;
10 import glutils;
13 // ////////////////////////////////////////////////////////////////////////// //
14 //version = use_vsync;
17 // ////////////////////////////////////////////////////////////////////////// //
18 enum WindowTitle = "ShaderToy emulator";
21 // ////////////////////////////////////////////////////////////////////////// //
22 //#extension GL_ARB_compatibility : enable
23 enum ShaderPre = q{
24 #version 120
25 uniform vec3 iResolution; // viewport resolution (in pixels)
26 uniform vec4 iMouse; // mouse pixel coords
27 uniform float iGlobalTime; // shader playback time (in seconds)
28 uniform float iGlobalFrame; // ???
29 uniform float iTimeDelta; // how long last frame took to render, in seconds (TODO)
30 uniform float iFrame; // buffer shaders seems to have this
31 uniform vec3 iChannelResolution[4]; // channel resolution (in pixels)
32 uniform float iChannelTime[4]; // channel playback time (in sec)
33 uniform vec4 iDate; // (year, month, day, time in seconds)
34 //uniform float iGlobalDelta; // i don't know
35 //uniform float iSampleRate; // sound sample rate (i.e., 44100)
38 enum ShaderMain = q{
39 void main (void) {
40 vec2 fc = vec2(gl_FragCoord.x, gl_FragCoord.y);
41 mainImage(gl_FragColor, fc);
42 gl_FragColor.w = 1.0;
46 // ////////////////////////////////////////////////////////////////////////// //
47 import arsd.color;
48 import arsd.png;
51 __gshared SimpleWindow sdwindow;
54 public enum vlWidth = 800;
55 public enum vlHeight = 500;
56 public enum scale = 1;
58 public enum vlEffectiveWidth = vlWidth*scale;
59 public enum vlEffectiveHeight = vlHeight*scale;
62 // ////////////////////////////////////////////////////////////////////////// //
63 __gshared string basePath;
64 __gshared string shaderFile;
65 __gshared string shaderSource;
66 __gshared Texture[4] textures; // 4 texture samplers
67 __gshared TextureCube[4] texturesCube; // 4 cube texture samplers (can be null)
68 __gshared Texture texMain;
69 __gshared Shader shader;
70 __gshared Shader[4] shaderbufs; // a,b,c,d
71 __gshared int[4] bufmap = -1; // bufmap[texnum] == bufN (or -1)
72 __gshared FBO[4] buffbos;
73 __gshared GLuint listQuad;
76 // ////////////////////////////////////////////////////////////////////////// //
77 void initOpenGL () {
78 static auto loadShader (string sname, string src) {
79 string spre = ShaderPre~"\n";
80 // setup texture samplers
81 foreach (int idx; 0..4) {
82 import std.string : format;
83 if (texturesCube[idx] !is null) {
84 spre ~= "uniform samplerCube iChannel%s;\n".format(idx);
85 } else {
86 spre ~= "uniform sampler2D iChannel%s;\n".format(idx);
89 spre ~= "#line 0\n";
90 auto shader = new Shader(sname, spre~src~"\n"~ShaderMain);
91 shader.exec({
92 shader["iResolution"] = SVec3F(vlWidth, vlHeight, 0.0f);
93 foreach (int idx; 0..4) {
94 char[9] vname = "iChannel0";
95 vname[$-1] = cast(char)('0'+idx);
96 int bufidx = bufmap[idx];
97 if (bufidx < 0) bufidx = idx;
98 shader[vname[]] = cast(int)bufidx;
100 auto cri = shader.varId("iChannelResolution");
101 float[3][4] cres = void;
102 foreach (int idx; 0..4) {
103 if (texturesCube[idx]) {
104 cres[idx][0] = texturesCube[idx].width;
105 cres[idx][1] = texturesCube[idx].height;
106 } else {
107 int bufidx = bufmap[idx];
108 if (bufidx < 0) bufidx = idx;
109 cres[idx][0] = textures[bufidx].width;
110 cres[idx][1] = textures[bufidx].height;
112 cres[idx][2] = 0;
114 glUniform3fv(cri, 4, &cres[0][0]);
116 return shader;
119 glEnable(GL_TEXTURE_2D);
120 glDisable(GL_LIGHTING);
121 glDisable(GL_DITHER);
122 glDisable(GL_BLEND);
123 glDisable(GL_DEPTH_TEST);
125 // load textures
126 try {
127 import std.path;
128 import std.stdio : File;
129 int num = 0;
130 foreach (/*immutable*/ line; File(shaderFile.setExtension(".tex")).byLine) {
131 Texture tex;
132 bufmap[num] = -1;
133 if (line == "bufa" || line == "bufb" || line == "bufc" || line == "bufd") {
134 // render buffer
135 int bufidx = line[3]-'a';
136 bufmap[num] = bufidx;
137 tex = new Texture(vlWidth, vlHeight, true, Texture.Option.fp, Texture.Option.nearest, Texture.Option.clamp); // it's floating point, i guess
138 } else if (line.length > 4 && line[0..4] == "cube") {
139 // load cubemap texture
140 import std.string : format;
141 try {
142 string fn = basePath~"/textures/cube/%s_%%s.png".format(line);
143 auto texc = new TextureCube(fn);
144 texturesCube[num++] = texc;
145 if (num >= textures.length) break;
146 continue;
147 } catch (Exception e) {
148 import std.stdio;
149 writeln("can't load cube texture '", line, "'");
150 assert(0);
151 //tex = null;
153 } else {
154 import std.string : format;
155 try {
156 string fn = basePath~"/textures/tex%s.png".format(line);
157 tex = new Texture(fn);
158 } catch (Exception e) {
159 import std.stdio;
160 writeln("can't load texture '", line, "'");
161 tex = null;
164 textures[num++] = tex;
165 if (num >= textures.length) break;
167 } catch (Exception e) {}
168 //foreach (immutable num; 0..4) if (textures[num] is null) textures[num] = new Texture(512, 512);
169 foreach (immutable num; 0..4) {
170 if (textures[num] is null && texturesCube[num] is null) {
171 //textures[num] = new Texture(basePath~"/textures/tex00.png");
172 textures[num] = new Texture(vlWidth, vlHeight, true/*, Texture.Option.fp*/);
175 texMain = new Texture(vlWidth, vlHeight, true);
177 foreach (int idx; 0..4) {
178 glActiveTexture(GL_TEXTURE0+idx);
179 if (texturesCube[idx] !is null) {
180 glBindTexture(GL_TEXTURE_CUBE_MAP, texturesCube[idx].tid);
181 } else {
182 int bufidx = bufmap[idx];
183 if (bufidx < 0) bufidx = idx;
184 glBindTexture(GL_TEXTURE_2D, textures[bufidx].tid);
187 glActiveTexture(GL_TEXTURE4);
188 glBindTexture(GL_TEXTURE_2D, texMain.tid);
190 // create shader
191 shader = loadShader("shader", shaderSource);
193 // load buffer shaders, if necessary
194 foreach (int idx; 0..4) {
195 int bufidx = bufmap[idx];
196 if (bufidx < 0) continue;
197 if (shaderbufs[bufidx] is null) {
198 // load shader
199 import std.file : readText;
200 import std.string : format;
201 import std.path : dirName, setExtension;
202 string fname = shaderFile.setExtension("")~"_buf%c.frag".format(cast(char)('a'+bufidx));
203 { import std.stdio; writeln("reading buffer shader: '", fname, "'"); }
204 shaderbufs[bufidx] = loadShader("buf%c".format(cast(char)('a'+bufidx)), readText(fname));
205 // create fbo
206 buffbos[bufidx] = new FBO(textures[bufidx]);
210 // setup matrices
212 glMatrixMode(GL_PROJECTION);
213 glLoadIdentity();
214 glOrtho(0, vlWidth, vlHeight, 0, -1, 1);
216 orthoCamera(vlWidth, vlHeight);
218 glMatrixMode(GL_MODELVIEW);
219 glLoadIdentity();
221 // create display list for quad
223 enum x0 = 0;
224 enum y0 = 0;
225 enum x1 = vlWidth;
226 enum y1 = vlHeight;
227 listQuad = glGenLists(1);
228 glNewList(listQuad, GL_COMPILE);
229 glBegin(GL_QUADS);
230 glTexCoord2f(0.0f, 0.0f); glVertex2i(x0, y0); // top-left
231 glTexCoord2f(1.0f, 0.0f); glVertex2i(x1, y0); // top-right
232 glTexCoord2f(1.0f, 1.0f); glVertex2i(x1, y1); // bottom-right
233 glTexCoord2f(0.0f, 1.0f); glVertex2i(x0, y1); // bottom-left
234 glEnd();
235 glEndList();
237 //glDeleteLists(index, 1);
241 // ////////////////////////////////////////////////////////////////////////// //
242 //__gshared int prevmouseX = vlWidth/2, prevmouseY = vlHeight/2;
243 __gshared int mouseX = vlWidth/2, mouseY = vlHeight/2;
244 __gshared bool mBut0 = false, mBut1 = false;
245 __gshared float globalTime = 0.0f, frameTime = 0.0f;
246 __gshared float globalFrame = 0.0f;
247 __gshared bool paused = false;
248 shared int diedie = 0;
251 // ////////////////////////////////////////////////////////////////////////// //
252 void setShaderArgs (Shader shader) {
253 //shader["iMouse"] = SVec4F(mouseX, vlHeight-1-mouseY, cast(float)mouseX/cast(float)(vlWidth-1), cast(float)(vlHeight-1-mouseY)/cast(float)(vlHeight-1));
254 if (mBut0) {
255 shader["iMouse"] = SVec4F(mouseX, vlHeight-1-mouseY, mouseX, vlHeight-1-mouseY);
256 } else {
257 shader["iMouse"] = SVec4F(0.0f, 0.0f, 0.0f, 0.0f);
259 //prevmouseX = mouseX;
260 //prevmouseY = (vlHeight-1-mouseY);
261 shader["iGlobalTime"] = globalTime;
262 shader["iChannelTime"] = SVec4F(globalTime, globalTime, globalTime, globalTime);
263 shader["iGlobalFrame"] = globalFrame;
264 shader["iFrame"] = globalFrame;
265 shader["iTimeDelta"] = frameTime;
266 auto vid = shader.varId("iDate");
267 if (vid >= 0) {
268 import std.datetime;
269 auto now = Clock.currTime;
270 glUniform4f(vid,
271 now.year,
272 now.month,
273 now.day,
274 cast(float)now.hour*3600.0f+cast(float)now.minute*60.0f+cast(float)now.second+cast(float)now.fracSecs.total!"msecs"/1000.0f
280 void renderFrame (bool paused) {
281 // first update buffers, if any
282 if (!paused) {
283 foreach (int idx; 0..4) {
284 if (auto sd = shaderbufs[idx]) {
285 buffbos[idx].exec({
286 sd.exec({
287 setShaderArgs(sd);
288 glCallList(listQuad);
294 shader.exec({
295 setShaderArgs(shader);
296 glCallList(listQuad);
301 // ////////////////////////////////////////////////////////////////////////// //
302 void renderThread () {
303 bool oldpaused = paused;
304 MonoTime prevFrameStartTime = MonoTime.currTime;
305 version(use_vsync) {} else MonoTime ltt = MonoTime.currTime;
306 MonoTime lasttime = MonoTime.currTime;
307 MonoTime time;
308 enum MaxFPSFrames = 16;
309 float frtimes = 0.0f;
310 int framenum = 0;
311 int prevFPS = -1;
312 bool first = true;
313 for (;;) {
314 if (sdwindow.closed) break;
315 if (atomicLoad(diedie) > 0) break;
317 time = MonoTime.currTime;
318 version(use_vsync) {
319 } else {
320 // max 60 FPS; capped by vsync
321 //{ import core.stdc.stdio; printf(" spent only %d msecs\n", cast(int)((time-ltt).total!"msecs")); }
322 if (!first && (time-ltt).total!"msecs" < 16) {
323 //{ import core.stdc.stdio; printf(" spent only %d msecs\n", cast(int)((time-ltt).total!"msecs")); }
324 import core.sys.posix.signal : timespec;
325 import core.sys.posix.time : nanosleep;
326 timespec ts = void;
327 ts.tv_sec = 0;
328 ts.tv_nsec = (16-cast(int)((time-ltt).total!"msecs"))*1000*1000; // milli to nano
329 nanosleep(&ts, null); // idc how much time was passed
330 time = MonoTime.currTime;
332 ltt = time;
333 first = false;
336 auto pau = paused;
337 if (oldpaused != pau) {
338 lasttime = time;
339 prevFrameStartTime = time;
340 oldpaused = pau;
342 if (!pau) {
343 globalTime += cast(float)(time-lasttime).total!"msecs"/1000.0f;
344 lasttime = time;
345 globalFrame += 1.0;
346 debug { import core.stdc.stdio; printf("globalTime=%f\n", globalTime); }
348 frameTime = cast(float)(time-prevFrameStartTime).total!"msecs"/1000.0f;
349 prevFrameStartTime = time;
351 //{ import core.stdc.stdio; printf("frametime: %f\n", frameTime*1000.0f); }
352 //{ import core.stdc.stdio; printf("FPS: %d\n", cast(int)(1.0f/frameTime)); }
353 frtimes += frameTime;
354 if (++framenum >= MaxFPSFrames || frtimes >= 3.0f) {
355 import std.string : format;
356 int newFPS = cast(int)(cast(float)MaxFPSFrames/frtimes+0.5);
357 if (newFPS != prevFPS) {
358 sdwindow.title = "%s / FPS:%s".format(WindowTitle, newFPS);
359 prevFPS = newFPS;
361 framenum = 0;
362 frtimes = 0.0f;
365 //debug { import core.stdc.stdio; printf("XLockDisplay()\n"); }
366 //XLockDisplay(sdwindow.display);
367 debug { import core.stdc.stdio; printf("glXMakeCurrent()\n"); }
368 if (glXMakeCurrent(sdwindow.display, sdwindow.window, sdwindow.glc) == 0) {
369 { import core.stdc.stdio; printf(" FUUUU\n"); }
371 debug { import core.stdc.stdio; printf("renderFrame()\n"); }
372 renderFrame(pau);
373 debug { import core.stdc.stdio; printf("glXSwapBuffers()\n"); }
374 glXSwapBuffers(sdwindow.display, sdwindow.window);
375 debug { import core.stdc.stdio; printf("glFinish()\n"); }
376 glFinish();
377 // release context
378 debug { import core.stdc.stdio; printf("glXMakeCurrent(0)\n"); }
379 glXMakeCurrent(sdwindow.display, 0, null);
380 //debug { import core.stdc.stdio; printf("XUnlockDisplay()\n"); }
381 //XUnlockDisplay(sdwindow.display);
384 atomicStore(diedie, 2);
388 // ////////////////////////////////////////////////////////////////////////// //
389 void closeWindow () {
390 if (atomicLoad(diedie) != 2) {
391 atomicStore(diedie, 1);
392 while (atomicLoad(diedie) != 2) {}
394 if (!sdwindow.closed) {
395 flushGui();
396 sdwindow.close();
401 // ////////////////////////////////////////////////////////////////////////// //
402 void main (string[] args) {
403 if (args.length < 2) assert(0, "filename?");
404 if (XInitThreads() == 0) assert(0, "XMT fucked");
407 import std.algorithm : startsWith, endsWith;
408 import std.file : readText, thisExePath;
409 import std.path : dirName;
410 version(rdmd) {
411 basePath = "/home/ketmar/DUMMY-FUCK-MC/glsl_raymarching/d_glsl";
412 } else {
413 basePath = thisExePath.dirName;
415 shaderFile = args[1];
416 if (shaderFile.startsWith("shaders/")) shaderFile = shaderFile[8..$];
417 if (shaderFile.endsWith(".frag")) shaderFile = shaderFile[0..$-5];
418 shaderFile = basePath~"/shaders/"~shaderFile~".frag";
419 shaderSource = readText(shaderFile);
422 Thread renderTid;
424 sdwindow = new SimpleWindow(vlEffectiveWidth, vlEffectiveHeight, WindowTitle, OpenGlOptions.yes, Resizablity.fixedSize);
426 sdwindow.visibleForTheFirstTime = delegate () {
427 sdwindow.setAsCurrentOpenGlContext(); // make this window active
428 version(use_vsync) {
429 sdwindow.vsync = true;
430 } else {
431 sdwindow.vsync = false;
433 //sdwindow.useGLFinish = false;
434 initOpenGL();
436 //sdwindow.redrawOpenGlScene();
437 renderFrame(paused);
438 sdwindow.swapOpenGlBuffers();
439 glFinish();
440 glFlush();
441 // release context
442 if (glXMakeCurrent(sdwindow.display, 0, null) == 0) { import core.stdc.stdio; printf("can't release OpenGL context(0)\n"); }
443 glFinish();
444 glFlush();
448 sdwindow.eventLoop(100,
449 delegate () {
450 if (sdwindow.closed) return;
451 if (!renderTid) {
452 if (glXMakeCurrent(sdwindow.display, 0, null) == 0) { import core.stdc.stdio; printf("can't release OpenGL context(1)\n"); }
453 renderTid = new Thread(&renderThread);
454 renderTid.start();
456 //sdwindow.redrawOpenGlSceneNow();
458 delegate (KeyEvent event) {
459 if (sdwindow.closed) return;
460 if (event.pressed && event.key == Key.Escape) closeWindow();
462 delegate (MouseEvent event) {
463 if (sdwindow.closed) return;
464 mouseX = event.x/scale;
465 mouseY = event.y/scale;
466 if (event.type == MouseEventType.buttonPressed) {
467 if (event.button == MouseButton.left) mBut0 = true;
468 if (event.button == MouseButton.right) mBut1 = true;
469 } else if (event.type == MouseEventType.buttonReleased) {
470 if (event.button == MouseButton.left) mBut0 = false;
471 if (event.button == MouseButton.right) mBut1 = false;
474 delegate (dchar ch) {
475 if (ch == 'q') closeWindow();
476 if (ch == '0') { mouseX = mouseY = 0; }
477 if (ch == ' ') { paused = !paused; }
480 closeWindow();
481 flushGui();