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