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