]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - cl_screen.c
cl_capturevideo_raw* modes now use O_NONBLOCKING file access for more performance
[xonotic/darkplaces.git] / cl_screen.c
index 0fa9a6d8e78dea02983c47a423988db41ce92f9b..dc0418a19968096b6fdf22f8af521f61bf972817 100644 (file)
@@ -17,22 +17,25 @@ cvar_t scr_showpause = {CVAR_SAVE, "showpause","1"};
 cvar_t scr_printspeed = {0, "scr_printspeed","8"};
 cvar_t vid_conwidth = {CVAR_SAVE, "vid_conwidth", "640"};
 cvar_t vid_conheight = {CVAR_SAVE, "vid_conheight", "480"};
+cvar_t vid_pixelaspect = {CVAR_SAVE, "vid_pixelaspect", "1"};
 cvar_t scr_screenshot_jpeg = {CVAR_SAVE, "scr_screenshot_jpeg","0"};
 cvar_t scr_screenshot_jpeg_quality = {CVAR_SAVE, "scr_screenshot_jpeg_quality","0.9"};
+cvar_t scr_screenshot_gamma = {CVAR_SAVE, "scr_screenshot_gamma","2.2"};
 cvar_t scr_screenshot_name = {0, "scr_screenshot_name","dp"};
-cvar_t cl_avidemo = {0, "cl_avidemo", "0"};
+cvar_t cl_capturevideo = {0, "cl_capturevideo", "0"};
+cvar_t cl_capturevideo_fps = {0, "cl_capturevideo_fps", "30"};
+cvar_t cl_capturevideo_rawrgb = {0, "cl_capturevideo_rawrgb", "0"};
+cvar_t cl_capturevideo_rawyv12 = {0, "cl_capturevideo_rawyv12", "0"};
+cvar_t r_textshadow = {0, "r_textshadow", "0"};
+cvar_t r_letterbox = {0, "r_letterbox", "0"};
 
 int jpeg_supported = false;
 
 qboolean       scr_initialized;                // ready to draw
 
 float          scr_con_current;
-float          scr_conlines;           // lines of console to display
 
-int                    clearconsole;
-int                    clearnotify;
-
-qboolean       scr_drawloading = false;
+extern int     con_vislines;
 
 void DrawCrosshair(int num);
 static void SCR_ScreenShot_f (void);
@@ -105,7 +108,7 @@ void SCR_DrawCenterString (void)
        do
        {
        // scan the width of the line
-               for (l=0 ; l<40 ; l++)
+               for (l=0 ; l<vid.conwidth/8 ; l++)
                        if (start[l] == '\n' || !start[l])
                                break;
                x = (vid.conwidth - l*8)/2;
@@ -218,19 +221,6 @@ void SCR_DrawPause (void)
 
 
 
-/*
-==============
-SCR_DrawLoading
-==============
-*/
-void SCR_DrawLoading (void)
-{
-       cachepic_t      *pic;
-
-       pic = Draw_CachePic ("gfx/loading.lmp");
-       DrawQ_Pic ((vid.conwidth - pic->width)/2, (vid.conheight - pic->height)/2, "gfx/loading.lmp", 0, 0, 1, 1, 1, 1, 0);
-}
-
 
 
 //=============================================================================
@@ -243,6 +233,9 @@ SCR_SetUpToDrawConsole
 */
 void SCR_SetUpToDrawConsole (void)
 {
+       // lines of console to display
+       float conlines;
+
        Con_CheckResize ();
 
        if (key_dest == key_game && cls.signon != SIGNONS && scr_conforcewhiledisconnected.integer)
@@ -251,31 +244,29 @@ void SCR_SetUpToDrawConsole (void)
                key_consoleactive &= ~KEY_CONSOLEACTIVE_FORCED;
 
 // decide on the height of the console
-       if (key_consoleactive & KEY_CONSOLEACTIVE_FORCED)
-               scr_conlines = vid.conheight; // full screen
-       else if (key_consoleactive & KEY_CONSOLEACTIVE_USER)
-               scr_conlines = vid.conheight/2; // half screen
+       if (key_consoleactive & KEY_CONSOLEACTIVE_USER)
+               conlines = vid.conheight/2;     // half screen
        else
-               scr_conlines = 0;                               // none visible
+               conlines = 0;                           // none visible
 
        if (scr_conspeed.value)
        {
-               if (scr_conlines < scr_con_current)
+               if (scr_con_current > conlines)
                {
                        scr_con_current -= scr_conspeed.value*host_realframetime;
-                       if (scr_conlines > scr_con_current)
-                               scr_con_current = scr_conlines;
+                       if (scr_con_current < conlines)
+                               scr_con_current = conlines;
 
                }
-               else if (scr_conlines > scr_con_current)
+               else if (scr_con_current < conlines)
                {
                        scr_con_current += scr_conspeed.value*host_realframetime;
-                       if (scr_conlines < scr_con_current)
-                               scr_con_current = scr_conlines;
+                       if (scr_con_current > conlines)
+                               scr_con_current = conlines;
                }
        }
        else
-               scr_con_current = scr_conlines;
+               scr_con_current = conlines;
 }
 
 /*
@@ -285,13 +276,16 @@ SCR_DrawConsole
 */
 void SCR_DrawConsole (void)
 {
-       if (scr_con_current)
+       if (key_consoleactive & KEY_CONSOLEACTIVE_FORCED)
        {
-               Con_DrawConsole (scr_con_current);
-               clearconsole = 0;
+               // full screen
+               Con_DrawConsole (vid.conheight);
        }
+       else if (scr_con_current)
+               Con_DrawConsole (scr_con_current);
        else
        {
+               con_vislines = 0;
                if (key_dest == key_game || key_dest == key_message)
                        Con_DrawNotify ();      // only draw notify in game
        }
@@ -305,15 +299,8 @@ SCR_BeginLoadingPlaque
 */
 void SCR_BeginLoadingPlaque (void)
 {
-       if (scr_drawloading)
-               return;
-
-       S_StopAllSounds (true);
-
-       scr_drawloading = true;
-       CL_UpdateScreen ();
-       scr_drawloading = true;
-       CL_UpdateScreen ();
+       S_StopAllSounds ();
+       SCR_UpdateLoadingScreen();
 }
 
 //=============================================================================
@@ -358,9 +345,6 @@ void R_TimeReport(char *desc)
        }
 }
 
