]> icculus.org git repositories - divverent/darkplaces.git/blob - cl_screen.c
496
[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 // we have to include snd_main.h here only to get access to snd_renderbuffer->format.speed when writing the AVI headers
11 #include "snd_main.h"
12
13 cvar_t scr_viewsize = {CVAR_SAVE, "viewsize","100", "how large the view should be, 110 disables inventory bar, 120 disables status bar"};
14 cvar_t scr_fov = {CVAR_SAVE, "fov","90", "field of vision, 1-170 degrees, default 90, some players use 110-130"};       // 1 - 170
15 cvar_t scr_conalpha = {CVAR_SAVE, "scr_conalpha", "1", "opacity of console background"};
16 cvar_t scr_conbrightness = {CVAR_SAVE, "scr_conbrightness", "1", "brightness of console background (0 = black, 1 = image)"};
17 cvar_t scr_conforcewhiledisconnected = {0, "scr_conforcewhiledisconnected", "1", "forces fullscreen console while disconnected"};
18 cvar_t scr_menuforcewhiledisconnected = {0, "scr_menuforcewhiledisconnected", "0", "forces menu while disconnected"};
19 cvar_t scr_centertime = {0, "scr_centertime","2", "how long centerprint messages show"};
20 cvar_t scr_showram = {CVAR_SAVE, "showram","1", "show ram icon if low on surface cache memory (not used)"};
21 cvar_t scr_showturtle = {CVAR_SAVE, "showturtle","0", "show turtle icon when framerate is too low (not used)"};
22 cvar_t scr_showpause = {CVAR_SAVE, "showpause","1", "show pause icon when game is paused"};
23 cvar_t scr_showbrand = {0, "showbrand","0", "shows gfx/brand.tga in a corner of the screen (different values select different positions, including centered)"};
24 cvar_t scr_printspeed = {0, "scr_printspeed","8", "speed of intermission printing (episode end texts)"};
25 cvar_t vid_conwidth = {CVAR_SAVE, "vid_conwidth", "640", "virtual width of 2D graphics system"};
26 cvar_t vid_conheight = {CVAR_SAVE, "vid_conheight", "480", "virtual height of 2D graphics system"};
27 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)"};
28 cvar_t scr_screenshot_jpeg = {CVAR_SAVE, "scr_screenshot_jpeg","1", "save jpeg instead of targa"};
29 cvar_t scr_screenshot_jpeg_quality = {CVAR_SAVE, "scr_screenshot_jpeg_quality","0.9", "image quality of saved jpeg"};
30 cvar_t scr_screenshot_gammaboost = {CVAR_SAVE, "scr_screenshot_gammaboost","1", "gamma correction on saved screenshots and videos, 1.0 saves unmodified images"};
31 // scr_screenshot_name is defined in fs.c
32 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 or avi_i420 cvars are on it saves those formats instead, note that scr_screenshot_gammaboost affects the brightness of the output)"};
33 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)"};
34 cvar_t cl_capturevideo_rawrgb = {0, "cl_capturevideo_rawrgb", "0", "saves a single .rgb video file containing uncompressed RGB images (you'll need special processing tools to encode this to something more useful)"};
35 cvar_t cl_capturevideo_rawyv12 = {0, "cl_capturevideo_rawyv12", "0", "saves a single .yv12 video file containing uncompressed YV12 images (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)"};
36 cvar_t cl_capturevideo_avi_i420 = {0, "cl_capturevideo_avi_i420", "1", "saves a single .avi video file containing uncompressed I420 images and PCM sound"};
37 cvar_t cl_capturevideo_number = {CVAR_SAVE, "cl_capturevideo_number", "1", "number to append to video filename, incremented each time a capture begins"};
38 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)"};
39 cvar_t r_stereo_separation = {0, "r_stereo_separation", "4", "separation of eyes in the world (try negative values too)"};
40 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)"};
41 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)"};
42 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"};
43 cvar_t r_stereo_redgreen = {0, "r_stereo_redgreen", "0", "red/green anaglyph stereo glasses (for those who don't mind yellow)"};
44 cvar_t scr_zoomwindow = {CVAR_SAVE, "scr_zoomwindow", "0", "displays a zoomed in overlay window"};
45 cvar_t scr_zoomwindow_viewsizex = {CVAR_SAVE, "scr_zoomwindow_viewsizex", "20", "horizontal viewsize of zoom window"};
46 cvar_t scr_zoomwindow_viewsizey = {CVAR_SAVE, "scr_zoomwindow_viewsizey", "20", "vertical viewsize of zoom window"};
47 cvar_t scr_zoomwindow_fov = {CVAR_SAVE, "scr_zoomwindow_fov", "20", "fov of zoom window"};
48
49
50 int jpeg_supported = false;
51
52 qboolean        scr_initialized;                // ready to draw
53
54 float           scr_con_current;
55
56 extern int      con_vislines;
57
58 static void SCR_ScreenShot_f (void);
59 static void R_Envmap_f (void);
60
61 // backend
62 void R_ClearScreen(void);
63
64 /*
65 ===============================================================================
66
67 CENTER PRINTING
68
69 ===============================================================================
70 */
71
72 char            scr_centerstring[MAX_INPUTLINE];
73 float           scr_centertime_start;   // for slow victory printing
74 float           scr_centertime_off;
75 int                     scr_center_lines;
76 int                     scr_erase_lines;
77 int                     scr_erase_center;
78
79 /*
80 ==============
81 SCR_CenterPrint
82
83 Called for important messages that should stay in the center of the screen
84 for a few moments
85 ==============
86 */
87 void SCR_CenterPrint(char *str)
88 {
89         strlcpy (scr_centerstring, str, sizeof (scr_centerstring));
90         scr_centertime_off = scr_centertime.value;
91         scr_centertime_start = cl.time;
92
93 // count the number of lines for centering
94         scr_center_lines = 1;
95         while (*str)
96         {
97                 if (*str == '\n')
98                         scr_center_lines++;
99                 str++;
100         }
101 }
102
103
104 void SCR_DrawCenterString (void)
105 {
106         char    *start;
107         int             l;
108         int             x, y;
109         int             remaining;
110         int             color;
111
112 // the finale prints the characters one at a time
113         if (cl.intermission)
114                 remaining = (int)(scr_printspeed.value * (cl.time - scr_centertime_start));
115         else
116                 remaining = 9999;
117
118         scr_erase_center = 0;
119         start = scr_centerstring;
120
121         if (remaining < 1)
122                 return;
123
124         if (scr_center_lines <= 4)
125                 y = (int)(vid_conheight.integer*0.35);
126         else
127                 y = 48;
128
129         color = -1;
130         do
131         {
132                 // scan the number of characters on the line, not counting color codes
133                 int chars = 0;
134                 for (l=0 ; l<vid_conwidth.integer/8 ; l++)
135                 {
136                         if (start[l] == '\n' || !start[l])
137                                 break;
138                         // color codes add no visible characters, so don't count them
139                         if (start[l] == STRING_COLOR_TAG && (start[l+1] >= '0' && start[l+1] <= '9'))
140                                 l++;
141                         else
142                                 chars++;
143                 }
144                 x = (vid_conwidth.integer - chars*8)/2;
145                 if (l > 0)
146                 {
147                         if (remaining < l)
148                                 l = remaining;
149                         DrawQ_ColoredString(x, y, start, l, 8, 8, 1, 1, 1, 1, 0, &color);
150                         remaining -= l;
151                         if (remaining <= 0)
152                                 return;
153                 }
154
155                 y += 8;
156
157                 while (*start && *start != '\n')
158                         start++;
159
160                 if (!*start)
161                         break;
162                 start++;                // skip the \n
163         } while (1);
164 }
165
166 void SCR_CheckDrawCenterString (void)
167 {
168         if (scr_center_lines > scr_erase_lines)
169                 scr_erase_lines = scr_center_lines;
170
171         scr_centertime_off -= cl.realframetime;
172
173         // don't draw if this is a normal stats-screen intermission,
174         // only if it is not an intermission, or a finale intermission
175         if (cl.intermission == 1)
176                 return;
177         if (scr_centertime_off <= 0 && !cl.intermission)
178                 return;
179         if (key_dest != key_game)
180                 return;
181
182         SCR_DrawCenterString ();
183 }
184
185 /*
186 ==============
187 SCR_DrawTurtle
188 ==============
189 */
190 void SCR_DrawTurtle (void)
191 {
192         static int      count;
193
194         if (cls.state != ca_connected)
195                 return;
196
197         if (!scr_showturtle.integer)
198                 return;
199
200         if (cl.realframetime < 0.1)
201         {
202                 count = 0;
203                 return;
204         }
205
206         count++;
207         if (count < 3)
208                 return;
209
210         DrawQ_Pic (0, 0, Draw_CachePic("gfx/turtle", true), 0, 0, 1, 1, 1, 1, 0);
211 }
212
213 /*
214 ==============
215 SCR_DrawNet
216 ==============
217 */
218 void SCR_DrawNet (void)
219 {
220         if (cls.state != ca_connected)
221                 return;
222         if (realtime - cl.last_received_message < 0.3)
223                 return;
224         if (cls.demoplayback)
225                 return;
226
227         DrawQ_Pic (64, 0, Draw_CachePic("gfx/net", true), 0, 0, 1, 1, 1, 1, 0);
228 }
229
230 /*
231 ==============
232 DrawPause
233 ==============
234 */
235 void SCR_DrawPause (void)
236 {
237         cachepic_t      *pic;
238
239         if (cls.state != ca_connected)
240                 return;
241
242         if (!scr_showpause.integer)             // turn off for screenshots
243                 return;
244
245         if (!cl.paused)
246                 return;
247
248         pic = Draw_CachePic ("gfx/pause", true);
249         DrawQ_Pic ((vid_conwidth.integer - pic->width)/2, (vid_conheight.integer - pic->height)/2, pic, 0, 0, 1, 1, 1, 1, 0);
250 }
251
252 /*
253 ==============
254 SCR_DrawBrand
255 ==============
256 */
257 void SCR_DrawBrand (void)
258 {
259         cachepic_t      *pic;
260         float           x, y;
261
262         if (!scr_showbrand.value)
263                 return;
264
265         pic = Draw_CachePic ("gfx/brand", true);
266
267         switch ((int)scr_showbrand.value)
268         {
269         case 1: // bottom left
270                 x = 0;
271                 y = vid_conheight.integer - pic->height;
272                 break;
273         case 2: // bottom centre
274                 x = (vid_conwidth.integer - pic->width) / 2;
275                 y = vid_conheight.integer - pic->height;
276                 break;
277         case 3: // bottom right
278                 x = vid_conwidth.integer - pic->width;
279                 y = vid_conheight.integer - pic->height;
280                 break;
281         case 4: // centre right
282                 x = vid_conwidth.integer - pic->width;
283                 y = (vid_conheight.integer - pic->height) / 2;
284                 break;
285         case 5: // top right
286                 x = vid_conwidth.integer - pic->width;
287                 y = 0;
288                 break;
289         case 6: // top centre
290                 x = (vid_conwidth.integer - pic->width) / 2;
291                 y = 0;
292                 break;
293         case 7: // top left
294                 x = 0;
295                 y = 0;
296                 break;
297         case 8: // centre left
298                 x = 0;
299                 y = (vid_conheight.integer - pic->height) / 2;
300                 break;
301         default:
302                 return;
303         }
304
305         DrawQ_Pic (x, y, pic, 0, 0, 1, 1, 1, 1, 0);
306 }
307
308 /*
309 ==============
310 SCR_DrawQWDownload
311 ==============
312 */
313 static int SCR_DrawQWDownload(int offset)
314 {
315         int len;
316         float x, y;
317         float size = 8;
318         char temp[256];
319         if (!cls.qw_downloadname[0])
320                 return 0;
321         dpsnprintf(temp, sizeof(temp), "Downloading %s ...  %3i%%\n", cls.qw_downloadname, cls.qw_downloadpercent);
322         len = (int)strlen(temp);
323         x = (vid_conwidth.integer - len*size) / 2;
324         y = vid_conheight.integer - size - offset;
325         DrawQ_Pic(0, y, NULL, vid_conwidth.integer, size, 0, 0, 0, 0.5, 0);
326         DrawQ_String(x, y, temp, len, size, size, 1, 1, 1, 1, 0);
327         return 8;
328 }
329
330 /*
331 ==============
332 SCR_DrawCurlDownload
333 ==============
334 */
335 static int SCR_DrawCurlDownload(int offset)
336 {
337         int len;
338         int nDownloads;
339         int i;
340         float x, y;
341         float size = 8;
342         Curl_downloadinfo_t *downinfo;
343         char temp[256];
344         const char *addinfo;
345
346         downinfo = Curl_GetDownloadInfo(&nDownloads, &addinfo);
347         if(!downinfo)
348                 return 0;
349
350         y = vid_conheight.integer - size * nDownloads - offset;
351
352         if(addinfo)
353         {
354                 len = (int)strlen(addinfo);
355                 x = (vid_conwidth.integer - len*size) / 2;
356                 DrawQ_Pic(0, y - size, NULL, vid_conwidth.integer, size, 1, 1, 1, 0.8, 0);
357                 DrawQ_String(x, y - size, addinfo, len, size, size, 0, 0, 0, 1, 0);
358         }
359
360         for(i = 0; i != nDownloads; ++i)
361         {
362                 if(downinfo[i].queued)
363                         dpsnprintf(temp, sizeof(temp), "Still in queue: %s\n", downinfo[i].filename);
364                 else if(downinfo[i].progress <= 0)
365                         dpsnprintf(temp, sizeof(temp), "Downloading %s ...  ???.?%% @ %.1f KiB/s\n", downinfo[i].filename, downinfo[i].speed / 1024.0);
366                 else
367                         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);
368                 len = (int)strlen(temp);
369                 x = (vid_conwidth.integer - len*size) / 2;
370                 DrawQ_Pic(0, y + i * size, NULL, vid_conwidth.integer, size, 0, 0, 0, 0.8, 0);
371                 DrawQ_String(x, y + i * size, temp, len, size, size, 1, 1, 1, 1, 0);
372         }
373
374         Z_Free(downinfo);
375
376         return 8 * (nDownloads + (addinfo ? 1 : 0));
377 }
378
379 /*
380 ==============
381 SCR_DrawDownload
382 ==============
383 */
384 static void SCR_DrawDownload()
385 {
386         int offset = 0;
387         offset += SCR_DrawQWDownload(offset);
388         offset += SCR_DrawCurlDownload(offset);
389 }
390
391 //=============================================================================
392
393 /*
394 ==================
395 SCR_SetUpToDrawConsole
396 ==================
397 */
398 void SCR_SetUpToDrawConsole (void)
399 {
400         // lines of console to display
401         float conlines;
402         static int framecounter = 0;
403
404         Con_CheckResize ();
405
406         if (scr_menuforcewhiledisconnected.integer && key_dest == key_game && cls.state == ca_disconnected)
407         {
408                 if (framecounter >= 2)
409                         MR_ToggleMenu_f();
410                 else
411                         framecounter++;
412         }
413         else
414                 framecounter = 0;
415
416         if (scr_conforcewhiledisconnected.integer && key_dest == key_game && cls.signon != SIGNONS)
417                 key_consoleactive |= KEY_CONSOLEACTIVE_FORCED;
418         else
419                 key_consoleactive &= ~KEY_CONSOLEACTIVE_FORCED;
420
421 // decide on the height of the console
422         if (key_consoleactive & KEY_CONSOLEACTIVE_USER)
423                 conlines = vid_conheight.integer/2;     // half screen
424         else
425                 conlines = 0;                           // none visible
426
427         scr_con_current = conlines;
428 }
429
430 /*
431 ==================
432 SCR_DrawConsole
433 ==================
434 */
435 void SCR_DrawConsole (void)
436 {
437         if (key_consoleactive & KEY_CONSOLEACTIVE_FORCED)
438         {
439                 // full screen
440                 Con_DrawConsole (vid_conheight.integer);
441         }
442         else if (scr_con_current)
443                 Con_DrawConsole ((int)scr_con_current);
444         else
445         {
446                 con_vislines = 0;
447                 if ((key_dest == key_game || key_dest == key_message) && !r_letterbox.value)
448                         Con_DrawNotify ();      // only draw notify in game
449         }
450 }
451
452 /*
453 ===============
454 SCR_BeginLoadingPlaque
455
456 ================
457 */
458 void SCR_BeginLoadingPlaque (void)
459 {
460         // save console log up to this point to log_file if it was set by configs
461         Log_Start();
462
463         Host_StartVideo();
464         S_StopAllSounds();
465         SCR_UpdateLoadingScreen();
466 }
467
468 //=============================================================================
469
470 char r_speeds_string[1024];
471 int speedstringcount, r_timereport_active;
472 double r_timereport_temp = 0, r_timereport_current = 0, r_timereport_start = 0;
473
474 void R_TimeReport(char *desc)
475 {
476         char tempbuf[256];
477         int length;
478         int t;
479
480         if (r_speeds.integer < 2 || !r_timereport_active)
481                 return;
482
483         CHECKGLERROR
484         qglFinish();CHECKGLERROR
485         r_timereport_temp = r_timereport_current;
486         r_timereport_current = Sys_DoubleTime();
487         t = (int) ((r_timereport_current - r_timereport_temp) * 1000000.0 + 0.5);
488
489         dpsnprintf(tempbuf, sizeof(tempbuf), "%8i %-11s", t, desc);
490         length = (int)strlen(tempbuf);
491         if (speedstringcount + length > (vid_conwidth.integer / 8))
492         {
493                 strlcat(r_speeds_string, "\n", sizeof(r_speeds_string));
494                 speedstringcount = 0;
495         }
496         strlcat(r_speeds_string, tempbuf, sizeof(r_speeds_string));
497         speedstringcount += length;
498 }
499
500 void R_TimeReport_Frame(void)
501 {
502         int i, j, lines, y;
503
504         if (r_speeds_string[0])
505         {
506                 if (r_timereport_active)
507                 {
508                         r_timereport_current = r_timereport_start;
509                         R_TimeReport("total");
510                 }
511
512                 if (r_speeds_string[strlen(r_speeds_string)-1] == '\n')
513                         r_speeds_string[strlen(r_speeds_string)-1] = 0;
514                 lines = 1;
515                 for (i = 0;r_speeds_string[i];i++)
516                         if (r_speeds_string[i] == '\n')
517                                 lines++;
518                 y = vid_conheight.integer - sb_lines - lines * 8;
519                 i = j = 0;
520                 DrawQ_Pic(0, y, NULL, vid_conwidth.integer, lines * 8, 0, 0, 0, 0.5, 0);
521                 while (r_speeds_string[i])
522                 {
523                         j = i;
524                         while (r_speeds_string[i] && r_speeds_string[i] != '\n')
525                                 i++;
526                         if (i - j > 0)
527                                 DrawQ_String(0, y, r_speeds_string + j, i - j, 8, 8, 1, 1, 1, 1, 0);
528                         if (r_speeds_string[i] == '\n')
529                                 i++;
530                         y += 8;
531                 }
532                 r_speeds_string[0] = 0;
533                 r_timereport_active = false;
534         }
535         if (r_speeds.integer && cls.signon == SIGNONS && cls.state == ca_connected)
536         {
537                 speedstringcount = 0;
538                 r_speeds_string[0] = 0;
539                 r_timereport_active = false;
540                 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]);
541                 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);
542                 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);
543                 if (r_refdef.stats.bloom)
544                         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);
545                 else
546                         sprintf(r_speeds_string + strlen(r_speeds_string), "rendered%6i meshes%8i triangles\n", r_refdef.stats.meshes, r_refdef.stats.meshes_elements / 3);
547
548                 memset(&r_refdef.stats, 0, sizeof(r_refdef.stats));
549
550                 if (r_speeds.integer >= 2)
551                 {
552                         r_timereport_active = true;
553                         r_timereport_start = r_timereport_current = Sys_DoubleTime();
554                 }
555         }
556 }
557
558 /*
559 =================
560 SCR_SizeUp_f
561
562 Keybinding command
563 =================
564 */
565 void SCR_SizeUp_f (void)
566 {
567         Cvar_SetValue ("viewsize",scr_viewsize.value+10);
568 }
569
570
571 /*
572 =================
573 SCR_SizeDown_f
574
575 Keybinding command
576 =================
577 */
578 void SCR_SizeDown_f (void)
579 {
580         Cvar_SetValue ("viewsize",scr_viewsize.value-10);
581 }
582
583 void CL_Screen_Init(void)
584 {
585         Cvar_RegisterVariable (&scr_fov);
586         Cvar_RegisterVariable (&scr_viewsize);
587         Cvar_RegisterVariable (&scr_conalpha);
588         Cvar_RegisterVariable (&scr_conbrightness);
589         Cvar_RegisterVariable (&scr_conforcewhiledisconnected);
590         Cvar_RegisterVariable (&scr_menuforcewhiledisconnected);
591         Cvar_RegisterVariable (&scr_showram);
592         Cvar_RegisterVariable (&scr_showturtle);
593         Cvar_RegisterVariable (&scr_showpause);
594         Cvar_RegisterVariable (&scr_showbrand);
595         Cvar_RegisterVariable (&scr_centertime);
596         Cvar_RegisterVariable (&scr_printspeed);
597         Cvar_RegisterVariable (&vid_conwidth);
598         Cvar_RegisterVariable (&vid_conheight);
599         Cvar_RegisterVariable (&vid_pixelheight);
600         Cvar_RegisterVariable (&scr_screenshot_jpeg);
601         Cvar_RegisterVariable (&scr_screenshot_jpeg_quality);
602         Cvar_RegisterVariable (&scr_screenshot_gammaboost);
603         Cvar_RegisterVariable (&cl_capturevideo);
604         Cvar_RegisterVariable (&cl_capturevideo_fps);
605         Cvar_RegisterVariable (&cl_capturevideo_rawrgb);
606         Cvar_RegisterVariable (&cl_capturevideo_rawyv12);
607         Cvar_RegisterVariable (&cl_capturevideo_avi_i420);
608         Cvar_RegisterVariable (&cl_capturevideo_number);
609         Cvar_RegisterVariable (&r_letterbox);
610         Cvar_RegisterVariable(&r_stereo_separation);
611         Cvar_RegisterVariable(&r_stereo_sidebyside);
612         Cvar_RegisterVariable(&r_stereo_redblue);
613         Cvar_RegisterVariable(&r_stereo_redcyan);
614         Cvar_RegisterVariable(&r_stereo_redgreen);
615         Cvar_RegisterVariable(&scr_zoomwindow);
616         Cvar_RegisterVariable(&scr_zoomwindow_viewsizex);
617         Cvar_RegisterVariable(&scr_zoomwindow_viewsizey);
618         Cvar_RegisterVariable(&scr_zoomwindow_fov);
619
620         Cmd_AddCommand ("sizeup",SCR_SizeUp_f, "increase view size (increases viewsize cvar)");
621         Cmd_AddCommand ("sizedown",SCR_SizeDown_f, "decrease view size (decreases viewsize cvar)");
622         Cmd_AddCommand ("screenshot",SCR_ScreenShot_f, "takes a screenshot of the next rendered frame");
623         Cmd_AddCommand ("envmap", R_Envmap_f, "render a cubemap (skybox) of the current scene");
624
625         scr_initialized = true;
626 }
627
628 /*
629 ==================
630 SCR_ScreenShot_f
631 ==================
632 */
633 void SCR_ScreenShot_f (void)
634 {
635         static int shotnumber;
636         static char oldname[MAX_QPATH];
637         char base[MAX_QPATH];
638         char filename[MAX_QPATH];
639         unsigned char *buffer1;
640         unsigned char *buffer2;
641         unsigned char *buffer3;
642         qboolean jpeg = (scr_screenshot_jpeg.integer != 0);
643
644         sprintf (base, "screenshots/%s", scr_screenshot_name.string);
645
646         if (strcmp (oldname, scr_screenshot_name.string))
647         {
648                 sprintf(oldname, "%s", scr_screenshot_name.string);
649                 shotnumber = 0;
650         }
651
652         // find a file name to save it to
653         for (;shotnumber < 1000000;shotnumber++)
654                 if (!FS_SysFileExists(va("%s/%s%06d.tga", fs_gamedir, base, shotnumber)) && !FS_SysFileExists(va("%s/%s%06d.jpg", fs_gamedir, base, shotnumber)))
655                         break;
656         if (shotnumber >= 1000000)
657         {
658                 Con_Print("SCR_ScreenShot_f: Couldn't create the image file\n");
659                 return;
660         }
661
662         sprintf(filename, "%s%06d.%s", base, shotnumber, jpeg ? "jpg" : "tga");
663
664         buffer1 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3);
665         buffer2 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3);
666         buffer3 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3 + 18);
667
668         if (SCR_ScreenShot (filename, buffer1, buffer2, buffer3, 0, 0, vid.width, vid.height, false, false, false, jpeg, true))
669                 Con_Printf("Wrote %s\n", filename);
670         else
671                 Con_Printf("unable to write %s\n", filename);
672
673         Mem_Free (buffer1);
674         Mem_Free (buffer2);
675         Mem_Free (buffer3);
676
677         shotnumber++;
678 }
679
680 static void SCR_CaptureVideo_RIFF_Start(void)
681 {
682         memset(&cls.capturevideo.riffbuffer, 0, sizeof(sizebuf_t));
683         cls.capturevideo.riffbuffer.maxsize = sizeof(cls.capturevideo.riffbufferdata);
684         cls.capturevideo.riffbuffer.data = cls.capturevideo.riffbufferdata;
685 }
686
687 static void SCR_CaptureVideo_RIFF_Flush(void)
688 {
689         if (cls.capturevideo.riffbuffer.cursize > 0)
690         {
691                 if (!FS_Write(cls.capturevideo.videofile, cls.capturevideo.riffbuffer.data, cls.capturevideo.riffbuffer.cursize))
692                         cls.capturevideo.error = true;
693                 cls.capturevideo.riffbuffer.cursize = 0;
694                 cls.capturevideo.riffbuffer.overflowed = false;
695         }
696 }
697
698 static void SCR_CaptureVideo_RIFF_WriteBytes(const unsigned char *data, size_t size)
699 {
700         SCR_CaptureVideo_RIFF_Flush();
701         if (!FS_Write(cls.capturevideo.videofile, data, size))
702                 cls.capturevideo.error = true;
703 }
704
705 static void SCR_CaptureVideo_RIFF_Write32(int n)
706 {
707         if (cls.capturevideo.riffbuffer.cursize + 4 > cls.capturevideo.riffbuffer.maxsize)
708                 SCR_CaptureVideo_RIFF_Flush();
709         MSG_WriteLong(&cls.capturevideo.riffbuffer, n);
710 }
711
712 static void SCR_CaptureVideo_RIFF_Write16(int n)
713 {
714         if (cls.capturevideo.riffbuffer.cursize + 2 > cls.capturevideo.riffbuffer.maxsize)
715                 SCR_CaptureVideo_RIFF_Flush();
716         MSG_WriteShort(&cls.capturevideo.riffbuffer, n);
717 }
718
719 static void SCR_CaptureVideo_RIFF_WriteFourCC(const char *chunkfourcc)
720 {
721         if (cls.capturevideo.riffbuffer.cursize + (int)strlen(chunkfourcc) > cls.capturevideo.riffbuffer.maxsize)
722                 SCR_CaptureVideo_RIFF_Flush();
723         MSG_WriteUnterminatedString(&cls.capturevideo.riffbuffer, chunkfourcc);
724 }
725
726 static void SCR_CaptureVideo_RIFF_WriteTerminatedString(const char *string)
727 {
728         if (cls.capturevideo.riffbuffer.cursize + (int)strlen(string) > cls.capturevideo.riffbuffer.maxsize)
729                 SCR_CaptureVideo_RIFF_Flush();
730         MSG_WriteString(&cls.capturevideo.riffbuffer, string);
731 }
732
733 static fs_offset_t SCR_CaptureVideo_RIFF_GetPosition(void)
734 {
735         SCR_CaptureVideo_RIFF_Flush();
736         return FS_Tell(cls.capturevideo.videofile);
737 }
738
739 static void SCR_CaptureVideo_RIFF_Push(const char *chunkfourcc, const char *listtypefourcc)
740 {
741         SCR_CaptureVideo_RIFF_WriteFourCC(chunkfourcc);
742         SCR_CaptureVideo_RIFF_Write32(0);
743         SCR_CaptureVideo_RIFF_Flush();
744         cls.capturevideo.riffstackstartoffset[cls.capturevideo.riffstacklevel++] = SCR_CaptureVideo_RIFF_GetPosition();
745         if (listtypefourcc)
746                 SCR_CaptureVideo_RIFF_WriteFourCC(listtypefourcc);
747 }
748
749 static void SCR_CaptureVideo_RIFF_Pop(void)
750 {
751         fs_offset_t offset;
752         int x;
753         unsigned char sizebytes[4];
754         // write out the chunk size and then return to the current file position
755         cls.capturevideo.riffstacklevel--;
756         offset = SCR_CaptureVideo_RIFF_GetPosition();
757         x = (int)(offset - (cls.capturevideo.riffstackstartoffset[cls.capturevideo.riffstacklevel]));
758         sizebytes[0] = (x) & 0xff;sizebytes[1] = (x >> 8) & 0xff;sizebytes[2] = (x >> 16) & 0xff;sizebytes[3] = (x >> 24) & 0xff;
759         FS_Seek(cls.capturevideo.videofile, -(x + 4), SEEK_END);
760         FS_Write(cls.capturevideo.videofile, sizebytes, 4);
761         FS_Seek(cls.capturevideo.videofile, 0, SEEK_END);
762         if (offset & 1)
763         {
764                 unsigned char c = 0;
765                 FS_Write(cls.capturevideo.videofile, &c, 1);
766         }
767 }
768
769 static void SCR_CaptureVideo_RIFF_IndexEntry(const char *chunkfourcc, int chunksize, int flags)
770 {
771         if (cls.capturevideo.riffstacklevel != 2)
772                 Sys_Error("SCR_Capturevideo_RIFF_IndexEntry: RIFF stack level is %i (should be 2)\n", cls.capturevideo.riffstacklevel);
773         if (cls.capturevideo.riffindexbuffer.cursize + 16 > cls.capturevideo.riffindexbuffer.maxsize)
774         {
775                 int oldsize = cls.capturevideo.riffindexbuffer.maxsize;
776                 unsigned char *olddata;
777                 olddata = cls.capturevideo.riffindexbuffer.data;
778                 cls.capturevideo.riffindexbuffer.maxsize = max(cls.capturevideo.riffindexbuffer.maxsize * 2, 4096);
779                 cls.capturevideo.riffindexbuffer.data = Mem_Alloc(tempmempool, cls.capturevideo.riffindexbuffer.maxsize);
780                 if (olddata)
781                 {
782                         memcpy(cls.capturevideo.riffindexbuffer.data, olddata, oldsize);
783                         Mem_Free(olddata);
784                 }
785         }
786         MSG_WriteUnterminatedString(&cls.capturevideo.riffindexbuffer, chunkfourcc);
787         MSG_WriteLong(&cls.capturevideo.riffindexbuffer, flags);
788         MSG_WriteLong(&cls.capturevideo.riffindexbuffer, (int)FS_Tell(cls.capturevideo.videofile) - cls.capturevideo.riffstackstartoffset[1]);
789         MSG_WriteLong(&cls.capturevideo.riffindexbuffer, chunksize);
790 }
791
792 static void SCR_CaptureVideo_RIFF_Finish(void)
793 {
794         // close the "movi" list
795         SCR_CaptureVideo_RIFF_Pop();
796         // write the idx1 chunk that we've been building while saving the frames
797         SCR_CaptureVideo_RIFF_Push("idx1", NULL);
798         SCR_CaptureVideo_RIFF_WriteBytes(cls.capturevideo.riffindexbuffer.data, cls.capturevideo.riffindexbuffer.cursize);
799         SCR_CaptureVideo_RIFF_Pop();
800         cls.capturevideo.riffindexbuffer.cursize = 0;
801         // pop the RIFF chunk itself
802         while (cls.capturevideo.riffstacklevel > 0)
803                 SCR_CaptureVideo_RIFF_Pop();
804         SCR_CaptureVideo_RIFF_Flush();
805 }
806
807 static void SCR_CaptureVideo_RIFF_OverflowCheck(int framesize)
808 {
809         fs_offset_t cursize;
810         if (cls.capturevideo.riffstacklevel != 2)
811                 Sys_Error("SCR_CaptureVideo_RIFF_OverflowCheck: chunk stack leakage!\n");
812         // check where we are in the file
813         SCR_CaptureVideo_RIFF_Flush();
814         cursize = SCR_CaptureVideo_RIFF_GetPosition() - cls.capturevideo.riffstackstartoffset[0];
815         // if this would overflow the windows limit of 1GB per RIFF chunk, we need
816         // to close the current RIFF chunk and open another for future frames
817         if (8 + cursize + framesize > 1<<30)
818         {
819                 SCR_CaptureVideo_RIFF_Finish();
820                 while (cls.capturevideo.riffstacklevel > 0)
821                         SCR_CaptureVideo_RIFF_Pop();
822                 // begin a new 1GB extended section of the AVI
823                 SCR_CaptureVideo_RIFF_Push("RIFF", "AVIX");
824                 SCR_CaptureVideo_RIFF_Push("LIST", "movi");
825         }
826 }
827
828 void SCR_CaptureVideo_BeginVideo(void)
829 {
830         double gamma, g;
831         int width = vid.width, height = vid.height, x;
832         unsigned int i;
833         unsigned char out[44];
834         if (cls.capturevideo.active)
835                 return;
836         memset(&cls.capturevideo, 0, sizeof(cls.capturevideo));
837         // soundrate is figured out on the first SoundFrame
838         cls.capturevideo.active = true;
839         cls.capturevideo.starttime = Sys_DoubleTime();
840         cls.capturevideo.framerate = bound(1, cl_capturevideo_fps.value, 1000);
841         cls.capturevideo.soundrate = S_GetSoundRate();
842         cls.capturevideo.frame = 0;
843         cls.capturevideo.soundsampleframe = 0;
844         cls.capturevideo.buffer = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * (3+3+3) + 18);
845         gamma = 1.0/scr_screenshot_gammaboost.value;
846         dpsnprintf(cls.capturevideo.basename, sizeof(cls.capturevideo.basename), "video/dpvideo%03i", cl_capturevideo_number.integer);
847         Cvar_SetValueQuick(&cl_capturevideo_number, cl_capturevideo_number.integer + 1);
848
849         /*
850         for (i = 0;i < 256;i++)
851         {
852                 unsigned char j = (unsigned char)bound(0, 255*pow(i/255.0, gamma), 255);
853                 cls.capturevideo.rgbgammatable[0][i] = j;
854                 cls.capturevideo.rgbgammatable[1][i] = j;
855                 cls.capturevideo.rgbgammatable[2][i] = j;
856         }
857         */
858 /*
859 R = Y + 1.4075 * (Cr - 128);
860 G = Y + -0.3455 * (Cb - 128) + -0.7169 * (Cr - 128);
861 B = Y + 1.7790 * (Cb - 128);
862 Y = R *  .299 + G *  .587 + B *  .114;
863 Cb = R * -.169 + G * -.332 + B *  .500 + 128.;
864 Cr = R *  .500 + G * -.419 + B * -.0813 + 128.;
865 */
866         for (i = 0;i < 256;i++)
867         {
868                 g = 255*pow(i/255.0, gamma);
869                 // Y weights from RGB
870                 cls.capturevideo.rgbtoyuvscaletable[0][0][i] = (short)(g *  0.299);
871                 cls.capturevideo.rgbtoyuvscaletable[0][1][i] = (short)(g *  0.587);
872                 cls.capturevideo.rgbtoyuvscaletable[0][2][i] = (short)(g *  0.114);
873                 // Cb weights from RGB
874                 cls.capturevideo.rgbtoyuvscaletable[1][0][i] = (short)(g * -0.169);
875                 cls.capturevideo.rgbtoyuvscaletable[1][1][i] = (short)(g * -0.332);
876                 cls.capturevideo.rgbtoyuvscaletable[1][2][i] = (short)(g *  0.500);
877                 // Cr weights from RGB
878                 cls.capturevideo.rgbtoyuvscaletable[2][0][i] = (short)(g *  0.500);
879                 cls.capturevideo.rgbtoyuvscaletable[2][1][i] = (short)(g * -0.419);
880                 cls.capturevideo.rgbtoyuvscaletable[2][2][i] = (short)(g * -0.0813);
881                 // range reduction of YCbCr to valid signal range
882                 cls.capturevideo.yuvnormalizetable[0][i] = 16 + i * (236-16) / 256;
883                 cls.capturevideo.yuvnormalizetable[1][i] = 16 + i * (240-16) / 256;
884                 cls.capturevideo.yuvnormalizetable[2][i] = 16 + i * (240-16) / 256;
885         }
886
887         if (cl_capturevideo_avi_i420.integer)
888         {
889                 cls.capturevideo.format = CAPTUREVIDEOFORMAT_AVI_I420;
890                 cls.capturevideo.videofile = FS_Open (va("%s.avi", cls.capturevideo.basename), "wb", false, true);
891                 SCR_CaptureVideo_RIFF_Start();
892                 // enclosing RIFF chunk (there can be multiple of these in >1GB files, the later ones are "AVIX" instead of "AVI " and have no header/stream info)
893                 SCR_CaptureVideo_RIFF_Push("RIFF", "AVI ");
894                 // AVI main header
895                 SCR_CaptureVideo_RIFF_Push("LIST", "hdrl");
896                 SCR_CaptureVideo_RIFF_Push("avih", NULL);
897                 SCR_CaptureVideo_RIFF_Write32((int)(1000000.0 / cls.capturevideo.framerate)); // microseconds per frame
898                 SCR_CaptureVideo_RIFF_Write32(0); // max bytes per second
899                 SCR_CaptureVideo_RIFF_Write32(0); // padding granularity
900                 SCR_CaptureVideo_RIFF_Write32(0x910); // flags (AVIF_HASINDEX | AVIF_ISINTERLEAVED | AVIF_TRUSTCKTYPE)
901                 cls.capturevideo.videofile_totalframes_offset1 = SCR_CaptureVideo_RIFF_GetPosition();
902                 SCR_CaptureVideo_RIFF_Write32(0); // total frames
903                 SCR_CaptureVideo_RIFF_Write32(0); // initial frames
904                 if (cls.capturevideo.soundrate)
905                         SCR_CaptureVideo_RIFF_Write32(2); // number of streams
906                 else
907                         SCR_CaptureVideo_RIFF_Write32(1); // number of streams
908                 SCR_CaptureVideo_RIFF_Write32(0); // suggested buffer size
909                 SCR_CaptureVideo_RIFF_Write32(width); // width
910                 SCR_CaptureVideo_RIFF_Write32(height); // height
911                 SCR_CaptureVideo_RIFF_Write32(0); // reserved[0]
912                 SCR_CaptureVideo_RIFF_Write32(0); // reserved[1]
913                 SCR_CaptureVideo_RIFF_Write32(0); // reserved[2]
914                 SCR_CaptureVideo_RIFF_Write32(0); // reserved[3]
915                 SCR_CaptureVideo_RIFF_Pop();
916                 // video stream info
917                 SCR_CaptureVideo_RIFF_Push("LIST", "strl");
918                 SCR_CaptureVideo_RIFF_Push("strh", "vids");
919                 SCR_CaptureVideo_RIFF_WriteFourCC("I420"); // stream fourcc (I420 colorspace, uncompressed)
920                 SCR_CaptureVideo_RIFF_Write32(0); // flags
921                 SCR_CaptureVideo_RIFF_Write16(0); // priority
922                 SCR_CaptureVideo_RIFF_Write16(0); // language
923                 SCR_CaptureVideo_RIFF_Write32(0); // initial frames
924                 // find an ideal divisor for the framerate
925                 for (x = 1;x < 1000;x++)
926                         if (cls.capturevideo.framerate * x == floor(cls.capturevideo.framerate * x))
927                                 break;
928                 SCR_CaptureVideo_RIFF_Write32(x); // samples/second divisor
929                 SCR_CaptureVideo_RIFF_Write32((int)(cls.capturevideo.framerate * x)); // samples/second multiplied by divisor
930                 SCR_CaptureVideo_RIFF_Write32(0); // start
931                 cls.capturevideo.videofile_totalframes_offset2 = SCR_CaptureVideo_RIFF_GetPosition();
932                 SCR_CaptureVideo_RIFF_Write32(0); // length
933                 SCR_CaptureVideo_RIFF_Write32(width*height+(width/2)*(height/2)*2); // suggested buffer size
934                 SCR_CaptureVideo_RIFF_Write32(0); // quality
935                 SCR_CaptureVideo_RIFF_Write32(0); // sample size
936                 SCR_CaptureVideo_RIFF_Write16(0); // frame left
937                 SCR_CaptureVideo_RIFF_Write16(0); // frame top
938                 SCR_CaptureVideo_RIFF_Write16(width); // frame right
939                 SCR_CaptureVideo_RIFF_Write16(height); // frame bottom
940                 SCR_CaptureVideo_RIFF_Pop();
941                 // video stream format
942                 SCR_CaptureVideo_RIFF_Push("strf", NULL);
943                 SCR_CaptureVideo_RIFF_Write32(40); // BITMAPINFO struct size
944                 SCR_CaptureVideo_RIFF_Write32(width); // width
945                 SCR_CaptureVideo_RIFF_Write32(height); // height
946                 SCR_CaptureVideo_RIFF_Write16(3); // planes
947                 SCR_CaptureVideo_RIFF_Write16(12); // bitcount
948                 SCR_CaptureVideo_RIFF_WriteFourCC("I420"); // compression
949                 SCR_CaptureVideo_RIFF_Write32(width*height+(width/2)*(height/2)*2); // size of image
950                 SCR_CaptureVideo_RIFF_Write32(0); // x pixels per meter
951                 SCR_CaptureVideo_RIFF_Write32(0); // y pixels per meter
952                 SCR_CaptureVideo_RIFF_Write32(0); // color used
953                 SCR_CaptureVideo_RIFF_Write32(0); // color important
954                 SCR_CaptureVideo_RIFF_Pop();
955                 SCR_CaptureVideo_RIFF_Pop();
956                 if (cls.capturevideo.soundrate)
957                 {
958                         // audio stream info
959                         SCR_CaptureVideo_RIFF_Push("LIST", "strl");
960                         SCR_CaptureVideo_RIFF_Push("strh", "auds");
961                         SCR_CaptureVideo_RIFF_Write32(1); // stream fourcc (PCM audio, uncompressed)
962                         SCR_CaptureVideo_RIFF_Write32(0); // flags
963                         SCR_CaptureVideo_RIFF_Write16(0); // priority
964                         SCR_CaptureVideo_RIFF_Write16(0); // language
965                         SCR_CaptureVideo_RIFF_Write32(0); // initial frames
966                         SCR_CaptureVideo_RIFF_Write32(1); // samples/second divisor
967                         SCR_CaptureVideo_RIFF_Write32((int)(cls.capturevideo.soundrate)); // samples/second multiplied by divisor
968                         SCR_CaptureVideo_RIFF_Write32(0); // start
969                         cls.capturevideo.videofile_totalsampleframes_offset = SCR_CaptureVideo_RIFF_GetPosition();
970                         SCR_CaptureVideo_RIFF_Write32(0); // length
971                         SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate * 2); // suggested buffer size (this is a half second)
972                         SCR_CaptureVideo_RIFF_Write32(0); // quality
973                         SCR_CaptureVideo_RIFF_Write32(4); // sample size
974                         SCR_CaptureVideo_RIFF_Write16(0); // frame left
975                         SCR_CaptureVideo_RIFF_Write16(0); // frame top
976                         SCR_CaptureVideo_RIFF_Write16(0); // frame right
977                         SCR_CaptureVideo_RIFF_Write16(0); // frame bottom
978                         SCR_CaptureVideo_RIFF_Pop();
979                         // audio stream format
980                         SCR_CaptureVideo_RIFF_Push("strf", NULL);
981                         SCR_CaptureVideo_RIFF_Write16(1); // format (uncompressed PCM?)
982                         SCR_CaptureVideo_RIFF_Write16(2); // channels (stereo)
983                         SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate); // sampleframes per second
984                         SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate * 4); // average bytes per second
985                         SCR_CaptureVideo_RIFF_Write16(4); // block align
986                         SCR_CaptureVideo_RIFF_Write16(16); // bits per sample
987                         SCR_CaptureVideo_RIFF_Write16(0); // size
988                         SCR_CaptureVideo_RIFF_Pop();
989                         SCR_CaptureVideo_RIFF_Pop();
990                 }
991                 // close the AVI header list
992                 SCR_CaptureVideo_RIFF_Pop();
993                 // software that produced this AVI video file
994                 SCR_CaptureVideo_RIFF_Push("LIST", "INFO");
995                 SCR_CaptureVideo_RIFF_Push("ISFT", NULL);
996                 SCR_CaptureVideo_RIFF_WriteTerminatedString(engineversion);
997                 SCR_CaptureVideo_RIFF_Pop();
998                 SCR_CaptureVideo_RIFF_Push("JUNK", NULL);
999                 x = 4096 - SCR_CaptureVideo_RIFF_GetPosition();
1000                 while (x > 0)
1001                 {
1002                         const char *junkfiller = "[ DarkPlaces junk data ]";
1003                         int i = min(x, (int)strlen(junkfiller));
1004                         SCR_CaptureVideo_RIFF_WriteBytes((const unsigned char *)junkfiller, i);
1005                         x -= i;
1006                 }
1007                 SCR_CaptureVideo_RIFF_Pop();
1008                 SCR_CaptureVideo_RIFF_Pop();
1009                 // begin the actual video section now
1010                 SCR_CaptureVideo_RIFF_Push("LIST", "movi");
1011                 // we're done with the headers now...
1012                 SCR_CaptureVideo_RIFF_Flush();
1013                 if (cls.capturevideo.riffstacklevel != 2)
1014                         Sys_Error("SCR_CaptureVideo_BeginVideo: broken AVI writing code (stack level is %i (should be 2) at end of headers)\n", cls.capturevideo.riffstacklevel);
1015         }
1016         else if (cl_capturevideo_rawrgb.integer)
1017         {
1018                 cls.capturevideo.format = CAPTUREVIDEOFORMAT_RAWRGB;
1019                 cls.capturevideo.videofile = FS_Open (va("%s.rgb", cls.capturevideo.basename), "wb", false, true);
1020         }
1021         else if (cl_capturevideo_rawyv12.integer)
1022         {
1023                 cls.capturevideo.format = CAPTUREVIDEOFORMAT_RAWYV12;
1024                 cls.capturevideo.videofile = FS_Open (va("%s.yv12", cls.capturevideo.basename), "wb", false, true);
1025         }
1026         else if (scr_screenshot_jpeg.integer)
1027         {
1028                 cls.capturevideo.format = CAPTUREVIDEOFORMAT_JPEG;
1029                 cls.capturevideo.videofile = NULL;
1030         }
1031         else
1032         {
1033                 cls.capturevideo.format = CAPTUREVIDEOFORMAT_TARGA;
1034                 cls.capturevideo.videofile = NULL;
1035         }
1036
1037         switch(cls.capturevideo.format)
1038         {
1039         case CAPTUREVIDEOFORMAT_AVI_I420:
1040                 cls.capturevideo.soundfile = NULL;
1041                 break;
1042         default:
1043                 cls.capturevideo.soundfile = FS_Open (va("%s.wav", cls.capturevideo.basename), "wb", false, true);
1044                 if (cls.capturevideo.soundfile)
1045                 {
1046                         // wave header will be filled out when video ends
1047                         memset(out, 0, 44);
1048                         FS_Write (cls.capturevideo.soundfile, out, 44);
1049                 }
1050                 else
1051                         Con_Printf("Could not open video/dpvideo.wav for writing, sound capture disabled\n");
1052                 break;
1053         }
1054 }
1055
1056 void SCR_CaptureVideo_EndVideo(void)
1057 {
1058         int i, n;
1059         unsigned char out[44];
1060         if (!cls.capturevideo.active)
1061                 return;
1062         cls.capturevideo.active = false;
1063         if (cls.capturevideo.videofile)
1064         {
1065                 switch(cls.capturevideo.format)
1066                 {
1067                 case CAPTUREVIDEOFORMAT_AVI_I420:
1068                         // close any open chunks
1069                         SCR_CaptureVideo_RIFF_Finish();
1070                         FS_Seek(cls.capturevideo.videofile, cls.capturevideo.videofile_totalframes_offset1, SEEK_SET);
1071                         SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.frame);
1072                         SCR_CaptureVideo_RIFF_Flush();
1073                         FS_Seek(cls.capturevideo.videofile, cls.capturevideo.videofile_totalframes_offset2, SEEK_SET);
1074                         SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.frame);
1075                         SCR_CaptureVideo_RIFF_Flush();
1076                         if (cls.capturevideo.soundrate)
1077                         {
1078                                 FS_Seek(cls.capturevideo.videofile, cls.capturevideo.videofile_totalsampleframes_offset, SEEK_SET);
1079                                 SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundsampleframe);
1080                                 SCR_CaptureVideo_RIFF_Flush();
1081                         }
1082                         break;
1083                 default:
1084                         break;
1085                 }
1086                 FS_Close(cls.capturevideo.videofile);
1087                 cls.capturevideo.videofile = NULL;
1088         }
1089
1090         // finish the wave file
1091         if (cls.capturevideo.soundfile)
1092         {
1093                 i = (int)FS_Tell (cls.capturevideo.soundfile);
1094                 //"RIFF", (int) unknown (chunk size), "WAVE",
1095                 //"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
1096                 //"data", (int) unknown (chunk size)
1097                 memcpy (out, "RIFF****WAVEfmt \x10\x00\x00\x00\x01\x00\x02\x00********\x04\x00\x10\0data****", 44);
1098                 // the length of the whole RIFF chunk
1099                 n = i - 8;
1100                 out[4] = (n) & 0xFF;
1101                 out[5] = (n >> 8) & 0xFF;
1102                 out[6] = (n >> 16) & 0xFF;
1103                 out[7] = (n >> 24) & 0xFF;
1104                 // rate
1105                 n = cls.capturevideo.soundrate;
1106                 out[24] = (n) & 0xFF;
1107                 out[25] = (n >> 8) & 0xFF;
1108                 out[26] = (n >> 16) & 0xFF;
1109                 out[27] = (n >> 24) & 0xFF;
1110                 // bytes per second (rate * channels * bytes per channel)
1111                 n = cls.capturevideo.soundrate * 2 * 2;
1112                 out[28] = (n) & 0xFF;
1113                 out[29] = (n >> 8) & 0xFF;
1114                 out[30] = (n >> 16) & 0xFF;
1115                 out[31] = (n >> 24) & 0xFF;
1116                 // the length of the data chunk
1117                 n = i - 44;
1118                 out[40] = (n) & 0xFF;
1119                 out[41] = (n >> 8) & 0xFF;
1120                 out[42] = (n >> 16) & 0xFF;
1121                 out[43] = (n >> 24) & 0xFF;
1122                 FS_Seek (cls.capturevideo.soundfile, 0, SEEK_SET);
1123                 FS_Write (cls.capturevideo.soundfile, out, 44);
1124                 FS_Close (cls.capturevideo.soundfile);
1125                 cls.capturevideo.soundfile = NULL;
1126         }
1127
1128         if (cls.capturevideo.buffer)
1129         {
1130                 Mem_Free (cls.capturevideo.buffer);
1131                 cls.capturevideo.buffer = NULL;
1132         }
1133
1134         memset(&cls.capturevideo, 0, sizeof(cls.capturevideo));
1135 }
1136
1137 // converts from RGB24 to YV12 colorspace (native colorspace used by MPEG encoders/decoders)
1138 void SCR_CaptureVideo_ConvertFrame_RGB_to_YV12_flip(int width, int height, unsigned char *instart, unsigned char *outstart)
1139 {
1140         int x, y;
1141         int outoffset = (width/2)*(height/2);
1142         unsigned char *b, *out;
1143         // process one line at a time, and CbCr every other line at 2 pixel intervals
1144         for (y = 0;y < height;y++)
1145         {
1146                 // 1x1 Y
1147                 for (b = instart + (height-1-y)*width*3, out = outstart + y*width, x = 0;x < width;x++, b += 3, out++)
1148                         *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]]];
1149                 if ((y & 1) == 0)
1150                 {
1151                         // 2x2 Cb and Cr planes
1152 #if 0
1153                         // low quality, no averaging
1154                         for (b = instart + (height-2-y)*width*3, out = outstart + width*height + (y/2)*(width/2), x = 0;x < width/2;x++, b += 6, out++)
1155                         {
1156                                 // Cb
1157                                 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];
1158                                 // Cr
1159                                 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];
1160                         }
1161 #else
1162                         // high quality, averaging
1163                         int inpitch = width*3;
1164                         for (b = instart + (height-2-y)*width*3, out = outstart + width*height + (y/2)*(width/2), x = 0;x < width/2;x++, b += 6, out++)
1165                         {
1166                                 int blockr, blockg, blockb;
1167                                 blockr = (b[0] + b[3] + b[inpitch+0] + b[inpitch+3]) >> 2;
1168                                 blockg = (b[1] + b[4] + b[inpitch+1] + b[inpitch+4]) >> 2;
1169                                 blockb = (b[2] + b[5] + b[inpitch+2] + b[inpitch+5]) >> 2;
1170                                 // Cb
1171                                 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];
1172                                 // Cr
1173                                 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];
1174                         }
1175 #endif
1176                 }
1177         }
1178 }
1179
1180 // converts from RGB24 to I420 colorspace (identical to YV12 except chroma plane order is reversed)
1181 void SCR_CaptureVideo_ConvertFrame_RGB_to_I420_flip(int width, int height, unsigned char *instart, unsigned char *outstart)
1182 {
1183         int x, y;
1184         int outoffset = (width/2)*(height/2);
1185         unsigned char *b, *out;
1186         // process one line at a time, and CbCr every other line at 2 pixel intervals
1187         for (y = 0;y < height;y++)
1188         {
1189                 // 1x1 Y
1190                 for (b = instart + (height-1-y)*width*3, out = outstart + y*width, x = 0;x < width;x++, b += 3, out++)
1191                         *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]]];
1192                 if ((y & 1) == 0)
1193                 {
1194                         // 2x2 Cr and Cb planes
1195 #if 0
1196                         // low quality, no averaging
1197                         for (b = instart + (height-2-y)*width*3, out = outstart + width*height + (y/2)*(width/2), x = 0;x < width/2;x++, b += 6, out++)
1198                         {
1199                                 // Cr
1200                                 out[0        ] = 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];
1201                                 // Cb
1202                                 out[outoffset] = 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];
1203                         }
1204 #else
1205                         // high quality, averaging
1206                         int inpitch = width*3;
1207                         for (b = instart + (height-2-y)*width*3, out = outstart + width*height + (y/2)*(width/2), x = 0;x < width/2;x++, b += 6, out++)
1208                         {
1209                                 int blockr, blockg, blockb;
1210                                 blockr = (b[0] + b[3] + b[inpitch+0] + b[inpitch+3]) >> 2;
1211                                 blockg = (b[1] + b[4] + b[inpitch+1] + b[inpitch+4]) >> 2;
1212                                 blockb = (b[2] + b[5] + b[inpitch+2] + b[inpitch+5]) >> 2;
1213                                 // Cr
1214                                 out[0        ] = cls.capturevideo.yuvnormalizetable[1][cls.capturevideo.rgbtoyuvscaletable[1][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[1][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[1][2][blockb] + 128];
1215                                 // Cb
1216                                 out[outoffset] = cls.capturevideo.yuvnormalizetable[2][cls.capturevideo.rgbtoyuvscaletable[2][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[2][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[2][2][blockb] + 128];
1217                         }
1218 #endif
1219                 }
1220         }
1221 }
1222
1223 qboolean SCR_CaptureVideo_VideoFrame(int newframenum)
1224 {
1225         int x = 0, y = 0, width = vid.width, height = vid.height;
1226         unsigned char *in, *out;
1227         char filename[32];
1228         CHECKGLERROR
1229         //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);
1230         // speed is critical here, so do saving as directly as possible
1231         switch (cls.capturevideo.format)
1232         {
1233         case CAPTUREVIDEOFORMAT_AVI_I420:
1234                 // if there's no videofile we have to just give up, and abort saving if there's no video or sound file
1235                 if (!cls.capturevideo.videofile)
1236                         return cls.capturevideo.soundfile != NULL;
1237                 // FIXME: width/height must be multiple of 2, enforce this?
1238                 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cls.capturevideo.buffer);CHECKGLERROR
1239                 in = cls.capturevideo.buffer;
1240                 out = cls.capturevideo.buffer + width*height*3;
1241                 SCR_CaptureVideo_ConvertFrame_RGB_to_I420_flip(width, height, in, out);
1242                 x = width*height+(width/2)*(height/2)*2;
1243                 SCR_CaptureVideo_RIFF_OverflowCheck(8 + x);
1244                 for (;cls.capturevideo.frame < newframenum;cls.capturevideo.frame++)
1245                 {
1246                         SCR_CaptureVideo_RIFF_IndexEntry("00dc", x, 0x10); // AVIIF_KEYFRAME
1247                         SCR_CaptureVideo_RIFF_Push("00dc", NULL);
1248                         SCR_CaptureVideo_RIFF_WriteBytes(out, x);
1249                         SCR_CaptureVideo_RIFF_Pop();
1250                 }
1251                 return true;
1252         case CAPTUREVIDEOFORMAT_RAWYV12:
1253                 // if there's no videofile we have to just give up, and abort saving if there's no video or sound file
1254                 if (!cls.capturevideo.videofile)
1255                         return cls.capturevideo.soundfile != NULL;
1256                 // FIXME: width/height must be multiple of 2, enforce this?
1257                 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cls.capturevideo.buffer);CHECKGLERROR
1258                 in = cls.capturevideo.buffer;
1259                 out = cls.capturevideo.buffer + width*height*3;
1260                 SCR_CaptureVideo_ConvertFrame_RGB_to_YV12_flip(width, height, in, out);
1261                 x = width*height+(width/2)*(height/2)*2;
1262                 for (;cls.capturevideo.frame < newframenum;cls.capturevideo.frame++)
1263                         if (!FS_Write (cls.capturevideo.videofile, out, x))
1264                                 return false;
1265                 return true;
1266         case CAPTUREVIDEOFORMAT_RAWRGB:
1267                 // if there's no videofile we have to just give up, and abort saving if there's no video or sound file
1268                 if (!cls.capturevideo.videofile)
1269                         return cls.capturevideo.soundfile != NULL;
1270                 // FIXME: this should flip the images...  ?
1271                 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cls.capturevideo.buffer);CHECKGLERROR
1272                 for (;cls.capturevideo.frame < newframenum;cls.capturevideo.frame++)
1273                         if (!FS_Write (cls.capturevideo.videofile, cls.capturevideo.buffer, width*height*3))
1274                                 return false;
1275                 return true;
1276         case CAPTUREVIDEOFORMAT_JPEG:
1277                 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cls.capturevideo.buffer);CHECKGLERROR
1278                 for (;cls.capturevideo.frame < newframenum;cls.capturevideo.frame++)
1279                 {
1280                         sprintf(filename, "%s_%06d.jpg", cls.capturevideo.basename, cls.capturevideo.frame);
1281                         if (!JPEG_SaveImage_preflipped (filename, width, height, cls.capturevideo.buffer))
1282                                 return false;
1283                 }
1284                 return true;
1285         case CAPTUREVIDEOFORMAT_TARGA:
1286                 //return Image_WriteTGARGB_preflipped (filename, width, height, cls.capturevideo.buffer, cls.capturevideo.buffer + vid.width * vid.height * 3, );
1287                 memset (cls.capturevideo.buffer, 0, 18);
1288                 cls.capturevideo.buffer[2] = 2;         // uncompressed type
1289                 cls.capturevideo.buffer[12] = (width >> 0) & 0xFF;
1290                 cls.capturevideo.buffer[13] = (width >> 8) & 0xFF;
1291                 cls.capturevideo.buffer[14] = (height >> 0) & 0xFF;
1292                 cls.capturevideo.buffer[15] = (height >> 8) & 0xFF;
1293                 cls.capturevideo.buffer[16] = 24;       // pixel size
1294                 qglReadPixels (x, y, width, height, GL_BGR, GL_UNSIGNED_BYTE, cls.capturevideo.buffer + 18);CHECKGLERROR
1295                 for (;cls.capturevideo.frame < newframenum;cls.capturevideo.frame++)
1296                 {
1297                         sprintf(filename, "%s_%06d.tga", cls.capturevideo.basename, cls.capturevideo.frame);
1298                         if (!FS_WriteFile (filename, cls.capturevideo.buffer, width*height*3 + 18))
1299                                 return false;
1300                 }
1301                 return true;
1302         default:
1303                 return false;
1304         }
1305 }
1306
1307 void SCR_CaptureVideo_SoundFrame(unsigned char *bufstereo16le, size_t length, int rate)
1308 {
1309         int x;
1310         cls.capturevideo.soundrate = rate;
1311         cls.capturevideo.soundsampleframe += length;
1312         switch (cls.capturevideo.format)
1313         {
1314         case CAPTUREVIDEOFORMAT_AVI_I420:
1315                 x = length*4;
1316                 SCR_CaptureVideo_RIFF_OverflowCheck(8 + x);
1317                 SCR_CaptureVideo_RIFF_IndexEntry("01wb", x, 0x10); // AVIIF_KEYFRAME
1318                 SCR_CaptureVideo_RIFF_Push("01wb", NULL);
1319                 SCR_CaptureVideo_RIFF_WriteBytes(bufstereo16le, x);
1320                 SCR_CaptureVideo_RIFF_Pop();
1321                 break;
1322         default:
1323                 if (cls.capturevideo.soundfile)
1324                         if (FS_Write(cls.capturevideo.soundfile, bufstereo16le, 4 * length) < (fs_offset_t)(4 * length))
1325                                 cls.capturevideo.error = true;
1326                 break;
1327         }
1328 }
1329
1330 void SCR_CaptureVideo(void)
1331 {
1332         int newframenum;
1333         if (cl_capturevideo.integer && r_render.integer)
1334         {
1335                 if (!cls.capturevideo.active)
1336                         SCR_CaptureVideo_BeginVideo();
1337                 if (cls.capturevideo.framerate != cl_capturevideo_fps.value)
1338                 {
1339                         Con_Printf("You can not change the video framerate while recording a video.\n");
1340                         Cvar_SetValueQuick(&cl_capturevideo_fps, cls.capturevideo.framerate);
1341                 }
1342                 // for AVI saving we have to make sure that sound is saved before video
1343                 if (cls.capturevideo.soundrate && !cls.capturevideo.soundsampleframe)
1344                         return;
1345 #if 0
1346                 if (cls.capturevideo.soundfile)
1347                 {
1348                         // preserve sound sync by duplicating frames when running slow
1349                         newframenum = (int)((Sys_DoubleTime() - cls.capturevideo.starttime) * cls.capturevideo.framerate);
1350                 }
1351                 else
1352 #endif
1353                         newframenum = cls.capturevideo.frame + 1;
1354                 // if falling behind more than one second, stop
1355                 if (newframenum - cls.capturevideo.frame > (int)ceil(cls.capturevideo.framerate))
1356                 {
1357                         Cvar_SetValueQuick(&cl_capturevideo, 0);
1358                         Con_Printf("video saving failed on frame %i, your machine is too slow for this capture speed.\n", cls.capturevideo.frame);
1359                         SCR_CaptureVideo_EndVideo();
1360                         return;
1361                 }
1362                 // write frames
1363                 SCR_CaptureVideo_VideoFrame(newframenum);
1364                 if (cls.capturevideo.error)
1365                 {
1366                         Cvar_SetValueQuick(&cl_capturevideo, 0);
1367                         Con_Printf("video saving failed on frame %i, out of disk space? stopping video capture.\n", cls.capturevideo.frame);
1368                         SCR_CaptureVideo_EndVideo();
1369                 }
1370         }
1371         else if (cls.capturevideo.active)
1372                 SCR_CaptureVideo_EndVideo();
1373 }
1374
1375 /*
1376 ===============
1377 R_Envmap_f
1378
1379 Grab six views for environment mapping tests
1380 ===============
1381 */
1382 struct envmapinfo_s
1383 {
1384         float angles[3];
1385         char *name;
1386         qboolean flipx, flipy, flipdiagonaly;
1387 }
1388 envmapinfo[12] =
1389 {
1390         {{  0,   0, 0}, "rt", false, false, false},
1391         {{  0, 270, 0}, "ft", false, false, false},
1392         {{  0, 180, 0}, "lf", false, false, false},
1393         {{  0,  90, 0}, "bk", false, false, false},
1394         {{-90, 180, 0}, "up",  true,  true, false},
1395         {{ 90, 180, 0}, "dn",  true,  true, false},
1396
1397         {{  0,   0, 0}, "px",  true,  true,  true},
1398         {{  0,  90, 0}, "py", false,  true, false},
1399         {{  0, 180, 0}, "nx", false, false,  true},
1400         {{  0, 270, 0}, "ny",  true, false, false},
1401         {{-90, 180, 0}, "pz", false, false,  true},
1402         {{ 90, 180, 0}, "nz", false, false,  true}
1403 };
1404
1405 static void R_Envmap_f (void)
1406 {
1407         int j, size;
1408         char filename[MAX_QPATH], basename[MAX_QPATH];
1409         unsigned char *buffer1;
1410         unsigned char *buffer2;
1411         unsigned char *buffer3;
1412
1413         if (Cmd_Argc() != 3)
1414         {
1415                 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");
1416                 return;
1417         }
1418
1419         strlcpy (basename, Cmd_Argv(1), sizeof (basename));
1420         size = atoi(Cmd_Argv(2));
1421         if (size != 128 && size != 256 && size != 512 && size != 1024)
1422         {
1423                 Con_Print("envmap: size must be one of 128, 256, 512, or 1024\n");
1424                 return;
1425         }
1426         if (size > vid.width || size > vid.height)
1427         {
1428                 Con_Print("envmap: your resolution is not big enough to render that size\n");
1429                 return;
1430         }
1431
1432         r_refdef.envmap = true;
1433
1434         R_UpdateVariables();
1435
1436         r_view.x = 0;
1437         r_view.y = 0;
1438         r_view.z = 0;
1439         r_view.width = size;
1440         r_view.height = size;
1441         r_view.depth = 1;
1442
1443         r_view.frustum_x = tan(90 * M_PI / 360.0);
1444         r_view.frustum_y = tan(90 * M_PI / 360.0);
1445
1446         buffer1 = (unsigned char *)Mem_Alloc(tempmempool, size * size * 3);
1447         buffer2 = (unsigned char *)Mem_Alloc(tempmempool, size * size * 3);
1448         buffer3 = (unsigned char *)Mem_Alloc(tempmempool, size * size * 3 + 18);
1449
1450         for (j = 0;j < 12;j++)
1451         {
1452                 sprintf(filename, "env/%s%s.tga", basename, envmapinfo[j].name);
1453                 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);
1454                 R_ClearScreen();
1455                 R_Mesh_Start();
1456                 R_RenderView();
1457                 R_Mesh_Finish();
1458                 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);
1459         }
1460
1461         Mem_Free (buffer1);
1462         Mem_Free (buffer2);
1463         Mem_Free (buffer3);
1464
1465         r_refdef.envmap = false;
1466 }
1467
1468 //=============================================================================
1469
1470 // LordHavoc: SHOWLMP stuff
1471 #define SHOWLMP_MAXLABELS 256
1472 typedef struct showlmp_s
1473 {
1474         qboolean        isactive;
1475         float           x;
1476         float           y;
1477         char            label[32];
1478         char            pic[128];
1479 }
1480 showlmp_t;
1481
1482 showlmp_t showlmp[SHOWLMP_MAXLABELS];
1483
1484 void SHOWLMP_decodehide(void)
1485 {
1486         int i;
1487         char *lmplabel;
1488         lmplabel = MSG_ReadString();
1489         for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1490                 if (showlmp[i].isactive && strcmp(showlmp[i].label, lmplabel) == 0)
1491                 {
1492                         showlmp[i].isactive = false;
1493                         return;
1494                 }
1495 }
1496
1497 void SHOWLMP_decodeshow(void)
1498 {
1499         int i, k;
1500         char lmplabel[256], picname[256];
1501         float x, y;
1502         strlcpy (lmplabel,MSG_ReadString(), sizeof (lmplabel));
1503         strlcpy (picname, MSG_ReadString(), sizeof (picname));
1504         if (gamemode == GAME_NEHAHRA) // LordHavoc: nasty old legacy junk
1505         {
1506                 x = MSG_ReadByte();
1507                 y = MSG_ReadByte();
1508         }
1509         else
1510         {
1511                 x = MSG_ReadShort();
1512                 y = MSG_ReadShort();
1513         }
1514         k = -1;
1515         for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1516                 if (showlmp[i].isactive)
1517                 {
1518                         if (strcmp(showlmp[i].label, lmplabel) == 0)
1519                         {
1520                                 k = i;
1521                                 break; // drop out to replace it
1522                         }
1523                 }
1524                 else if (k < 0) // find first empty one to replace
1525                         k = i;
1526         if (k < 0)
1527                 return; // none found to replace
1528         // change existing one
1529         showlmp[k].isactive = true;
1530         strlcpy (showlmp[k].label, lmplabel, sizeof (showlmp[k].label));
1531         strlcpy (showlmp[k].pic, picname, sizeof (showlmp[k].pic));
1532         showlmp[k].x = x;
1533         showlmp[k].y = y;
1534 }
1535
1536 void SHOWLMP_drawall(void)
1537 {
1538         int i;
1539         for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1540                 if (showlmp[i].isactive)
1541                         DrawQ_Pic(showlmp[i].x, showlmp[i].y, Draw_CachePic(showlmp[i].pic, true), 0, 0, 1, 1, 1, 1, 0);
1542 }
1543
1544 void SHOWLMP_clear(void)
1545 {
1546         int i;
1547         for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1548                 showlmp[i].isactive = false;
1549 }
1550
1551 /*
1552 ==============================================================================
1553
1554                                                 SCREEN SHOTS
1555
1556 ==============================================================================
1557 */
1558
1559 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)
1560 {
1561         int     indices[3] = {0,1,2};
1562         qboolean ret;
1563
1564         if (!r_render.integer)
1565                 return false;
1566
1567         CHECKGLERROR
1568         qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, buffer1);CHECKGLERROR
1569
1570         if (scr_screenshot_gammaboost.value != 1 && gammacorrect)
1571         {
1572                 int i;
1573                 double igamma = 1.0 / scr_screenshot_gammaboost.value;
1574                 unsigned char ramp[256];
1575                 for (i = 0;i < 256;i++)
1576                         ramp[i] = (unsigned char) (pow(i * (1.0 / 255.0), igamma) * 255.0);
1577                 for (i = 0;i < width*height*3;i++)
1578                         buffer1[i] = ramp[buffer1[i]];
1579         }
1580
1581         Image_CopyMux (buffer2, buffer1, width, height, flipx, flipy, flipdiagonal, 3, 3, indices);
1582
1583         if (jpeg)
1584                 ret = JPEG_SaveImage_preflipped (filename, width, height, buffer2);
1585         else
1586                 ret = Image_WriteTGARGB_preflipped (filename, width, height, buffer2, buffer3);
1587
1588         return ret;
1589 }
1590
1591 //=============================================================================
1592
1593 void R_ClearScreen(void)
1594 {
1595         // clear to black
1596         CHECKGLERROR
1597         if (r_refdef.fogenabled)
1598         {
1599                 qglClearColor(r_refdef.fogcolor[0],r_refdef.fogcolor[1],r_refdef.fogcolor[2],0);CHECKGLERROR
1600         }
1601         else
1602         {
1603                 qglClearColor(0,0,0,0);CHECKGLERROR
1604         }
1605         qglClearDepth(1);CHECKGLERROR
1606         if (gl_stencil)
1607         {
1608                 // LordHavoc: we use a stencil centered around 128 instead of 0,
1609                 // to avoid clamping interfering with strange shadow volume
1610                 // drawing orders
1611                 qglClearStencil(128);CHECKGLERROR
1612         }
1613         // clear the screen
1614         if (r_render.integer)
1615                 GL_Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | (gl_stencil ? GL_STENCIL_BUFFER_BIT : 0));
1616         // set dithering mode
1617         if (gl_dither.integer)
1618         {
1619                 qglEnable(GL_DITHER);CHECKGLERROR
1620         }
1621         else
1622         {
1623                 qglDisable(GL_DITHER);CHECKGLERROR
1624         }
1625 }
1626
1627 qboolean CL_VM_UpdateView (void);
1628 void SCR_DrawConsole (void);
1629 void R_Shadow_EditLights_DrawSelectedLightProperties(void);
1630
1631 int r_stereo_side;
1632
1633 void SCR_DrawScreen (void)
1634 {
1635         R_Mesh_Start();
1636
1637         if (r_timereport_active)
1638                 R_TimeReport("setup");
1639
1640         R_UpdateVariables();
1641
1642         if (cls.signon == SIGNONS)
1643         {
1644                 float size;
1645
1646                 size = scr_viewsize.value * (1.0 / 100.0);
1647                 size = min(size, 1);
1648
1649                 if (r_stereo_sidebyside.integer)
1650                 {
1651                         r_view.width = (int)(vid.width * size / 2.5);
1652                         r_view.height = (int)(vid.height * size / 2.5 * (1 - bound(0, r_letterbox.value, 100) / 100));
1653                         r_view.depth = 1;
1654                         r_view.x = (int)((vid.width - r_view.width * 2.5) * 0.5);
1655                         r_view.y = (int)((vid.height - r_view.height)/2);
1656                         r_view.z = 0;
1657                         if (r_stereo_side)
1658                                 r_view.x += (int)(r_view.width * 1.5);
1659                 }
1660                 else
1661                 {
1662                         r_view.width = (int)(vid.width * size);
1663                         r_view.height = (int)(vid.height * size * (1 - bound(0, r_letterbox.value, 100) / 100));
1664                         r_view.depth = 1;
1665                         r_view.x = (int)((vid.width - r_view.width)/2);
1666                         r_view.y = (int)((vid.height - r_view.height)/2);
1667                         r_view.z = 0;
1668                 }
1669
1670                 // LordHavoc: viewzoom (zoom in for sniper rifles, etc)
1671                 // LordHavoc: this is designed to produce widescreen fov values
1672                 // when the screen is wider than 4/3 width/height aspect, to do
1673                 // this it simply assumes the requested fov is the vertical fov
1674                 // for a 4x3 display, if the ratio is not 4x3 this makes the fov
1675                 // higher/lower according to the ratio
1676                 r_view.frustum_y = tan(scr_fov.value * cl.viewzoom * M_PI / 360.0) * (3.0/4.0);
1677                 r_view.frustum_x = r_view.frustum_y * (float)r_view.width / (float)r_view.height / vid_pixelheight.value;
1678
1679                 r_view.frustum_x *= r_refdef.frustumscale_x;
1680                 r_view.frustum_y *= r_refdef.frustumscale_y;
1681
1682                 if(!CL_VM_UpdateView())
1683                         R_RenderView();
1684                 else
1685                         SCR_DrawConsole();
1686
1687                 if (scr_zoomwindow.integer)
1688                 {
1689                         float sizex = bound(10, scr_zoomwindow_viewsizex.value, 100) / 100.0;
1690                         float sizey = bound(10, scr_zoomwindow_viewsizey.value, 100) / 100.0;
1691                         r_view.width = (int)(vid.width * sizex);
1692                         r_view.height = (int)(vid.height * sizey);
1693                         r_view.depth = 1;
1694                         r_view.x = (int)((vid.width - r_view.width)/2);
1695                         r_view.y = 0;
1696                         r_view.z = 0;
1697
1698                         r_view.frustum_y = tan(scr_zoomwindow_fov.value * cl.viewzoom * M_PI / 360.0) * (3.0/4.0);
1699                         r_view.frustum_x = r_view.frustum_y * vid_pixelheight.value * (float)r_view.width / (float)r_view.height;
1700
1701                         r_view.frustum_x *= r_refdef.frustumscale_x;
1702                         r_view.frustum_y *= r_refdef.frustumscale_y;
1703
1704                         if(!CL_VM_UpdateView())
1705                                 R_RenderView();
1706                 }
1707         }
1708
1709         if (!r_stereo_sidebyside.integer)
1710         {
1711                 r_view.width = vid.width;
1712                 r_view.height = vid.height;
1713                 r_view.depth = 1;
1714                 r_view.x = 0;
1715                 r_view.y = 0;
1716                 r_view.z = 0;
1717         }
1718
1719         // draw 2D stuff
1720
1721         //FIXME: force menu if nothing else to look at?
1722         //if (key_dest == key_game && cls.signon != SIGNONS && cls.state == ca_disconnected)
1723
1724         if (cls.signon == SIGNONS)
1725         {
1726                 SCR_DrawNet ();
1727                 SCR_DrawTurtle ();
1728                 SCR_DrawPause ();
1729                 if (!r_letterbox.value)
1730                         Sbar_Draw();
1731                 SHOWLMP_drawall();
1732                 SCR_CheckDrawCenterString();
1733         }
1734         MR_Draw();
1735         CL_DrawVideo();
1736         R_Shadow_EditLights_DrawSelectedLightProperties();
1737
1738         if(!csqc_loaded)
1739                 SCR_DrawConsole();
1740
1741         SCR_DrawBrand();
1742
1743         SCR_DrawDownload();
1744
1745         if (r_timereport_active)
1746                 R_TimeReport("2d");
1747
1748         if (cls.signon == SIGNONS)
1749                 R_TimeReport_Frame();
1750
1751         DrawQ_Finish();
1752
1753         R_DrawGamma();
1754
1755         R_Mesh_Finish();
1756
1757         if (r_timereport_active)
1758                 R_TimeReport("meshfinish");
1759 }
1760
1761 void SCR_UpdateLoadingScreen (void)
1762 {
1763         float x, y;
1764         cachepic_t *pic;
1765         float vertex3f[12];
1766         float texcoord2f[8];
1767         // don't do anything if not initialized yet
1768         if (vid_hidden)
1769                 return;
1770         CHECKGLERROR
1771         qglViewport(0, 0, vid.width, vid.height);CHECKGLERROR
1772         //qglDisable(GL_SCISSOR_TEST);CHECKGLERROR
1773         //qglDepthMask(1);CHECKGLERROR
1774         qglColorMask(1,1,1,1);CHECKGLERROR
1775         //qglClearColor(0,0,0,0);CHECKGLERROR
1776         //qglClear(GL_COLOR_BUFFER_BIT);CHECKGLERROR
1777         //qglCullFace(GL_FRONT);CHECKGLERROR
1778         //qglDisable(GL_CULL_FACE);CHECKGLERROR
1779         //R_ClearScreen();
1780         R_Textures_Frame();
1781         GL_SetupView_Mode_Ortho(0, 0, vid_conwidth.integer, vid_conheight.integer, -10, 100);
1782         R_Mesh_Start();
1783         R_Mesh_Matrix(&identitymatrix);
1784         // draw the loading plaque
1785         pic = Draw_CachePic("gfx/loading", true);
1786         x = (vid_conwidth.integer - pic->width)/2;
1787         y = (vid_conheight.integer - pic->height)/2;
1788         GL_Color(1,1,1,1);
1789         GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1790         GL_DepthTest(false);
1791         R_Mesh_VertexPointer(vertex3f);
1792         R_Mesh_ColorPointer(NULL);
1793         R_Mesh_ResetTextureState();
1794         R_Mesh_TexBind(0, R_GetTexture(pic->tex));
1795         R_Mesh_TexCoordPointer(0, 2, texcoord2f);
1796         vertex3f[2] = vertex3f[5] = vertex3f[8] = vertex3f[11] = 0;
1797         vertex3f[0] = vertex3f[9] = x;
1798         vertex3f[1] = vertex3f[4] = y;
1799         vertex3f[3] = vertex3f[6] = x + pic->width;
1800         vertex3f[7] = vertex3f[10] = y + pic->height;
1801         texcoord2f[0] = 0;texcoord2f[1] = 0;
1802         texcoord2f[2] = 1;texcoord2f[3] = 0;
1803         texcoord2f[4] = 1;texcoord2f[5] = 1;
1804         texcoord2f[6] = 0;texcoord2f[7] = 1;
1805         R_Mesh_Draw(0, 4, 2, polygonelements);
1806         R_Mesh_Finish();
1807         // refresh
1808         VID_Finish(false);
1809 }
1810
1811 void CL_UpdateScreen(void)
1812 {
1813         float conwidth, conheight;
1814
1815         if (vid_hidden)
1816                 return;
1817
1818         if (!scr_initialized || !con_initialized || vid_hidden)
1819                 return;                         // not initialized yet
1820
1821         // don't allow cheats in multiplayer
1822         if (!cl.islocalgame && cl.worldmodel)
1823         {
1824                 if (r_fullbright.integer != 0)
1825                         Cvar_Set ("r_fullbright", "0");
1826                 if (r_ambient.value != 0)
1827                         Cvar_Set ("r_ambient", "0");
1828         }
1829
1830         conwidth = bound(320, vid_conwidth.value, 2048);
1831         conheight = bound(200, vid_conheight.value, 1536);
1832         if (vid_conwidth.value != conwidth)
1833                 Cvar_SetValue("vid_conwidth", conwidth);
1834         if (vid_conheight.value != conheight)
1835                 Cvar_SetValue("vid_conheight", conheight);
1836
1837         // bound viewsize
1838         if (scr_viewsize.value < 30)
1839                 Cvar_Set ("viewsize","30");
1840         if (scr_viewsize.value > 120)
1841                 Cvar_Set ("viewsize","120");
1842
1843         // bound field of view
1844         if (scr_fov.value < 1)
1845                 Cvar_Set ("fov","1");
1846         if (scr_fov.value > 170)
1847                 Cvar_Set ("fov","170");
1848
1849         // validate r_textureunits cvar
1850         if (r_textureunits.integer > gl_textureunits)
1851                 Cvar_SetValueQuick(&r_textureunits, gl_textureunits);
1852         if (r_textureunits.integer < 1)
1853                 Cvar_SetValueQuick(&r_textureunits, 1);
1854
1855         // validate gl_combine cvar
1856         if (gl_combine.integer && !gl_combine_extension)
1857                 Cvar_SetValueQuick(&gl_combine, 0);
1858
1859         // intermission is always full screen
1860         if (cl.intermission)
1861                 sb_lines = 0;
1862         else
1863         {
1864                 if (scr_viewsize.value >= 120)
1865                         sb_lines = 0;           // no status bar at all
1866                 else if (scr_viewsize.value >= 110)
1867                         sb_lines = 24;          // no inventory
1868                 else
1869                         sb_lines = 24+16+8;
1870         }
1871
1872         r_view.colormask[0] = 1;
1873         r_view.colormask[1] = 1;
1874         r_view.colormask[2] = 1;
1875
1876         if (r_timereport_active)
1877                 R_TimeReport("other");
1878
1879         SCR_SetUpToDrawConsole();
1880
1881         if (r_timereport_active)
1882                 R_TimeReport("start");
1883
1884         CHECKGLERROR
1885         qglViewport(0, 0, vid.width, vid.height);CHECKGLERROR
1886         qglDisable(GL_SCISSOR_TEST);CHECKGLERROR
1887         qglDepthMask(1);CHECKGLERROR
1888         qglColorMask(1,1,1,1);CHECKGLERROR
1889         qglClearColor(0,0,0,0);CHECKGLERROR
1890         qglClear(GL_COLOR_BUFFER_BIT);CHECKGLERROR
1891
1892         if (r_timereport_active)
1893                 R_TimeReport("clear");
1894
1895         if (r_stereo_redblue.integer || r_stereo_redgreen.integer || r_stereo_redcyan.integer || r_stereo_sidebyside.integer)
1896         {
1897                 matrix4x4_t originalmatrix = r_view.matrix;
1898                 r_view.matrix.m[0][3] = originalmatrix.m[0][3] + r_stereo_separation.value * -0.5f * r_view.matrix.m[0][1];
1899                 r_view.matrix.m[1][3] = originalmatrix.m[1][3] + r_stereo_separation.value * -0.5f * r_view.matrix.m[1][1];
1900                 r_view.matrix.m[2][3] = originalmatrix.m[2][3] + r_stereo_separation.value * -0.5f * r_view.matrix.m[2][1];
1901
1902                 if (r_stereo_sidebyside.integer)
1903                         r_stereo_side = 0;
1904
1905                 if (r_stereo_redblue.integer || r_stereo_redgreen.integer || r_stereo_redcyan.integer)
1906                 {
1907                         r_view.colormask[0] = 1;
1908                         r_view.colormask[1] = 0;
1909                         r_view.colormask[2] = 0;
1910                 }
1911
1912                 SCR_DrawScreen();
1913
1914                 r_view.matrix.m[0][3] = originalmatrix.m[0][3] + r_stereo_separation.value * 0.5f * r_view.matrix.m[0][1];
1915                 r_view.matrix.m[1][3] = originalmatrix.m[1][3] + r_stereo_separation.value * 0.5f * r_view.matrix.m[1][1];
1916                 r_view.matrix.m[2][3] = originalmatrix.m[2][3] + r_stereo_separation.value * 0.5f * r_view.matrix.m[2][1];
1917
1918                 if (r_stereo_sidebyside.integer)
1919                         r_stereo_side = 1;
1920
1921                 if (r_stereo_redblue.integer || r_stereo_redgreen.integer || r_stereo_redcyan.integer)
1922                 {
1923                         r_view.colormask[0] = 0;
1924                         r_view.colormask[1] = r_stereo_redcyan.integer || r_stereo_redgreen.integer;
1925                         r_view.colormask[2] = r_stereo_redcyan.integer || r_stereo_redblue.integer;
1926                 }
1927
1928                 SCR_DrawScreen();
1929
1930                 r_view.matrix = originalmatrix;
1931         }
1932         else
1933                 SCR_DrawScreen();
1934
1935         SCR_CaptureVideo();
1936
1937         VID_Finish(true);
1938         if (r_timereport_active)
1939                 R_TimeReport("finish");
1940 }
1941
1942 void CL_Screen_NewMap(void)
1943 {
1944         SHOWLMP_clear();
1945 }