1 // Hyperbolic Rogue -- basic graphics
2 // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file basegraph.cpp
5  *  \brief This file implements the basic graphical routines
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 #if HDR
12 /** configuration of the current view */
13 struct display_data {
14   /** The cell which is currently in the center. */
15   cell *precise_center;
16   /** The current rotation, relative to precise_center. */
17   transmatrix view_matrix;
18   /** Camera rotation, used in nonisotropic geometries. */
19   transmatrix local_perspective;
20   /** The view relative to the player character. */
21   shiftmatrix player_matrix;
22   /** On-screen coordinates for all the visible cells. */
23   map<cell*, shiftmatrix> cellmatrices, old_cellmatrices;
24   /** Position of the current map view, relative to the screen (0 to 1). */
25   ld xmin, ymin, xmax, ymax;
26   /** Position of the current map view, in pixels. */
27   ld xtop, ytop, xsize, ysize;
display_datahr::display_data28   display_data() { xmin = ymin = 0; xmax = ymax = 1; }
29 
30   /** Center of the current map view, in pixels. */
31   int xcenter, ycenter;
32   ld radius;
33   int scrsize;
34   bool sidescreen;
35 
36   ld tanfov;
37   flagtype next_shader_flags;
38 
39   ld eyewidth();
40   bool stereo_active();
41   bool in_anaglyph();
42 
43   void set_viewport(int ed);
44   void set_projection(int ed, ld shift);
45   void set_mask(int ed);
46 
47   void set_all(int ed, ld shift);
48   /** Which copy of the player cell? */
49   transmatrix which_copy;
50   /** On-screen coordinates for all the visible cells. */
51   map<cell*, vector<shiftmatrix>> all_drawn_copies;
52   };
53 
54 #define View (::hr::current_display->view_matrix)
55 #define cwtV (::hr::current_display->player_matrix)
56 #define centerover (::hr::current_display->precise_center)
57 #define gmatrix (::hr::current_display->cellmatrices)
58 #define gmatrix0 (::hr::current_display->old_cellmatrices)
59 #define NLP (::hr::current_display->local_perspective)
60 
61 #endif
62 
63 EX display_data default_display;
64 EX display_data *current_display = &default_display;
65 
66 /** Color of the background. */
67 EX unsigned backcolor = 0;
68 EX unsigned bordcolor = 0;
69 EX unsigned forecolor = 0xFFFFFF;
70 
utfsize(char c)71 int utfsize(char c) {
72   unsigned char cu = c;
73   if(cu < 128) return 1;
74   if(cu < 224) return 2;
75   if(cu < 0xF0) return 3;
76   return 4;
77   }
78 
get_sightrange()79 EX int get_sightrange() { return getDistLimit() + sightrange_bonus; }
80 
get_sightrange_ambush()81 EX int get_sightrange_ambush() {
82   #if CAP_COMPLEX2
83   return max(get_sightrange(), ambush::distance);
84   #else
85   return get_sightrange();
86   #endif
87   }
88 
in_anaglyph()89 bool display_data::in_anaglyph() { return vid.stereo_mode == sAnaglyph; }
stereo_active()90 bool display_data::stereo_active() { return vid.stereo_mode != sOFF; }
91 
eyewidth()92 ld display_data::eyewidth() {
93   switch(vid.stereo_mode) {
94     case sAnaglyph:
95       return vid.anaglyph_eyewidth;
96     case sLR:
97       return vid.lr_eyewidth;
98     default:
99       return 0;
100     }
101   }
102 
eqs(const char * x,const char * y)103 bool eqs(const char* x, const char* y) {
104   return *y? *x==*y?eqs(x+1,y+1):false:true;
105   }
106 
getnext(const char * s,int & i)107 EX int getnext(const char* s, int& i) {
108 
109   int siz = utfsize(s[i]);
110 // if(fontdeb) printf("s=%s i=%d siz=%d\n", s, i, siz);
111   if(siz == 1) return s[i++];
112   for(int k=0; k<NUMEXTRA; k++)
113     if(eqs(s+i, natchars[k])) {
114       i += siz; return 128+k;
115       }
116 
117 #ifdef REPLACE_LETTERS
118   for(int j=0; j<isize(dialog::latin_letters_l); j++)
119     if(s[i] == dialog::foreign_letters[2*j] && s[i+1] == dialog::foreign_letters[2*j+1]) {
120       i += 2;
121       return int(dialog::latin_letters_l[j]);
122       }
123 #endif
124 
125   printf("Unknown character in: '%s' at position %d\n", s, i);
126   i += siz; return '?';
127   }
128 
129 #if CAP_SDLTTF
130 const int max_font_size = 288;
131 TTF_Font* font[max_font_size+1];
132 
fix_font_size(int & size)133 void fix_font_size(int& size) {
134   if(size < 1) size = 1;
135   if(size > max_font_size) size = max_font_size;
136   if(size > 72) size &=~ 3;
137   if(size > 144) size &=~ 7;
138   }
139 #endif
140 
141 #if CAP_SDL
142 
143 #if !CAP_SDL2
144 #if HDR
145 typedef SDL_Surface SDL_Renderer;
146 #define srend s
147 #endif
148 #endif
149 
150 EX SDL_Surface *s;
151 EX SDL_Surface *s_screen;
152 #if CAP_SDL2
153 EX SDL_Renderer *s_renderer, *s_software_renderer;
154 #if HDR
155 #define srend s_software_renderer
156 #endif
157 EX SDL_Texture *s_texture;
158 EX SDL_Window *s_window;
159 #endif
160 
161 EX color_t qpixel_pixel_outside;
162 
qpixel(SDL_Surface * surf,int x,int y)163 EX color_t& qpixel(SDL_Surface *surf, int x, int y) {
164   if(x<0 || y<0 || x >= surf->w || y >= surf->h) return qpixel_pixel_outside;
165   char *p = (char*) surf->pixels;
166   p += y * surf->pitch;
167   color_t *pi = (color_t*) (p);
168   return pi[x];
169   }
170 
present_surface()171 EX void present_surface() {
172   #if CAP_SDL2
173   SDL_UpdateTexture(s_texture, nullptr, s->pixels, s->w * sizeof (Uint32));
174   SDL_RenderClear(s_renderer);
175   SDL_RenderCopy(s_renderer, s_texture, nullptr, nullptr);
176   SDL_RenderPresent(s_renderer);
177   #else
178   SDL_UpdateRect(s, 0, 0, 0, 0);
179   #endif
180   }
181 
present_screen()182 EX void present_screen() {
183 #if CAP_GL
184   if(vid.usingGL) {
185     #if CAP_SDL2
186     SDL_GL_SwapWindow(s_window);
187     #else
188     SDL_GL_SwapBuffers();
189     #endif
190     return;
191     }
192 #endif
193   present_surface();
194   }
195 
196 #endif
197 
198 #if CAP_SDLTTF
199 
200 EX string fontpath = ISWEB ? "sans-serif" : HYPERPATH "DejaVuSans-Bold.ttf";
201 
loadfont(int siz)202 void loadfont(int siz) {
203   fix_font_size(siz);
204   if(!font[siz]) {
205     font[siz] = TTF_OpenFont(fontpath.c_str(), siz);
206     // Destination set by ./configure (in the GitHub repository)
207     #ifdef FONTDESTDIR
208     if (font[siz] == NULL) {
209       font[siz] = TTF_OpenFont(FONTDESTDIR, siz);
210       }
211     #endif
212     if (font[siz] == NULL) {
213       printf("error: Font file not found: %s\n", fontpath.c_str());
214       exit(1);
215       }
216     }
217   }
218 #endif
219 
220 #if !ISFAKEMOBILE && !ISANDROID & !ISIOS
textwidth(int siz,const string & str)221 int textwidth(int siz, const string &str) {
222   if(isize(str) == 0) return 0;
223 
224 #if CAP_SDLTTF
225   fix_font_size(siz);
226   loadfont(siz);
227 
228   int w, h;
229   TTF_SizeUTF8(font[siz], str.c_str(), &w, &h);
230   // printf("width = %d [%d]\n", w, isize(str));
231   return w;
232 
233 #elif CAP_GL
234   return gl_width(siz, str.c_str());
235 #else
236   return 0;
237 #endif
238   }
239 #endif
240 
241 #if ISIOS
textwidth(int siz,const string & str)242 int textwidth(int siz, const string &str) {
243   return mainfont->getSize(str, siz / 36.0).width;
244   }
245 #endif
246 
247 #if !CAP_GL
setcameraangle(bool b)248 EX void setcameraangle(bool b) { }
249 #endif
250 
251 #if !CAP_GL
reset_projection()252 EX void reset_projection() { }
glflush()253 EX void glflush() { }
model_needs_depth()254 EX bool model_needs_depth() { return false; }
set_all(int ed,ld lshift)255 void display_data::set_all(int ed, ld lshift) {}
256 #endif
257 
258 #if CAP_GL
259 
eyewidth_translate(int ed)260 EX void eyewidth_translate(int ed) {
261   glhr::using_eyeshift = false;
262   if(ed) glhr::projection_multiply(glhr::translate(-ed * current_display->eyewidth(), 0, 0));
263   }
264 
265 tuple<int, eModel, display_data*, int> last_projection;
266 EX bool new_projection_needed;
267 #if HDR
reset_projection()268 inline void reset_projection() { new_projection_needed = true; }
269 #endif
270 
271 EX ld lband_shift;
272 
set_all(int ed,ld shift)273 void display_data::set_all(int ed, ld shift) {
274   auto t = this;
275   auto current_projection = tie(ed, pmodel, t, current_rbuffer);
276   if(new_projection_needed || !glhr::current_glprogram || (next_shader_flags & GF_which) != (glhr::current_glprogram->shader_flags & GF_which) || current_projection != last_projection || shift != lband_shift) {
277     last_projection = current_projection;
278     lband_shift = shift;
279     set_projection(ed, shift);
280     set_mask(ed);
281     set_viewport(ed);
282     new_projection_needed = false;
283     }
284   }
285 
set_mask(int ed)286 void display_data::set_mask(int ed) {
287   if(ed == 0 || vid.stereo_mode != sAnaglyph) {
288     glColorMask( GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE );
289     }
290   else if(ed == 1) {
291     glColorMask( GL_TRUE,GL_FALSE,GL_FALSE,GL_TRUE );
292     }
293   else if(ed == -1) {
294     glColorMask( GL_FALSE,GL_TRUE,GL_TRUE,GL_TRUE );
295     }
296   }
297 
set_viewport(int ed)298 void display_data::set_viewport(int ed) {
299   ld xtop = current_display->xtop;
300   ld ytop = current_display->ytop;
301   ld xsize = current_display->xsize;
302   ld ysize = current_display->ysize;
303 
304   if(ed == 0 || vid.stereo_mode != sLR) ;
305   else if(ed == 1) xsize /= 2;
306   else if(ed == -1) xsize /= 2, xtop += xsize;
307 
308   glViewport(xtop, ytop, xsize, ysize);
309   }
310 
model_needs_depth()311 EX bool model_needs_depth() {
312   return GDIM == 3 || pmodel == mdBall;
313   }
314 
setGLProjection(color_t col IS (backcolor))315 EX void setGLProjection(color_t col IS(backcolor)) {
316   DEBBI(DF_GRAPH, ("setGLProjection"));
317   GLERR("pre_setGLProjection");
318 
319   glClearColor(part(col, 2) / 255.0, part(col, 1) / 255.0, part(col, 0) / 255.0, 1);
320   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
321 
322   GLERR("setGLProjection #1");
323 
324   glEnable(GL_BLEND);
325 #ifndef GLES_ONLY
326   if(vid.antialias & AA_LINES) {
327     glEnable(GL_LINE_SMOOTH);
328     glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
329     }
330   else glDisable(GL_LINE_SMOOTH);
331 #endif
332 
333   glLineWidth(vid.linewidth);
334 
335   GLERR("setGLProjection #2");
336 
337 #ifndef GLES_ONLY
338   if(vid.antialias & AA_POLY) {
339     glEnable(GL_POLYGON_SMOOTH);
340     glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
341     }
342   else glDisable(GL_POLYGON_SMOOTH);
343 #endif
344 
345   GLERR("setGLProjection #3");
346 
347   //glLineWidth(1.0f);
348   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
349 
350 #ifdef GL_ES
351   glClearDepthf(1.0f);
352 #else
353   glClearDepth(1.0f);
354 #endif
355   glDepthFunc(GL_LEQUAL);
356 
357   GLERR("setGLProjection");
358   reset_projection();
359 
360   glhr::set_depthwrite(true);
361   glClear(GL_DEPTH_BUFFER_BIT);
362   }
363 
next_p2(int a)364 EX  int next_p2 (int a ) {
365     int rval=1;
366     // rval<<=1 Is A Prettier Way Of Writing rval*=2;
367     while(rval<a) rval<<=1;
368     return rval;
369 }
370 
371 #if CAP_GLFONT
372 
373 #define CHARS (128+NUMEXTRA)
374 
375 #if HDR
376 struct charinfo_t {
377   int w, h;
378   float tx0, ty0, tx1, ty1;
379   };
380 
381 struct glfont_t {
382   GLuint texture;                                     // Holds The Texture Id
383 //GLuint list_base;                                   // Holds The First Display List ID
384   vector<charinfo_t> chars;
385   };
386 
387 const int max_glfont_size = 72;
388 #endif
389 
390 EX glfont_t *glfont[max_glfont_size+1];
391 
392 typedef Uint16 texturepixel;
393 
394 #define FONTTEXTURESIZE 2048
395 
396 int curx = 0, cury = 0, theight = 0;
397 texturepixel fontdata[FONTTEXTURESIZE][FONTTEXTURESIZE];
398 
sdltogl(SDL_Surface * txt,glfont_t & f,int ch)399 void sdltogl(SDL_Surface *txt, glfont_t& f, int ch) {
400 #if CAP_TABFONT
401   if(ch < 32) return;
402   int otwidth, otheight, tpixindex = 0;
403   unsigned char tpix[3000];
404   loadCompressedChar(otwidth, otheight, tpix);
405 #else
406   if(!txt) return;
407   int otwidth = txt->w;
408   int otheight = txt->h;
409 #endif
410 
411   if(otwidth+curx+1 > FONTTEXTURESIZE) curx = 0, cury += theight+1, theight = 0;
412 
413   theight = max(theight, otheight);
414 
415   for(int j=0; j<otheight;j++) for(int i=0; i<otwidth; i++) {
416     fontdata[j+cury][i+curx] =
417 #if CAP_TABFONT
418     (i>=otwidth || j>=otheight) ? 0 : (tpix[tpixindex++] * 0x100) | 0xFF;
419 #else
420     ((i>=txt->w || j>=txt->h) ? 0 : ((qpixel(txt, i, j)>>24)&0xFF) * 0x100) | 0x00FF;
421 #endif
422     }
423 
424   auto& c = f.chars[ch];
425 
426   c.w = otwidth;
427   c.h = otheight;
428 
429   c.tx0 = (float) curx / (float) FONTTEXTURESIZE;
430   c.tx1 = (float) (curx+otwidth) / (float) FONTTEXTURESIZE;
431   c.ty0 = (float) cury;
432   c.ty1 = (float) (cury+otheight);
433   curx += otwidth+1;
434   }
435 
init_glfont(int size)436 EX void init_glfont(int size) {
437   if(glfont[size]) return;
438   DEBBI(DF_GRAPH, ("init GL font: ", size));
439 
440 #if !CAP_TABFONT
441   loadfont(size);
442   if(!font[size]) return;
443 #endif
444 
445   glfont[size] = new glfont_t;
446 
447   glfont_t& f(*(glfont[size]));
448 
449   f.chars.resize(CHARS);
450 
451 //f.list_base = glGenLists(128);
452   glGenTextures(1, &f.texture );
453 
454 #if !CAP_TABFONT
455   char str[2]; str[1] = 0;
456 
457   SDL_Color white;
458   white.r = white.g = white.b = 255;
459 #endif
460 
461   for(int y=0; y<FONTTEXTURESIZE; y++)
462   for(int x=0; x<FONTTEXTURESIZE; x++)
463     fontdata[y][x] = 0;
464 
465 #if CAP_TABFONT
466   resetTabFont();
467 #endif
468 
469 //  glListBase(0);
470 
471   curx = 0, cury = 0, theight = 0;
472 
473   for(int ch=1;ch<CHARS;ch++) {
474 
475     if(ch<32) continue;
476 
477 #if CAP_TABFONT
478     sdltogl(NULL, f, ch);
479 
480 #else
481     SDL_Surface *txt;
482     int siz = size;
483     fix_font_size(siz);
484     if(ch < 128) {
485       str[0] = ch;
486       txt = TTF_RenderText_Blended(font[siz], str, white);
487       }
488     else {
489       txt = TTF_RenderUTF8_Blended(font[siz], natchars[ch-128], white);
490       }
491     if(txt == NULL) continue;
492 #if CAP_CREATEFONT
493     generateFont(ch, txt);
494 #endif
495     sdltogl(txt, f, ch);
496     SDL_FreeSurface(txt);
497 #endif
498     }
499 
500   glBindTexture( GL_TEXTURE_2D, f.texture);
501   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
502   glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
503 
504   theight = next_p2(cury + theight);
505 
506   glTexImage2D( GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, FONTTEXTURESIZE, theight, 0,
507     GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE,
508     fontdata);
509 
510   for(int ch=0; ch<CHARS; ch++) f.chars[ch].ty0 /= theight, f.chars[ch].ty1 /= theight;
511 
512 #if CAP_CREATEFONT
513   printf("#define NUMEXTRA %d\n", NUMEXTRA);
514 #define DEMACRO(x) #x
515   printf("#define NATCHARS " DEMACRO(NATCHARS) "\n");
516 #endif
517 
518 //printf("init size=%d ok\n", size);
519   GLERR("initfont");
520   }
521 
gl_width(int size,const char * s)522 int gl_width(int size, const char *s) {
523   int gsiz = size;
524   if(size > vid.fsize || size > max_glfont_size) gsiz = max_glfont_size;
525 
526 #if CAP_FIXEDSIZE
527   gsiz = CAP_FIXEDSIZE;
528 #endif
529 
530   init_glfont(gsiz);
531   if(!glfont[gsiz]) return 0;
532 
533   glfont_t& f(*glfont[gsiz]);
534 
535   int x = 0;
536   for(int i=0; s[i];) {
537     int tabid = getnext(s,i);
538     x += f.chars[tabid].w * size/gsiz;
539     }
540 
541   return x;
542   }
543 
charvertex(int x1,int y1,ld tx,ld ty)544 glhr::textured_vertex charvertex(int x1, int y1, ld tx, ld ty) {
545   glhr::textured_vertex res;
546   res.coords[0] = x1;
547   res.coords[1] = y1;
548   res.coords[2] = 0;
549   res.coords[3] = 1;
550   res.texture[0] = tx;
551   res.texture[1] = ty;
552   return res;
553   }
554 
gl_print(int x,int y,int shift,int size,const char * s,color_t color,int align)555 bool gl_print(int x, int y, int shift, int size, const char *s, color_t color, int align) {
556   int gsiz = size;
557   if(size > vid.fsize || size > max_glfont_size) gsiz = max_glfont_size;
558 
559 #if CAP_FIXEDSIZE
560   gsiz = CAP_FIXEDSIZE;
561 #endif
562 
563   init_glfont(gsiz);
564   if(!glfont[gsiz]) return false;
565 
566   glfont_t& f(*glfont[gsiz]);
567 
568   int tsize = 0;
569 
570   for(int i=0; s[i];) {
571     tsize += f.chars[getnext(s,i)].w * size/gsiz;
572     }
573   x -= tsize * align / 16;
574   y += f.chars[32].h * size / (gsiz*2);
575 
576   int ysiz = f.chars[32].h * size / gsiz;
577 
578   bool clicked = (mousex >= x && mousey <= y && mousex <= x+tsize && mousey >= y-ysiz);
579 
580   color_t icolor = (color << 8) | 0xFF;
581   if(icolor != text_color || f.texture != text_texture || shift != text_shift || shapes_merged) {
582     glflush();
583     text_color = icolor;
584     text_texture = f.texture;
585     text_shift = shift;
586     }
587   texts_merged++;
588 
589   auto& tver = text_vertices;
590 
591   glBindTexture(GL_TEXTURE_2D, f.texture);
592 
593   for(int i=0; s[i];) {
594 
595     int tabid = getnext(s,i);
596     auto& c = f.chars[tabid];
597     int wi = c.w * size/gsiz;
598     int hi = c.h * size/gsiz;
599 
600     GLERR("pre-print");
601 
602     glhr::textured_vertex t00 = charvertex(x,    y-hi, c.tx0, c.ty0);
603     glhr::textured_vertex t01 = charvertex(x,    y,    c.tx0, c.ty1);
604     glhr::textured_vertex t11 = charvertex(x+wi, y,    c.tx1, c.ty1);
605     glhr::textured_vertex t10 = charvertex(x+wi, y-hi, c.tx1, c.ty0);
606 
607     tver.push_back(t00);
608     tver.push_back(t01);
609     tver.push_back(t10);
610     tver.push_back(t10);
611     tver.push_back(t01);
612     tver.push_back(t11);
613 
614     x += wi;
615     }
616 
617   return clicked;
618   }
619 
620 #endif
621 
622 EX purehookset hooks_resetGL;
623 
resetGL()624 EX void resetGL() {
625   DEBBI(DF_INIT | DF_GRAPH, ("reset GL"))
626   callhooks(hooks_resetGL);
627 #if CAP_GLFONT
628   for(int i=0; i<=max_glfont_size; i++) if(glfont[i]) {
629     delete glfont[i];
630     glfont[i] = NULL;
631     }
632 #endif
633 #if MAXMDIM >= 4
634   if(floor_textures) {
635     delete floor_textures;
636     floor_textures = NULL;
637     }
638 #endif
639   #if MAXMDIM >= 4 && CAP_GL
640   if(airbuf) {
641     delete airbuf;
642     airbuf = nullptr;
643     }
644   #endif
645   check_cgi();
646   if(currentmap) cgi.require_shapes();
647   #if MAXMDIM >= 4
648   if(GDIM == 3 && !floor_textures) make_floor_textures();
649   #endif
650   cgi.initPolyForGL();
651   compiled_programs.clear();
652   matched_programs.clear();
653   glhr::current_glprogram = nullptr;
654   ray::reset_raycaster();
655   #if CAP_RUG
656   if(rug::glbuf) rug::close_glbuf();
657   #endif
658   }
659 
660 #endif
661 
662 #if CAP_XGD
663 
664 vector<int> graphdata;
665 
gdpush(int t)666 EX void gdpush(int t) {
667   graphdata.push_back(t);
668   }
669 
displaychr(int x,int y,int shift,int size,char chr,color_t col)670 EX bool displaychr(int x, int y, int shift, int size, char chr, color_t col) {
671   gdpush(2); gdpush(x); gdpush(y); gdpush(8);
672   gdpush(col); gdpush(size); gdpush(0);
673   gdpush(1); gdpush(chr);
674   return false;
675   }
676 
gdpush_utf8(const string & s)677 void gdpush_utf8(const string& s) {
678   int g = (int) graphdata.size(), q = 0;
679   gdpush((int) s.size()); for(int i=0; i<isize(s); i++) {
680 #if ISANDROID
681     unsigned char uch = (unsigned char) s[i];
682     if(uch >= 192 && uch < 224) {
683       int u = ((s[i] - 192)&31) << 6;
684       i++;
685       u += (s[i] - 128) & 63;
686       gdpush(u); q++;
687       }
688     else if(uch >= 224 && uch < 240) {
689       int u = ((s[i] - 224)&15) << 12;
690       i++;
691       u += (s[i] & 63) << 6;
692       i++;
693       u += (s[i] & 63) << 0;
694       gdpush(u); q++;
695       }
696     else
697 #endif
698       {
699       gdpush(s[i]); q++;
700       }
701     }
702   graphdata[g] = q;
703   }
704 
displayfr(int x,int y,int b,int size,const string & s,color_t color,int align)705 EX bool displayfr(int x, int y, int b, int size, const string &s, color_t color, int align) {
706   gdpush(2); gdpush(x); gdpush(y); gdpush(align);
707   gdpush(color); gdpush(size); gdpush(b);
708   gdpush_utf8(s);
709   int mx = mousex - x;
710   int my = mousey - y;
711   int len = textwidth(size, s);
712   return
713     mx >= -len*align/32   && mx <= +len*(16-align)/32 &&
714     my >= -size*3/4 && my <= +size*3/4;
715   }
716 
displaystr(int x,int y,int shift,int size,const string & s,color_t color,int align)717 EX bool displaystr(int x, int y, int shift, int size, const string &s, color_t color, int align) {
718   return displayfr(x,y,0,size,s,color,align);
719   }
720 
displaystr(int x,int y,int shift,int size,char const * s,color_t color,int align)721 EX bool displaystr(int x, int y, int shift, int size, char const *s, color_t color, int align) {
722   return displayfr(x,y,0,size,s,color,align);
723   }
724 
725 #endif
726 #if !CAP_XGD
displaystr(int x,int y,int shift,int size,const char * str,color_t color,int align)727 EX bool displaystr(int x, int y, int shift, int size, const char *str, color_t color, int align) {
728 
729   if(strlen(str) == 0) return false;
730 
731   if(size < 4 || size > 2000) {
732     return false;
733     }
734 
735 #if CAP_GLFONT
736   if(vid.usingGL) return gl_print(x, y, shift, size, str, color, align);
737 #endif
738 
739 #if !CAP_SDLTTF
740   static bool towarn = true;
741   if(towarn) towarn = false, printf("WARNING: NOTTF works only with OpenGL!\n");
742   return false;
743 #else
744 
745   SDL_Color col;
746   col.r = (color >> 16) & 255;
747   col.g = (color >> 8 ) & 255;
748   col.b = (color >> 0 ) & 255;
749 
750   col.r >>= darken; col.g >>= darken; col.b >>= darken;
751 
752   fix_font_size(size);
753   loadfont(size);
754 
755   SDL_Surface *txt = ((vid.antialias & AA_FONT)?TTF_RenderUTF8_Blended:TTF_RenderUTF8_Solid)(font[size], str, col);
756 
757   if(txt == NULL) return false;
758 
759   SDL_Rect rect;
760 
761   rect.w = txt->w;
762   rect.h = txt->h;
763 
764   rect.x = x - rect.w * align / 16;
765   rect.y = y - rect.h/2;
766 
767   bool clicked = (mousex >= rect.x && mousey >= rect.y && mousex <= rect.x+rect.w && mousey <= rect.y+rect.h);
768 
769   if(shift) {
770     #if CAP_SDL2
771     SDL_Surface* txt2 = SDL_ConvertSurfaceFormat(txt, SDL_PIXELFORMAT_RGBA8888, 0);
772     #else
773     SDL_Surface* txt2 = SDL_DisplayFormat(txt);
774     #endif
775     SDL_LockSurface(txt2);
776     SDL_LockSurface(s);
777     color_t c0 = qpixel(txt2, 0, 0);
778     for(int yy=0; yy<rect.h; yy++)
779     for(int xx=0; xx<rect.w; xx++) if(qpixel(txt2, xx, yy) != c0)
780       qpixel(s, rect.x+xx-shift, rect.y+yy) |= color & 0xFF0000,
781       qpixel(s, rect.x+xx+shift, rect.y+yy) |= color & 0x00FFFF;
782     SDL_UnlockSurface(s);
783     SDL_UnlockSurface(txt2);
784     SDL_FreeSurface(txt2);
785     }
786   else {
787     SDL_BlitSurface(txt, NULL, s,&rect);
788     }
789   SDL_FreeSurface(txt);
790 
791   return clicked;
792 #endif
793   }
794 
displaystr(int x,int y,int shift,int size,const string & s,color_t color,int align)795 EX bool displaystr(int x, int y, int shift, int size, const string &s, color_t color, int align) {
796   return displaystr(x, y, shift, size, s.c_str(), color, align);
797   }
798 
displayfrSP(int x,int y,int sh,int b,int size,const string & s,color_t color,int align,int p)799 EX bool displayfrSP(int x, int y, int sh, int b, int size, const string &s, color_t color, int align, int p) {
800   if(b) {
801     displaystr(x-b, y, 0, size, s, p, align);
802     displaystr(x+b, y, 0, size, s, p, align);
803     displaystr(x, y-b, 0, size, s, p, align);
804     displaystr(x, y+b, 0, size, s, p, align);
805     }
806   if(b >= 2) {
807     int b1 = b-1;
808     displaystr(x-b1, y-b1, 0, size, s, p, align);
809     displaystr(x-b1, y+b1, 0, size, s, p, align);
810     displaystr(x+b1, y-b1, 0, size, s, p, align);
811     displaystr(x+b1, y+b1, 0, size, s, p, align);
812     }
813   return displaystr(x, y, 0, size, s, color, align);
814   }
815 
displayfr(int x,int y,int b,int size,const string & s,color_t color,int align)816 EX bool displayfr(int x, int y, int b, int size, const string &s, color_t color, int align) {
817   return displayfrSP(x, y, 0, b, size, s, color, align, poly_outline>>8);
818   }
819 
displaychr(int x,int y,int shift,int size,char chr,color_t col)820 EX bool displaychr(int x, int y, int shift, int size, char chr, color_t col) {
821 
822   char buf[2];
823   buf[0] = chr; buf[1] = 0;
824   return displaystr(x, y, shift, size, buf, col, 8);
825   }
826 #endif
827 
828 #if HDR
829 struct msginfo {
830   int stamp;
831   time_t rtstamp;
832   int gtstamp;
833   int turnstamp;
834   char flashout;
835   char spamtype;
836   int quantity;
837   string msg;
838   };
839 #endif
840 
841 EX vector<msginfo> msgs;
842 
843 EX vector<msginfo> gamelog;
844 
flashMessages()845 EX void flashMessages() {
846   for(int i=0; i<isize(msgs); i++)
847     if(msgs[i].stamp < ticks - 1000 && !msgs[i].flashout) {
848       msgs[i].flashout = true;
849       msgs[i].stamp = ticks;
850       }
851   }
852 
fullmsg(msginfo & m)853 EX string fullmsg(msginfo& m) {
854   string s = m.msg;
855   if(m.quantity > 1) s += " (x" + its(m.quantity) + ")";
856   return s;
857   }
858 
addMessageToLog(msginfo & m,vector<msginfo> & log)859 void addMessageToLog(msginfo& m, vector<msginfo>& log) {
860 
861   if(isize(log) != 0) {
862     msginfo& last = log[isize(log)-1];
863     if(last.msg == m.msg) {
864       int q = m.quantity + last.quantity;
865       last = m; last.quantity = q;
866       return;
867       }
868     }
869   if(isize(log) < 1000)
870     log.push_back(m);
871   else {
872     for(int i=0; i<isize(log)-1; i++) swap(log[i], log[i+1]);
873     log[isize(log)-1] = m;
874     }
875   }
876 
clearMessages()877 EX void clearMessages() { msgs.clear(); }
878 
addMessage(string s,char spamtype)879 EX void addMessage(string s, char spamtype) {
880   LATE( addMessage(s, spamtype); )
881   DEBB(DF_MSG, ("addMessage: ", s));
882 
883   msginfo m;
884   m.msg = s; m.spamtype = spamtype; m.flashout = false; m.stamp = ticks;
885   m.rtstamp = time(NULL);
886   m.gtstamp = getgametime();
887   m.turnstamp = turncount;
888   m.quantity = 1;
889 
890   addMessageToLog(m, gamelog);
891   addMessageToLog(m, msgs);
892   }
893 
colormix(color_t a,color_t b,color_t c)894 EX color_t colormix(color_t a, color_t b, color_t c) {
895   for(int p=0; p<3; p++)
896     part(a, p) = part(a,p) + (part(b,p) - part(a,p)) * part(c,p) / 255;
897   return a;
898   }
899 
rhypot(int a,int b)900 EX int rhypot(int a, int b) { return (int) sqrt(a*a - b*b); }
901 
realradius()902 EX ld realradius() {
903   ld vradius = current_display->radius;
904   if(sphere) {
905     if(sphereflipped())
906       vradius /= sqrt(pconf.alpha*pconf.alpha - 1);
907     else
908       vradius = 1e12; // use the following
909     }
910   if(euclid)
911     vradius = current_display->radius * get_sightrange() / (1 + pconf.alpha) / 2.5;
912   vradius = min<ld>(vradius, min(vid.xres, vid.yres) / 2);
913   return vradius;
914   }
915 
drawmessage(const string & s,int & y,color_t col)916 EX void drawmessage(const string& s, int& y, color_t col) {
917   if(nomsg) return;
918   int rrad = (int) realradius();
919   int space;
920   if(dual::state)
921     space = vid.xres;
922   else if(y > current_display->ycenter + rrad * pconf.stretch)
923     space = vid.xres;
924   else if(y > current_display->ycenter)
925     space = current_display->xcenter - rhypot(rrad, (y-current_display->ycenter) / pconf.stretch);
926   else if(y > current_display->ycenter - vid.fsize)
927     space = current_display->xcenter - rrad;
928   else if(y > current_display->ycenter - vid.fsize - rrad * pconf.stretch)
929     space = current_display->xcenter - rhypot(rrad, (current_display->ycenter-vid.fsize-y) / pconf.stretch);
930   else
931     space = vid.xres;
932 
933   if(textwidth(vid.fsize, s) <= space) {
934     displayfr(0, y, 1, vid.fsize, s, col, 0);
935     y -= vid.fsize;
936     return;
937     }
938 
939   for(int i=1; i<isize(s); i++)
940     if(s[i-1] == ' ' && textwidth(vid.fsize, "..."+s.substr(i)) <= space) {
941       displayfr(0, y, 1, vid.fsize, "..."+s.substr(i), col, 0);
942       y -= vid.fsize;
943       drawmessage(s.substr(0, i-1), y, col);
944       return;
945       }
946 
947   // no chance
948   displayfr(0, y, 1, vid.fsize, s, col, 0);
949   y -= vid.fsize;
950   return;
951   }
952 
drawmessages()953 EX void drawmessages() {
954   DEBBI(DF_GRAPH, ("draw messages"));
955   int i = 0;
956   int t = ticks;
957   for(int j=0; j<isize(msgs); j++) {
958     if(j < isize(msgs) - vid.msglimit) continue;
959     int age = msgs[j].flashout * (t - msgs[j].stamp);
960     if(msgs[j].spamtype) {
961       for(int i=j+1; i<isize(msgs); i++) if(msgs[i].spamtype == msgs[j].spamtype)
962         msgs[j].flashout = 2;
963       }
964     if(age < 256*vid.flashtime)
965       msgs[i++] = msgs[j];
966     }
967   msgs.resize(i);
968   if(vid.msgleft == 2) {
969     int y = vid.yres - vid.fsize - hud_margin(1);
970     for(int j=isize(msgs)-1; j>=0; j--) {
971       int age = msgs[j].flashout * (t - msgs[j].stamp);
972       poly_outline = gradient(bordcolor, backcolor, 0, age, 256*vid.flashtime) << 8;
973       color_t col = gradient(forecolor, backcolor, 0, age, 256*vid.flashtime);
974       drawmessage(fullmsg(msgs[j]), y, col);
975       }
976     }
977   else {
978     for(int j=0; j<isize(msgs); j++) {
979       int age = msgs[j].flashout * (t - msgs[j].stamp);
980       int x = vid.msgleft ? 0 : vid.xres / 2;
981       int y = vid.yres - vid.fsize * (isize(msgs) - j) - (ISIOS ? 4 : 0);
982       poly_outline = gradient(bordcolor, backcolor, 0, age, 256*vid.flashtime) << 8;
983       displayfr(x, y, 1, vid.fsize, fullmsg(msgs[j]), gradient(forecolor, backcolor, 0, age, 256*vid.flashtime), vid.msgleft ? 0 : 8);
984       }
985     }
986   }
987 
988 EX void drawCircle(int x, int y, int size, color_t color, color_t fillcolor IS(0)) {
989   if(size < 0) size = -size;
990   #if CAP_GL && CAP_POLY
991   if(vid.usingGL) {
992     glflush();
993     glhr::be_nontextured();
994     glhr::id_modelview();
995     dynamicval<eModel> em(pmodel, mdPixel);
996     glcoords.clear();
997     x -= current_display->xcenter; y -= current_display->ycenter;
998     int pts = size * 4;
999     if(pts > 1500) pts = 1500;
1000     if(ISMOBILE && pts > 72) pts = 72;
1001     for(int r=0; r<pts; r++) {
1002       float rr = (M_PI * 2 * r) / pts;
1003       glcoords.push_back(glhr::makevertex(x + size * sin(rr), y + size * pconf.stretch * cos(rr), 0));
1004       }
1005     current_display->set_all(0, lband_shift);
1006     glhr::vertices(glcoords);
1007     glhr::set_depthtest(false);
1008     if(fillcolor) {
1009       glhr::color2(fillcolor);
1010       glDrawArrays(GL_TRIANGLE_FAN, 0, pts);
1011       }
1012     if(color) {
1013       glhr::color2(color);
1014       glDrawArrays(GL_LINE_LOOP, 0, pts);
1015       }
1016     return;
1017     }
1018   #endif
1019 
1020 #if CAP_XGD
1021   gdpush(4); gdpush(color); gdpush(fillcolor); gdpush(x); gdpush(y); gdpush(size);
1022 #elif CAP_SDLGFX
1023   if(pconf.stretch == 1) {
1024     if(fillcolor) filledCircleColor(srend, x, y, size, fillcolor);
1025     if(color) ((vid.antialias && AA_NOGL)?aacircleColor:circleColor) (srend, x, y, size, align(color));
1026     }
1027   else {
1028     if(fillcolor) filledEllipseColor(srend, x, y, size, size * pconf.stretch, fillcolor);
1029     if(color) ((vid.antialias && AA_NOGL)?aaellipseColor:ellipseColor) (srend, x, y, size, size * pconf.stretch, align(color));
1030     }
1031 #elif CAP_SDL
1032   int pts = size * 4;
1033   if(pts > 1500) pts = 1500;
1034   for(int r=0; r<pts; r++)
1035     qpixel(s, x + int(size * sin(r)), y + int(size * cos(r))) = color;
1036 #endif
1037   }
1038 
1039 EX void displayButton(int x, int y, const string& name, int key, int align, int rad IS(0)) {
1040   if(displayfr(x, y, rad, vid.fsize, name, 0x808080, align)) {
1041     displayfr(x, y, rad, vid.fsize, name, 0xFFFF00, align);
1042     getcstat = key;
1043     }
1044   }
1045 
1046 #if HDR
1047 #define SETMOUSEKEY 5000
1048 #endif
1049 
1050 EX char mousekey = 'n';
1051 EX char newmousekey;
1052 
displaymm(char c,int x,int y,int rad,int size,const string & title,int align)1053 EX void displaymm(char c, int x, int y, int rad, int size, const string& title, int align) {
1054   if(displayfr(x, y, rad, size, title, c == mousekey ? 0xFF8000 : 0xC0C0C0, align)) {
1055     displayfr(x, y, rad, size, title, 0xFFFF00, align);
1056     getcstat = SETMOUSEKEY, newmousekey = c;
1057     }
1058   }
1059 
displayButtonS(int x,int y,const string & name,color_t col,int align,int size)1060 EX bool displayButtonS(int x, int y, const string& name, color_t col, int align, int size) {
1061   if(displaystr(x, y, 0, size, name, col, align)) {
1062     displaystr(x, y, 0, size, name, 0xFFFF00, align);
1063     return true;
1064     }
1065   else return false;
1066   }
1067 
1068 EX void displayColorButton(int x, int y, const string& name, int key, int align, int rad, color_t color, color_t color2 IS(0)) {
1069   if(displayfr(x, y, rad, vid.fsize, name, color, align)) {
1070     if(color2) displayfr(x, y, rad, vid.fsize, name, color2, align);
1071     getcstat = key;
1072     }
1073   }
1074 
textscale()1075 ld textscale() {
1076   return vid.fsize / (current_display->radius * cgi.crossf) * (1+pconf.alpha) * 2;
1077   }
1078 
compute_fsize()1079 EX void compute_fsize() {
1080   dual::split_or_do([&] {
1081     if(vid.relative_font)
1082       vid.fsize = min(vid.yres * vid.fontscale/ 3200, vid.xres * vid.fontscale/ 4800);
1083     else
1084       vid.fsize = vid.abs_fsize;
1085     if(vid.fsize < 6) vid.fsize = 6;
1086     });
1087   }
1088 
1089 EX bool graphics_on;
1090 
want_vsync()1091 EX bool want_vsync() {
1092   if(vrhr::active())
1093     return false;
1094   return vid.want_vsync;
1095   }
1096 
need_to_reopen_window()1097 EX bool need_to_reopen_window() {
1098   if(vid.want_antialias != vid.antialias)
1099     return true;
1100   if(vid.wantGL != vid.usingGL)
1101     return true;
1102   if(want_vsync() != vid.current_vsync)
1103     return true;
1104   return false;
1105   }
1106 
need_to_apply_screen_settings()1107 EX bool need_to_apply_screen_settings() {
1108   if(need_to_reopen_window())
1109     return true;
1110   if(vid.want_fullscreen != vid.full)
1111     return true;
1112   if(make_pair(vid.xres, vid.yres) != get_requested_resolution())
1113     return true;
1114   return false;
1115   }
1116 
close_renderer()1117 EX void close_renderer() {
1118   #if CAP_SDL2
1119   if(s_renderer) SDL_DestroyRenderer(s_renderer), s_renderer = nullptr;
1120   if(s_texture) SDL_DestroyTexture(s_texture), s_texture = nullptr;
1121   if(s) SDL_FreeSurface(s), s = nullptr;
1122   if(s_software_renderer) SDL_DestroyRenderer(s_software_renderer), s_software_renderer = nullptr;
1123   #endif
1124   }
1125 
close_window()1126 EX void close_window() {
1127   #if CAP_SDL2
1128   close_renderer();
1129   if(s_window) SDL_DestroyWindow(s_window), s_window = nullptr;
1130   #endif
1131   }
1132 
apply_screen_settings()1133 EX void apply_screen_settings() {
1134   if(!need_to_apply_screen_settings()) return;
1135   if(!graphics_on) return;
1136 
1137 #if ISANDROID
1138   if(vid.full != vid.want_fullscreen)
1139     addMessage(XLAT("Reenter HyperRogue to apply this setting"));
1140 #endif
1141 
1142   close_renderer();
1143   #if CAP_VR
1144   if(vrhr::state) vrhr::shutdown_vr();
1145   #endif
1146 
1147   #if CAP_SDL
1148   #if !CAP_SDL2
1149   if(need_to_reopen_window())
1150     SDL_QuitSubSystem(SDL_INIT_VIDEO);
1151   #endif
1152   #endif
1153 
1154   graphics_on = false;
1155   android_settings_changed();
1156   init_graph();
1157   #if CAP_GL
1158   if(vid.usingGL) {
1159     glhr::be_textured(); glhr::be_nontextured();
1160     }
1161   #endif
1162   }
1163 
get_requested_resolution()1164 EX pair<int, int> get_requested_resolution() {
1165   #if ISMOBILE || ISFAKEMOBILE
1166   return { vid.xres, vid.yres };
1167   #endif
1168   if(vid.want_fullscreen && vid.change_fullscr)
1169     return { vid.fullscreen_x, vid.fullscreen_y };
1170   else if(vid.want_fullscreen)
1171     return { vid.xres = vid.xscr, vid.yres = vid.yscr };
1172   else if(vid.relative_window_size)
1173     return { vid.xscr * vid.window_rel_x + .5, vid.yscr * vid.window_rel_y + .5 };
1174   else
1175     return { vid.window_x, vid.window_y };
1176   }
1177 
1178 #ifndef CUSTOM_CAPTION
1179 #define CUSTOM_CAPTION ("HyperRogue " VER)
1180 #endif
1181 
1182 EX bool resizable = true;
1183 
setvideomode_android()1184 EX void setvideomode_android() {
1185   vid.usingGL = vid.wantGL;
1186   vid.full = vid.want_fullscreen;
1187   vid.antialias = vid.want_antialias;
1188   }
1189 
1190 #if CAP_SDL
1191 
1192 EX int current_window_flags = -1;
1193 
setvideomode()1194 EX void setvideomode() {
1195 
1196   DEBBI(DF_INIT | DF_GRAPH, ("setvideomode"));
1197 
1198   vid.full = vid.want_fullscreen;
1199 
1200   tie(vid.xres, vid.yres) = get_requested_resolution();
1201 
1202   compute_fsize();
1203 
1204   int flags = 0;
1205 
1206   vid.antialias = vid.want_antialias;
1207 
1208 #if CAP_GL
1209   vid.usingGL = vid.wantGL;
1210   if(vid.usingGL) {
1211     flags = SDL12(SDL_OPENGL | SDL_HWSURFACE, SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI);
1212     SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
1213     SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1);
1214 
1215     vid.current_vsync = want_vsync();
1216     #if !ISMOBWEB && !CAP_SDL2
1217     if(vid.current_vsync)
1218       SDL_GL_SetAttribute( SDL_GL_SWAP_CONTROL, 1 );
1219     else
1220       SDL_GL_SetAttribute( SDL_GL_SWAP_CONTROL, 0 );
1221     #endif
1222     if(vid.antialias & AA_MULTI) {
1223       SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
1224       SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, (vid.antialias & AA_MULTI16) ? 16 : 4);
1225       }
1226     }
1227 #else
1228   vid.usingGL = false;
1229 #endif
1230 
1231   int sizeflag = SDL12(vid.full ? SDL_FULLSCREEN : resizable ? SDL_RESIZABLE : 0, vid.full ? SDL_WINDOW_FULLSCREEN : resizable ? SDL_WINDOW_RESIZABLE : 0);
1232 
1233   #ifdef WINDOWS
1234   #ifndef OLD_MINGW
1235   static bool set_awareness = true;
1236   if(set_awareness) {
1237     set_awareness = false;
1238     HMODULE user32_dll = LoadLibraryA("User32.dll");
1239     if (user32_dll) {
1240       DPI_AWARENESS_CONTEXT (WINAPI * Loaded_SetProcessDpiAwarenessContext) (DPI_AWARENESS_CONTEXT) =
1241         (DPI_AWARENESS_CONTEXT (WINAPI *) (DPI_AWARENESS_CONTEXT)) (void*)
1242         GetProcAddress(user32_dll, "SetProcessDpiAwarenessContext");
1243       if(Loaded_SetProcessDpiAwarenessContext) {
1244         Loaded_SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
1245         }
1246       FreeLibrary(user32_dll);
1247       }
1248     }
1249   #endif
1250   #endif
1251 
1252   #if CAP_SDL2
1253   if(s_renderer) SDL_DestroyRenderer(s_renderer), s_renderer = nullptr;
1254   #endif
1255 
1256   auto create_win = [&] {
1257     #if CAP_SDL2
1258     if(s_window && current_window_flags != (flags | sizeflag))
1259       SDL_DestroyWindow(s_window), s_window = nullptr;
1260     if(s_window)
1261       SDL_SetWindowSize(s_window, vid.xres, vid.yres);
1262     else
1263       s_window = SDL_CreateWindow(CUSTOM_CAPTION, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
1264       vid.xres, vid.yres,
1265       flags | sizeflag
1266       );
1267     current_window_flags = (flags | sizeflag);
1268     #else
1269     s = SDL_SetVideoMode(vid.xres, vid.yres, 32, flags | sizeflag);
1270     #endif
1271     };
1272 
1273   create_win();
1274 
1275   auto& sw = SDL12(s, s_window);
1276 
1277   if(vid.full && !sw) {
1278     vid.xres = vid.xscr;
1279     vid.yres = vid.yscr;
1280     vid.fsize = 10;
1281     sizeflag = SDL12(SDL_FULLSCREEN, SDL_WINDOW_FULLSCREEN);
1282     create_win();
1283     }
1284 
1285   if(!sw) {
1286     addMessage("Failed to set the graphical mode: "+its(vid.xres)+"x"+its(vid.yres)+(vid.full ? " fullscreen" : " windowed"));
1287     vid.xres = 640;
1288     vid.yres = 480;
1289     SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
1290     vid.antialias &= ~AA_MULTI;
1291     sizeflag = SDL12(SDL_RESIZABLE, SDL_WINDOW_RESIZABLE);
1292     create_win();
1293     }
1294 
1295   #if CAP_SDL2
1296   s_renderer = SDL_CreateRenderer(s_window, -1, vid.current_vsync ? SDL_RENDERER_PRESENTVSYNC : 0);
1297   SDL_GetRendererOutputSize(s_renderer, &vid.xres, &vid.yres);
1298 
1299   if(s_texture) SDL_DestroyTexture(s_texture), s_texture = nullptr;
1300   s_texture = SDL_CreateTexture(s_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, vid.xres, vid.yres);
1301 
1302   if(s) SDL_FreeSurface(s), s = nullptr;
1303   s = shot::empty_surface(vid.xres, vid.yres, false);
1304 
1305   if(s_software_renderer) SDL_DestroyRenderer(s_software_renderer), s_software_renderer = nullptr;
1306   s_software_renderer = SDL_CreateSoftwareRenderer(s);
1307   #endif
1308   s_screen = s;
1309 
1310 #if CAP_GL
1311   if(vid.usingGL) {
1312 
1313     if(vid.antialias & AA_MULTI) {
1314       glEnable(GL_MULTISAMPLE);
1315       glEnable(GL_MULTISAMPLE_ARB);
1316       }
1317     else {
1318       glDisable(GL_MULTISAMPLE);
1319       glDisable(GL_MULTISAMPLE_ARB);
1320       }
1321 
1322     glViewport(0, 0, vid.xres, vid.yres);
1323     glhr::init();
1324     resetGL();
1325     }
1326 #endif
1327   }
1328 #endif
1329 
1330 EX bool noGUI = false;
1331 
1332 #if CAP_SDL
1333 EX bool sdl_on = false;
SDL_Init1(Uint32 flags)1334 EX int SDL_Init1(Uint32 flags) {
1335   if(!sdl_on) {
1336     sdl_on = true;
1337     return SDL_Init(flags);
1338     }
1339   else
1340     return SDL_InitSubSystem(flags);
1341   }
1342 #endif
1343 
init_font()1344 EX void init_font() {
1345 #if CAP_SDLTTF
1346   if(TTF_Init() != 0) {
1347     printf("Failed to initialize TTF.\n");
1348     exit(2);
1349     }
1350 #endif
1351   }
1352 
close_font()1353 EX void close_font() {
1354 #if CAP_SDLTTF
1355   for(int i=0; i<=max_font_size; i++) if(font[i]) {
1356     TTF_CloseFont(font[i]);
1357     font[i] = nullptr;
1358     }
1359   TTF_Quit();
1360 #endif
1361 #if CAL_GLFONT
1362   for(int i=0; i<=max_glfont_size; i++) if(glfont[i]) {
1363     delete glfont[i];
1364     glfont[i] = nullptr;
1365     }
1366 #endif
1367   }
1368 
init_graph()1369 EX void init_graph() {
1370 #if CAP_SDL
1371   if (SDL_Init1(SDL_INIT_VIDEO) == -1)
1372   {
1373     printf("Failed to initialize video.\n");
1374     exit(2);
1375   }
1376 
1377 #if ISWEB
1378   get_canvas_size();
1379 #else
1380   if(!vid.xscr) {
1381     #if CAP_SDL2
1382     SDL_DisplayMode dm;
1383     SDL_GetCurrentDisplayMode(0, &dm);
1384     vid.xscr = vid.xres = dm.w;
1385     vid.yscr = vid.yres = dm.h;
1386     #else
1387     const SDL_VideoInfo *inf = SDL_GetVideoInfo();
1388     vid.xscr = vid.xres = inf->current_w;
1389     vid.yscr = vid.yres = inf->current_h;
1390     #endif
1391     }
1392 #endif
1393 
1394 #if !CAP_SDL2
1395   SDL_WM_SetCaption(CUSTOM_CAPTION, CUSTOM_CAPTION);
1396 #endif
1397 #endif
1398 
1399   graphics_on = true;
1400 
1401 #if ISIOS
1402   vid.usingGL = true;
1403 #endif
1404 
1405 #if ISANDROID
1406   setvideomode_android();
1407 #endif
1408 
1409 #if CAP_SDL
1410   setvideomode();
1411   if(!s) {
1412     printf("Failed to initialize graphics.\n");
1413     exit(2);
1414     }
1415 
1416   #if !CAP_SDL2
1417   SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
1418   SDL_EnableUNICODE(1);
1419   #endif
1420 #endif
1421 
1422 #if ISANDROID
1423   vid.full = vid.want_fullscreen;
1424 #endif
1425   }
1426 
initialize_all()1427 EX void initialize_all() {
1428 
1429   DEBBI(DF_INIT | DF_GRAPH, ("initgraph"));
1430 
1431   initConfig();
1432 
1433 #if CAP_SDLJOY
1434   joyx = joyy = 0; joydir.d = -1;
1435 #endif
1436 
1437   restartGraph();
1438 
1439   if(noGUI) {
1440 #if CAP_COMMANDLINE
1441     arg::read(2);
1442 #endif
1443     return;
1444     }
1445 
1446   preparesort();
1447 #if CAP_CONFIG
1448   loadConfig();
1449 #endif
1450 #if CAP_ARCM
1451   arcm::current.parse();
1452 #endif
1453   if(hybri) geometry = hybrid::underlying;
1454 
1455 #if CAP_COMMANDLINE
1456   arg::read(2);
1457 #endif
1458 
1459   init_graph();
1460   check_cgi();
1461   cgi.require_basics();
1462 
1463   init_font();
1464 
1465 #if CAP_SDLJOY
1466   initJoysticks();
1467 #endif
1468 
1469 #if CAP_SDLAUDIO
1470   initAudio();
1471 #endif
1472   }
1473 
quit_all()1474 EX void quit_all() {
1475   DEBBI(DF_INIT, ("clear graph"));
1476 #if CAP_SDLJOY
1477   closeJoysticks();
1478 #endif
1479 #if CAP_SDL
1480   close_window();
1481   SDL_Quit();
1482   sdl_on = false;
1483 #endif
1484   }
1485 
calcfps()1486 EX int calcfps() {
1487   #define CFPS 30
1488   static int last[CFPS], lidx = 0;
1489   int ct = ticks;
1490   int ret = ct - last[lidx];
1491   last[lidx] = ct;
1492   lidx++; lidx %= CFPS;
1493   if(ret == 0) return 0;
1494   return (1000 * CFPS) / ret;
1495   }
1496 
1497 EX namespace subscreens {
1498 
1499   EX vector<display_data> player_displays;
1500   EX bool in;
1501   EX int current_player;
1502 
is_current_player(int id)1503   EX bool is_current_player(int id) {
1504     if(!in) return true;
1505     return id == current_player;
1506     }
1507 
prepare()1508   EX void prepare() {
1509     int N = multi::players;
1510     if(N > 1) {
1511       player_displays.resize(N, *current_display);
1512       int qrows[10] = {1, 1, 1, 1, 2, 2, 2, 3, 3, 3};
1513       int rows = qrows[N];
1514       int cols = (N + rows - 1) / rows;
1515       for(int i=0; i<N; i++) {
1516         auto& pd = player_displays[i];
1517         pd.xmin = (i % cols) * 1. / cols;
1518         pd.xmax = ((i % cols) + 1.) / cols;
1519         pd.ymin = (i / cols) * 1. / rows;
1520         pd.ymax = ((i / cols) + 1.) / rows;
1521         }
1522       }
1523     else {
1524       player_displays.clear();
1525       }
1526     }
1527 
split(reaction_t what)1528   EX bool split(reaction_t what) {
1529     using namespace racing;
1530     if(in) return false;
1531     if(!racing::on && !(shmup::on && GDIM == 3)) return false;
1532     if(!player_displays.empty()) {
1533       in = true;
1534       int& p = current_player;
1535       for(p = 0; p < multi::players; p++) {
1536         dynamicval<display_data*> c(current_display, &player_displays[p]);
1537         what();
1538         }
1539       in = false;
1540       return true;
1541       }
1542     return false;
1543     }
1544 
1545   }
1546 
1547 }
1548