-extern int c_rt_lights, c_rt_clears, c_rt_scissored;
-extern int c_rt_shadowmeshes, c_rt_shadowtris, c_rt_lightmeshes, c_rt_lighttris;
-extern int c_rtcached_shadowmeshes, c_rtcached_shadowtris;
 void R_TimeReport_Start(void)
 {
        r_timereport_active = r_speeds.integer && cls.signon == SIGNONS && cls.state == ca_connected;
@@ -368,23 +352,14 @@ void R_TimeReport_Start(void)
        if (r_timereport_active)
        {
                speedstringcount = 0;
-               sprintf(r_speeds_string,
-                       "org:'%+8.2f %+8.2f %+8.2f' dir:'%+2.3f %+2.3f %+2.3f'\n"
-                       "world:%6i faces%6i nodes%6i leafs%6i dlitwalls\n"
-                       "%5i models%5i bmodels%5i sprites%6i particles%4i dlights\n"
-                       "%6i modeltris%6i meshs%6i meshtris\n",
-                       r_vieworigin[0], r_vieworigin[1], r_vieworigin[2], r_viewforward[0], r_viewforward[1], r_viewforward[2],
-                       c_faces, c_nodes, c_leafs, c_light_polys,
-                       c_models, c_bmodels, c_sprites, c_particles, c_dlights,
-                       c_alias_polys, c_meshs, c_meshelements / 3);
-
-               sprintf(r_speeds_string + strlen(r_speeds_string),
-                       "realtime lighting:%4i lights%4i clears%4i scissored\n"
-                       "dynamic: %6i shadowmeshes%6i shadowtris%6i lightmeshes%6i lighttris\n"
-                       "precomputed: %6i shadowmeshes%6i shadowtris\n",
-                       c_rt_lights, c_rt_clears, c_rt_scissored,
-                       c_rt_shadowmeshes, c_rt_shadowtris, c_rt_lightmeshes, c_rt_lighttris,
-                       c_rtcached_shadowmeshes, c_rtcached_shadowtris);
+               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]);
+               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);
+               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);
+               sprintf(r_speeds_string + strlen(r_speeds_string), "%6i modeltris%6i meshs%6i meshtris\n", c_alias_polys, c_meshs, c_meshelements / 3);
+               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);
+               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);
+               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);
+               sprintf(r_speeds_string + strlen(r_speeds_string), "precomputed: %6i shadowmeshes%6i shadowtris\n", c_rtcached_shadowmeshes, c_rtcached_shadowtris);
 
                c_alias_polys = 0;
                c_light_polys = 0;
@@ -395,8 +370,23 @@ void R_TimeReport_Start(void)
                c_bmodels = 0;
                c_sprites = 0;
                c_particles = 0;
+               c_dlights = 0;
                c_meshs = 0;
                c_meshelements = 0;
