Added colored string support to centerprint.
[divverent/darkplaces.git] / cl_screen.c
1
2 #include "quakedef.h"
3 #include "cl_video.h"
4 #include "jpeg.h"
5 #include "cl_collision.h"
6
7 cvar_t scr_viewsize = {CVAR_SAVE, "viewsize","100"};
8 cvar_t scr_fov = {CVAR_SAVE, "fov","90"};       // 1 - 170
9 cvar_t scr_conspeed = {CVAR_SAVE, "scr_conspeed","900"}; // LordHavoc: quake used 300
10 cvar_t scr_conalpha = {CVAR_SAVE, "scr_conalpha", "1"};
11 cvar_t scr_conbrightness = {CVAR_SAVE, "scr_conbrightness", "0.2"};
12 cvar_t scr_conforcewhiledisconnected = {CVAR_SAVE, "scr_conforcewhiledisconnected", "1"};
13 cvar_t scr_centertime = {0, "scr_centertime","2"};
14 cvar_t scr_showram = {CVAR_SAVE, "showram","1"};
15 cvar_t scr_showturtle = {CVAR_SAVE, "showturtle","0"};
16 cvar_t scr_showpause = {CVAR_SAVE, "showpause","1"};
17 cvar_t scr_printspeed = {0, "scr_printspeed","8"};
18 cvar_t vid_conwidth = {CVAR_SAVE, "vid_conwidth", "640"};
19 cvar_t vid_conheight = {CVAR_SAVE, "vid_conheight", "480"};
20 cvar_t vid_pixelaspect = {CVAR_SAVE, "vid_pixelaspect", "1"};
21 cvar_t scr_screenshot_jpeg = {CVAR_SAVE, "scr_screenshot_jpeg","0"};
22 cvar_t scr_screenshot_jpeg_quality = {CVAR_SAVE, "scr_screenshot_jpeg_quality","0.9"};
23 cvar_t scr_screenshot_gamma = {CVAR_SAVE, "scr_screenshot_gamma","2.2"};
24 cvar_t scr_screenshot_name = {0, "scr_screenshot_name","dp"};
25 cvar_t cl_capturevideo = {0, "cl_capturevideo", "0"};
26 cvar_t cl_capturevideo_sound = {0, "cl_capturevideo_sound", "0"};
27 cvar_t cl_capturevideo_fps = {0, "cl_capturevideo_fps", "30"};
28 cvar_t cl_capturevideo_rawrgb = {0, "cl_capturevideo_rawrgb", "0"};
29 cvar_t cl_capturevideo_rawyv12 = {0, "cl_capturevideo_rawyv12", "0"};
30 cvar_t r_textshadow = {0, "r_textshadow", "0"};
31 cvar_t r_letterbox = {0, "r_letterbox", "0"};
32
33 int jpeg_supported = false;
34
35 qboolean        scr_initialized;                // ready to draw
36
37 float           scr_con_current;
38
39 extern int      con_vislines;
40
41 void DrawCrosshair(int num);
42 static void SCR_ScreenShot_f (void);
43 static void R_Envmap_f (void);
44
45 // backend
46 void R_ClearScreen(void);
47
48 // color tag printing
49 static vec4_t string_colors[] =
50 {
51         // Quake3 colors
52         // LordHavoc: why on earth is cyan before magenta in Quake3?
53         // LordHavoc: note: Doom3 uses white for [0] and [7]
54         {0.0, 0.0, 0.0, 1.0}, // black
55         {1.0, 0.0, 0.0, 1.0}, // red
56         {0.0, 1.0, 0.0, 1.0}, // green
57         {1.0, 1.0, 0.0, 1.0}, // yellow
58         {0.0, 0.0, 1.0, 1.0}, // blue
59         {0.0, 1.0, 1.0, 1.0}, // cyan
60         {1.0, 0.0, 1.0, 1.0}, // magenta
61         {1.0, 1.0, 1.0, 1.0}, // white
62         // [515]'s BX_COLOREDTEXT extension
63         {1.0, 1.0, 1.0, 0.5}, // half transparent
64         {0.5, 0.5, 0.5, 1.0}  // half brightness
65         // Black's color table
66         //{1.0, 1.0, 1.0, 1.0},
67         //{1.0, 0.0, 0.0, 1.0},
68         //{0.0, 1.0, 0.0, 1.0},
69         //{0.0, 0.0, 1.0, 1.0},
70         //{1.0, 1.0, 0.0, 1.0},
71         //{0.0, 1.0, 1.0, 1.0},
72         //{1.0, 0.0, 1.0, 1.0},
73         //{0.1, 0.1, 0.1, 1.0}
74 };
75
76 #define STRING_COLORS_COUNT     (sizeof(string_colors) / sizeof(vec4_t))
77
78 // color is read and changed in the end
79 void DrawQ_ColoredString( float x, float y, const char *text, int maxlen, float scalex, float scaley, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor )
80 {
81         vec_t *color;
82         int len;
83         int colorindex;
84         const char *start, *current;
85
86         if( !outcolor || *outcolor == -1 ) {
87                 colorindex = STRING_COLOR_DEFAULT;
88         } else {
89                 colorindex = *outcolor;
90         }
91         color = string_colors[colorindex];
92
93         if( maxlen < 1)
94                 len = (int)strlen( text );
95         else
96                 len = min( maxlen, (int) strlen( text ) );
97
98         start = current = text;
99         while( len > 0 ) {
100                 // check for color control char
101                 if( *current == STRING_COLOR_TAG ) {
102                         // get next char
103                         current++;
104                         len--;
105                         if( len == 0 ) {
106                                 break;
107                         }
108                         // display the tag char?
109                         if( *current == STRING_COLOR_TAG ) {
110                                 // only display one of the two
111                                 start = current;
112                                 // get the next char
113                                 current++;
114                                 len--;
115                         } else if( '0' <= *current && *current <= '9' ) {
116                                 colorindex = 0;
117                                 do {
118                                         colorindex = colorindex * 10 + (*current - '0');
119                                         // only read as long as it makes a valid index
120                                         if( colorindex >= (int)STRING_COLORS_COUNT ) {
121                                                 // undo the last operation
122                                                 colorindex /= 10;
123                                                 break;
124                                         }
125                                         current++;
126                                         len--;
127                                 } while( len > 0 && '0' <= *current && *current <= '9' );
128                                 // set the color
129                                 color = string_colors[colorindex];
130                                 // we jump over the color tag
131                                 start = current;
132                         }
133                 }
134                 // go on and read normal text in until the next control char
135                 while( len > 0 && *current != STRING_COLOR_TAG ) {
136                         current++;
137                         len--;
138                 }
139                 // display the text
140                 if( start != current ) {
141                         // draw the string
142                         DrawQ_String( x, y, start, current - start, scalex, scaley, basered * color[0], basegreen * color[1], baseblue * color[2], basealpha * color[3], flags );
143                         // update x to be at the new start position
144                         x += (current - start) * scalex;
145                         // set start accordingly
146                         start = current;
147                 }
148         }
149
150         // return the last colorindex
151         if( outcolor ) {
152                 *outcolor = colorindex;
153         }
154 }
155
156 /*
157 ===============================================================================
158
159 CENTER PRINTING
160
161 ===============================================================================
162 */
163
164 char            scr_centerstring[1024];
165 float           scr_centertime_start;   // for slow victory printing
166 float           scr_centertime_off;
167 int                     scr_center_lines;
168 int                     scr_erase_lines;
169 int                     scr_erase_center;
170
171 /*
172 ==============
173 SCR_CenterPrint
174
175 Called for important messages that should stay in the center of the screen
176 for a few moments
177 ==============
178 */
179 void SCR_CenterPrint(char *str)
180 {
181         strlcpy (scr_centerstring, str, sizeof (scr_centerstring));
182         scr_centertime_off = scr_centertime.value;
183         scr_centertime_start = cl.time;
184
185 // count the number of lines for centering
186         scr_center_lines = 1;
187         while (*str)
188         {
189                 if (*str == '\n')
190                         scr_center_lines++;
191                 str++;
192         }
193 }
194
195
196 void SCR_DrawCenterString (void)
197 {
198         char    *start;
199         int             l;
200         int             x, y;
201         int             remaining;
202         int             color;
203
204 // the finale prints the characters one at a time
205         if (cl.intermission)
206                 remaining = scr_printspeed.value * (cl.time - scr_centertime_start);
207         else
208                 remaining = 9999;
209
210         scr_erase_center = 0;
211         start = scr_centerstring;
212
213         if (scr_center_lines <= 4)
214                 y = vid_conheight.integer*0.35;
215         else
216                 y = 48;
217
218         color = -1;
219         do
220         {
221         // scan the width of the line
222                 for (l=0 ; l<vid_conwidth.integer/8 ; l++)
223                         if (start[l] == '\n' || !start[l])
224                                 break;
225                 x = (vid_conwidth.integer - l*8)/2;
226                 if (l > 0)
227                 {
228                         if (remaining < l)
229                                 l = remaining;
230                         DrawQ_ColoredString(x, y, start, l, 8, 8, 1, 1, 1, 1, 0, &color);
231                         remaining -= l;
232                         if (remaining <= 0)
233                                 return;
234                 }
235
236                 y += 8;
237
238                 while (*start && *start != '\n')
239                         start++;
240
241                 if (!*start)
242                         break;
243                 start++;                // skip the \n
244         } while (1);
245 }
246
247 void SCR_CheckDrawCenterString (void)
248 {
249         if (scr_center_lines > scr_erase_lines)
250                 scr_erase_lines = scr_center_lines;
251
252         scr_centertime_off -= host_frametime;
253
254         // don't draw if this is a normal stats-screen intermission,
255         // only if it is not an intermission, or a finale intermission
256         if (cl.intermission == 1)
257                 return;
258         if (scr_centertime_off <= 0 && !cl.intermission)
259                 return;
260         if (key_dest != key_game)
261                 return;
262
263         SCR_DrawCenterString ();
264 }
265
266 /*
267 ==============
268 SCR_DrawTurtle
269 ==============
270 */
271 void SCR_DrawTurtle (void)
272 {
273         static int      count;
274
275         if (cls.state != ca_connected)
276                 return;
277
278         if (!scr_showturtle.integer)
279                 return;
280
281         if (host_frametime < 0.1)
282         {
283                 count = 0;
284                 return;
285         }
286
287         count++;
288         if (count < 3)
289                 return;
290
291         DrawQ_Pic (0, 0, "gfx/turtle", 0, 0, 1, 1, 1, 1, 0);
292 }
293
294 /*
295 ==============
296 SCR_DrawNet
297 ==============
298 */
299 void SCR_DrawNet (void)
300 {
301         if (cls.state != ca_connected)
302                 return;
303         if (realtime - cl.last_received_message < 0.3)
304                 return;
305         if (cls.demoplayback)
306                 return;
307
308         DrawQ_Pic (64, 0, "gfx/net", 0, 0, 1, 1, 1, 1, 0);
309 }
310
311 /*
312 ==============
313 DrawPause
314 ==============
315 */
316 void SCR_DrawPause (void)
317 {
318         cachepic_t      *pic;
319
320         if (cls.state != ca_connected)
321                 return;
322
323         if (!scr_showpause.integer)             // turn off for screenshots
324                 return;
325
326         if (!cl.paused)
327                 return;
328
329         pic = Draw_CachePic ("gfx/pause", true);
330         DrawQ_Pic ((vid_conwidth.integer - pic->width)/2, (vid_conheight.integer - pic->height)/2, "gfx/pause", 0, 0, 1, 1, 1, 1, 0);
331 }
332
333
334
335
336
337 //=============================================================================
338
339
340 /*
341 ==================
342 SCR_SetUpToDrawConsole
343 ==================
344 */
345 void SCR_SetUpToDrawConsole (void)
346 {
347         // lines of console to display
348         float conlines;
349
350         Con_CheckResize ();
351
352         if (key_dest == key_game && cls.signon != SIGNONS && scr_conforcewhiledisconnected.integer)
353                 key_consoleactive |= KEY_CONSOLEACTIVE_FORCED;
354         else
355                 key_consoleactive &= ~KEY_CONSOLEACTIVE_FORCED;
356
357 // decide on the height of the console
358         if (key_consoleactive & KEY_CONSOLEACTIVE_USER)
359                 conlines = vid_conheight.integer/2;     // half screen
360         else
361                 conlines = 0;                           // none visible
362
363         if (scr_conspeed.value)
364         {
365                 if (scr_con_current > conlines)
366                 {
367                         scr_con_current -= scr_conspeed.value*host_realframetime;
368                         if (scr_con_current < conlines)
369                                 scr_con_current = conlines;
370
371                 }
372                 else if (scr_con_current < conlines)
373                 {
374                         scr_con_current += scr_conspeed.value*host_realframetime;
375                         if (scr_con_current > conlines)
376                                 scr_con_current = conlines;
377                 }
378         }
379         else
380                 scr_con_current = conlines;
381 }
382
383 /*
384 ==================
385 SCR_DrawConsole
386 ==================
387 */
388 void SCR_DrawConsole (void)
389 {
390         if (key_consoleactive & KEY_CONSOLEACTIVE_FORCED)
391         {
392                 // full screen
393                 Con_DrawConsole (vid_conheight.integer);
394         }
395         else if (scr_con_current)
396                 Con_DrawConsole (scr_con_current);
397         else
398         {
399                 con_vislines = 0;
400                 if (key_dest == key_game || key_dest == key_message)
401                         Con_DrawNotify ();      // only draw notify in game
402         }
403 }
404
405 /*
406 ===============
407 SCR_BeginLoadingPlaque
408
409 ================
410 */
411 void SCR_BeginLoadingPlaque (void)
412 {
413         Host_StartVideo();
414         S_StopAllSounds();
415         SCR_UpdateLoadingScreen();
416 }
417
418 //=============================================================================
419
420 char r_speeds_string[1024];
421 int speedstringcount, r_timereport_active;
422 double r_timereport_temp = 0, r_timereport_current = 0, r_timereport_start = 0;
423
424 void R_TimeReport(char *desc)
425 {
426         char tempbuf[256];
427         int length;
428         int t;
429
430         if (!r_timereport_active || r_showtrispass)
431                 return;
432
433         r_timereport_temp = r_timereport_current;
434         r_timereport_current = Sys_DoubleTime();
435         t = (int) ((r_timereport_current - r_timereport_temp) * 1000000.0);
436
437         dpsnprintf(tempbuf, sizeof(tempbuf), "%8i %s", t, desc);
438         length = (int)strlen(tempbuf);
439         while (length < 20)
440                 tempbuf[length++] = ' ';
441         tempbuf[length] = 0;
442         if (speedstringcount + length > (vid_conwidth.integer / 8))
443         {
444                 strlcat(r_speeds_string, "\n", sizeof(r_speeds_string));
445                 speedstringcount = 0;
446         }
447         // skip the space at the beginning if it's the first on the line
448         if (speedstringcount == 0)
449         {
450                 strlcat(r_speeds_string, tempbuf + 1, sizeof(r_speeds_string));
451                 speedstringcount = length - 1;
452         }
453         else
454         {
455                 strlcat(r_speeds_string, tempbuf, sizeof(r_speeds_string));
456                 speedstringcount += length;
457         }
458 }
459
460 void R_TimeReport_Start(void)
461 {
462         r_timereport_active = r_speeds.integer && cls.signon == SIGNONS && cls.state == ca_connected;
463         r_speeds_string[0] = 0;
464         if (r_timereport_active)
465         {
466                 speedstringcount = 0;
467                 sprintf(r_speeds_string + strlen(r_speeds_string), "org:'%+8.2f %+8.2f %+8.2f' dir:'%+2.3f %+2.3f %+2.3f'\n", r_vieworigin[0], r_vieworigin[1], r_vieworigin[2], r_viewforward[0], r_viewforward[1], r_viewforward[2]);
468                 sprintf(r_speeds_string + strlen(r_speeds_string), "world:%6i faces%6i nodes%6i leafs%6i dlitwalls\n", c_faces, c_nodes, c_leafs, c_light_polys);
469                 sprintf(r_speeds_string + strlen(r_speeds_string), "%5i models%5i bmodels%5i sprites%6i particles%4i dlights\n", c_models, c_bmodels, c_sprites, c_particles, c_dlights);
470                 sprintf(r_speeds_string + strlen(r_speeds_string), "%6i modeltris%6i meshs%6i meshtris\n", c_alias_polys, c_meshs, c_meshelements / 3);
471                 sprintf(r_speeds_string + strlen(r_speeds_string), "bloom %s: %i copies (%i pixels) %i draws (%i pixels)\n", c_bloom ? "active" : "inactive", c_bloomcopies, c_bloomcopypixels, c_bloomdraws, c_bloomdrawpixels);
472                 sprintf(r_speeds_string + strlen(r_speeds_string), "realtime lighting:%4i lights%4i clears%4i scissored\n", c_rt_lights, c_rt_clears, c_rt_scissored);
473                 sprintf(r_speeds_string + strlen(r_speeds_string), "dynamic: %6i shadowmeshes%6i shadowtris%6i lightmeshes%6i lighttris\n", c_rt_shadowmeshes, c_rt_shadowtris, c_rt_lightmeshes, c_rt_lighttris);
474                 sprintf(r_speeds_string + strlen(r_speeds_string), "precomputed: %6i shadowmeshes%6i shadowtris\n", c_rtcached_shadowmeshes, c_rtcached_shadowtris);
475
476                 c_alias_polys = 0;
477                 c_light_polys = 0;
478                 c_faces = 0;
479                 c_nodes = 0;
480                 c_leafs = 0;
481                 c_models = 0;
482                 c_bmodels = 0;
483                 c_sprites = 0;
484                 c_particles = 0;
485                 c_dlights = 0;
486                 c_meshs = 0;
487                 c_meshelements = 0;
488                 c_rt_lights = 0;
489                 c_rt_clears = 0;
490                 c_rt_scissored = 0;
491                 c_rt_shadowmeshes = 0;
492                 c_rt_shadowtris = 0;
493                 c_rt_lightmeshes = 0;
494                 c_rt_lighttris = 0;
495                 c_rtcached_shadowmeshes = 0;
496                 c_rtcached_shadowtris = 0;
497                 c_bloom = 0;
498                 c_bloomcopies = 0;
499                 c_bloomcopypixels = 0;
500                 c_bloomdraws = 0;
501                 c_bloomdrawpixels = 0;
502
503                 r_timereport_start = Sys_DoubleTime();
504         }
505 }
506
507 void R_TimeReport_End(void)
508 {
509         r_timereport_current = r_timereport_start;
510         R_TimeReport("total");
511
512         if (r_timereport_active)
513         {
514                 int i, j, lines, y;
515                 lines = 1;
516                 for (i = 0;r_speeds_string[i];i++)
517                         if (r_speeds_string[i] == '\n')
518                                 lines++;
519                 y = vid_conheight.integer - sb_lines - lines * 8;
520                 i = j = 0;
521                 DrawQ_Fill(0, y, vid_conwidth.integer, lines * 8, 0, 0, 0, 0.5, 0);
522                 while (r_speeds_string[i])
523                 {
524                         j = i;
525                         while (r_speeds_string[i] && r_speeds_string[i] != '\n')
526                                 i++;
527                         if (i - j > 0)
528                                 DrawQ_String(0, y, r_speeds_string + j, i - j, 8, 8, 1, 1, 1, 1, 0);
529                         if (r_speeds_string[i] == '\n')
530                                 i++;
531                         y += 8;
532                 }
533         }
534 }
535
536 /*
537 =================
538 SCR_SizeUp_f
539
540 Keybinding command
541 =================
542 */
543 void SCR_SizeUp_f (void)
544 {
545         Cvar_SetValue ("viewsize",scr_viewsize.value+10);
546 }
547
548
549 /*
550 =================
551 SCR_SizeDown_f
552
553 Keybinding command
554 =================
555 */
556 void SCR_SizeDown_f (void)
557 {
558         Cvar_SetValue ("viewsize",scr_viewsize.value-10);
559 }
560
561 void CL_Screen_Init(void)
562 {
563         Cvar_RegisterVariable (&scr_fov);
564         Cvar_RegisterVariable (&scr_viewsize);
565         Cvar_RegisterVariable (&scr_conspeed);
566         Cvar_RegisterVariable (&scr_conalpha);
567         Cvar_RegisterVariable (&scr_conbrightness);
568         Cvar_RegisterVariable (&scr_conforcewhiledisconnected);
569         Cvar_RegisterVariable (&scr_showram);
570         Cvar_RegisterVariable (&scr_showturtle);
571         Cvar_RegisterVariable (&scr_showpause);
572         Cvar_RegisterVariable (&scr_centertime);
573         Cvar_RegisterVariable (&scr_printspeed);
574         Cvar_RegisterVariable (&vid_conwidth);
575         Cvar_RegisterVariable (&vid_conheight);
576         Cvar_RegisterVariable (&vid_pixelaspect);
577         Cvar_RegisterVariable (&scr_screenshot_jpeg);
578         Cvar_RegisterVariable (&scr_screenshot_jpeg_quality);
579         Cvar_RegisterVariable (&scr_screenshot_gamma);
580         Cvar_RegisterVariable (&cl_capturevideo);
581         Cvar_RegisterVariable (&cl_capturevideo_sound);
582         Cvar_RegisterVariable (&cl_capturevideo_fps);
583         Cvar_RegisterVariable (&cl_capturevideo_rawrgb);
584         Cvar_RegisterVariable (&cl_capturevideo_rawyv12);
585         Cvar_RegisterVariable (&r_textshadow);
586         Cvar_RegisterVariable (&r_letterbox);
587
588         Cmd_AddCommand ("sizeup",SCR_SizeUp_f);
589         Cmd_AddCommand ("sizedown",SCR_SizeDown_f);
590         Cmd_AddCommand ("screenshot",SCR_ScreenShot_f);
591         Cmd_AddCommand ("envmap", R_Envmap_f);
592
593         scr_initialized = true;
594 }
595
596 void DrawQ_Clear(void)
597 {
598         r_refdef.drawqueuesize = 0;
599 }
600
601 static int picelements[6] = {0, 1, 2, 0, 2, 3};
602 void DrawQ_Pic(float x, float y, const char *picname, float width, float height, float red, float green, float blue, float alpha, int flags)
603 {
604         DrawQ_SuperPic(x,y,picname,width,height,0,0,red,green,blue,alpha,1,0,red,green,blue,alpha,0,1,red,green,blue,alpha,1,1,red,green,blue,alpha,flags);
605 }
606
607 void DrawQ_String_Real(float x, float y, const char *string, int maxlen, float scalex, float scaley, float red, float green, float blue, float alpha, int flags)
608 {
609         int size, len;
610         drawqueue_t *dq;
611         char *out;
612         if (alpha < (1.0f / 255.0f))
613                 return;
614         if (maxlen < 1)
615                 len = (int)strlen(string);
616         else
617                 for (len = 0;len < maxlen && string[len];len++);
618         for (;len > 0 && string[0] == ' ';string++, x += scalex, len--);
619         for (;len > 0 && string[len - 1] == ' ';len--);
620         if (len < 1)
621                 return;
622         if (x >= vid_conwidth.integer || y >= vid_conheight.integer || x < (-scalex * len) || y < (-scaley))
623                 return;
624         size = sizeof(*dq) + ((len + 1 + 3) & ~3);
625         if (r_refdef.drawqueuesize + size > r_refdef.maxdrawqueuesize)
626                 return;
627         red = bound(0, red, 1);
628         green = bound(0, green, 1);
629         blue = bound(0, blue, 1);
630         alpha = bound(0, alpha, 1);
631         dq = (void *)(r_refdef.drawqueue + r_refdef.drawqueuesize);
632         dq->size = size;
633         dq->command = DRAWQUEUE_STRING;
634         dq->flags = flags;
635         dq->color = ((unsigned int) (red * 255.0f) << 24) | ((unsigned int) (green * 255.0f) << 16) | ((unsigned int) (blue * 255.0f) << 8) | ((unsigned int) (alpha * 255.0f));
636         dq->x = x;
637         dq->y = y;
638         dq->scalex = scalex;
639         dq->scaley = scaley;
640         out = (char *)(dq + 1);
641         memcpy(out, string, len);
642         out[len] = 0;
643         r_refdef.drawqueuesize += dq->size;
644 }
645
646 void DrawQ_String(float x, float y, const char *string, int maxlen, float scalex, float scaley, float red, float green, float blue, float alpha, int flags)
647 {
648         if (r_textshadow.integer)
649                 DrawQ_String_Real(x+scalex*0.25,y+scaley*0.25,string,maxlen,scalex,scaley,0,0,0,alpha*0.8,flags);
650
651         DrawQ_String_Real(x,y,string,maxlen,scalex,scaley,red,green,blue,alpha,flags);
652 }
653
654
655
656 void DrawQ_Fill (float x, float y, float w, float h, float red, float green, float blue, float alpha, int flags)
657 {
658         DrawQ_SuperPic(x,y,NULL,w,h,0,0,red,green,blue,alpha,1,0,red,green,blue,alpha,0,1,red,green,blue,alpha,1,1,red,green,blue,alpha,flags);
659 }
660
661 void DrawQ_SuperPic(float x, float y, const char *picname, float width, float height, float s1, float t1, float r1, float g1, float b1, float a1, float s2, float t2, float r2, float g2, float b2, float a2, float s3, float t3, float r3, float g3, float b3, float a3, float s4, float t4, float r4, float g4, float b4, float a4, int flags)
662 {
663         float floats[36];
664         cachepic_t *pic;
665         drawqueuemesh_t mesh;
666         memset(&mesh, 0, sizeof(mesh));
667         if (picname && picname[0])
668         {
669                 pic = Draw_CachePic(picname, false);
670                 if (width == 0)
671                         width = pic->width;
672                 if (height == 0)
673                         height = pic->height;
674                 mesh.texture = pic->tex;
675         }
676         mesh.num_triangles = 2;
677         mesh.num_vertices = 4;
678         mesh.data_element3i = picelements;
679         mesh.data_vertex3f = floats;
680         mesh.data_texcoord2f = floats + 12;
681         mesh.data_color4f = floats + 20;
682         memset(floats, 0, sizeof(floats));
683         mesh.data_vertex3f[0] = mesh.data_vertex3f[9] = x;
684         mesh.data_vertex3f[1] = mesh.data_vertex3f[4] = y;
685         mesh.data_vertex3f[3] = mesh.data_vertex3f[6] = x + width;
686         mesh.data_vertex3f[7] = mesh.data_vertex3f[10] = y + height;
687         mesh.data_texcoord2f[0] = s1;mesh.data_texcoord2f[1] = t1;mesh.data_color4f[ 0] = r1;mesh.data_color4f[ 1] = g1;mesh.data_color4f[ 2] = b1;mesh.data_color4f[ 3] = a1;
688         mesh.data_texcoord2f[2] = s2;mesh.data_texcoord2f[3] = t2;mesh.data_color4f[ 4] = r2;mesh.data_color4f[ 5] = g2;mesh.data_color4f[ 6] = b2;mesh.data_color4f[ 7] = a2;
689         mesh.data_texcoord2f[4] = s4;mesh.data_texcoord2f[5] = t4;mesh.data_color4f[ 8] = r4;mesh.data_color4f[ 9] = g4;mesh.data_color4f[10] = b4;mesh.data_color4f[11] = a4;
690         mesh.data_texcoord2f[6] = s3;mesh.data_texcoord2f[7] = t3;mesh.data_color4f[12] = r3;mesh.data_color4f[13] = g3;mesh.data_color4f[14] = b3;mesh.data_color4f[15] = a3;
691         DrawQ_Mesh (&mesh, flags);
692 }
693
694 void DrawQ_Mesh (drawqueuemesh_t *mesh, int flags)
695 {
696         int size;
697         void *p;
698         drawqueue_t *dq;
699         drawqueuemesh_t *m;
700         size = sizeof(*dq);
701         size += sizeof(drawqueuemesh_t);
702         size += sizeof(int[3]) * mesh->num_triangles;
703         size += sizeof(float[3]) * mesh->num_vertices;
704         size += sizeof(float[2]) * mesh->num_vertices;
705         size += sizeof(float[4]) * mesh->num_vertices;
706         if (r_refdef.drawqueuesize + size > r_refdef.maxdrawqueuesize)
707                 return;
708         dq = (void *)(r_refdef.drawqueue + r_refdef.drawqueuesize);
709         dq->size = size;
710         dq->command = DRAWQUEUE_MESH;
711         dq->flags = flags;
712         dq->color = 0;
713         dq->x = 0;
714         dq->y = 0;
715         dq->scalex = 0;
716         dq->scaley = 0;
717         p = (void *)(dq + 1);
718         m = p;p = (qbyte*)p + sizeof(drawqueuemesh_t);
719         m->num_triangles = mesh->num_triangles;
720         m->num_vertices = mesh->num_vertices;
721         m->texture = mesh->texture;
722         m->data_element3i  = p;memcpy(m->data_element3i , mesh->data_element3i , m->num_triangles * sizeof(int[3]));p = (qbyte*)p + m->num_triangles * sizeof(int[3]);
723         m->data_vertex3f   = p;memcpy(m->data_vertex3f  , mesh->data_vertex3f  , m->num_vertices * sizeof(float[3]));p = (qbyte*)p + m->num_vertices * sizeof(float[3]);
724         m->data_texcoord2f = p;memcpy(m->data_texcoord2f, mesh->data_texcoord2f, m->num_vertices * sizeof(float[2]));p = (qbyte*)p + m->num_vertices * sizeof(float[2]);
725         m->data_color4f    = p;memcpy(m->data_color4f   , mesh->data_color4f   , m->num_vertices * sizeof(float[4]));p = (qbyte*)p + m->num_vertices * sizeof(float[4]);
726         r_refdef.drawqueuesize += dq->size;
727 }
728
729 void DrawQ_SetClipArea(float x, float y, float width, float height)
730 {
731         drawqueue_t * dq;
732         if(r_refdef.drawqueuesize + (int)sizeof(*dq) > r_refdef.maxdrawqueuesize)
733         {
734                 Con_DPrint("DrawQueue full !\n");
735                 return;
736         }
737         dq = (void*) (r_refdef.drawqueue + r_refdef.drawqueuesize);
738         dq->size = sizeof(*dq);
739         dq->command = DRAWQUEUE_SETCLIP;
740         dq->x = x;
741         dq->y = y;
742         dq->scalex = width;
743         dq->scaley = height;
744         dq->flags = 0;
745         dq->color = 0;
746
747         r_refdef.drawqueuesize += dq->size;
748 }
749
750 void DrawQ_ResetClipArea(void)
751 {
752         drawqueue_t *dq;
753         if(r_refdef.drawqueuesize + (int)sizeof(*dq) > r_refdef.maxdrawqueuesize)
754         {
755                 Con_DPrint("DrawQueue full !\n");
756                 return;
757         }
758         dq = (void*) (r_refdef.drawqueue + r_refdef.drawqueuesize);
759         dq->size = sizeof(*dq);
760         dq->command = DRAWQUEUE_RESETCLIP;
761         dq->x = 0;
762         dq->y = 0;
763         dq->scalex = 0;
764         dq->scaley = 0;
765         dq->flags = 0;
766         dq->color = 0;
767
768         r_refdef.drawqueuesize += dq->size;
769 }
770
771 /*
772 ==================
773 SCR_ScreenShot_f
774 ==================
775 */
776 void SCR_ScreenShot_f (void)
777 {
778         static int shotnumber;
779         static char oldname[MAX_QPATH];
780         char base[MAX_QPATH];
781         char filename[MAX_QPATH];
782         qbyte *buffer1;
783         qbyte *buffer2;
784         qbyte *buffer3;
785         qboolean jpeg = (scr_screenshot_jpeg.integer != 0);
786
787         sprintf (base, "screenshots/%s", scr_screenshot_name.string);
788
789         if (strcmp (oldname, scr_screenshot_name.string))
790         {
791                 sprintf(oldname, "%s", scr_screenshot_name.string);
792                 shotnumber = 0;
793         }
794
795         // find a file name to save it to
796         for (;shotnumber < 1000000;shotnumber++)
797                 if (!FS_SysFileExists(va("%s/%s%06d.tga", fs_gamedir, base, shotnumber)) && !FS_SysFileExists(va("%s/%s%06d.jpg", fs_gamedir, base, shotnumber)))
798                         break;
799         if (shotnumber >= 1000000)
800         {
801                 Con_Print("SCR_ScreenShot_f: Couldn't create the image file\n");
802                 return;
803         }
804
805         sprintf(filename, "%s%06d.%s", base, shotnumber, jpeg ? "jpg" : "tga");
806
807         buffer1 = Mem_Alloc(tempmempool, vid.width * vid.height * 3);
808         buffer2 = Mem_Alloc(tempmempool, vid.width * vid.height * 3);
809         buffer3 = Mem_Alloc(tempmempool, vid.width * vid.height * 3 + 18);
810
811         if (SCR_ScreenShot (filename, buffer1, buffer2, buffer3, 0, 0, vid.width, vid.height, false, false, false, jpeg, true))
812                 Con_Printf("Wrote %s\n", filename);
813         else
814                 Con_Printf("unable to write %s\n", filename);
815
816         Mem_Free (buffer1);
817         Mem_Free (buffer2);
818         Mem_Free (buffer3);
819
820         shotnumber++;
821 }
822
823 typedef enum capturevideoformat_e
824 {
825         CAPTUREVIDEOFORMAT_TARGA,
826         CAPTUREVIDEOFORMAT_JPEG,
827         CAPTUREVIDEOFORMAT_RAWRGB,
828         CAPTUREVIDEOFORMAT_RAWYV12
829 }
830 capturevideoformat_t;
831
832 qboolean cl_capturevideo_active = false;
833 capturevideoformat_t cl_capturevideo_format;
834 static double cl_capturevideo_starttime = 0;
835 double cl_capturevideo_framerate = 0;
836 static int cl_capturevideo_soundrate = 0;
837 static int cl_capturevideo_frame = 0;
838 static qbyte *cl_capturevideo_buffer = NULL;
839 static qfile_t *cl_capturevideo_videofile = NULL;
840 qfile_t *cl_capturevideo_soundfile = NULL;
841 static short cl_capturevideo_rgbtoyuvscaletable[3][3][256];
842 static unsigned char cl_capturevideo_yuvnormalizetable[3][256];
843 //static unsigned char cl_capturevideo_rgbgammatable[3][256];
844
845 void SCR_CaptureVideo_BeginVideo(void)
846 {
847         double gamma, g;
848         unsigned int i;
849         qbyte out[44];
850         if (cl_capturevideo_active)
851                 return;
852         // soundrate is figured out on the first SoundFrame
853         cl_capturevideo_active = true;
854         cl_capturevideo_starttime = Sys_DoubleTime();
855         cl_capturevideo_framerate = bound(1, cl_capturevideo_fps.value, 1000);
856         cl_capturevideo_soundrate = 0;
857         cl_capturevideo_frame = 0;
858         cl_capturevideo_buffer = Mem_Alloc(tempmempool, vid.width * vid.height * (3+3+3) + 18);
859         gamma = 1.0/scr_screenshot_gamma.value;
860
861         /*
862         for (i = 0;i < 256;i++)
863         {
864                 unsigned char j = (unsigned char)bound(0, 255*pow(i/255.0, gamma), 255);
865                 cl_capturevideo_rgbgammatable[0][i] = j;
866                 cl_capturevideo_rgbgammatable[1][i] = j;
867                 cl_capturevideo_rgbgammatable[2][i] = j;
868         }
869         */
870 /*
871 R = Y + 1.4075 * (Cr - 128);
872 G = Y + -0.3455 * (Cb - 128) + -0.7169 * (Cr - 128);
873 B = Y + 1.7790 * (Cb - 128);
874 Y = R *  .299 + G *  .587 + B *  .114;
875 Cb = R * -.169 + G * -.332 + B *  .500 + 128.;
876 Cr = R *  .500 + G * -.419 + B * -.0813 + 128.;
877 */
878         for (i = 0;i < 256;i++)
879         {
880                 g = 255*pow(i/255.0, gamma);
881                 // Y weights from RGB
882                 cl_capturevideo_rgbtoyuvscaletable[0][0][i] = (short)(g *  0.299);
883                 cl_capturevideo_rgbtoyuvscaletable[0][1][i] = (short)(g *  0.587);
884                 cl_capturevideo_rgbtoyuvscaletable[0][2][i] = (short)(g *  0.114);
885                 // Cb weights from RGB
886                 cl_capturevideo_rgbtoyuvscaletable[1][0][i] = (short)(g * -0.169);
887                 cl_capturevideo_rgbtoyuvscaletable[1][1][i] = (short)(g * -0.332);
888                 cl_capturevideo_rgbtoyuvscaletable[1][2][i] = (short)(g *  0.500);
889                 // Cr weights from RGB
890                 cl_capturevideo_rgbtoyuvscaletable[2][0][i] = (short)(g *  0.500);
891                 cl_capturevideo_rgbtoyuvscaletable[2][1][i] = (short)(g * -0.419);
892                 cl_capturevideo_rgbtoyuvscaletable[2][2][i] = (short)(g * -0.0813);
893                 // range reduction of YCbCr to valid signal range
894                 cl_capturevideo_yuvnormalizetable[0][i] = 16 + i * (236-16) / 256;
895                 cl_capturevideo_yuvnormalizetable[1][i] = 16 + i * (240-16) / 256;
896                 cl_capturevideo_yuvnormalizetable[2][i] = 16 + i * (240-16) / 256;
897         }
898
899         if (cl_capturevideo_rawrgb.integer)
900         {
901                 cl_capturevideo_format = CAPTUREVIDEOFORMAT_RAWRGB;
902                 cl_capturevideo_videofile = FS_Open ("video/dpvideo.rgb", "wb", false, true);
903         }
904         else if (cl_capturevideo_rawyv12.integer)
905         {
906                 cl_capturevideo_format = CAPTUREVIDEOFORMAT_RAWYV12;
907                 cl_capturevideo_videofile = FS_Open ("video/dpvideo.yv12", "wb", false, true);
908         }
909         else if (scr_screenshot_jpeg.integer)
910         {
911                 cl_capturevideo_format = CAPTUREVIDEOFORMAT_JPEG;
912                 cl_capturevideo_videofile = NULL;
913         }
914         else
915         {
916                 cl_capturevideo_format = CAPTUREVIDEOFORMAT_TARGA;
917                 cl_capturevideo_videofile = NULL;
918         }
919
920         if (cl_capturevideo_sound.integer)
921         {
922                 cl_capturevideo_soundfile = FS_Open ("video/dpvideo.wav", "wb", false, true);
923                 // wave header will be filled out when video ends
924                 memset(out, 0, 44);
925                 FS_Write (cl_capturevideo_soundfile, out, 44);
926         }
927         else
928                 cl_capturevideo_soundfile = NULL;
929 }
930
931 void SCR_CaptureVideo_EndVideo(void)
932 {
933         int i, n;
934         qbyte out[44];
935         if (!cl_capturevideo_active)
936                 return;
937         cl_capturevideo_active = false;
938
939         if (cl_capturevideo_videofile)
940         {
941                 FS_Close(cl_capturevideo_videofile);
942                 cl_capturevideo_videofile = NULL;
943         }
944
945         // finish the wave file
946         if (cl_capturevideo_soundfile)
947         {
948                 i = (int)FS_Tell (cl_capturevideo_soundfile);
949                 //"RIFF", (int) unknown (chunk size), "WAVE",
950                 //"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
951                 //"data", (int) unknown (chunk size)
952                 memcpy (out, "RIFF****WAVEfmt \x10\x00\x00\x00\x01\x00\x02\x00********\x04\x00\x10\0data****", 44);
953                 // the length of the whole RIFF chunk
954                 n = i - 8;
955                 out[4] = (n) & 0xFF;
956                 out[5] = (n >> 8) & 0xFF;
957                 out[6] = (n >> 16) & 0xFF;
958                 out[7] = (n >> 24) & 0xFF;
959                 // rate
960                 n = cl_capturevideo_soundrate;
961                 out[24] = (n) & 0xFF;
962                 out[25] = (n >> 8) & 0xFF;
963                 out[26] = (n >> 16) & 0xFF;
964                 out[27] = (n >> 24) & 0xFF;
965                 // bytes per second (rate * channels * bytes per channel)
966                 n = cl_capturevideo_soundrate * 2 * 2;
967                 out[28] = (n) & 0xFF;
968                 out[29] = (n >> 8) & 0xFF;
969                 out[30] = (n >> 16) & 0xFF;
970                 out[31] = (n >> 24) & 0xFF;
971                 // the length of the data chunk
972                 n = i - 44;
973                 out[40] = (n) & 0xFF;
974                 out[41] = (n >> 8) & 0xFF;
975                 out[42] = (n >> 16) & 0xFF;
976                 out[43] = (n >> 24) & 0xFF;
977                 FS_Seek (cl_capturevideo_soundfile, 0, SEEK_SET);
978                 FS_Write (cl_capturevideo_soundfile, out, 44);
979                 FS_Close (cl_capturevideo_soundfile);
980                 cl_capturevideo_soundfile = NULL;
981         }
982
983         if (cl_capturevideo_buffer)
984         {
985                 Mem_Free (cl_capturevideo_buffer);
986                 cl_capturevideo_buffer = NULL;
987         }
988
989         cl_capturevideo_starttime = 0;
990         cl_capturevideo_framerate = 0;
991         cl_capturevideo_frame = 0;
992 }
993
994 qboolean SCR_CaptureVideo_VideoFrame(int newframenum)
995 {
996         int x = 0, y = 0, width = vid.width, height = vid.height;
997         unsigned char *b, *out;
998         char filename[32];
999         int outoffset = (width/2)*(height/2);
1000         //return SCR_ScreenShot(filename, cl_capturevideo_buffer, cl_capturevideo_buffer + vid.width * vid.height * 3, cl_capturevideo_buffer + vid.width * vid.height * 6, 0, 0, vid.width, vid.height, false, false, false, jpeg, true);
1001         // speed is critical here, so do saving as directly as possible
1002         switch (cl_capturevideo_format)
1003         {
1004         case CAPTUREVIDEOFORMAT_RAWYV12:
1005                 // FIXME: width/height must be multiple of 2, enforce this?
1006                 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cl_capturevideo_buffer);
1007                 CHECKGLERROR
1008                 // process one line at a time, and CbCr every other line at 2 pixel intervals
1009                 for (y = 0;y < height;y++)
1010                 {
1011                         // 1x1 Y
1012                         for (b = cl_capturevideo_buffer + (height-1-y)*width*3, out = cl_capturevideo_buffer + width*height*3 + y*width, x = 0;x < width;x++, b += 3, out++)
1013                                 *out = cl_capturevideo_yuvnormalizetable[0][cl_capturevideo_rgbtoyuvscaletable[0][0][b[0]] + cl_capturevideo_rgbtoyuvscaletable[0][1][b[1]] + cl_capturevideo_rgbtoyuvscaletable[0][2][b[2]]];
1014                         if ((y & 1) == 0)
1015                         {
1016                                 // 2x2 Cb and Cr planes
1017 #if 1
1018                                 // low quality, no averaging
1019                                 for (b = cl_capturevideo_buffer + (height-2-y)*width*3, out = cl_capturevideo_buffer + width*height*3 + width*height + (y/2)*(width/2), x = 0;x < width/2;x++, b += 6, out++)
1020                                 {
1021                                         // Cr
1022                                         out[0        ] = cl_capturevideo_yuvnormalizetable[2][cl_capturevideo_rgbtoyuvscaletable[2][0][b[0]] + cl_capturevideo_rgbtoyuvscaletable[2][1][b[1]] + cl_capturevideo_rgbtoyuvscaletable[2][2][b[2]] + 128];
1023                                         // Cb
1024                                         out[outoffset] = cl_capturevideo_yuvnormalizetable[1][cl_capturevideo_rgbtoyuvscaletable[1][0][b[0]] + cl_capturevideo_rgbtoyuvscaletable[1][1][b[1]] + cl_capturevideo_rgbtoyuvscaletable[1][2][b[2]] + 128];
1025                                 }
1026 #else
1027                                 // high quality, averaging
1028                                 int inpitch = width*3;
1029                                 for (b = cl_capturevideo_buffer + (height-2-y)*width*3, out = cl_capturevideo_buffer + width*height*3 + width*height + (y/2)*(width/2), x = 0;x < width/2;x++, b += 6, out++)
1030                                 {
1031                                         int blockr, blockg, blockb;
1032                                         blockr = (b[0] + b[3] + b[inpitch+0] + b[inpitch+3]) >> 2;
1033                                         blockg = (b[1] + b[4] + b[inpitch+1] + b[inpitch+4]) >> 2;
1034                                         blockb = (b[2] + b[5] + b[inpitch+2] + b[inpitch+5]) >> 2;
1035                                         // Cr
1036                                         out[0        ] = cl_capturevideo_yuvnormalizetable[2][cl_capturevideo_rgbtoyuvscaletable[2][0][blockr] + cl_capturevideo_rgbtoyuvscaletable[2][1][blockg] + cl_capturevideo_rgbtoyuvscaletable[2][2][blockb] + 128];
1037                                         // Cb
1038                                         out[outoffset] = cl_capturevideo_yuvnormalizetable[1][cl_capturevideo_rgbtoyuvscaletable[1][0][blockr] + cl_capturevideo_rgbtoyuvscaletable[1][1][blockg] + cl_capturevideo_rgbtoyuvscaletable[1][2][blockb] + 128];
1039                                 }
1040 #endif
1041                         }
1042                 }
1043                 for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
1044                         if (!FS_Write (cl_capturevideo_videofile, cl_capturevideo_buffer + width*height*3, width*height+(width/2)*(height/2)*2))
1045                                 return false;
1046                 return true;
1047         case CAPTUREVIDEOFORMAT_RAWRGB:
1048                 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cl_capturevideo_buffer);
1049                 CHECKGLERROR
1050                 for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
1051                         if (!FS_Write (cl_capturevideo_videofile, cl_capturevideo_buffer, width*height*3))
1052                                 return false;
1053                 return true;
1054         case CAPTUREVIDEOFORMAT_JPEG:
1055                 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cl_capturevideo_buffer);
1056                 CHECKGLERROR
1057                 for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
1058                 {
1059                         sprintf(filename, "video/dp%06d.jpg", cl_capturevideo_frame);
1060                         if (!JPEG_SaveImage_preflipped (filename, width, height, cl_capturevideo_buffer))
1061                                 return false;
1062                 }
1063                 return true;
1064         case CAPTUREVIDEOFORMAT_TARGA:
1065                 //return Image_WriteTGARGB_preflipped (filename, width, height, cl_capturevideo_buffer, cl_capturevideo_buffer + vid.width * vid.height * 3, );
1066                 memset (cl_capturevideo_buffer, 0, 18);
1067                 cl_capturevideo_buffer[2] = 2;          // uncompressed type
1068                 cl_capturevideo_buffer[12] = (width >> 0) & 0xFF;
1069                 cl_capturevideo_buffer[13] = (width >> 8) & 0xFF;
1070                 cl_capturevideo_buffer[14] = (height >> 0) & 0xFF;
1071                 cl_capturevideo_buffer[15] = (height >> 8) & 0xFF;
1072                 cl_capturevideo_buffer[16] = 24;        // pixel size
1073                 qglReadPixels (x, y, width, height, GL_BGR, GL_UNSIGNED_BYTE, cl_capturevideo_buffer + 18);
1074                 CHECKGLERROR
1075                 for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
1076                 {
1077                         sprintf(filename, "video/dp%06d.tga", cl_capturevideo_frame);
1078                         if (!FS_WriteFile (filename, cl_capturevideo_buffer, width*height*3 + 18))
1079                                 return false;
1080                 }
1081                 return true;
1082         default:
1083                 return false;
1084         }
1085 }
1086
1087 void SCR_CaptureVideo_SoundFrame(qbyte *bufstereo16le, size_t length, int rate)
1088 {
1089         if (!cl_capturevideo_soundfile)
1090                 return;
1091         cl_capturevideo_soundrate = rate;
1092         if (FS_Write (cl_capturevideo_soundfile, bufstereo16le, 4 * length) < (fs_offset_t)(4 * length))
1093         {
1094                 Cvar_SetValueQuick(&cl_capturevideo, 0);
1095                 Con_Printf("video sound saving failed on frame %i, out of disk space? stopping video capture.\n", cl_capturevideo_frame);
1096                 SCR_CaptureVideo_EndVideo();
1097         }
1098 }
1099
1100 void SCR_CaptureVideo(void)
1101 {
1102         int newframenum;
1103         if (cl_capturevideo.integer && r_render.integer)
1104         {
1105                 if (!cl_capturevideo_active)
1106                         SCR_CaptureVideo_BeginVideo();
1107                 if (cl_capturevideo_framerate != cl_capturevideo_fps.value)
1108                 {
1109                         Con_Printf("You can not change the video framerate while recording a video.\n");
1110                         Cvar_SetValueQuick(&cl_capturevideo_fps, cl_capturevideo_framerate);
1111                 }
1112                 if (cl_capturevideo_soundfile)
1113                 {
1114                         // preserve sound sync by duplicating frames when running slow
1115                         newframenum = (Sys_DoubleTime() - cl_capturevideo_starttime) * cl_capturevideo_framerate;
1116                 }
1117                 else
1118                         newframenum = cl_capturevideo_frame + 1;
1119                 // if falling behind more than one second, stop
1120                 if (newframenum - cl_capturevideo_frame > (int)ceil(cl_capturevideo_framerate))
1121                 {
1122                         Cvar_SetValueQuick(&cl_capturevideo, 0);
1123                         Con_Printf("video saving failed on frame %i, your machine is too slow for this capture speed.\n", cl_capturevideo_frame);
1124                         SCR_CaptureVideo_EndVideo();
1125                         return;
1126                 }
1127                 // write frames
1128                 if (!SCR_CaptureVideo_VideoFrame(newframenum))
1129                 {
1130                         Cvar_SetValueQuick(&cl_capturevideo, 0);
1131                         Con_Printf("video saving failed on frame %i, out of disk space? stopping video capture.\n", cl_capturevideo_frame);
1132                         SCR_CaptureVideo_EndVideo();
1133                 }
1134         }
1135         else if (cl_capturevideo_active)
1136                 SCR_CaptureVideo_EndVideo();
1137 }
1138
1139 /*
1140 ===============
1141 R_Envmap_f
1142
1143 Grab six views for environment mapping tests
1144 ===============
1145 */
1146 struct
1147 {
1148         float angles[3];
1149         char *name;
1150         qboolean flipx, flipy, flipdiagonaly;
1151 }
1152 envmapinfo[12] =
1153 {
1154         {{  0,   0, 0}, "rt", false, false, false},
1155         {{  0, 270, 0}, "ft", false, false, false},
1156         {{  0, 180, 0}, "lf", false, false, false},
1157         {{  0,  90, 0}, "bk", false, false, false},
1158         {{-90, 180, 0}, "up",  true,  true, false},
1159         {{ 90, 180, 0}, "dn",  true,  true, false},
1160
1161         {{  0,   0, 0}, "px",  true,  true,  true},
1162         {{  0,  90, 0}, "py", false,  true, false},
1163         {{  0, 180, 0}, "nx", false, false,  true},
1164         {{  0, 270, 0}, "ny",  true, false, false},
1165         {{-90, 180, 0}, "pz", false, false,  true},
1166         {{ 90, 180, 0}, "nz", false, false,  true}
1167 };
1168
1169 static void R_Envmap_f (void)
1170 {
1171         int j, size;
1172         char filename[256], basename[256];
1173         qbyte *buffer1;
1174         qbyte *buffer2;
1175         qbyte *buffer3;
1176
1177         if (Cmd_Argc() != 3)
1178         {
1179                 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");
1180                 return;
1181         }
1182
1183         strlcpy (basename, Cmd_Argv(1), sizeof (basename));
1184         size = atoi(Cmd_Argv(2));
1185         if (size != 128 && size != 256 && size != 512 && size != 1024)
1186         {
1187                 Con_Print("envmap: size must be one of 128, 256, 512, or 1024\n");
1188                 return;
1189         }
1190         if (size > vid.width || size > vid.height)
1191         {
1192                 Con_Print("envmap: your resolution is not big enough to render that size\n");
1193                 return;
1194         }
1195
1196         envmap = true;
1197
1198         r_refdef.x = 0;
1199         r_refdef.y = 0;
1200         r_refdef.width = size;
1201         r_refdef.height = size;
1202
1203         r_refdef.fov_x = 90;
1204         r_refdef.fov_y = 90;
1205
1206         buffer1 = Mem_Alloc(tempmempool, size * size * 3);
1207         buffer2 = Mem_Alloc(tempmempool, size * size * 3);
1208         buffer3 = Mem_Alloc(tempmempool, size * size * 3 + 18);
1209
1210         for (j = 0;j < 12;j++)
1211         {
1212                 sprintf(filename, "env/%s%s.tga", basename, envmapinfo[j].name);
1213                 Matrix4x4_CreateFromQuakeEntity(&r_refdef.viewentitymatrix, r_vieworigin[0], r_vieworigin[1], r_vieworigin[2], envmapinfo[j].angles[0], envmapinfo[j].angles[1], envmapinfo[j].angles[2], 1);
1214                 R_ClearScreen();
1215                 R_Mesh_Start();
1216                 R_RenderView();
1217                 R_Mesh_Finish();
1218                 SCR_ScreenShot(filename, buffer1, buffer2, buffer3, 0, vid.height - (r_refdef.y + r_refdef.height), size, size, envmapinfo[j].flipx, envmapinfo[j].flipy, envmapinfo[j].flipdiagonaly, false, false);
1219         }
1220
1221         Mem_Free (buffer1);
1222         Mem_Free (buffer2);
1223         Mem_Free (buffer3);
1224
1225         envmap = false;
1226 }
1227
1228 //=============================================================================
1229
1230 // LordHavoc: SHOWLMP stuff
1231 #define SHOWLMP_MAXLABELS 256
1232 typedef struct showlmp_s
1233 {
1234         qboolean        isactive;
1235         float           x;
1236         float           y;
1237         char            label[32];
1238         char            pic[128];
1239 }
1240 showlmp_t;
1241
1242 showlmp_t showlmp[SHOWLMP_MAXLABELS];
1243
1244 void SHOWLMP_decodehide(void)
1245 {
1246         int i;
1247         qbyte *lmplabel;
1248         lmplabel = MSG_ReadString();
1249         for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1250                 if (showlmp[i].isactive && strcmp(showlmp[i].label, lmplabel) == 0)
1251                 {
1252                         showlmp[i].isactive = false;
1253                         return;
1254                 }
1255 }
1256
1257 void SHOWLMP_decodeshow(void)
1258 {
1259         int i, k;
1260         qbyte lmplabel[256], picname[256];
1261         float x, y;
1262         strlcpy (lmplabel,MSG_ReadString(), sizeof (lmplabel));
1263         strlcpy (picname, MSG_ReadString(), sizeof (picname));
1264         if (gamemode == GAME_NEHAHRA) // LordHavoc: nasty old legacy junk
1265         {
1266                 x = MSG_ReadByte();
1267                 y = MSG_ReadByte();
1268         }
1269         else
1270         {
1271                 x = MSG_ReadShort();
1272                 y = MSG_ReadShort();
1273         }
1274         k = -1;
1275         for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1276                 if (showlmp[i].isactive)
1277                 {
1278                         if (strcmp(showlmp[i].label, lmplabel) == 0)
1279                         {
1280                                 k = i;
1281                                 break; // drop out to replace it
1282                         }
1283                 }
1284                 else if (k < 0) // find first empty one to replace
1285                         k = i;
1286         if (k < 0)
1287                 return; // none found to replace
1288         // change existing one
1289         showlmp[k].isactive = true;
1290         strlcpy (showlmp[k].label, lmplabel, sizeof (showlmp[k].label));
1291         strlcpy (showlmp[k].pic, picname, sizeof (showlmp[k].pic));
1292         showlmp[k].x = x;
1293         showlmp[k].y = y;
1294 }
1295
1296 void SHOWLMP_drawall(void)
1297 {
1298         int i;
1299         for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1300                 if (showlmp[i].isactive)
1301                         DrawQ_Pic(showlmp[i].x, showlmp[i].y, showlmp[i].pic, 0, 0, 1, 1, 1, 1, 0);
1302 }
1303
1304 void SHOWLMP_clear(void)
1305 {
1306         int i;
1307         for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1308                 showlmp[i].isactive = false;
1309 }
1310
1311 void CL_SetupScreenSize(void)
1312 {
1313         float conwidth, conheight;
1314
1315         VID_UpdateGamma(false);
1316
1317         conwidth = bound(320, vid_conwidth.value, 2048);
1318         conheight = bound(200, vid_conheight.value, 1536);
1319         if (vid_conwidth.value != conwidth)
1320                 Cvar_SetValue("vid_conwidth", conwidth);
1321         if (vid_conheight.value != conheight)
1322                 Cvar_SetValue("vid_conheight", conheight);
1323
1324         vid_conwidth.integer = vid_conwidth.integer;
1325         vid_conheight.integer = vid_conheight.integer;
1326
1327         SCR_SetUpToDrawConsole();
1328 }
1329
1330 extern void R_Shadow_EditLights_DrawSelectedLightProperties(void);
1331 void CL_UpdateScreen(void)
1332 {
1333         if (!scr_initialized || !con_initialized || vid_hidden)
1334                 return;                         // not initialized yet
1335
1336         // don't allow cheats in multiplayer
1337         if (!cl.islocalgame && cl.worldmodel)
1338         {
1339                 if (r_fullbright.integer != 0)
1340                         Cvar_Set ("r_fullbright", "0");
1341                 if (r_ambient.value != 0)
1342                         Cvar_Set ("r_ambient", "0");
1343         }
1344
1345         // bound viewsize
1346         if (scr_viewsize.value < 30)
1347                 Cvar_Set ("viewsize","30");
1348         if (scr_viewsize.value > 120)
1349                 Cvar_Set ("viewsize","120");
1350
1351         // bound field of view
1352         if (scr_fov.value < 1)
1353                 Cvar_Set ("fov","1");
1354         if (scr_fov.value > 170)
1355                 Cvar_Set ("fov","170");
1356
1357         // intermission is always full screen
1358         if (cl.intermission)
1359                 sb_lines = 0;
1360         else
1361         {
1362                 if (scr_viewsize.value >= 120)
1363                         sb_lines = 0;           // no status bar at all
1364                 else if (scr_viewsize.value >= 110)
1365                         sb_lines = 24;          // no inventory
1366                 else
1367                         sb_lines = 24+16+8;
1368         }
1369
1370         r_refdef.colormask[0] = 1;
1371         r_refdef.colormask[1] = 1;
1372         r_refdef.colormask[2] = 1;
1373
1374         SCR_CaptureVideo();
1375
1376         if (cls.signon == SIGNONS)
1377                 R_TimeReport("other");
1378
1379         CL_SetupScreenSize();
1380
1381         DrawQ_Clear();
1382
1383         if (cls.signon == SIGNONS)
1384                 R_TimeReport("setup");
1385
1386         //FIXME: force menu if nothing else to look at?
1387         //if (key_dest == key_game && cls.signon != SIGNONS && cls.state == ca_disconnected)
1388
1389         if (cls.signon == SIGNONS)
1390         {
1391                 SCR_DrawNet ();
1392                 SCR_DrawTurtle ();
1393                 SCR_DrawPause ();
1394                 if (!r_letterbox.value)
1395                         Sbar_Draw();
1396                 SHOWLMP_drawall();
1397                 SCR_CheckDrawCenterString();
1398         }
1399         MR_Draw();
1400         UI_Callback_Draw();
1401         CL_DrawVideo();
1402         //ui_draw();
1403         if (cls.signon == SIGNONS)
1404         {
1405                 R_TimeReport("2d");
1406                 R_TimeReport_End();
1407                 R_TimeReport_Start();
1408         }
1409         R_Shadow_EditLights_DrawSelectedLightProperties();
1410
1411         SCR_DrawConsole();
1412
1413         SCR_UpdateScreen();
1414 }
1415
1416 void CL_Screen_NewMap(void)
1417 {
1418         SHOWLMP_clear();
1419 }