1 // Hyperbolic Rogue -- shaders
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file shaders.cpp
5  *  \brief shaders
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 EX ld levellines;
12 EX bool disable_texture;
13 
14 #if CAP_GL
15 #if HDR
16 constexpr flagtype GF_TEXTURE  = 1;
17 constexpr flagtype GF_VARCOLOR = 2;
18 constexpr flagtype GF_LIGHTFOG = 4;
19 constexpr flagtype GF_LEVELS   = 8;
20 constexpr flagtype GF_TEXTURE_SHADED  = 16;
21 
22 constexpr flagtype GF_which    = 31;
23 
24 constexpr flagtype SF_PERS3        = 256;
25 constexpr flagtype SF_BAND         = 512;
26 constexpr flagtype SF_USE_ALPHA    = 1024;
27 constexpr flagtype SF_DIRECT       = 2048;
28 constexpr flagtype SF_PIXELS       = 4096;
29 constexpr flagtype SF_HALFPLANE    = 8192;
30 constexpr flagtype SF_ORIENT       = 16384;
31 constexpr flagtype SF_BOX          = 32768;
32 constexpr flagtype SF_ZFOG         = 65536;
33 constexpr flagtype SF_ODSBOX       = (1<<17);
34 #endif
35 
36 EX bool solv_all;
37 
38 #if HDR
39 /* standard attribute bindings */
40 /* taken from: https://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/attributes.php */
41 constexpr int aPosition = 0;
42 constexpr int aColor = 3;
43 constexpr int aTexture = 8;
44 
45 /* texture bindings */
46 constexpr int INVERSE_EXP_BINDING = 2;
47 constexpr int AIR_BINDING = 4;
48 #endif
49 
50 EX map<string, shared_ptr<glhr::GLprogram>> compiled_programs;
51 
52 EX map<unsigned, shared_ptr<glhr::GLprogram>> matched_programs;
53 
model_orientation_gl()54 glhr::glmatrix model_orientation_gl() {
55   glhr::glmatrix s = glhr::id;
56   for(int a=0; a<GDIM; a++)
57     models::apply_orientation(s[a][1], s[a][0]);
58   if(GDIM == 3) for(int a=0; a<GDIM; a++)
59     models::apply_orientation_yz(s[a][2], s[a][1]);
60   return s;
61   }
62 
reset_all_shaders()63 EX void reset_all_shaders() {
64   ray::reset_raycaster();
65   compiled_programs.clear();
66   matched_programs.clear();
67   }
68 
panini_shader()69 EX string panini_shader() {
70   return
71     "t.w += 1.; t *= 2. / t.w; t.w -= 1.;\n"
72     "float s = t.z;\n"
73     "float l = length(t.xyz);\n"
74     "t /= max(length(t.xz), 1e-2);\n"
75     "t.z += " + glhr::to_glsl(panini_alpha) + ";\n"
76     "t *= l;\n"
77     "t.w = 1.;\n";
78   }
79 
stereo_shader()80 EX string stereo_shader() {
81   return
82     "t.w += 1.; t *= 2. / t.w; t.w -= 1.;\n"
83     "float s = t.z;\n"
84     "float l = length(t.xyz);\n"
85     "t /= max(l, 1e-2);\n"
86     "t.z += " + glhr::to_glsl(panini_alpha) + ";\n"
87     "t *= l;\n"
88     "t.w = 1.;\n";
89   }
90 
write_shader(flagtype shader_flags)91 shared_ptr<glhr::GLprogram> write_shader(flagtype shader_flags) {
92   string varying, vsh, fsh, vmain = "void main() {\n", fmain = "void main() {\n";
93 
94   vsh += "attribute mediump vec4 aPosition;\n";
95   varying += "varying mediump vec4 vColor;\n";
96 
97   fmain += "gl_FragColor = vColor;\n";
98 
99   bool have_texture = false;
100 
101   if(shader_flags & GF_TEXTURE_SHADED) {
102     vsh += "attribute mediump vec3 aTexture;\n";
103     varying += "varying mediump vec3 vTexCoord;\n";
104     fsh += "uniform mediump sampler2D tTexture;\n";
105     vmain += "vTexCoord = aTexture;\n";
106     fmain += "gl_FragColor *= texture2D(tTexture, vTexCoord.xy);\n";
107     fmain += "gl_FragColor.rgb *= vTexCoord.z;\n";
108     }
109   else if(shader_flags & GF_TEXTURE) {
110     vsh += "attribute mediump vec2 aTexture;\n";
111     varying += "varying mediump vec2 vTexCoord;\n";
112     fsh += "uniform mediump sampler2D tTexture;\n";
113     vmain += "vTexCoord = aTexture;\n",
114     have_texture = true;
115     }
116   if(shader_flags & GF_LEVELS) {
117     fsh += "uniform mediump float uLevelLines;\n";
118     varying += "varying mediump vec4 vPos;\n";
119     fmain += "gl_FragColor.rgb *= 0.5 + 0.5 * cos(vPos.z/vPos.w * uLevelLines * 2. * PI);\n";
120     }
121 
122   if(shader_flags & GF_VARCOLOR) {
123     vsh += "attribute mediump vec4 aColor;\n";
124     vmain += "vColor = aColor;\n";
125     }
126   else {
127     vmain += "vColor = uColor;\n";
128     vsh += "uniform mediump vec4 uColor;\n";
129     }
130 
131   bool have_vfogs = false, have_vfogcolor = false;
132 
133   if(shader_flags & GF_LIGHTFOG) {
134     vsh += "uniform mediump float uFog;\n";
135     vmain += "vFogs = clamp(1.0 + aPosition.z * uFog, 0.0, 1.0);\n";
136     have_vfogs = true;
137     }
138 
139   string coordinator;
140   string distfun;
141   bool treset = false;
142 
143   bool dim2 = GDIM == 2;
144   bool dim3 = GDIM == 3;
145 
146   bool skip_t = false;
147 
148   bool azi_hyperbolic = false;
149 
150   if(vid.stereo_mode == sODS) {
151     shader_flags |= SF_DIRECT | SF_ODSBOX;
152     vmain += "// this is ODS shader\n";
153     distfun = "aPosition.z";
154     }
155   else if(pmodel == mdPixel) {
156     vmain += "mediump vec4 pos = aPosition; pos[3] = 1.0;\n";
157     vmain += "pos = uMV * pos;\n";
158     if(shader_flags & GF_LEVELS) vmain += "vPos = pos;\n";
159     vmain += "gl_Position = uP * pos;\n";
160     skip_t = true;
161     shader_flags |= SF_PIXELS | SF_DIRECT;
162     }
163   else if(pmodel == mdManual) {
164     vmain += "mediump vec4 pos = uMV * aPosition;\n";
165     if(shader_flags & GF_LEVELS)
166       vmain += "vPos = pos;\n";
167     vmain += "gl_Position = uP * pos;\n";
168     skip_t = true;
169     shader_flags |= SF_DIRECT;
170     }
171   else if(!vid.consider_shader_projection) {
172     shader_flags |= SF_PIXELS;
173     }
174   else if(among(pmodel, mdDisk, mdBall) && GDIM == 2 && vrhr::rendering() && !sphere) {
175     shader_flags |= SF_DIRECT | SF_BOX;
176     vsh += "uniform mediump float uAlpha, uDepth, uDepthScaling, uCamera;";
177 
178     if(hyperbolic) coordinator +=
179       "float zlev = sqrt(t.z*t.z-t.x*t.x-t.y*t.y);\n"
180       "float zl = uDepth - uDepthScaling * (uDepth - atanh(tanh(uDepth)/zlev));\n"
181       "float dd = sqrt(t.x*t.x+t.y*t.y);\n"
182       "float d  = acosh(t.z/zlev);\n"
183       "float uz = uAlpha + cosh(zl) * cosh(d) * cosh(uCamera) + sinh(zl) * sinh(uCamera);\n"
184       "float ux = cosh(zl) * sinh(d) / uz;\n"
185       "t.xy = ux * t.xy / dd;\n"
186       "t.z = (sinh(zl) * cosh(uCamera) + sinh(uCamera) * cosh(zl) * cosh(d)) / uz;\n"
187       ;
188 
189     else if(euclid) coordinator +=
190       "t.z = uDepth * (1. - (t.z - 1.) * uDepthScaling) + uAlpha + uCamera;\n";
191 
192     else if(sphere) coordinator +=
193       "float zlev = sqrt(t.z*t.z+t.x*t.x+t.y*t.y);\n"
194       "float zl = uDepth - uDepthScaling * (uDepth - atan(tan(uDepth)/zlev));\n"
195       "float dd = sqrt(t.x*t.x+t.y*t.y);\n"
196       "float d  = acos(t.z/zlev);\n"
197       "float uz = uAlpha + cos(zl) * cos(d) * cos(uCamera) + sin(zl) * sin(uCamera);\n"
198       "float ux = cos(zl) * sin(d) / uz;\n"
199       "t.xy = ux * t.xy / dd;\n"
200       "t.z = (sin(uCamera) * cos(zl) * cos(d) - sin(zl) * cos(uCamera)) / uz;\n"
201       ;
202     }
203   else if(pmodel == mdDisk && MDIM == 3 && !spherespecial && !prod) {
204     shader_flags |= SF_DIRECT;
205     }
206   else if(glhr::noshaders) {
207     shader_flags |= SF_PIXELS;
208     }
209   else if(pmodel == mdDisk && GDIM == 3 && !spherespecial && !nonisotropic && !prod) {
210     coordinator += "t /= (t[3] + uAlpha);\n";
211     vsh += "uniform mediump float uAlpha;";
212     shader_flags |= SF_DIRECT | SF_BOX | SF_ZFOG;
213     treset = true;
214     }
215   else if(pmodel == mdBand && hyperbolic) {
216     shader_flags |= SF_BAND | SF_ORIENT | SF_BOX | SF_DIRECT;
217     coordinator += "t = uPP * t;", vsh += "uniform mediump mat4 uPP;";
218     if(dim2) coordinator += "mediump float zlev = zlevel(t); t /= zlev;\n";
219     if(dim3) coordinator += "mediump float r = sqrt(t.y*t.y+t.z*t.z); float ty = asinh(r);\n";
220     if(dim2) coordinator += "mediump float ty = asinh(t.y);\n";
221     coordinator += "mediump float tx = asinh(t.x / cosh(ty)); ty = 2.0 * atan(tanh(ty/2.0));\n";
222     if(dim2) coordinator += "t[0] = tx; t[1] = ty; t[2] = 1.0; t[3] = 1.0;\n";
223     if(dim3) coordinator += "t[0] = tx; t[1] = ty*t.y/r; t[2] = ty*t.z/r; t[3] = 1.0;\n";
224     if(dim3) shader_flags |= SF_ZFOG;
225     }
226   else if(pmodel == mdHalfplane && hyperbolic) {
227     shader_flags |= SF_HALFPLANE | SF_ORIENT | SF_BOX | SF_DIRECT;
228     if(dim2) shader_flags |= SF_USE_ALPHA;
229     coordinator += "t = uPP * t;", vsh += "uniform mediump mat4 uPP;";
230     if(dim2) coordinator +=
231       "mediump float zlev = zlevel(t); t /= zlev;\n"
232       "t.xy /= t.z; t.y += 1.0;\n"
233       "mediump float rads = dot(t.xy, t.xy);\n"
234       "t.xy /= -rads; t.z = 1.0; t[3] = 1.0;\n";
235     if(dim3) coordinator +=
236       "t.xyz /= (t.w + 1.0); t.y += 1.0;\n"
237       "mediump float rads = dot(t.xyz, t.xyz);\n"
238       "t.xyz /= -rads; t[3] = 1.0;\n";
239     if(dim3) shader_flags |= SF_ZFOG;
240     }
241   else if(pmodel == mdGeodesic) {
242     shader_flags |= SF_PERS3 | SF_DIRECT;
243     coordinator += "t = inverse_exp(t);\n";
244     if(sn::in()) {
245       coordinator +=
246         "mediump float d = dot(t.xyz, t.xyz);\n"
247         "mediump float hz = (1.+d) / (1.-d);\n"
248         "mediump float ad = acosh(hz);\n"
249         "mediump float m = d == 0. ? 0. : d >= 1. ? 1.e4 : (hz+1.) * ad / sinh(ad);\n";
250       #if CAP_VR
251       if(vrhr::rendering_eye())
252         coordinator += "t.xyz *= ad/d;\n";
253       else
254       #endif
255       coordinator +=
256         "t.xyz *= m;\n";
257       distfun = "ad";
258       }
259     else
260       distfun = "length(t.xyz)";
261     switch(cgclass) {
262       #if CAP_SOLV
263       case gcSolNIH:
264         switch(sn::geom()) {
265           case gSol:
266             if(solv_all) {
267               vsh += "\n#define SOLV_ALL\n";
268               }
269             vsh += sn::shader_symsol;
270             break;
271           case gNIH:
272             vsh += sn::shader_nsym;
273             break;
274           case gSolN:
275             vsh += sn::shader_nsymsol;
276             break;
277           default:
278             println(hlog, "error: unknown sn geometry");
279           }
280         treset = true;
281         break;
282       #endif
283       case gcNil:
284         vsh += nilv::nilshader;
285         break;
286       case gcSL2:
287         vsh += slr::slshader;
288         break;
289       default:
290         println(hlog, "error: unknown geometry in geodesic");
291         break;
292       }
293     }
294   else if(in_h2xe() && pmodel == mdPerspective) {
295     shader_flags |= SF_PERS3 | SF_DIRECT;
296     coordinator +=
297       "mediump float z = log(t[2] * t[2] - t[0] * t[0] - t[1] * t[1]) / 2.;\n"
298       "mediump float r = length(t.xy);\n"
299       "mediump float t2 = t[2] / exp(z);\n"
300       "mediump float d = t2 >= 1. ? acosh(t2) : 0.;\n"
301       "if(r != 0.) r = d / r;\n"
302       "t.xy *= r; t.z = z;\n";
303     distfun = "sqrt(z*z+d*d)";
304     treset = true;
305     }
306   else if(in_e2xe() && pmodel == mdPerspective) {
307     shader_flags |= SF_PERS3 | SF_DIRECT;
308     coordinator +=
309       "t.xy /= t.z;\n"
310       "t.z = log(t.z);\n";
311     distfun = "length(t.xyz)";
312     treset = true;
313     }
314   else if(in_s2xe() && pmodel == mdPerspective) {
315     shader_flags |= SF_PERS3 | SF_DIRECT;
316     distfun = "length(t.xyz)", treset = true;
317     }
318   else if(pmodel == mdPerspective) {
319     shader_flags |= SF_PERS3 | SF_DIRECT;
320     #if CAP_VR
321     if(vrhr::rendering() && hyperbolic && vrhr::eyes != vrhr::eEyes::truesim) {
322       azi_hyperbolic = true;
323       coordinator += "mediump vec4 orig_t = t;\n";
324       coordinator +=
325         "t = t * acosh(t[3]) / length(t.xyz);\n"
326         "t[3] = 1.;\n";
327       distfun = "length(t.xyz)";
328       }
329     else
330     #endif
331     if(hyperbolic)
332       distfun = "acosh(t[3])";
333     else if(euclid || nonisotropic || stretch::in() || (sphere && ray::in_use))
334       distfun = "length(t.xyz)", treset = true;
335     else {
336       if(spherephase & 4) coordinator += "t = -t;\n";
337       switch(spherephase & 3) {
338         case 0: distfun = "(2. * PI - acos(-t[3]))"; coordinator += "t = -t;\n"; break;
339         case 1: distfun = "(2. * PI - acos(t[3]))"; coordinator += "t.xyz = -t.xyz;\n"; break;
340         case 2: distfun = "acos(-t[3])"; coordinator += "t.w = -t.w;\n"; break;
341         case 3: distfun = "acos(t[3])"; break;
342         }
343       }
344     }
345   else {
346     shader_flags |= SF_PIXELS;
347     if(dim3) shader_flags |= SF_ZFOG;
348     }
349 
350   #if CAP_VR
351   /* no z-fog in VR */
352   if((shader_flags & SF_ZFOG) && vrhr::rendering())
353     shader_flags &= ~SF_ZFOG;
354   #endif
355 
356   if(nil && pmodel == mdPerspective)  {
357     vsh += "uniform mediump float uRotCos, uRotSin, uRotNil;\n";
358     coordinator +=
359       "t.z += (uRotCos * t.x + uRotSin * t.y) * (uRotCos * t.y - uRotSin * t.x) * uRotNil / 2. - t.x * t.y / 2.;\n";
360     }
361 
362   if(!skip_t) {
363     vmain += "mediump vec4 t = uMV * aPosition;\n";
364     vmain += coordinator;
365     if(GDIM == 3 && WDIM == 2 && hyperbolic && context_fog && pmodel == mdPerspective) {
366       vsh +=
367         "uniform mediump mat4 uRadarTransform;\n"
368         "uniform mediump sampler2D tAirMap;\n"
369         "uniform mediump float uFog;\n"
370         "uniform mediump float uFogBase;\n"
371         "vec4 color_at(vec4 ending, float dist) {"
372         "    vec3 pt = ending.xyz * sinh(dist);\n"
373         "    pt.xy /= sqrt(pt.z*pt.z+1.);\n"
374         "    pt.xy /= 2. * (1. + sqrt(1.+pt.x*pt.x+pt.y*pt.y));\n"
375         "    pt.xy += vec2(.5, .5);\n"
376         "    return texture2D(tAirMap, pt.xy);\n"
377         "    }\n";
378 
379 
380       if(azi_hyperbolic) vmain +=
381         "vec4 ending = uRadarTransform * orig_t;\n";
382       else vmain +=
383         "vec4 ending = uRadarTransform * t;\n";
384 
385       have_vfogs = true; have_vfogcolor = true;
386 
387       vmain +=
388         "float len = acosh(ending.w);\n"
389         "float eulen = length(ending.xyz);\n"
390         "ending.xyz /= eulen;\n"
391         "ending.y *= -1.;\n"
392         "vec4 fog = vec4(1e-3,0,1e-3,1e-3);\n"
393         "vec4 last = vec4(0,0,0,0);\n"
394         "for(int i=0; i<50; i++) {\n"
395         "  vec4 px = color_at(ending, ((float(i) + .5) / 50.) * min(len, uFog));\n"
396         "  if(px.r < .9 || px.b < .9 || px.g > .1) last = px;\n"
397         "  fog += last;\n"
398         "  }\n"
399         "mediump float fogs = (uFogBase - len / uFog);\n"
400         "if(fogs < 0.) fogs = 0.;\n"
401         "vFogs = fogs; vFogColor = fog / fog.w;\n";
402       }
403     else if(distfun != "") {
404       have_vfogs = true;
405       vmain += "vFogs = (uFogBase - " + distfun + " / uFog);\n";
406       vsh +=
407         "uniform mediump float uFog;\n"
408         "uniform mediump float uFogBase;\n";
409       }
410     if(shader_flags & GF_LEVELS) vmain += "vPos = t;\n";
411     if(treset) vmain += "t[3] = 1.0;\n";
412 
413     if((shader_flags & SF_PERS3) && panini_alpha && !vrhr::rendering_eye()) {
414       vmain += "t = uPP * t;", vsh += "uniform mediump mat4 uPP;";
415       /* panini */
416       vmain += panini_shader();
417       shader_flags |= SF_ORIENT;
418       }
419     else if((shader_flags & SF_PERS3) && stereo_alpha && !vrhr::rendering_eye()) {
420       vmain += "t = uPP * t;", vsh += "uniform mediump mat4 uPP;";
421       vmain += stereo_shader();
422       }
423 
424     vmain += "gl_Position = uP * t;\n";
425     }
426 
427   if(shader_flags & SF_ZFOG) {
428     have_vfogs = true;
429     vmain +=
430       "vFogs = 0.5 - gl_Position.z / 2.0;\n";
431     }
432 
433   if(have_texture) {
434     fmain += "gl_FragColor *= texture2D(tTexture, vTexCoord);\n";
435     fmain += "if(gl_FragColor.a == 0.) discard;\n";
436     }
437 
438   if(have_vfogcolor) {
439     varying +=
440       "varying mediump vec4 vFogColor;\n"
441       "varying mediump float vFogs;\n";
442     fmain += "gl_FragColor.xyz = gl_FragColor.xyz * vFogs + vFogColor.xyz * (1.0-vFogs);\n";
443     }
444   else if(have_vfogs) {
445     varying +=
446       "uniform mediump vec4 uFogColor;\n"
447       "varying mediump float vFogs;\n";
448     fmain += "gl_FragColor.xyz = gl_FragColor.xyz * vFogs + uFogColor.xyz * (1.0-vFogs);\n";
449     }
450 
451   vsh +=
452     "uniform mediump mat4 uMV;\n"
453     "uniform mediump mat4 uP;\n";
454 
455   vmain += "}";
456   fmain += "}";
457 
458   fsh += varying;
459   fsh += fmain;
460   vsh += varying;
461   vsh += vmain;
462 
463   if(glhr::noshaders || !vid.usingGL) fsh = vsh = "";
464 
465   string both = fsh + "*" + vsh + "*" + llts(shader_flags);
466   if(compiled_programs.count(both))
467     return compiled_programs[both];
468   else {
469     auto res = make_shared<glhr::GLprogram>(vsh, fsh);
470     res->shader_flags = shader_flags;
471     return res;
472     }
473   }
474 
set_projection(int ed,ld shift)475 void display_data::set_projection(int ed, ld shift) {
476   flagtype shader_flags = current_display->next_shader_flags;
477   unsigned id;
478   id = geometry;
479   id <<= 6; id |= pmodel;
480   if(levellines && pmodel != mdPixel) {
481     shader_flags |= GF_LEVELS;
482     if(disable_texture) shader_flags &=~ GF_TEXTURE;
483     }
484   id <<= 6; id |= shader_flags;
485   id <<= 6; id |= spherephase;
486   id <<= 1; if(vid.consider_shader_projection) id |= 1;
487   #if CAP_VR
488   id <<= 3; id |= vrhr::state;
489   if(vrhr::rendering() && vrhr::eyes == vrhr::eEyes::truesim) id += 3;
490   #endif
491   id <<= 2; id |= (spherespecial & 3);
492   if(sol && solv_all) id |= 1;
493   if(in_h2xe()) id |= 1;
494   if(in_s2xe()) id |= 2;
495   if(WDIM == 2 && GDIM == 3 && hyperbolic && context_fog) id |= 1;
496   shared_ptr<glhr::GLprogram> selected;
497 
498   if(matched_programs.count(id)) selected = matched_programs[id];
499   else {
500     selected = write_shader(shader_flags);
501     matched_programs[id] = selected;
502     }
503 
504   if(glhr::current_glprogram != selected) full_enable(selected);
505 
506   shader_flags = selected->shader_flags;
507   auto cd = current_display;
508 
509   #if CAP_SOLV
510   if(selected->uPRECX != -1) {
511     auto &tab = sn::get_tabled();
512 
513     GLuint invexpid = tab.get_texture_id();
514 
515     glActiveTexture(GL_TEXTURE0 + INVERSE_EXP_BINDING);
516     glBindTexture(GL_TEXTURE_3D, invexpid);
517 
518     glActiveTexture(GL_TEXTURE0 + 0);
519 
520     glhr::set_solv_prec(tab.PRECX, tab.PRECY, tab.PRECZ);
521     }
522   #endif
523 
524   #if MAXMDIM >= 4
525   if(selected->tAirMap != -1 && airbuf) {
526     glActiveTexture(GL_TEXTURE0 + AIR_BINDING);
527     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
528     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
529     glBindTexture(GL_TEXTURE_2D, airbuf->renderedTexture);
530     glUniform1i(selected->tAirMap, AIR_BINDING);
531     glActiveTexture(GL_TEXTURE0 + 0);
532     glUniformMatrix4fv(selected->uRadarTransform, 1, 0, glhr::tmtogl_transpose3(radar_transform).as_array());
533     }
534   #endif
535 
536   if(selected->uIterations != -1) {
537     glhr::set_index_sl(0);
538     glhr::set_sv(stretch::not_squared());
539     glhr::set_sl_iterations(slr::steps);
540     }
541 
542   glhr::new_projection();
543 
544   #if CAP_VR
545   if(!vrhr::rendering_eye()) {
546   #else
547   if(true) {
548   #endif
549     if(ed && vid.stereo_mode == sLR) {
550       glhr::projection_multiply(glhr::translate(ed, 0, 0));
551       glhr::projection_multiply(glhr::scale(2, 1, 1));
552       }
553 
554     ld tx = (cd->xcenter-cd->xtop)*2./cd->xsize - 1;
555     ld ty = (cd->ycenter-cd->ytop)*2./cd->ysize - 1;
556     glhr::projection_multiply(glhr::translate(tx, -ty, 0));
557 
558     if(pmodel == mdManual) return;
559 
560     if(pconf.stretch != 1 && (shader_flags & SF_DIRECT) && pmodel != mdPixel) glhr::projection_multiply(glhr::scale(1, pconf.stretch, 1));
561 
562     if(vid.stereo_mode != sODS)
563       eyewidth_translate(ed);
564     }
565 
566   auto ortho = [&] (ld x, ld y) {
567     glhr::glmatrix M = glhr::ortho(x, y, 1);
568     if(shader_flags & SF_ZFOG) {
569       M[2][2] = 2 / (pconf.clip_max - pconf.clip_min);
570       M[3][2] = (pconf.clip_min + pconf.clip_max) / (pconf.clip_max - pconf.clip_min);
571       auto cols = glhr::acolor(darkena(backcolor, 0, 0xFF));
572       glUniform4f(selected->uFogColor, cols[0], cols[1], cols[2], cols[3]);
573       }
574     else M[2][2] /= 1000;
575     glhr::projection_multiply(M);
576     if(nisot::local_perspective_used() && (shader_flags & SF_BOX))
577       glhr::projection_multiply(glhr::tmtogl_transpose(NLP));
578     if(ed && vid.stereo_mode != sODS) {
579       glhr::glmatrix m = glhr::id;
580       m[2][0] -= ed;
581       glhr::projection_multiply(m);
582       }
583     glhr::id_modelview();
584     };
585 
586   bool u_alpha = false;
587 
588   glhr::glmatrix pp0 = glhr::id;
589 
590   auto use_mv = [&] {
591     #if CAP_VR
592     auto cd = current_display;
593     if(vrhr::rendering_eye()) {
594       glhr::projection_multiply(glhr::tmtogl_transpose(vrhr::hmd_mvp));
595       glhr::id_modelview();
596       }
597     else {
598       glhr::projection_multiply(glhr::frustum(cd->tanfov, cd->tanfov * cd->ysize / cd->xsize));
599       if(selected->uPP != -1) {
600         transmatrix swapz = Id;
601         swapz[2][2] = -1;
602         pp0 = glhr::tmtogl_transpose(swapz * vrhr::hmd_mv);
603         glhr::projection_multiply(glhr::tmtogl(swapz));
604         }
605       else
606         glhr::projection_multiply(glhr::tmtogl_transpose(vrhr::hmd_mv));
607       }
608     #endif
609     };
610 
611   if(shader_flags & SF_PIXELS) {
612     if(vrhr::rendering()) use_mv();
613     else ortho(cd->xsize/2, -cd->ysize/2);
614     }
615   else if(shader_flags & SF_BOX) {
616     if(vrhr::rendering()) use_mv();
617     else ortho(cd->xsize/current_display->radius/2, -cd->ysize/current_display->radius/2);
618     }
619   else if(shader_flags & SF_ODSBOX) {
620     ortho(M_PI, M_PI);
621     glhr::fog_max(1/sightranges[geometry], darkena(backcolor, 0, 0xFF));
622     }
623   else if(shader_flags & SF_PERS3) {
624     if(vrhr::rendering()) use_mv();
625     else {
626       glhr::projection_multiply(glhr::frustum(cd->tanfov, cd->tanfov * cd->ysize / cd->xsize));
627       glhr::projection_multiply(glhr::scale(1, -1, -1));
628       if(nisot::local_perspective_used()) {
629         if(prod) {
630           for(int i=0; i<3; i++) NLP[3][i] = NLP[i][3] = 0;
631           NLP[3][3] = 1;
632           }
633         if(!(shader_flags & SF_ORIENT))
634           glhr::projection_multiply(glhr::tmtogl_transpose(NLP));
635         }
636       if(ed) {
637         glhr::using_eyeshift = true;
638         glhr::eyeshift = glhr::tmtogl(xpush(vid.ipd * ed/2));
639         }
640       }
641     glhr::fog_max(1/sightranges[geometry], darkena(backcolor, 0, 0xFF));
642     }
643   else {
644     if(pconf.alpha > -1) {
645       // Because of the transformation from H3 to the Minkowski hyperboloid,
646       // points with negative Z can be generated in some 3D settings.
647       // This happens for points below the camera, but above the plane.
648       // These points should still be viewed, though, so we disable the
649       // depth clipping
650       glhr::projection_multiply(glhr::scale(1,1,0));
651       }
652     GLfloat sc = current_display->radius / (cd->ysize/2.);
653     glhr::projection_multiply(glhr::frustum(cd->xsize / cd->ysize, 1));
654     glhr::projection_multiply(glhr::scale(sc, -sc, -1));
655     u_alpha = true;
656     }
657 
658   if(selected->uRotNil != -1) {
659     glUniform1f(selected->uRotCos, models::ocos);
660     glUniform1f(selected->uRotSin, models::osin);
661     glUniform1f(selected->uRotNil, pconf.rotational_nil);
662     }
663 
664   if(selected->uPP != -1) {
665 
666     glhr::glmatrix pp = glhr::id;
667     if(get_shader_flags() & SF_USE_ALPHA)
668       pp[3][2] = GLfloat(pconf.alpha);
669 
670     pp = pp * pp0;
671 
672     if(nisot::local_perspective_used())
673       pp = glhr::tmtogl_transpose(NLP) * pp;
674 
675     if(get_shader_flags() & SF_ORIENT) {
676       if(GDIM == 3) for(int a=0; a<4; a++)
677         models::apply_orientation_yz(pp[a][1], pp[a][2]);
678       for(int a=0; a<4; a++)
679         models::apply_orientation(pp[a][0], pp[a][1]);
680       }
681 
682     glUniformMatrix4fv(selected->uPP, 1, 0, pp.as_array());
683     }
684 
685   if(selected->uAlpha != -1)
686     glhr::set_ualpha(pconf.alpha);
687 
688   if(selected->uDepth != -1)
689     glUniform1f(selected->uDepth, vid.depth);
690 
691   if(selected->uCamera != -1)
692     glUniform1f(selected->uCamera, vid.camera);
693 
694   if(selected->uDepthScaling != -1)
695     glUniform1f(selected->uDepthScaling, pconf.depth_scaling);
696 
697   if(selected->uLevelLines != -1) {
698     glUniform1f(selected->uLevelLines, levellines);
699     }
700 
701   if(selected->shader_flags & SF_ORIENT)
702     glhr::projection_multiply(model_orientation_gl());
703 
704   if(selected->shader_flags & SF_BAND)
705     glhr::projection_multiply(glhr::scale(2 / M_PI, 2 / M_PI, GDIM == 3 ? 2/M_PI : 1));
706 
707   if(selected->shader_flags & SF_BAND) {
708     glhr::projection_multiply(glhr::translate(shift, 0, 0));
709     }
710 
711   if(in_h2xe() || in_s2xe()) {
712     glhr::projection_multiply(glhr::translate(0, 0, shift));
713     }
714 
715   if(selected->shader_flags & SF_HALFPLANE) {
716     glhr::projection_multiply(glhr::translate(0, 1, 0));
717     glhr::projection_multiply(glhr::scale(-1, 1, 1));
718     glhr::projection_multiply(glhr::scale(pconf.halfplane_scale, pconf.halfplane_scale, GDIM == 3 ? pconf.halfplane_scale : 1));
719     glhr::projection_multiply(glhr::translate(0, 0.5, 0));
720     }
721 
722   if(pconf.camera_angle && pmodel != mdPixel) {
723     ld cam = pconf.camera_angle * degree;
724 
725     GLfloat cc = cos(cam);
726     GLfloat ss = sin(cam);
727 
728     GLfloat yzspin[16] = {
729       1, 0, 0, 0,
730       0, cc, ss, 0,
731       0, -ss, cc, 0,
732       0, 0, 0, 1
733       };
734 
735     glhr::projection_multiply(glhr::as_glmatrix(yzspin));
736     }
737 
738   if(u_alpha) {
739     glhr::projection_multiply(glhr::translate(0, 0, pconf.alpha));
740     if(ed) glhr::projection_multiply(glhr::translate(vid.ipd * ed/2, 0, 0));
741     }
742   }
743 
744 EX void add_if(string& shader, const string& seek, const string& function) {
745   if(shader.find(seek) != string::npos)
746     shader = function + shader;
747   }
748 
749 EX void add_fixed_functions(string& shader) {
750   /* from the most complex to the simplest */
751 
752   add_if(shader, "tanh", "mediump float tanh(mediump float x) { return sinh(x) / cosh(x); }\n");
753   add_if(shader, "sinh", "mediump float sinh(mediump float x) { return (exp(x) - exp(-x)) / 2.0; }\n");
754   add_if(shader, "cosh", "mediump float cosh(mediump float x) { return (exp(x) + exp(-x)) / 2.0; }\n");
755   add_if(shader, "asinh", "mediump float asinh(mediump float x) { return log(sqrt(x*x + 1.0) + x); }\n");
756   add_if(shader, "acosh", "mediump float acosh(mediump float x) { return log(sqrt(x*x - 1.0) + x); }\n");
757   add_if(shader, "atanh", "mediump float atanh(mediump float x) { return (log(1.+x)-log(1.-x))/2.; }\n");
758   add_if(shader, "zlevel", "mediump float zlevel(mediump vec4 h) { return (h[2] < 0.0 ? -1.0 : 1.0) * sqrt(h[2]*h[2] - h[0]*h[0] - h[1]*h[1]); }\n");
759   add_if(shader, "atan2", "mediump float atan2(mediump float y, mediump float x) {\n"
760     "if(x == 0.) return y > 0. ? PI/2. : -PI/2.;\n"
761     "if(x > 0.) return atan(y / x);\n"
762     "if(y >= 0.) return atan(y / x) + PI;\n"
763     "if(y < 0.) return atan(y / x) - PI;\n"
764     "}\n");
765 
766   add_if(shader, "PI", "#define PI 3.14159265358979324\n");
767   #ifndef GLES_ONLY
768   add_if(shader, "mediump", "#define mediump\n");
769   #endif
770   }
771 
772 EX flagtype get_shader_flags() {
773   if(!glhr::current_glprogram) return 0;
774   return glhr::current_glprogram->shader_flags;
775   }
776 
777 EX void glapplymatrix(const transmatrix& V) {
778   #if CAP_VR
779   transmatrix V3;
780   bool use_vr = vrhr::rendering();
781   if(use_vr) V3 = vrhr::hmd_pre * V;
782   const transmatrix& V2 = use_vr ? V3 : V;
783   #else
784   const transmatrix& V2 = V;
785   #endif
786   GLfloat mat[16];
787   int id = 0;
788 
789   if(MXDIM == 3) {
790     for(int y=0; y<3; y++) {
791       for(int x=0; x<3; x++) mat[id++] = V2[x][y];
792       mat[id++] = 0;
793       }
794     mat[12] = 0;
795     mat[13] = 0;
796     mat[14] = 0;
797     mat[15] = 1;
798     }
799   else {
800     for(int y=0; y<4; y++)
801       for(int x=0; x<4; x++) mat[id++] = V2[x][y];
802     }
803   glhr::set_modelview(glhr::as_glmatrix(mat));
804   }
805 
806 #endif
807 }
808