+               c_rt_lights = 0;
+               c_rt_clears = 0;
+               c_rt_scissored = 0;
+               c_rt_shadowmeshes = 0;
+               c_rt_shadowtris = 0;
+               c_rt_lightmeshes = 0;
+               c_rt_lighttris = 0;
+               c_rtcached_shadowmeshes = 0;
+               c_rtcached_shadowtris = 0;
+               c_bloom = 0;
+               c_bloomcopies = 0;
+               c_bloomcopypixels = 0;
+               c_bloomdraws = 0;
+               c_bloomdrawpixels = 0;
 
                r_timereport_start = Sys_DoubleTime();
        }
@@ -471,9 +461,16 @@ void CL_Screen_Init(void)
        Cvar_RegisterVariable (&scr_printspeed);
        Cvar_RegisterVariable (&vid_conwidth);
        Cvar_RegisterVariable (&vid_conheight);
+       Cvar_RegisterVariable (&vid_pixelaspect);
        Cvar_RegisterVariable (&scr_screenshot_jpeg);
        Cvar_RegisterVariable (&scr_screenshot_jpeg_quality);
-       Cvar_RegisterVariable (&cl_avidemo);
+       Cvar_RegisterVariable (&scr_screenshot_gamma);
+       Cvar_RegisterVariable (&cl_capturevideo);
+       Cvar_RegisterVariable (&cl_capturevideo_fps);
+       Cvar_RegisterVariable (&cl_capturevideo_rawrgb);
+       Cvar_RegisterVariable (&cl_capturevideo_rawyv12);
+       Cvar_RegisterVariable (&r_textshadow);
+       Cvar_RegisterVariable (&r_letterbox);
 
        Cmd_AddCommand ("sizeup",SCR_SizeUp_f);
        Cmd_AddCommand ("sizedown",SCR_SizeDown_f);
@@ -494,7 +491,7 @@ void DrawQ_Pic(float x, float y, char *picname, float width, float height, float
        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);
 }
 
-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)
+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)
 {
        int size, len;
        drawqueue_t *dq;
@@ -509,7 +506,7 @@ void DrawQ_String(float x, float y, const char *string, int maxlen, float scalex
        for (;len > 0 && string[len - 1] == ' ';len--);
        if (len < 1)
                return;
-       if (x >= vid.conwidth || y >= vid.conheight || x < (-scalex * maxlen) || y < (-scaley))
+       if (x >= vid.conwidth || y >= vid.conheight || x < (-scalex * len) || y < (-scaley))
                return;
        size = sizeof(*dq) + ((len + 1 + 3) & ~3);
        if (r_refdef.drawqueuesize + size > r_refdef.maxdrawqueuesize)
@@ -533,6 +530,14 @@ void DrawQ_String(float x, float y, const char *string, int maxlen, float scalex
        r_refdef.drawqueuesize += dq->size;
 }
 
+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)
+{
+       if (r_textshadow.integer)
+               DrawQ_String_Real(x+scalex*0.25,y+scaley*0.25,string,maxlen,scalex,scaley,0,0,0,alpha*0.8,flags);
+
+       DrawQ_String_Real(x,y,string,maxlen,scalex,scaley,red,green,blue,alpha,flags);
+}
+
 void DrawQ_Fill (float x, float y, float w, float h, float red, float green, float blue, float alpha, int flags)
 {
        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);
@@ -655,15 +660,25 @@ SCR_ScreenShot_f
 */
 void SCR_ScreenShot_f (void)
 {
-       int shotnumber;
+       static int shotnumber;
+       static char oldname[MAX_QPATH];
        char base[MAX_QPATH];
        char filename[MAX_QPATH];
+       qbyte *buffer1;
+       qbyte *buffer2;
+       qbyte *buffer3;
        qboolean jpeg = (scr_screenshot_jpeg.integer != 0);
 
        sprintf (base, "screenshots/%s", scr_screenshot_name.string);
-       
+
+       if (strcmp (oldname, scr_screenshot_name.string))
+       {
+               sprintf(oldname, "%s", scr_screenshot_name.string);
+               shotnumber = 0;
+       }
+
        // find a file name to save it to
-       for (shotnumber=0;shotnumber < 1000000;shotnumber++)
+       for (;shotnumber < 1000000;shotnumber++)
                if (!FS_SysFileExists(va("%s/%s%06d.tga", fs_gamedir, base, shotnumber)) && !FS_SysFileExists(va("%s/%s%06d.jpg", fs_gamedir, base, shotnumber)))
                        break;
        if (shotnumber >= 1000000)
@@ -672,38 +687,326 @@ void SCR_ScreenShot_f (void)
                return;
        }
 
-       if (jpeg)
-               sprintf(filename, "%s%06d.jpg", base, shotnumber);
-       else
-               sprintf(filename, "%s%06d.tga", base, shotnumber);
+       sprintf(filename, "%s%06d.%s", base, shotnumber, jpeg ? "jpg" : "tga");
+
+       buffer1 = Mem_Alloc(tempmempool, vid.realwidth * vid.realheight * 3);
+       buffer2 = Mem_Alloc(tempmempool, vid.realwidth * vid.realheight * 3);
+       buffer3 = Mem_Alloc(tempmempool, vid.realwidth * vid.realheight * 3 + 18);
 
-       if (SCR_ScreenShot(filename, vid.realx, vid.realy, vid.realwidth, vid.realheight, false, false, false, jpeg))
+       if (SCR_ScreenShot (filename, buffer1, buffer2, buffer3, vid.realx, vid.realy, vid.realwidth, vid.realheight, false, false, false, jpeg))
                Con_Printf("Wrote %s\n", filename);
        else
                Con_Printf("unable to write %s\n", filename);
+
+       Mem_Free (buffer1);
+       Mem_Free (buffer2);
+       Mem_Free (buffer3);
+
        shotnumber++;
 }
 
