]> icculus.org git repositories - divverent/darkplaces.git/blob - cl_screen.c
don't allow $ expansion or sendcvar on rcon_password (added CVAR_PRIVATE flag for...
[divverent/darkplaces.git] / cl_screen.c
1
2 #include "quakedef.h"
3 #include "cl_video.h"
4 #include "image.h"
5 #include "jpeg.h"
6 #include "cl_collision.h"
7 #include "libcurl.h"
8 #include "csprogs.h"
9
10 cvar_t scr_viewsize = {CVAR_SAVE, "viewsize","100", "how large the view should be, 110 disables inventory bar, 120 disables status bar"};
11 cvar_t scr_fov = {CVAR_SAVE, "fov","90", "field of vision, 1-170 degrees, default 90, some players use 110-130"};       // 1 - 170
12 cvar_t scr_conalpha = {CVAR_SAVE, "scr_conalpha", "1", "opacity of console background"};
13 cvar_t scr_conbrightness = {CVAR_SAVE, "scr_conbrightness", "1", "brightness of console background (0 = black, 1 = image)"};
14 cvar_t scr_conforcewhiledisconnected = {0, "scr_conforcewhiledisconnected", "1", "forces fullscreen console while disconnected"};
15 cvar_t scr_menuforcewhiledisconnected = {0, "scr_menuforcewhiledisconnected", "0", "forces menu while disconnected"};
16 cvar_t scr_centertime = {0, "scr_centertime","2", "how long centerprint messages show"};
17 cvar_t scr_showram = {CVAR_SAVE, "showram","1", "show ram icon if low on surface cache memory (not used)"};
18 cvar_t scr_showturtle = {CVAR_SAVE, "showturtle","0", "show turtle icon when framerate is too low (not used)"};
19 cvar_t scr_showpause = {CVAR_SAVE, "showpause","1", "show pause icon when game is paused"};
20 cvar_t scr_showbrand = {0, "showbrand","0", "shows gfx/brand.tga in a corner of the screen (different values select different positions, including centered)"};
21 cvar_t scr_printspeed = {0, "scr_printspeed","8", "speed of intermission printing (episode end texts)"};
22 cvar_t vid_conwidth = {CVAR_SAVE, "vid_conwidth", "640", "virtual width of 2D graphics system"};
23 cvar_t vid_conheight = {CVAR_SAVE, "vid_conheight", "480", "virtual height of 2D graphics system"};
24 cvar_t vid_pixelheight = {CVAR_SAVE, "vid_pixelheight", "1", "adjusts vertical field of vision to account for non-square pixels (1280x1024 on a CRT monitor for example)"};
25 cvar_t scr_screenshot_jpeg = {CVAR_SAVE, "scr_screenshot_jpeg","1", "save jpeg instead of targa"};
26 cvar_t scr_screenshot_jpeg_quality = {CVAR_SAVE, "scr_screenshot_jpeg_quality","0.9", "image quality of saved jpeg"};
27 cvar_t scr_screenshot_gammaboost = {CVAR_SAVE, "scr_screenshot_gammaboost","1", "gamma correction on saved screenshots and videos, 1.0 saves unmodified images"};
28 // scr_screenshot_name is defined in fs.c
29 cvar_t cl_capturevideo = {0, "cl_capturevideo", "0", "enables saving of video to a file or files (default is .tga files, if scr_screenshot_jpeg is on it saves .jpg files (VERY SLOW), if any rawrgb or rawyv12 are on it saves those formats instead, note that scr_screenshot_gammaboost affects the brightness of the output)"};
30 cvar_t cl_capturevideo_fps = {0, "cl_capturevideo_fps", "30", "how many frames per second to save (29.97 for NTSC, 30 for typical PC video, 15 can be useful)"};
31 cvar_t cl_capturevideo_rawrgb = {0, "cl_capturevideo_rawrgb", "0", "saves a single .rgb video file containing raw RGB images (you'll need special processing tools to encode this to something more useful)"};
32 cvar_t cl_capturevideo_rawyv12 = {0, "cl_capturevideo_rawyv12", "0", "saves a single .yv12 video file containing raw YV12 (luma plane, then half resolution chroma planes, first chroma blue then chroma red, this is the format used internally by many encoders, some tools can read it directly)"};
33 cvar_t cl_capturevideo_number = {CVAR_SAVE, "cl_capturevideo_number", "1", "number to append to video filename, incremented each time a capture begins"};
34 cvar_t r_letterbox = {0, "r_letterbox", "0", "reduces vertical height of view to simulate a letterboxed movie effect (can be used by mods for cutscenes)"};
35 cvar_t r_stereo_separation = {0, "r_stereo_separation", "4", "separation of eyes in the world (try negative values too)"};
36 cvar_t r_stereo_sidebyside = {0, "r_stereo_sidebyside", "0", "side by side views (for those who can't afford glasses but can afford eye strain)"};
37 cvar_t r_stereo_redblue = {0, "r_stereo_redblue", "0", "red/blue anaglyph stereo glasses (note: most of these glasses are actually red/cyan, try that one too)"};
38 cvar_t r_stereo_redcyan = {0, "r_stereo_redcyan", "0", "red/cyan anaglyph stereo glasses, the kind given away at drive-in movies like Creature From The Black Lagoon In 3D"};
39 cvar_t r_stereo_redgreen = {0, "r_stereo_redgreen", "0", "red/green anaglyph stereo glasses (for those who don't mind yellow)"};
40 cvar_t scr_zoomwindow = {CVAR_SAVE, "scr_zoomwindow", "0", "displays a zoomed in overlay window"};
41 cvar_t scr_zoomwindow_viewsizex = {CVAR_SAVE, "scr_zoomwindow_viewsizex", "20", "horizontal viewsize of zoom window"};
42 cvar_t scr_zoomwindow_viewsizey = {CVAR_SAVE, "scr_zoomwindow_viewsizey", "20", "vertical viewsize of zoom window"};
43 cvar_t scr_zoomwindow_fov = {CVAR_SAVE, "scr_zoomwindow_fov", "20", "fov of zoom window"};
44
45
46 int jpeg_supported = false;
47
48 qboolean        scr_initialized;                // ready to draw
49
50 float           scr_con_current;
51
52 extern int      con_vislines;
53
54 static void SCR_ScreenShot_f (void);
55 static void R_Envmap_f (void);
56
57 // backend
58 void R_ClearScreen(void);
59
60 /*
61 ===============================================================================
62
63 CENTER PRINTING
64
65 ===============================================================================
66 */
67
68 char            scr_centerstring[MAX_INPUTLINE];
69 float           scr_centertime_start;   // for slow victory printing
70 float           scr_centertime_off;
71 int                     scr_center_lines;
72 int                     scr_erase_lines;
73 int                     scr_erase_center;
74
75 /*
76 ==============
77 SCR_CenterPrint
78
79 Called for important messages that should stay in the center of the screen
80 for a few moments
81 ==============
82 */
83 void SCR_CenterPrint(char *str)
84 {
85         strlcpy (scr_centerstring, str, sizeof (scr_centerstring));
86         scr_centertime_off = scr_centertime.value;
87         scr_centertime_start = cl.time;
88
89 // count the number of lines for centering
90         scr_center_lines = 1;
91         while (*str)
92         {
93                 if (*str == '\n')
94                         scr_center_lines++;
95                 str++;
96         }
97 }
98
99
100 void SCR_DrawCenterString (void)
101 {
102         char    *start;
103         int             l;
104         int             x, y;
105         int             remaining;
106         int             color;
107
108 // the finale prints the characters one at a time
109         if (cl.intermission)
110                 remaining = (int)(scr_printspeed.value * (cl.time - scr_centertime_start));
111         else
112                 remaining = 9999;
113
114         scr_erase_center = 0;
115         start = scr_centerstring;
116
117         if (remaining < 1)
118                 return;
119
120         if (scr_center_lines <= 4)
121                 y = (int)(vid_conheight.integer*0.35);
122         else
123                 y = 48;
124
125         color = -1;
126         do
127         {
128                 // scan the number of characters on the line, not counting color codes
129                 int chars = 0;
130                 for (l=0 ; l<vid_conwidth.integer/8 ; l++)
131                 {
132                         if (start[l] == '\n' || !start[l])
133                                 break;
134                         // color codes add no visible characters, so don't count them
135                         if (start[l] == '^' && (start[l+1] >= '0' && start[l+1] <= '9'))
136                                 l++;
137                         else
138                                 chars++;
139                 }
140                 x = (vid_conwidth.integer - chars*8)/2;
141                 if (l > 0)
142                 {
143                         if (remaining < l)
144                                 l = remaining;
145                         DrawQ_ColoredString(x, y, start, l, 8, 8, 1, 1, 1, 1, 0, &color);
146                         remaining -= l;
147                         if (remaining <= 0)
148                                 return;
149                 }
150
151                 y += 8;
152
153                 while (*start && *start != '\n')
154                         start++;
155
156                 if (!*start)
157                         break;
158                 start++;                // skip the \n
159         } while (1);
160 }
161
162 void SCR_CheckDrawCenterString (void)
163 {
164         if (scr_center_lines > scr_erase_lines)
165                 scr_erase_lines = scr_center_lines;
166
167         scr_centertime_off -= cl.realframetime;
168
169         // don't draw if this is a normal stats-screen intermission,
170         // only if it is not an intermission, or a finale intermission
171         if (cl.intermission == 1)
172                 return;
173         if (scr_centertime_off <= 0 && !cl.intermission)
174                 return;
175         if (key_dest != key_game)
176                 return;
177
178         SCR_DrawCenterString ();
179 }
180
181 /*
182 ==============
183 SCR_DrawTurtle
184 ==============
185 */
186 void SCR_DrawTurtle (void)
187 {
188         static int      count;
189
190         if (cls.state != ca_connected)
191                 return;
192
193         if (!scr_showturtle.integer)
194                 return;
195
196         if (cl.realframetime < 0.1)
197         {
198                 count = 0;
199                 return;
200         }
201
202         count++;
203         if (count < 3)
204                 return;
205
206         DrawQ_Pic (0, 0, Draw_CachePic("gfx/turtle", true), 0, 0, 1, 1, 1, 1, 0);
207 }
208
209 /*
210 ==============
211 SCR_DrawNet
212 ==============
213 */
214 void SCR_DrawNet (void)
215 {
216         if (cls.state != ca_connected)
217                 return;
218         if (realtime - cl.last_received_message < 0.3)
219                 return;
220         if (cls.demoplayback)
221                 return;
222
223         DrawQ_Pic (64, 0, Draw_CachePic("gfx/net", true), 0, 0, 1, 1, 1, 1, 0);
224 }
225
226 /*
227 ==============
228 DrawPause
229 ==============
230 */
231 void SCR_DrawPause (void)
232 {
233         cachepic_t      *pic;
234
235         if (cls.state != ca_connected)
236                 return;
237
238         if (!scr_showpause.integer)             // turn off for screenshots
239                 return;
240
241         if (!cl.paused)
242                 return;
243
244         pic = Draw_CachePic ("gfx/pause", true);
245         DrawQ_Pic ((vid_conwidth.integer - pic->width)/2, (vid_conheight.integer - pic->height)/2, pic, 0, 0, 1, 1, 1, 1, 0);
246 }
247
248 /*
249 ==============
250 SCR_DrawBrand
251 ==============
252 */
253 void SCR_DrawBrand (void)
254 {
255         cachepic_t      *pic;
256         float           x, y;
257
258         if (!scr_showbrand.value)
259                 return;
260
261         pic = Draw_CachePic ("gfx/brand", true);
262
263         switch ((int)scr_showbrand.value)
264         {
265         case 1: // bottom left
266                 x = 0;
267                 y = vid_conheight.integer - pic->height;
268                 break;
269         case 2: // bottom centre
270                 x = (vid_conwidth.integer - pic->width) / 2;
271                 y = vid_conheight.integer - pic->height;
272                 break;
273         case 3: // bottom right
274                 x = vid_conwidth.integer - pic->width;
275                 y = vid_conheight.integer - pic->height;
276                 break;
277         case 4: // centre right
278                 x = vid_conwidth.integer - pic->width;
279                 y = (vid_conheight.integer - pic->height) / 2;
280                 break;
281         case 5: // top right
282                 x = vid_conwidth.integer - pic->width;
283                 y = 0;
284                 break;
285         case 6: // top centre
286                 x = (vid_conwidth.integer - pic->width) / 2;
287                 y = 0;
288                 break;
289         case 7: // top left
290                 x = 0;
291                 y = 0;
292                 break;
293         case 8: // centre left
294                 x = 0;
295                 y = (vid_conheight.integer - pic->height) / 2;
296                 break;
297         default:
298                 return;
299         }
300
301         DrawQ_Pic (x, y, pic, 0, 0, 1, 1, 1, 1, 0);
302 }
303
304 /*
305 ==============
306 SCR_DrawQWDownload
307 ==============
308 */
309 static int SCR_DrawQWDownload(int offset)
310 {
311         int len;
312         float x, y;
313         float size = 8;
314         char temp[256];
315         if (!cls.qw_downloadname[0])
316                 return 0;
317         dpsnprintf(temp, sizeof(temp), "Downloading %s ...  %3i%%\n", cls.qw_downloadname, cls.qw_downloadpercent);
318         len = (int)strlen(temp);
319         x = (vid_conwidth.integer - len*size) / 2;
320         y = vid_conheight.integer - size - offset;
321         DrawQ_Pic(0, y, NULL, vid_conwidth.integer, size, 0, 0, 0, 0.5, 0);
322         DrawQ_String(x, y, temp, len, size, size, 1, 1, 1, 1, 0);
323         return 8;
324 }
325
326 /*
327 ==============
328 SCR_DrawCurlDownload
329 ==============
330 */
331 static int SCR_DrawCurlDownload(int offset)
332 {
333         int len;
334         int nDownloads;
335         int i;
336         float x, y;
337         float size = 8;
338         Curl_downloadinfo_t *downinfo;
339         char temp[256];
340         const char *addinfo;
341
342         downinfo = Curl_GetDownloadInfo(&nDownloads, &addinfo);
343         if(!downinfo)
344                 return 0;
345
346         y = vid_conheight.integer - size * nDownloads - offset;
347
348         if(addinfo)
349         {
350                 len = (int)strlen(addinfo);
351                 x = (vid_conwidth.integer - len*size) / 2;
352                 DrawQ_Pic(0, y - size, NULL, vid_conwidth.integer, size, 1, 1, 1, 0.8, 0);
353                 DrawQ_String(x, y - size, addinfo, len, size, size, 0, 0, 0, 1, 0);
354         }
355
356         for(i = 0; i != nDownloads; ++i)
357         {
358                 if(downinfo[i].queued)
359                         dpsnprintf(temp, sizeof(temp), "Still in queue: %s\n", downinfo[i].filename);
360                 else if(downinfo[i].progress <= 0)
361                         dpsnprintf(temp, sizeof(temp), "Downloading %s ...  ???.?%% @ %.1f KiB/s\n", downinfo[i].filename, downinfo[i].speed / 1024.0);
362                 else
363                         dpsnprintf(temp, sizeof(temp), "Downloading %s ...  %5.1f%% @ %.1f KiB/s\n", downinfo[i].filename, 100.0 * downinfo[i].progress, downinfo[i].speed / 1024.0);
364                 len = (int)strlen(temp);
365                 x = (vid_conwidth.integer - len*size) / 2;
366                 DrawQ_Pic(0, y + i * size, NULL, vid_conwidth.integer, size, 0, 0, 0, 0.8, 0);
367                 DrawQ_String(x, y + i * size, temp, len, size, size, 1, 1, 1, 1, 0);
368         }
369
370         Z_Free(downinfo);
371
372         return 8 * (nDownloads + (addinfo ? 1 : 0));
373 }
374
375 /*
376 ==============
377 SCR_DrawDownload
378 ==============
379 */
380 static void SCR_DrawDownload()
381 {
382         int offset = 0;
383         offset += SCR_DrawQWDownload(offset);
384         offset += SCR_DrawCurlDownload(offset);
385 }
386
387 //=============================================================================
388
389 /*
390 ==================
391 SCR_SetUpToDrawConsole
392 ==================
393 */
394 void SCR_SetUpToDrawConsole (void)
395 {
396         // lines of console to display
397         float conlines;
398         static int framecounter = 0;
399
400         Con_CheckResize ();
401
402         if (scr_menuforcewhiledisconnected.integer && key_dest == key_game && cls.state == ca_disconnected)
403         {
404                 if (framecounter >= 2)
405                         MR_ToggleMenu_f();
406                 else
407                         framecounter++;
408         }
409         else
410                 framecounter = 0;
411
412         if (scr_conforcewhiledisconnected.integer && key_dest == key_game && cls.signon != SIGNONS)
413                 key_consoleactive |= KEY_CONSOLEACTIVE_FORCED;
414         else
415                 key_consoleactive &= ~KEY_CONSOLEACTIVE_FORCED;
416
417 // decide on the height of the console
418         if (key_consoleactive & KEY_CONSOLEACTIVE_USER)
419                 conlines = vid_conheight.integer/2;     // half screen
420         else
421                 conlines = 0;                           // none visible
422
423         scr_con_current = conlines;
424 }
425
426 /*
427 ==================
428 SCR_DrawConsole
429 ==================
430 */
431 void SCR_DrawConsole (void)
432 {
433         if (key_consoleactive & KEY_CONSOLEACTIVE_FORCED)
434         {
435                 // full screen
436                 Con_DrawConsole (vid_conheight.integer);
437         }
438         else if (scr_con_current)
439                 Con_DrawConsole ((int)scr_con_current);
440         else
441         {
442                 con_vislines = 0;
443                 if ((key_dest == key_game || key_dest == key_message) && !r_letterbox.value)
444                         Con_DrawNotify ();      // only draw notify in game
445         }
446 }
447
448 /*
449 ===============
450 SCR_BeginLoadingPlaque
451
452 ================
453 */
454 void SCR_BeginLoadingPlaque (void)
455 {
456         // save console log up to this point to log_file if it was set by configs
457         Log_Start();
458
459         Host_StartVideo();
460         S_StopAllSounds();
461         SCR_UpdateLoadingScreen();
462 }
463
464 //=============================================================================
465
466 char r_speeds_string[1024];
467 int speedstringcount, r_timereport_active;
468 double r_timereport_temp = 0, r_timereport_current = 0, r_timereport_start = 0;
469
470 void R_TimeReport(char *desc)
471 {
472         char tempbuf[256];
473         int length;
474         int t;
475
476         if (r_speeds.integer < 2 || !r_timereport_active)
477                 return;
478
479         CHECKGLERROR
480         qglFinish();CHECKGLERROR
481         r_timereport_temp = r_timereport_current;
482         r_timereport_current = Sys_DoubleTime();
483         t = (int) ((r_timereport_current - r_timereport_temp) * 1000000.0 + 0.5);
484
485         dpsnprintf(tempbuf, sizeof(tempbuf), "%8i %-11s", t, desc);
486         length = (int)strlen(tempbuf);
487         if (speedstringcount + length > (vid_conwidth.integer / 8))
488         {
489                 strlcat(r_speeds_string, "\n", sizeof(r_speeds_string));
490                 speedstringcount = 0;
491         }
492         strlcat(r_speeds_string, tempbuf, sizeof(r_speeds_string));
493         speedstringcount += length;
494 }
495
496 void R_TimeReport_Frame(void)
497 {
498         int i, j, lines, y;
499
500         if (r_speeds_string[0])
501         {
502                 if (r_timereport_active)
503                 {
504                         r_timereport_current = r_timereport_start;
505                         R_TimeReport("total");
506                 }
507
508                 if (r_speeds_string[strlen(r_speeds_string)-1] == '\n')
509                         r_speeds_string[strlen(r_speeds_string)-1] = 0;
510                 lines = 1;
511                 for (i = 0;r_speeds_string[i];i++)
512                         if (r_speeds_string[i] == '\n')
513                                 lines++;
514                 y = vid_conheight.integer - sb_lines - lines * 8;
515                 i = j = 0;
516                 DrawQ_Pic(0, y, NULL, vid_conwidth.integer, lines * 8, 0, 0, 0, 0.5, 0);
517                 while (r_speeds_string[i])
518                 {
519                         j = i;
520                         while (r_speeds_string[i] && r_speeds_string[i] != '\n')
521                                 i++;
522                         if (i - j > 0)
523                                 DrawQ_String(0, y, r_speeds_string + j, i - j, 8, 8, 1, 1, 1, 1, 0);
524                         if (r_speeds_string[i] == '\n')
525                                 i++;
526                         y += 8;
527                 }
528                 r_speeds_string[0] = 0;
529                 r_timereport_active = false;
530         }
531         if (r_speeds.integer && cls.signon == SIGNONS && cls.state == ca_connected)
532         {
533                 speedstringcount = 0;
534                 r_speeds_string[0] = 0;
535                 r_timereport_active = false;
536                 sprintf(r_speeds_string + strlen(r_speeds_string), "org:'%+8.2f %+8.2f %+8.2f' dir:'%+2.3f %+2.3f %+2.3f'\n", r_view.origin[0], r_view.origin[1], r_view.origin[2], r_view.forward[0], r_view.forward[1], r_view.forward[2]);
537                 sprintf(r_speeds_string + strlen(r_speeds_string), "%5i entities%6i surfaces%6i triangles%5i leafs%5i portals%6i particles\n", r_refdef.stats.entities, r_refdef.stats.entities_surfaces, r_refdef.stats.entities_triangles, r_refdef.stats.world_leafs, r_refdef.stats.world_portals, r_refdef.stats.particles);
538                 sprintf(r_speeds_string + strlen(r_speeds_string), "%4i lights%4i clears%4i scissored%7i light%7i shadow%7i dynamic\n", r_refdef.stats.lights, r_refdef.stats.lights_clears, r_refdef.stats.lights_scissored, r_refdef.stats.lights_lighttriangles, r_refdef.stats.lights_shadowtriangles, r_refdef.stats.lights_dynamicshadowtriangles);
539                 if (r_refdef.stats.bloom)
540                         sprintf(r_speeds_string + strlen(r_speeds_string), "rendered%6i meshes%8i triangles bloompixels%8i copied%8i drawn\n", r_refdef.stats.meshes, r_refdef.stats.meshes_elements / 3, r_refdef.stats.bloom_copypixels, r_refdef.stats.bloom_drawpixels);
541                 else
542                         sprintf(r_speeds_string + strlen(r_speeds_string), "rendered%6i meshes%8i triangles\n", r_refdef.stats.meshes, r_refdef.stats.meshes_elements / 3);
543
544                 memset(&r_refdef.stats, 0, sizeof(r_refdef.stats));
545
546                 if (r_speeds.integer >= 2)
547                 {
548                         r_timereport_active = true;
549                         r_timereport_start = r_timereport_current = Sys_DoubleTime();
550                 }
551         }
552 }
553
554 /*
555 =================
556 SCR_SizeUp_f
557
558 Keybinding command
559 =================
560 */
561 void SCR_SizeUp_f (void)
562 {
563         Cvar_SetValue ("viewsize",scr_viewsize.value+10);
564 }
565
566
567 /*
568 =================
569 SCR_SizeDown_f
570
571 Keybinding command
572 =================
573 */
574 void SCR_SizeDown_f (void)
575 {
576         Cvar_SetValue ("viewsize",scr_viewsize.value-10);
577 }
578
579 void CL_Screen_Init(void)
580 {
581         Cvar_RegisterVariable (&scr_fov);
582         Cvar_RegisterVariable (&scr_viewsize);
583         Cvar_RegisterVariable (&scr_conalpha);
584         Cvar_RegisterVariable (&scr_conbrightness);
585         Cvar_RegisterVariable (&scr_conforcewhiledisconnected);
586         Cvar_RegisterVariable (&scr_menuforcewhiledisconnected);
587         Cvar_RegisterVariable (&scr_showram);
588         Cvar_RegisterVariable (&scr_showturtle);
589         Cvar_RegisterVariable (&scr_showpause);
590         Cvar_RegisterVariable (&scr_showbrand);
591         Cvar_RegisterVariable (&scr_centertime);
592         Cvar_RegisterVariable (&scr_printspeed);
593         Cvar_RegisterVariable (&vid_conwidth);
594         Cvar_RegisterVariable (&vid_conheight);
595         Cvar_RegisterVariable (&vid_pixelheight);
596         Cvar_RegisterVariable (&scr_screenshot_jpeg);
597         Cvar_RegisterVariable (&scr_screenshot_jpeg_quality);
598         Cvar_RegisterVariable (&scr_screenshot_gammaboost);
599         Cvar_RegisterVariable (&cl_capturevideo);
600         Cvar_RegisterVariable (&cl_capturevideo_fps);
601         Cvar_RegisterVariable (&cl_capturevideo_rawrgb);
602         Cvar_RegisterVariable (&cl_capturevideo_rawyv12);
603         Cvar_RegisterVariable (&cl_capturevideo_number);
604         Cvar_RegisterVariable (&r_letterbox);
605         Cvar_RegisterVariable(&r_stereo_separation);
606         Cvar_RegisterVariable(&r_stereo_sidebyside);
607         Cvar_RegisterVariable(&r_stereo_redblue);
608         Cvar_RegisterVariable(&r_stereo_redcyan);
609         Cvar_RegisterVariable(&r_stereo_redgreen);
610         Cvar_RegisterVariable(&scr_zoomwindow);
611         Cvar_RegisterVariable(&scr_zoomwindow_viewsizex);
612         Cvar_RegisterVariable(&scr_zoomwindow_viewsizey);
613         Cvar_RegisterVariable(&scr_zoomwindow_fov);
614
615         Cmd_AddCommand ("sizeup",SCR_SizeUp_f, "increase view size (increases viewsize cvar)");
616         Cmd_AddCommand ("sizedown",SCR_SizeDown_f, "decrease view size (decreases viewsize cvar)");
617         Cmd_AddCommand ("screenshot",SCR_ScreenShot_f, "takes a screenshot of the next rendered frame");
618         Cmd_AddCommand ("envmap", R_Envmap_f, "render a cubemap (skybox) of the current scene");
619
620         scr_initialized = true;
621 }
622
623 /*
624 ==================
625 SCR_ScreenShot_f
626 ==================
627 */
628 void SCR_ScreenShot_f (void)
629 {
630         static int shotnumber;
631         static char oldname[MAX_QPATH];
632         char base[MAX_QPATH];
633         char filename[MAX_QPATH];
634         unsigned char *buffer1;
635         unsigned char *buffer2;
636         unsigned char *buffer3;
637         qboolean jpeg = (scr_screenshot_jpeg.integer != 0);
638
639         sprintf (base, "screenshots/%s", scr_screenshot_name.string);
640
641         if (strcmp (oldname, scr_screenshot_name.string))
642         {
643                 sprintf(oldname, "%s", scr_screenshot_name.string);
644                 shotnumber = 0;
645         }
646
647         // find a file name to save it to
648         for (;shotnumber < 1000000;shotnumber++)
649                 if (!FS_SysFileExists(va("%s/%s%06d.tga", fs_gamedir, base, shotnumber)) && !FS_SysFileExists(va("%s/%s%06d.jpg", fs_gamedir, base, shotnumber)))
650                         break;
651         if (shotnumber >= 1000000)
652         {
653                 Con_Print("SCR_ScreenShot_f: Couldn't create the image file\n");
654                 return;
655         }
656
657         sprintf(filename, "%s%06d.%s", base, shotnumber, jpeg ? "jpg" : "tga");
658
659         buffer1 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3);
660         buffer2 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3);
661         buffer3 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3 + 18);
662
663         if (SCR_ScreenShot (filename, buffer1, buffer2, buffer3, 0, 0, vid.width, vid.height, false, false, false, jpeg, true))
664                 Con_Printf("Wrote %s\n", filename);
665         else
666                 Con_Printf("unable to write %s\n", filename);
667
668         Mem_Free (buffer1);
669         Mem_Free (buffer2);
670         Mem_Free (buffer3);
671
672         shotnumber++;
673 }
674
675 void SCR_CaptureVideo_BeginVideo(void)
676 {
677         double gamma, g;
678         unsigned int i;
679         unsigned char out[44];
680         if (cls.capturevideo_active)
681                 return;
682         // soundrate is figured out on the first SoundFrame
683         cls.capturevideo_active = true;
684         cls.capturevideo_starttime = Sys_DoubleTime();
685         cls.capturevideo_framerate = bound(1, cl_capturevideo_fps.value, 1000);
686         cls.capturevideo_soundrate = 0;
687         cls.capturevideo_frame = 0;
688         cls.capturevideo_buffer = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * (3+3+3) + 18);
689         gamma = 1.0/scr_screenshot_gammaboost.value;
690         dpsnprintf(cls.capturevideo_basename, sizeof(cls.capturevideo_basename), "video/dpvideo%03i", cl_capturevideo_number.integer);
691         Cvar_SetValueQuick(&cl_capturevideo_number, cl_capturevideo_number.integer + 1);
692
693         /*
694         for (i = 0;i < 256;i++)
695         {
696                 unsigned char j = (unsigned char)bound(0, 255*pow(i/255.0, gamma), 255);
697                 cls.capturevideo_rgbgammatable[0][i] = j;
698                 cls.capturevideo_rgbgammatable[1][i] = j;
699                 cls.capturevideo_rgbgammatable[2][i] = j;
700         }
701         */
702 /*
703 R = Y + 1.4075 * (Cr - 128);
704 G = Y + -0.3455 * (Cb - 128) + -0.7169 * (Cr - 128);
705 B = Y + 1.7790 * (Cb - 128);
706 Y = R *  .299 + G *  .587 + B *  .114;
707 Cb = R * -.169 + G * -.332 + B *  .500 + 128.;
708 Cr = R *  .500 + G * -.419 + B * -.0813 + 128.;
709 */
710         for (i = 0;i < 256;i++)
711         {
712                 g = 255*pow(i/255.0, gamma);
713                 // Y weights from RGB
714                 cls.capturevideo_rgbtoyuvscaletable[0][0][i] = (short)(g *  0.299);
715                 cls.capturevideo_rgbtoyuvscaletable[0][1][i] = (short)(g *  0.587);
716                 cls.capturevideo_rgbtoyuvscaletable[0][2][i] = (short)(g *  0.114);
717                 // Cb weights from RGB
718                 cls.capturevideo_rgbtoyuvscaletable[1][0][i] = (short)(g * -0.169);
719                 cls.capturevideo_rgbtoyuvscaletable[1][1][i] = (short)(g * -0.332);
720                 cls.capturevideo_rgbtoyuvscaletable[1][2][i] = (short)(g *  0.500);
721                 // Cr weights from RGB
722                 cls.capturevideo_rgbtoyuvscaletable[2][0][i] = (short)(g *  0.500);
723                 cls.capturevideo_rgbtoyuvscaletable[2][1][i] = (short)(g * -0.419);
724                 cls.capturevideo_rgbtoyuvscaletable[2][2][i] = (short)(g * -0.0813);
725                 // range reduction of YCbCr to valid signal range
726                 cls.capturevideo_yuvnormalizetable[0][i] = 16 + i * (236-16) / 256;
727                 cls.capturevideo_yuvnormalizetable[1][i] = 16 + i * (240-16) / 256;
728                 cls.capturevideo_yuvnormalizetable[2][i] = 16 + i * (240-16) / 256;
729         }
730
731         if (cl_capturevideo_rawrgb.integer)
732         {
733                 cls.capturevideo_format = CAPTUREVIDEOFORMAT_RAWRGB;
734                 cls.capturevideo_videofile = FS_Open (va("%s.rgb", cls.capturevideo_basename), "wb", false, true);
735         }
736         else if (cl_capturevideo_rawyv12.integer)
737         {
738                 cls.capturevideo_format = CAPTUREVIDEOFORMAT_RAWYV12;
739                 cls.capturevideo_videofile = FS_Open (va("%s.yv12", cls.capturevideo_basename), "wb", false, true);
740         }
741         else if (scr_screenshot_jpeg.integer)
742         {
743                 cls.capturevideo_format = CAPTUREVIDEOFORMAT_JPEG;
744                 cls.capturevideo_videofile = NULL;
745         }
746         else
747         {
748                 cls.capturevideo_format = CAPTUREVIDEOFORMAT_TARGA;
749                 cls.capturevideo_videofile = NULL;
750         }
751
752         cls.capturevideo_soundfile = FS_Open (va("%s.wav", cls.capturevideo_basename), "wb", false, true);
753         if (cls.capturevideo_soundfile)
754         {
755                 // wave header will be filled out when video ends
756                 memset(out, 0, 44);
757                 FS_Write (cls.capturevideo_soundfile, out, 44);
758         }
759         else
760                 Con_Printf("Could not open video/dpvideo.wav for writing, sound capture disabled\n");
761 }
762
763 void SCR_CaptureVideo_EndVideo(void)
764 {
765         int i, n;
766         unsigned char out[44];
767         if (!cls.capturevideo_active)
768                 return;
769         cls.capturevideo_active = false;
770
771         if (cls.capturevideo_videofile)
772         {
773                 FS_Close(cls.capturevideo_videofile);
774                 cls.capturevideo_videofile = NULL;
775         }
776
777         // finish the wave file
778         if (cls.capturevideo_soundfile)
779         {
780                 i = (int)FS_Tell (cls.capturevideo_soundfile);
781                 //"RIFF", (int) unknown (chunk size), "WAVE",
782                 //"fmt ", (int) 16 (chunk size), (short) format 1 (uncompressed PCM), (short) 2 channels, (int) unknown rate, (int) unknown bytes per second, (short) 4 bytes per sample (channels * bytes per channel), (short) 16 bits per channel
783                 //"data", (int) unknown (chunk size)
784                 memcpy (out, "RIFF****WAVEfmt \x10\x00\x00\x00\x01\x00\x02\x00********\x04\x00\x10\0data****", 44);
785                 // the length of the whole RIFF chunk
786                 n = i - 8;
787                 out[4] = (n) & 0xFF;
788                 out[5] = (n >> 8) & 0xFF;
789                 out[6] = (n >> 16) & 0xFF;
790                 out[7] = (n >> 24) & 0xFF;
791                 // rate
792                 n = cls.capturevideo_soundrate;
793                 out[24] = (n) & 0xFF;
794                 out[25] = (n >> 8) & 0xFF;
795                 out[26] = (n >> 16) & 0xFF;
796                 out[27] = (n >> 24) & 0xFF;
797                 // bytes per second (rate * channels * bytes per channel)
798                 n = cls.capturevideo_soundrate * 2 * 2;
799                 out[28] = (n) & 0xFF;
800                 out[29] = (n >> 8) & 0xFF;
801                 out[30] = (n >> 16) & 0xFF;
802                 out[31] = (n >> 24) & 0xFF;
803                 // the length of the data chunk
804                 n = i - 44;
805                 out[40] = (n) & 0xFF;
806                 out[41] = (n >> 8) & 0xFF;
807                 out[42] = (n >> 16) & 0xFF;
808                 out[43] = (n >> 24) & 0xFF;
809                 FS_Seek (cls.capturevideo_soundfile, 0, SEEK_SET);
810                 FS_Write (cls.capturevideo_soundfile, out, 44);
811                 FS_Close (cls.capturevideo_soundfile);
812                 cls.capturevideo_soundfile = NULL;
813         }
814
815         if (cls.capturevideo_buffer)
816         {
817                 Mem_Free (cls.capturevideo_buffer);
818                 cls.capturevideo_buffer = NULL;
819         }
820
821         cls.capturevideo_starttime = 0;
822         cls.capturevideo_framerate = 0;
823         cls.capturevideo_frame = 0;
824 }
825
826 qboolean SCR_CaptureVideo_VideoFrame(int newframenum)
827 {
828         int x = 0, y = 0, width = vid.width, height = vid.height;
829         unsigned char *b, *out;
830         char filename[32];
831         int outoffset = (width/2)*(height/2);
832         CHECKGLERROR
833         //return SCR_ScreenShot(filename, cls.capturevideo_buffer, cls.capturevideo_buffer + vid.width * vid.height * 3, cls.capturevideo_buffer + vid.width * vid.height * 6, 0, 0, vid.width, vid.height, false, false, false, jpeg, true);
834         // speed is critical here, so do saving as directly as possible
835         switch (cls.capturevideo_format)
836         {
837         case CAPTUREVIDEOFORMAT_RAWYV12:
838                 // if there's no videofile we have to just give up, and abort saving if there's no video or sound file
839                 if (!cls.capturevideo_videofile)
840                         return cls.capturevideo_soundfile != NULL;
841                 // FIXME: width/height must be multiple of 2, enforce this?
842                 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cls.capturevideo_buffer);CHECKGLERROR
843                 // process one line at a time, and CbCr every other line at 2 pixel intervals
844                 for (y = 0;y < height;y++)
845                 {
846                         // 1x1 Y
847                         for (b = cls.capturevideo_buffer + (height-1-y)*width*3, out = cls.capturevideo_buffer + width*height*3 + y*width, x = 0;x < width;x++, b += 3, out++)
848                                 *out = cls.capturevideo_yuvnormalizetable[0][cls.capturevideo_rgbtoyuvscaletable[0][0][b[0]] + cls.capturevideo_rgbtoyuvscaletable[0][1][b[1]] + cls.capturevideo_rgbtoyuvscaletable[0][2][b[2]]];
849                         if ((y & 1) == 0)
850                         {
851                                 // 2x2 Cb and Cr planes
852 #if 1
853                                 // low quality, no averaging
854                                 for (b = cls.capturevideo_buffer + (height-2-y)*width*3, out = cls.capturevideo_buffer + width*height*3 + width*height + (y/2)*(width/2), x = 0;x < width/2;x++, b += 6, out++)
855                                 {
856                                         // Cr
857                                         out[0        ] = cls.capturevideo_yuvnormalizetable[2][cls.capturevideo_rgbtoyuvscaletable[2][0][b[0]] + cls.capturevideo_rgbtoyuvscaletable[2][1][b[1]] + cls.capturevideo_rgbtoyuvscaletable[2][2][b[2]] + 128];
858                                         // Cb
859                                         out[outoffset] = cls.capturevideo_yuvnormalizetable[1][cls.capturevideo_rgbtoyuvscaletable[1][0][b[0]] + cls.capturevideo_rgbtoyuvscaletable[1][1][b[1]] + cls.capturevideo_rgbtoyuvscaletable[1][2][b[2]] + 128];
860                                 }
861 #else
862                                 // high quality, averaging
863                                 int inpitch = width*3;
864                                 for (b = cls.capturevideo_buffer + (height-2-y)*width*3, out = cls.capturevideo_buffer + width*height*3 + width*height + (y/2)*(width/2), x = 0;x < width/2;x++, b += 6, out++)
865                                 {
866                                         int blockr, blockg, blockb;
867                                         blockr = (b[0] + b[3] + b[inpitch+0] + b[inpitch+3]) >> 2;
868                                         blockg = (b[1] + b[4] + b[inpitch+1] + b[inpitch+4]) >> 2;
869                                         blockb = (b[2] + b[5] + b[inpitch+2] + b[inpitch+5]) >> 2;
870                                         // Cr
871                                         out[0        ] = cls.capturevideo_yuvnormalizetable[2][cls.capturevideo_rgbtoyuvscaletable[2][0][blockr] + cls.capturevideo_rgbtoyuvscaletable[2][1][blockg] + cls.capturevideo_rgbtoyuvscaletable[2][2][blockb] + 128];
872                                         // Cb
873                                         out[outoffset] = cls.capturevideo_yuvnormalizetable[1][cls.capturevideo_rgbtoyuvscaletable[1][0][blockr] + cls.capturevideo_rgbtoyuvscaletable[1][1][blockg] + cls.capturevideo_rgbtoyuvscaletable[1][2][blockb] + 128];
874                                 }
875 #endif
876                         }
877                 }
878                 for (;cls.capturevideo_frame < newframenum;cls.capturevideo_frame++)
879                         if (!FS_Write (cls.capturevideo_videofile, cls.capturevideo_buffer + width*height*3, width*height+(width/2)*(height/2)*2))
880                                 return false;
881                 return true;
882         case CAPTUREVIDEOFORMAT_RAWRGB:
883                 // if there's no videofile we have to just give up, and abort saving if there's no video or sound file
884                 if (!cls.capturevideo_videofile)
885                         return cls.capturevideo_soundfile != NULL;
886                 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cls.capturevideo_buffer);CHECKGLERROR
887                 for (;cls.capturevideo_frame < newframenum;cls.capturevideo_frame++)
888                         if (!FS_Write (cls.capturevideo_videofile, cls.capturevideo_buffer, width*height*3))
889                                 return false;
890                 return true;
891         case CAPTUREVIDEOFORMAT_JPEG:
892                 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cls.capturevideo_buffer);CHECKGLERROR
893                 for (;cls.capturevideo_frame < newframenum;cls.capturevideo_frame++)
894                 {
895                         sprintf(filename, "%s_%06d.jpg", cls.capturevideo_basename, cls.capturevideo_frame);
896                         if (!JPEG_SaveImage_preflipped (filename, width, height, cls.capturevideo_buffer))
897                                 return false;
898                 }
899                 return true;
900         case CAPTUREVIDEOFORMAT_TARGA:
901                 //return Image_WriteTGARGB_preflipped (filename, width, height, cls.capturevideo_buffer, cls.capturevideo_buffer + vid.width * vid.height * 3, );
902                 memset (cls.capturevideo_buffer, 0, 18);
903                 cls.capturevideo_buffer[2] = 2;         // uncompressed type
904                 cls.capturevideo_buffer[12] = (width >> 0) & 0xFF;
905                 cls.capturevideo_buffer[13] = (width >> 8) & 0xFF;
906                 cls.capturevideo_buffer[14] = (height >> 0) & 0xFF;
907                 cls.capturevideo_buffer[15] = (height >> 8) & 0xFF;
908                 cls.capturevideo_buffer[16] = 24;       // pixel size
909                 qglReadPixels (x, y, width, height, GL_BGR, GL_UNSIGNED_BYTE, cls.capturevideo_buffer + 18);CHECKGLERROR
910                 for (;cls.capturevideo_frame < newframenum;cls.capturevideo_frame++)
911                 {
912                         sprintf(filename, "%s_%06d.tga", cls.capturevideo_basename, cls.capturevideo_frame);
913                         if (!FS_WriteFile (filename, cls.capturevideo_buffer, width*height*3 + 18))
914                                 return false;
915                 }
916                 return true;
917         default:
918                 return false;
919         }
920 }
921
922 void SCR_CaptureVideo_SoundFrame(unsigned char *bufstereo16le, size_t length, int rate)
923 {
924         if (!cls.capturevideo_soundfile)
925                 return;
926         cls.capturevideo_soundrate = rate;
927         if (FS_Write (cls.capturevideo_soundfile, bufstereo16le, 4 * length) < (fs_offset_t)(4 * length))
928         {
929                 Cvar_SetValueQuick(&cl_capturevideo, 0);
930                 Con_Printf("video sound saving failed on frame %i, out of disk space? stopping video capture.\n", cls.capturevideo_frame);
931                 SCR_CaptureVideo_EndVideo();
932         }
933 }
934
935 void SCR_CaptureVideo(void)
936 {
937         int newframenum;
938         if (cl_capturevideo.integer && r_render.integer)
939         {
940                 if (!cls.capturevideo_active)
941                         SCR_CaptureVideo_BeginVideo();
942                 if (cls.capturevideo_framerate != cl_capturevideo_fps.value)
943                 {
944                         Con_Printf("You can not change the video framerate while recording a video.\n");
945                         Cvar_SetValueQuick(&cl_capturevideo_fps, cls.capturevideo_framerate);
946                 }
947 #if 0
948                 if (cls.capturevideo_soundfile)
949                 {
950                         // preserve sound sync by duplicating frames when running slow
951                         newframenum = (int)((Sys_DoubleTime() - cls.capturevideo_starttime) * cls.capturevideo_framerate);
952                 }
953                 else
954 #endif
955                         newframenum = cls.capturevideo_frame + 1;
956                 // if falling behind more than one second, stop
957                 if (newframenum - cls.capturevideo_frame > (int)ceil(cls.capturevideo_framerate))
958                 {
959                         Cvar_SetValueQuick(&cl_capturevideo, 0);
960                         Con_Printf("video saving failed on frame %i, your machine is too slow for this capture speed.\n", cls.capturevideo_frame);
961                         SCR_CaptureVideo_EndVideo();
962                         return;
963                 }
964                 // write frames
965                 if (!SCR_CaptureVideo_VideoFrame(newframenum))
966                 {
967                         Cvar_SetValueQuick(&cl_capturevideo, 0);
968                         Con_Printf("video saving failed on frame %i, out of disk space? stopping video capture.\n", cls.capturevideo_frame);
969                         SCR_CaptureVideo_EndVideo();
970                 }
971         }
972         else if (cls.capturevideo_active)
973                 SCR_CaptureVideo_EndVideo();
974 }
975
976 /*
977 ===============
978 R_Envmap_f
979
980 Grab six views for environment mapping tests
981 ===============
982 */
983 struct envmapinfo_s
984 {
985         float angles[3];
986         char *name;
987         qboolean flipx, flipy, flipdiagonaly;
988 }
989 envmapinfo[12] =
990 {
991         {{  0,   0, 0}, "rt", false, false, false},
992         {{  0, 270, 0}, "ft", false, false, false},
993         {{  0, 180, 0}, "lf", false, false, false},
994         {{  0,  90, 0}, "bk", false, false, false},
995         {{-90, 180, 0}, "up",  true,  true, false},
996         {{ 90, 180, 0}, "dn",  true,  true, false},
997
998         {{  0,   0, 0}, "px",  true,  true,  true},
999         {{  0,  90, 0}, "py", false,  true, false},
1000         {{  0, 180, 0}, "nx", false, false,  true},
1001         {{  0, 270, 0}, "ny",  true, false, false},
1002         {{-90, 180, 0}, "pz", false, false,  true},
1003         {{ 90, 180, 0}, "nz", false, false,  true}
1004 };
1005
1006 static void R_Envmap_f (void)
1007 {
1008         int j, size;
1009         char filename[MAX_QPATH], basename[MAX_QPATH];
1010         unsigned char *buffer1;
1011         unsigned char *buffer2;
1012         unsigned char *buffer3;
1013
1014         if (Cmd_Argc() != 3)
1015         {
1016                 Con_Print("envmap <basename> <size>: save out 6 cubic environment map images, usable with loadsky, note that size must one of 128, 256, 512, or 1024 and can't be bigger than your current resolution\n");
1017                 return;
1018         }
1019
1020         strlcpy (basename, Cmd_Argv(1), sizeof (basename));
1021         size = atoi(Cmd_Argv(2));
1022         if (size != 128 && size != 256 && size != 512 && size != 1024)
1023         {
1024                 Con_Print("envmap: size must be one of 128, 256, 512, or 1024\n");
1025                 return;
1026         }
1027         if (size > vid.width || size > vid.height)
1028         {
1029                 Con_Print("envmap: your resolution is not big enough to render that size\n");
1030                 return;
1031         }
1032
1033         r_refdef.envmap = true;
1034
1035         R_UpdateVariables();
1036
1037         r_view.x = 0;
1038         r_view.y = 0;
1039         r_view.z = 0;
1040         r_view.width = size;
1041         r_view.height = size;
1042         r_view.depth = 1;
1043
1044         r_view.frustum_x = tan(90 * M_PI / 360.0);
1045         r_view.frustum_y = tan(90 * M_PI / 360.0);
1046
1047         buffer1 = (unsigned char *)Mem_Alloc(tempmempool, size * size * 3);
1048         buffer2 = (unsigned char *)Mem_Alloc(tempmempool, size * size * 3);
1049         buffer3 = (unsigned char *)Mem_Alloc(tempmempool, size * size * 3 + 18);
1050
1051         for (j = 0;j < 12;j++)
1052         {
1053                 sprintf(filename, "env/%s%s.tga", basename, envmapinfo[j].name);
1054                 Matrix4x4_CreateFromQuakeEntity(&r_view.matrix, r_view.origin[0], r_view.origin[1], r_view.origin[2], envmapinfo[j].angles[0], envmapinfo[j].angles[1], envmapinfo[j].angles[2], 1);
1055                 R_ClearScreen();
1056                 R_Mesh_Start();
1057                 R_RenderView();
1058                 R_Mesh_Finish();
1059                 SCR_ScreenShot(filename, buffer1, buffer2, buffer3, 0, vid.height - (r_view.y + r_view.height), size, size, envmapinfo[j].flipx, envmapinfo[j].flipy, envmapinfo[j].flipdiagonaly, false, false);
1060         }
1061
1062         Mem_Free (buffer1);
1063         Mem_Free (buffer2);
1064         Mem_Free (buffer3);
1065
1066         r_refdef.envmap = false;
1067 }
1068
1069 //=============================================================================
1070
1071 // LordHavoc: SHOWLMP stuff
1072 #define SHOWLMP_MAXLABELS 256
1073 typedef struct showlmp_s
1074 {
1075         qboolean        isactive;
1076         float           x;
1077         float           y;
1078         char            label[32];
1079         char            pic[128];
1080 }
1081 showlmp_t;
1082
1083 showlmp_t showlmp[SHOWLMP_MAXLABELS];
1084
1085 void SHOWLMP_decodehide(void)
1086 {
1087         int i;
1088         char *lmplabel;
1089         lmplabel = MSG_ReadString();
1090         for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1091                 if (showlmp[i].isactive && strcmp(showlmp[i].label, lmplabel) == 0)
1092                 {
1093                         showlmp[i].isactive = false;
1094                         return;
1095                 }
1096 }
1097
1098 void SHOWLMP_decodeshow(void)
1099 {
1100         int i, k;
1101         char lmplabel[256], picname[256];
1102         float x, y;
1103         strlcpy (lmplabel,MSG_ReadString(), sizeof (lmplabel));
1104         strlcpy (picname, MSG_ReadString(), sizeof (picname));
1105         if (gamemode == GAME_NEHAHRA) // LordHavoc: nasty old legacy junk
1106         {
1107                 x = MSG_ReadByte();
1108                 y = MSG_ReadByte();
1109         }
1110         else
1111         {
1112                 x = MSG_ReadShort();
1113                 y = MSG_ReadShort();
1114         }
1115         k = -1;
1116         for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1117                 if (showlmp[i].isactive)
1118                 {
1119                         if (strcmp(showlmp[i].label, lmplabel) == 0)
1120                         {
1121                                 k = i;
1122                                 break; // drop out to replace it
1123                         }
1124                 }
1125                 else if (k < 0) // find first empty one to replace
1126                         k = i;
1127         if (k < 0)
1128                 return; // none found to replace
1129         // change existing one
1130         showlmp[k].isactive = true;
1131         strlcpy (showlmp[k].label, lmplabel, sizeof (showlmp[k].label));
1132         strlcpy (showlmp[k].pic, picname, sizeof (showlmp[k].pic));
1133         showlmp[k].x = x;
1134         showlmp[k].y = y;
1135 }
1136
1137 void SHOWLMP_drawall(void)
1138 {
1139         int i;
1140         for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1141                 if (showlmp[i].isactive)
1142                         DrawQ_Pic(showlmp[i].x, showlmp[i].y, Draw_CachePic(showlmp[i].pic, true), 0, 0, 1, 1, 1, 1, 0);
1143 }
1144
1145 void SHOWLMP_clear(void)
1146 {
1147         int i;
1148         for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1149                 showlmp[i].isactive = false;
1150 }
1151
1152 /*
1153 ==============================================================================
1154
1155                                                 SCREEN SHOTS
1156
1157 ==============================================================================
1158 */
1159
1160 qboolean SCR_ScreenShot(char *filename, unsigned char *buffer1, unsigned char *buffer2, unsigned char *buffer3, int x, int y, int width, int height, qboolean flipx, qboolean flipy, qboolean flipdiagonal, qboolean jpeg, qboolean gammacorrect)
1161 {
1162         int     indices[3] = {0,1,2};
1163         qboolean ret;
1164
1165         if (!r_render.integer)
1166                 return false;
1167
1168         CHECKGLERROR
1169         qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, buffer1);CHECKGLERROR
1170
1171         if (scr_screenshot_gammaboost.value != 1 && gammacorrect)
1172         {
1173                 int i;
1174                 double igamma = 1.0 / scr_screenshot_gammaboost.value;
1175                 unsigned char ramp[256];
1176                 for (i = 0;i < 256;i++)
1177                         ramp[i] = (unsigned char) (pow(i * (1.0 / 255.0), igamma) * 255.0);
1178                 for (i = 0;i < width*height*3;i++)
1179                         buffer1[i] = ramp[buffer1[i]];
1180         }
1181
1182         Image_CopyMux (buffer2, buffer1, width, height, flipx, flipy, flipdiagonal, 3, 3, indices);
1183
1184         if (jpeg)
1185                 ret = JPEG_SaveImage_preflipped (filename, width, height, buffer2);
1186         else
1187                 ret = Image_WriteTGARGB_preflipped (filename, width, height, buffer2, buffer3);
1188
1189         return ret;
1190 }
1191
1192 //=============================================================================
1193
1194 void R_ClearScreen(void)
1195 {
1196         // clear to black
1197         CHECKGLERROR
1198         if (r_refdef.fogenabled)
1199         {
1200                 qglClearColor(r_refdef.fogcolor[0],r_refdef.fogcolor[1],r_refdef.fogcolor[2],0);CHECKGLERROR
1201         }
1202         else
1203         {
1204                 qglClearColor(0,0,0,0);CHECKGLERROR
1205         }
1206         qglClearDepth(1);CHECKGLERROR
1207         if (gl_stencil)
1208         {
1209                 // LordHavoc: we use a stencil centered around 128 instead of 0,
1210                 // to avoid clamping interfering with strange shadow volume
1211                 // drawing orders
1212                 qglClearStencil(128);CHECKGLERROR
1213         }
1214         // clear the screen
1215         if (r_render.integer)
1216                 GL_Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | (gl_stencil ? GL_STENCIL_BUFFER_BIT : 0));
1217         // set dithering mode
1218         if (gl_dither.integer)
1219         {
1220                 qglEnable(GL_DITHER);CHECKGLERROR
1221         }
1222         else
1223         {
1224                 qglDisable(GL_DITHER);CHECKGLERROR
1225         }
1226 }
1227
1228 qboolean CL_VM_UpdateView (void);
1229 void SCR_DrawConsole (void);
1230 void R_Shadow_EditLights_DrawSelectedLightProperties(void);
1231
1232 int r_stereo_side;
1233
1234 void SCR_DrawScreen (void)
1235 {
1236         R_Mesh_Start();
1237
1238         if (r_timereport_active)
1239                 R_TimeReport("setup");
1240
1241         R_UpdateVariables();
1242
1243         if (cls.signon == SIGNONS)
1244         {
1245                 float size;
1246
1247                 size = scr_viewsize.value * (1.0 / 100.0);
1248                 size = min(size, 1);
1249
1250                 if (r_stereo_sidebyside.integer)
1251                 {
1252                         r_view.width = (int)(vid.width * size / 2.5);
1253                         r_view.height = (int)(vid.height * size / 2.5 * (1 - bound(0, r_letterbox.value, 100) / 100));
1254                         r_view.depth = 1;
1255                         r_view.x = (int)((vid.width - r_view.width * 2.5) * 0.5);
1256                         r_view.y = (int)((vid.height - r_view.height)/2);
1257                         r_view.z = 0;
1258                         if (r_stereo_side)
1259                                 r_view.x += (int)(r_view.width * 1.5);
1260                 }
1261                 else
1262                 {
1263                         r_view.width = (int)(vid.width * size);
1264                         r_view.height = (int)(vid.height * size * (1 - bound(0, r_letterbox.value, 100) / 100));
1265                         r_view.depth = 1;
1266                         r_view.x = (int)((vid.width - r_view.width)/2);
1267                         r_view.y = (int)((vid.height - r_view.height)/2);
1268                         r_view.z = 0;
1269                 }
1270
1271                 // LordHavoc: viewzoom (zoom in for sniper rifles, etc)
1272                 // LordHavoc: this is designed to produce widescreen fov values
1273                 // when the screen is wider than 4/3 width/height aspect, to do
1274                 // this it simply assumes the requested fov is the vertical fov
1275                 // for a 4x3 display, if the ratio is not 4x3 this makes the fov
1276                 // higher/lower according to the ratio
1277                 r_view.frustum_y = tan(scr_fov.value * cl.viewzoom * M_PI / 360.0) * (3.0/4.0);
1278                 r_view.frustum_x = r_view.frustum_y * (float)r_view.width / (float)r_view.height / vid_pixelheight.value;
1279
1280                 r_view.frustum_x *= r_refdef.frustumscale_x;
1281                 r_view.frustum_y *= r_refdef.frustumscale_y;
1282
1283                 if(!CL_VM_UpdateView())
1284                         R_RenderView();
1285                 else
1286                         SCR_DrawConsole();
1287
1288                 if (scr_zoomwindow.integer)
1289                 {
1290                         float sizex = bound(10, scr_zoomwindow_viewsizex.value, 100) / 100.0;
1291                         float sizey = bound(10, scr_zoomwindow_viewsizey.value, 100) / 100.0;
1292                         r_view.width = (int)(vid.width * sizex);
1293                         r_view.height = (int)(vid.height * sizey);
1294                         r_view.depth = 1;
1295                         r_view.x = (int)((vid.width - r_view.width)/2);
1296                         r_view.y = 0;
1297                         r_view.z = 0;
1298
1299                         r_view.frustum_y = tan(scr_zoomwindow_fov.value * cl.viewzoom * M_PI / 360.0) * (3.0/4.0);
1300                         r_view.frustum_x = r_view.frustum_y * vid_pixelheight.value * (float)r_view.width / (float)r_view.height;
1301
1302                         r_view.frustum_x *= r_refdef.frustumscale_x;
1303                         r_view.frustum_y *= r_refdef.frustumscale_y;
1304
1305                         if(!CL_VM_UpdateView())
1306                                 R_RenderView();
1307                 }
1308         }
1309
1310         if (!r_stereo_sidebyside.integer)
1311         {
1312                 r_view.width = vid.width;
1313                 r_view.height = vid.height;
1314                 r_view.depth = 1;
1315                 r_view.x = 0;
1316                 r_view.y = 0;
1317                 r_view.z = 0;
1318         }
1319
1320         // draw 2D stuff
1321         DrawQ_Begin();
1322
1323         //FIXME: force menu if nothing else to look at?
1324         //if (key_dest == key_game && cls.signon != SIGNONS && cls.state == ca_disconnected)
1325
1326         if (cls.signon == SIGNONS)
1327         {
1328                 SCR_DrawNet ();
1329                 SCR_DrawTurtle ();
1330                 SCR_DrawPause ();
1331                 if (!r_letterbox.value)
1332                         Sbar_Draw();
1333                 SHOWLMP_drawall();
1334                 SCR_CheckDrawCenterString();
1335         }
1336         MR_Draw();
1337         CL_DrawVideo();
1338         R_Shadow_EditLights_DrawSelectedLightProperties();
1339
1340         if(!csqc_loaded)
1341                 SCR_DrawConsole();
1342
1343         SCR_DrawBrand();
1344
1345         SCR_DrawDownload();
1346
1347         if (r_timereport_active)
1348                 R_TimeReport("2d");
1349
1350         if (cls.signon == SIGNONS)
1351                 R_TimeReport_Frame();
1352
1353         DrawQ_Finish();
1354
1355         R_DrawGamma();
1356
1357         R_Mesh_Finish();
1358
1359         if (r_timereport_active)
1360                 R_TimeReport("meshfinish");
1361 }
1362
1363 void SCR_UpdateLoadingScreen (void)
1364 {
1365         float x, y;
1366         cachepic_t *pic;
1367         float vertex3f[12];
1368         float texcoord2f[8];
1369         // don't do anything if not initialized yet
1370         if (vid_hidden)
1371                 return;
1372         CHECKGLERROR
1373         qglViewport(0, 0, vid.width, vid.height);CHECKGLERROR
1374         //qglDisable(GL_SCISSOR_TEST);CHECKGLERROR
1375         //qglDepthMask(1);CHECKGLERROR
1376         qglColorMask(1,1,1,1);CHECKGLERROR
1377         //qglClearColor(0,0,0,0);CHECKGLERROR
1378         //qglClear(GL_COLOR_BUFFER_BIT);CHECKGLERROR
1379         //qglCullFace(GL_FRONT);CHECKGLERROR
1380         //qglDisable(GL_CULL_FACE);CHECKGLERROR
1381         //R_ClearScreen();
1382         R_Textures_Frame();
1383         GL_SetupView_Mode_Ortho(0, 0, vid_conwidth.integer, vid_conheight.integer, -10, 100);
1384         R_Mesh_Start();
1385         R_Mesh_Matrix(&identitymatrix);
1386         // draw the loading plaque
1387         pic = Draw_CachePic("gfx/loading", true);
1388         x = (vid_conwidth.integer - pic->width)/2;
1389         y = (vid_conheight.integer - pic->height)/2;
1390         GL_Color(1,1,1,1);
1391         GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1392         GL_DepthTest(false);
1393         R_Mesh_VertexPointer(vertex3f);
1394         R_Mesh_ColorPointer(NULL);
1395         R_Mesh_ResetTextureState();
1396         R_Mesh_TexBind(0, R_GetTexture(pic->tex));
1397         R_Mesh_TexCoordPointer(0, 2, texcoord2f);
1398         vertex3f[2] = vertex3f[5] = vertex3f[8] = vertex3f[11] = 0;
1399         vertex3f[0] = vertex3f[9] = x;
1400         vertex3f[1] = vertex3f[4] = y;
1401         vertex3f[3] = vertex3f[6] = x + pic->width;
1402         vertex3f[7] = vertex3f[10] = y + pic->height;
1403         texcoord2f[0] = 0;texcoord2f[1] = 0;
1404         texcoord2f[2] = 1;texcoord2f[3] = 0;
1405         texcoord2f[4] = 1;texcoord2f[5] = 1;
1406         texcoord2f[6] = 0;texcoord2f[7] = 1;
1407         R_Mesh_Draw(0, 4, 2, polygonelements);
1408         R_Mesh_Finish();
1409         // refresh
1410         VID_Finish(false);
1411 }
1412
1413 void CL_UpdateScreen(void)
1414 {
1415         float conwidth, conheight;
1416
1417         if (vid_hidden)
1418                 return;
1419
1420         if (!scr_initialized || !con_initialized || vid_hidden)
1421                 return;                         // not initialized yet
1422
1423         // don't allow cheats in multiplayer
1424         if (!cl.islocalgame && cl.worldmodel)
1425         {
1426                 if (r_fullbright.integer != 0)
1427                         Cvar_Set ("r_fullbright", "0");
1428                 if (r_ambient.value != 0)
1429                         Cvar_Set ("r_ambient", "0");
1430         }
1431
1432         conwidth = bound(320, vid_conwidth.value, 2048);
1433         conheight = bound(200, vid_conheight.value, 1536);
1434         if (vid_conwidth.value != conwidth)
1435                 Cvar_SetValue("vid_conwidth", conwidth);
1436         if (vid_conheight.value != conheight)
1437                 Cvar_SetValue("vid_conheight", conheight);
1438
1439         // bound viewsize
1440         if (scr_viewsize.value < 30)
1441                 Cvar_Set ("viewsize","30");
1442         if (scr_viewsize.value > 120)
1443                 Cvar_Set ("viewsize","120");
1444
1445         // bound field of view
1446         if (scr_fov.value < 1)
1447                 Cvar_Set ("fov","1");
1448         if (scr_fov.value > 170)
1449                 Cvar_Set ("fov","170");
1450
1451         // validate r_textureunits cvar
1452         if (r_textureunits.integer > gl_textureunits)
1453                 Cvar_SetValueQuick(&r_textureunits, gl_textureunits);
1454         if (r_textureunits.integer < 1)
1455                 Cvar_SetValueQuick(&r_textureunits, 1);
1456
1457         // validate gl_combine cvar
1458         if (gl_combine.integer && !gl_combine_extension)
1459                 Cvar_SetValueQuick(&gl_combine, 0);
1460
1461         // intermission is always full screen
1462         if (cl.intermission)
1463                 sb_lines = 0;
1464         else
1465         {
1466                 if (scr_viewsize.value >= 120)
1467                         sb_lines = 0;           // no status bar at all
1468                 else if (scr_viewsize.value >= 110)
1469                         sb_lines = 24;          // no inventory
1470                 else
1471                         sb_lines = 24+16+8;
1472         }
1473
1474         r_view.colormask[0] = 1;
1475         r_view.colormask[1] = 1;
1476         r_view.colormask[2] = 1;
1477
1478         if (r_timereport_active)
1479                 R_TimeReport("other");
1480
1481         SCR_SetUpToDrawConsole();
1482
1483         if (r_timereport_active)
1484                 R_TimeReport("start");
1485
1486         CHECKGLERROR
1487         qglViewport(0, 0, vid.width, vid.height);CHECKGLERROR
1488         qglDisable(GL_SCISSOR_TEST);CHECKGLERROR
1489         qglDepthMask(1);CHECKGLERROR
1490         qglColorMask(1,1,1,1);CHECKGLERROR
1491         qglClearColor(0,0,0,0);CHECKGLERROR
1492         qglClear(GL_COLOR_BUFFER_BIT);CHECKGLERROR
1493
1494         if (r_timereport_active)
1495                 R_TimeReport("clear");
1496
1497         if (r_stereo_redblue.integer || r_stereo_redgreen.integer || r_stereo_redcyan.integer || r_stereo_sidebyside.integer)
1498         {
1499                 matrix4x4_t originalmatrix = r_view.matrix;
1500                 r_view.matrix.m[0][3] = originalmatrix.m[0][3] + r_stereo_separation.value * -0.5f * r_view.matrix.m[0][1];
1501                 r_view.matrix.m[1][3] = originalmatrix.m[1][3] + r_stereo_separation.value * -0.5f * r_view.matrix.m[1][1];
1502                 r_view.matrix.m[2][3] = originalmatrix.m[2][3] + r_stereo_separation.value * -0.5f * r_view.matrix.m[2][1];
1503
1504                 if (r_stereo_sidebyside.integer)
1505                         r_stereo_side = 0;
1506
1507                 if (r_stereo_redblue.integer || r_stereo_redgreen.integer || r_stereo_redcyan.integer)
1508                 {
1509                         r_view.colormask[0] = 1;
1510                         r_view.colormask[1] = 0;
1511                         r_view.colormask[2] = 0;
1512                 }
1513
1514                 SCR_DrawScreen();
1515
1516                 r_view.matrix.m[0][3] = originalmatrix.m[0][3] + r_stereo_separation.value * 0.5f * r_view.matrix.m[0][1];
1517                 r_view.matrix.m[1][3] = originalmatrix.m[1][3] + r_stereo_separation.value * 0.5f * r_view.matrix.m[1][1];
1518                 r_view.matrix.m[2][3] = originalmatrix.m[2][3] + r_stereo_separation.value * 0.5f * r_view.matrix.m[2][1];
1519
1520                 if (r_stereo_sidebyside.integer)
1521                         r_stereo_side = 1;
1522
1523                 if (r_stereo_redblue.integer || r_stereo_redgreen.integer || r_stereo_redcyan.integer)
1524                 {
1525                         r_view.colormask[0] = 0;
1526                         r_view.colormask[1] = r_stereo_redcyan.integer || r_stereo_redgreen.integer;
1527                         r_view.colormask[2] = r_stereo_redcyan.integer || r_stereo_redblue.integer;
1528                 }
1529
1530                 SCR_DrawScreen();
1531
1532                 r_view.matrix = originalmatrix;
1533         }
1534         else
1535                 SCR_DrawScreen();
1536
1537         SCR_CaptureVideo();
1538
1539         VID_Finish(true);
1540         if (r_timereport_active)
1541                 R_TimeReport("finish");
1542 }
1543
1544 void CL_Screen_NewMap(void)
1545 {
1546         SHOWLMP_clear();
1547 }