-static int cl_avidemo_frame = 0;
+typedef enum capturevideoformat_e
+{
+       CAPTUREVIDEOFORMAT_TARGA,
+       CAPTUREVIDEOFORMAT_JPEG,
+       CAPTUREVIDEOFORMAT_RAWRGB,
+       CAPTUREVIDEOFORMAT_RAWYV12
+}
+capturevideoformat_t;
+
+qboolean cl_capturevideo_active = false;
+capturevideoformat_t cl_capturevideo_format;
+static double cl_capturevideo_starttime = 0;
+double cl_capturevideo_framerate = 0;
+static int cl_capturevideo_soundrate = 0;
+static int cl_capturevideo_frame = 0;
+static qbyte *cl_capturevideo_buffer = NULL;
+static qfile_t *cl_capturevideo_videofile = NULL;
+static qfile_t *cl_capturevideo_soundfile = NULL;
+static short cl_capturevideo_rgbtoyuvscaletable[3][3][256];
+static unsigned char cl_capturevideo_yuvnormalizetable[3][256];
+//static unsigned char cl_capturevideo_rgbgammatable[3][256];
+
+void SCR_CaptureVideo_BeginVideo(void)
+{
+       double gamma, g;
+       unsigned int i;
+       qbyte out[44];
+       if (cl_capturevideo_active)
+               return;
+       // soundrate is figured out on the first SoundFrame
+       cl_capturevideo_active = true;
+       cl_capturevideo_starttime = Sys_DoubleTime();
+       cl_capturevideo_framerate = bound(1, cl_capturevideo_fps.value, 1000);
+       cl_capturevideo_soundrate = 0;
+       cl_capturevideo_frame = 0;
+       cl_capturevideo_buffer = Mem_Alloc(tempmempool, vid.realwidth * vid.realheight * (3+3+3) + 18);
+       gamma = 1.0/scr_screenshot_gamma.value;
+
+       /*
+       for (i = 0;i < 256;i++)
+       {
+               unsigned char j = (unsigned char)bound(0, 255*pow(i/255.0, gamma), 255);
+               cl_capturevideo_rgbgammatable[0][i] = j;
+               cl_capturevideo_rgbgammatable[1][i] = j;
+               cl_capturevideo_rgbgammatable[2][i] = j;
+       }
+       */
+/*
+R = Y + 1.4075 * (Cr - 128);
+G = Y + -0.3455 * (Cb - 128) + -0.7169 * (Cr - 128);
+B = Y + 1.7790 * (Cb - 128);
+Y = R *  .299 + G *  .587 + B *  .114;
+Cb = R * -.169 + G * -.332 + B *  .500 + 128.;
+Cr = R *  .500 + G * -.419 + B * -.0813 + 128.;
+*/
+       for (i = 0;i < 256;i++)
+       {
+               g = 255*pow(i/255.0, gamma);
+               // Y weights from RGB
+               cl_capturevideo_rgbtoyuvscaletable[0][0][i] = (short)(g *  0.299);
+               cl_capturevideo_rgbtoyuvscaletable[0][1][i] = (short)(g *  0.587);
+               cl_capturevideo_rgbtoyuvscaletable[0][2][i] = (short)(g *  0.114);
+               // Cb weights from RGB
+               cl_capturevideo_rgbtoyuvscaletable[1][0][i] = (short)(g * -0.169);
+               cl_capturevideo_rgbtoyuvscaletable[1][1][i] = (short)(g * -0.332);
+               cl_capturevideo_rgbtoyuvscaletable[1][2][i] = (short)(g *  0.500);
+               // Cr weights from RGB
+               cl_capturevideo_rgbtoyuvscaletable[2][0][i] = (short)(g *  0.500);
+               cl_capturevideo_rgbtoyuvscaletable[2][1][i] = (short)(g * -0.419);
+               cl_capturevideo_rgbtoyuvscaletable[2][2][i] = (short)(g * -0.0813);
+               // range reduction of YCbCr to valid signal range
+               cl_capturevideo_yuvnormalizetable[0][i] = 16 + i * (236-16) / 256;
+               cl_capturevideo_yuvnormalizetable[1][i] = 16 + i * (240-16) / 256;
+               cl_capturevideo_yuvnormalizetable[2][i] = 16 + i * (240-16) / 256;
+       }
 
-void SCR_CaptureAVIDemo(void)
+       if (cl_capturevideo_rawrgb.integer)
+       {
+               cl_capturevideo_format = CAPTUREVIDEOFORMAT_RAWRGB;
+               cl_capturevideo_videofile = FS_Open ("video/dpvideo.rgb", "wb", false, true);
+       }
+       else if (cl_capturevideo_rawyv12.integer)
+       {
+               cl_capturevideo_format = CAPTUREVIDEOFORMAT_RAWYV12;
+               cl_capturevideo_videofile = FS_Open ("video/dpvideo.yv12", "wb", false, true);
+       }
+       else if (scr_screenshot_jpeg.integer)
+       {
+               cl_capturevideo_format = CAPTUREVIDEOFORMAT_JPEG;
+               cl_capturevideo_videofile = NULL;
+       }
+       else
+       {
+               cl_capturevideo_format = CAPTUREVIDEOFORMAT_TARGA;
+               cl_capturevideo_videofile = NULL;
+       }
+
+       cl_capturevideo_soundfile = FS_Open ("video/dpvideo.wav", "wb", false, true);
+
+       // wave header will be filled out when video ends
+       memset(out, 0, 44);
+       FS_Write (cl_capturevideo_soundfile, out, 44);
+}
+
+void SCR_CaptureVideo_EndVideo(void)
 {
+       int i, n;
+       qbyte out[44];
+       if (!cl_capturevideo_active)
+               return;
+       cl_capturevideo_active = false;
+
+       if (cl_capturevideo_videofile)
+       {
+               FS_Close(cl_capturevideo_videofile);
+               cl_capturevideo_videofile = NULL;
+       }
+
+       // finish the wave file
+       if (cl_capturevideo_soundfile)
+       {
+               i = FS_Tell (cl_capturevideo_soundfile);
+               //"RIFF", (int) unknown (chunk size), "WAVE",
+               //"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
+               //"data", (int) unknown (chunk size)
+               memcpy (out, "RIFF****WAVEfmt \x10\x00\x00\x00\x01\x00\x02\x00********\x04\x00\x10\0data****", 44);
+               // the length of the whole RIFF chunk
+               n = i - 8;
+               out[4] = (n) & 0xFF;
+               out[5] = (n >> 8) & 0xFF;
+               out[6] = (n >> 16) & 0xFF;
+               out[7] = (n >> 24) & 0xFF;
+               // rate
+               n = cl_capturevideo_soundrate;
+               out[24] = (n) & 0xFF;
+               out[25] = (n >> 8) & 0xFF;
+               out[26] = (n >> 16) & 0xFF;
+               out[27] = (n >> 24) & 0xFF;
+               // bytes per second (rate * channels * bytes per channel)
+               n = cl_capturevideo_soundrate * 2 * 2;
+               out[28] = (n) & 0xFF;
+               out[29] = (n >> 8) & 0xFF;
+               out[30] = (n >> 16) & 0xFF;
+               out[31] = (n >> 24) & 0xFF;
+               // the length of the data chunk
+               n = i - 44;
+               out[40] = (n) & 0xFF;
+               out[41] = (n >> 8) & 0xFF;
+               out[42] = (n >> 16) & 0xFF;
+               out[43] = (n >> 24) & 0xFF;
+               FS_Seek (cl_capturevideo_soundfile, 0, SEEK_SET);
+               FS_Write (cl_capturevideo_soundfile, out, 44);
+               FS_Close (cl_capturevideo_soundfile);
+               cl_capturevideo_soundfile = NULL;
+       }
+
+       if (cl_capturevideo_buffer)
+       {
+               Mem_Free (cl_capturevideo_buffer);
+               cl_capturevideo_buffer = NULL;
+       }
+
+       cl_capturevideo_starttime = 0;
+       cl_capturevideo_framerate = 0;
+       cl_capturevideo_frame = 0;
+}
+
+qboolean SCR_CaptureVideo_VideoFrame(int newframenum)
+{
+       int x = vid.realx, y = vid.realy, width = vid.realwidth, height = vid.realheight;
+       unsigned char *b, *out;
        char filename[32];
-       qboolean jpeg = (scr_screenshot_jpeg.integer != 0);
+       int outoffset = (width/2)*(height/2);
+       //return SCR_ScreenShot(filename, cl_capturevideo_buffer, cl_capturevideo_buffer + vid.realwidth * vid.realheight * 3, cl_capturevideo_buffer + vid.realwidth * vid.realheight * 6, vid.realx, vid.realy, vid.realwidth, vid.realheight, false, false, false, jpeg);
+       // speed is critical here, so do saving as directly as possible
+       switch (cl_capturevideo_format)
+       {
+       case CAPTUREVIDEOFORMAT_RAWYV12:
+               // FIXME: width/height must be multiple of 2, enforce this?
+               qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cl_capturevideo_buffer);
+               CHECKGLERROR
+               // process one line at a time, and CbCr every other line at 2 pixel intervals
+               for (y = 0;y < height;y++)
+               {
+                       // 1x1 Y
+                       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++)
+                               *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]]];
+                       if ((y & 1) == 0)
+                       {
+                               // 2x2 Cb and Cr planes
+#if 1
+                               // low quality, no averaging
+                               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++)
+                               {
+                                       // Cr
+                                       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];
+                                       // Cb
+                                       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];
+                               }
+#else
+                               // high quality, averaging
+                               int inpitch = width*3;
+                               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++)
+                               {
+                                       int blockr, blockg, blockb;
+                                       blockr = (b[0] + b[3] + b[inpitch+0] + b[inpitch+3]) >> 2;
+                                       blockg = (b[1] + b[4] + b[inpitch+1] + b[inpitch+4]) >> 2;
+                                       blockb = (b[2] + b[5] + b[inpitch+2] + b[inpitch+5]) >> 2;
+                                       // Cr
+                                       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];
+                                       // Cb
+                                       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];
+                               }
+#endif
+                       }
+               }
+               for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
+                       if (!FS_Write (cl_capturevideo_videofile, cl_capturevideo_buffer + width*height*3, width*height+(width/2)*(height/2)*2))
+                               return false;
+               return true;
+       case CAPTUREVIDEOFORMAT_RAWRGB:
+               qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cl_capturevideo_buffer);
+               CHECKGLERROR
+               for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
+                       if (!FS_Write (cl_capturevideo_videofile, cl_capturevideo_buffer, width*height*3))
+                               return false;
+               return true;
+       case CAPTUREVIDEOFORMAT_JPEG:
+               qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cl_capturevideo_buffer);
+               CHECKGLERROR
+               for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
+               {
+                       sprintf(filename, "video/dp%06d.jpg", cl_capturevideo_frame);
+                       if (!JPEG_SaveImage_preflipped (filename, width, height, cl_capturevideo_buffer))
+                               return false;
+               }
+               return true;
+       case CAPTUREVIDEOFORMAT_TARGA:
+               //return Image_WriteTGARGB_preflipped (filename, width, height, cl_capturevideo_buffer, cl_capturevideo_buffer + vid.realwidth * vid.realheight * 3, );
+               memset (cl_capturevideo_buffer, 0, 18);
+               cl_capturevideo_buffer[2] = 2;          // uncompressed type
+               cl_capturevideo_buffer[12] = (width >> 0) & 0xFF;
+               cl_capturevideo_buffer[13] = (width >> 8) & 0xFF;
+               cl_capturevideo_buffer[14] = (height >> 0) & 0xFF;
+               cl_capturevideo_buffer[15] = (height >> 8) & 0xFF;
+               cl_capturevideo_buffer[16] = 24;        // pixel size
+               qglReadPixels (x, y, width, height, GL_BGR, GL_UNSIGNED_BYTE, cl_capturevideo_buffer + 18);
+               CHECKGLERROR
+               for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
+               {
+                       sprintf(filename, "video/dp%06d.tga", cl_capturevideo_frame);
+                       if (!FS_WriteFile (filename, cl_capturevideo_buffer, width*height*3 + 18))
+                               return false;
+               }
+               return true;
+       default:
+               return false;
+       }
+}
 
-       if (jpeg)
-               sprintf(filename, "video/dp%06d.jpg", cl_avidemo_frame);
-       else
-               sprintf(filename, "video/dp%06d.tga", cl_avidemo_frame);
+void SCR_CaptureVideo_SoundFrame(qbyte *bufstereo16le, size_t length, int rate)
+{
+       cl_capturevideo_soundrate = rate;
+       if (FS_Write (cl_capturevideo_soundfile, bufstereo16le, 4 * length) < 4 * length)
+       {
+               Cvar_SetValueQuick(&cl_capturevideo, 0);
+               Con_Printf("video sound saving failed on frame %i, out of disk space? stopping video capture.\n", cl_capturevideo_frame);
+               SCR_CaptureVideo_EndVideo();
+       }
+}
 
-       if (SCR_ScreenShot(filename, vid.realx, vid.realy, vid.realwidth, vid.realheight, false, false, false, jpeg))
-               cl_avidemo_frame++;
-       else
+void SCR_CaptureVideo(void)
+{
+       int newframenum;
+       if (cl_capturevideo.integer && r_render.integer)
        {
-               Cvar_SetValueQuick(&cl_avidemo, 0);
-               Con_Printf("avi saving failed on frame %i, out of disk space?  stopping avi demo catpure.\n", cl_avidemo_frame);
-               cl_avidemo_frame = 0;
+               if (!cl_capturevideo_active)
+                       SCR_CaptureVideo_BeginVideo();
+               if (cl_capturevideo_framerate != cl_capturevideo_fps.value)
+               {
+                       Con_Printf("You can not change the video framerate while recording a video.\n");
+                       Cvar_SetValueQuick(&cl_capturevideo_fps, cl_capturevideo_framerate);
+               }
+               newframenum = (Sys_DoubleTime() - cl_capturevideo_starttime) * cl_capturevideo_framerate;
+               // if falling behind more than one second, stop
+               if (newframenum - cl_capturevideo_frame > (int)ceil(cl_capturevideo_framerate))
+               {
+                       Cvar_SetValueQuick(&cl_capturevideo, 0);
+                       Con_Printf("video saving failed on frame %i, your machine is too slow for this capture speed.\n", cl_capturevideo_frame);
+                       SCR_CaptureVideo_EndVideo();
+                       return;
+               }
+               // write frames
+               if (!SCR_CaptureVideo_VideoFrame(newframenum))
+               {
+                       Cvar_SetValueQuick(&cl_capturevideo, 0);
+                       Con_Printf("video saving failed on frame %i, out of disk space? stopping video capture.\n", cl_capturevideo_frame);
+                       SCR_CaptureVideo_EndVideo();
+               }
        }
+       else if (cl_capturevideo_active)
+               SCR_CaptureVideo_EndVideo();
 }
 
 /*
@@ -717,22 +1020,32 @@ struct
 {
        float angles[3];
        char *name;
-       qboolean flipx, flipy;
+       qboolean flipx, flipy, flipdiagonaly;
 }
-envmapinfo[6] =
+envmapinfo[12] =
 {
-       {{  0,   0, 0}, "rt",  true, false},
-       {{  0,  90, 0}, "ft",  true, false},
-       {{  0, 180, 0}, "lf",  true, false},
-       {{  0, 270, 0}, "bk",  true, false},
-       {{-90, 180, 0}, "up", false,  true},
-       {{ 90, 180, 0}, "dn", false,  true}
+       {{  0,   0, 0}, "rt",  true, false, false},
+       {{  0,  90, 0}, "ft",  true, false, false},
+       {{  0, 180, 0}, "lf",  true, false, false},
+       {{  0, 270, 0}, "bk",  true, false, false},
+       {{-90, 180, 0}, "up", false,  true, false},
+       {{ 90, 180, 0}, "dn", false,  true, false},
+
+       {{  0,   0, 0}, "px",  true,  true,  true},
+       {{  0,  90, 0}, "py", false,  true, false},
+       {{  0, 180, 0}, "nx", false, false,  true},
+       {{  0, 270, 0}, "ny",  true, false, false},
+       {{-90, 180, 0}, "pz", false, false,  true},
+       {{ 90, 180, 0}, "nz", false, false,  true}
 };
 
 static void R_Envmap_f (void)
 {
        int j, size;
        char filename[256], basename[256];
+       qbyte *buffer1;
+       qbyte *buffer2;
+       qbyte *buffer3;
 
        if (Cmd_Argc() != 3)
        {
@@ -763,7 +1076,11 @@ static void R_Envmap_f (void)
        r_refdef.fov_x = 90;
        r_refdef.fov_y = 90;
 
-       for (j = 0;j < 6;j++)
+       buffer1 = Mem_Alloc(tempmempool, size * size * 3);
+       buffer2 = Mem_Alloc(tempmempool, size * size * 3);
+       buffer3 = Mem_Alloc(tempmempool, size * size * 3 + 18);
+
+       for (j = 0;j < 12;j++)
        {
                sprintf(filename, "env/%s%s.tga", basename, envmapinfo[j].name);
                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);
@@ -771,9 +1088,13 @@ static void R_Envmap_f (void)
                R_Mesh_Start();
                R_RenderView();
                R_Mesh_Finish();
-               SCR_ScreenShot(filename, vid.realx, vid.realy + vid.realheight - (r_refdef.y + r_refdef.height), size, size, envmapinfo[j].flipx, envmapinfo[j].flipy, false, false);
+               SCR_ScreenShot(filename, buffer1, buffer2, buffer3, vid.realx, vid.realy + vid.realheight - (r_refdef.y + r_refdef.height), size, size, envmapinfo[j].flipx, envmapinfo[j].flipy, envmapinfo[j].flipdiagonaly, false);
        }
 
+       Mem_Free (buffer1);
+       Mem_Free (buffer2);
+       Mem_Free (buffer3);
+
        envmap = false;
 }
 
@@ -895,10 +1216,45 @@ void CL_UpdateScreen(void)
        if (!scr_initialized || !con_initialized || vid_hidden)
                return;                         // not initialized yet
 
-       if (cl_avidemo.integer)
-               SCR_CaptureAVIDemo();
+       // don't allow cheats in multiplayer
+       if (!cl.islocalgame && cl.worldmodel)
+       {
+               if (r_fullbright.integer != 0)
+                       Cvar_Set ("r_fullbright", "0");
+               if (r_ambient.value != 0)
+                       Cvar_Set ("r_ambient", "0");
+       }
+
+       // bound viewsize
+       if (scr_viewsize.value < 30)
+               Cvar_Set ("viewsize","30");
+       if (scr_viewsize.value > 120)
+               Cvar_Set ("viewsize","120");
+
+       // bound field of view
+       if (scr_fov.value < 1)
+               Cvar_Set ("fov","1");
+       if (scr_fov.value > 170)
+               Cvar_Set ("fov","170");
+
+       // intermission is always full screen
+       if (cl.intermission)
+               sb_lines = 0;
        else
-               cl_avidemo_frame = 0;
+       {
+               if (scr_viewsize.value >= 120)
+                       sb_lines = 0;           // no status bar at all
+               else if (scr_viewsize.value >= 110)
+                       sb_lines = 24;          // no inventory
+               else
+                       sb_lines = 24+16+8;
+       }
+
+       r_refdef.colormask[0] = 1;
+       r_refdef.colormask[1] = 1;
+       r_refdef.colormask[2] = 1;
+
+       SCR_CaptureVideo();
 
        if (cls.signon == SIGNONS)
                R_TimeReport("other");
@@ -913,34 +1269,28 @@ void CL_UpdateScreen(void)
        //FIXME: force menu if nothing else to look at?
        //if (key_dest == key_game && cls.signon != SIGNONS && cls.state == ca_disconnected)
 
-       if (scr_drawloading)
+       if (cls.signon == SIGNONS)
        {
-               scr_drawloading = false;
-               SCR_DrawLoading();
+               SCR_DrawNet ();
+               SCR_DrawTurtle ();
+               SCR_DrawPause ();
+               if (!r_letterbox.value)
+                       Sbar_Draw();
+               SHOWLMP_drawall();
+               SCR_CheckDrawCenterString();
        }
-       else
+       MR_Draw();
+       UI_Callback_Draw();
+       CL_DrawVideo();
+       //ui_draw();
+       if (cls.signon == SIGNONS)
        {
-               if (cls.signon == SIGNONS)
-               {
-                       SCR_DrawNet ();
-                       SCR_DrawTurtle ();
-                       SCR_DrawPause ();
-                       Sbar_Draw();
-                       SHOWLMP_drawall();
-                       SCR_CheckDrawCenterString();
-               }
-               MR_Draw();
-               UI_Callback_Draw();
-               CL_DrawVideo();
-               //ui_draw();
-               if (cls.signon == SIGNONS)
-               {
-                       R_TimeReport("2d");
-                       R_TimeReport_End();
-                       R_TimeReport_Start();
-               }
-               R_Shadow_EditLights_DrawSelectedLightProperties();
+               R_TimeReport("2d");
+               R_TimeReport_End();
+               R_TimeReport_Start();
        }
+       R_Shadow_EditLights_DrawSelectedLightProperties();
+
        SCR_DrawConsole();
 
        SCR_UpdateScreen();