X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fdarkplaces.git;a=blobdiff_plain;f=cl_screen.c;h=6599c20f0c7eed9a52c3d073759c779c406dd224;hp=0070005eae5a0c97827bb7c16d11be2694498f81;hb=50099d9c0934fe93646fa2792fa7a9657422333b;hpb=d8eed1d934ecf4b6108a8692d7a6cd207a515530 diff --git a/cl_screen.c b/cl_screen.c index 0070005e..6599c20f 100644 --- a/cl_screen.c +++ b/cl_screen.c @@ -18,10 +18,10 @@ cvar_t scr_conforcewhiledisconnected = {0, "scr_conforcewhiledisconnected", "1", cvar_t scr_menuforcewhiledisconnected = {0, "scr_menuforcewhiledisconnected", "0", "forces menu while disconnected"}; cvar_t scr_centertime = {0, "scr_centertime","2", "how long centerprint messages show"}; cvar_t scr_showram = {CVAR_SAVE, "showram","1", "show ram icon if low on surface cache memory (not used)"}; -cvar_t scr_showturtle = {CVAR_SAVE, "showturtle","0", "show turtle icon when framerate is too low (not used)"}; +cvar_t scr_showturtle = {CVAR_SAVE, "showturtle","0", "show turtle icon when framerate is too low"}; cvar_t scr_showpause = {CVAR_SAVE, "showpause","1", "show pause icon when game is paused"}; cvar_t scr_showbrand = {0, "showbrand","0", "shows gfx/brand.tga in a corner of the screen (different values select different positions, including centered)"}; -cvar_t scr_printspeed = {0, "scr_printspeed","1000000", "speed of intermission printing (episode end texts), a value of 1000000 or higher disables the slow printing"}; +cvar_t scr_printspeed = {0, "scr_printspeed","0", "speed of intermission printing (episode end texts), a value of 0 disables the slow printing"}; cvar_t vid_conwidth = {CVAR_SAVE, "vid_conwidth", "640", "virtual width of 2D graphics system"}; cvar_t vid_conheight = {CVAR_SAVE, "vid_conheight", "480", "virtual height of 2D graphics system"}; cvar_t vid_pixelheight = {CVAR_SAVE, "vid_pixelheight", "1", "adjusts vertical field of vision to account for non-square pixels (1280x1024 on a CRT monitor for example)"}; @@ -30,21 +30,25 @@ cvar_t scr_screenshot_jpeg_quality = {CVAR_SAVE, "scr_screenshot_jpeg_quality"," cvar_t scr_screenshot_gammaboost = {CVAR_SAVE, "scr_screenshot_gammaboost","1", "gamma correction on saved screenshots and videos, 1.0 saves unmodified images"}; // scr_screenshot_name is defined in fs.c cvar_t cl_capturevideo = {0, "cl_capturevideo", "0", "enables saving of video to a .avi file using uncompressed I420 colorspace and PCM audio, note that scr_screenshot_gammaboost affects the brightness of the output)"}; +cvar_t cl_capturevideo_width = {0, "cl_capturevideo_width", "0", "scales all frames to this resolution before saving the video"}; +cvar_t cl_capturevideo_height = {0, "cl_capturevideo_height", "0", "scales all frames to this resolution before saving the video"}; cvar_t cl_capturevideo_realtime = {0, "cl_capturevideo_realtime", "0", "causes video saving to operate in realtime (mostly useful while playing, not while capturing demos), this can produce a much lower quality video due to poor sound/video sync and will abort saving if your machine stalls for over 1 second"}; cvar_t cl_capturevideo_fps = {0, "cl_capturevideo_fps", "30", "how many frames per second to save (29.97 for NTSC, 30 for typical PC video, 15 can be useful)"}; cvar_t cl_capturevideo_number = {CVAR_SAVE, "cl_capturevideo_number", "1", "number to append to video filename, incremented each time a capture begins"}; cvar_t r_letterbox = {0, "r_letterbox", "0", "reduces vertical height of view to simulate a letterboxed movie effect (can be used by mods for cutscenes)"}; -cvar_t r_stereo_separation = {0, "r_stereo_separation", "4", "separation of eyes in the world (try negative values too)"}; -cvar_t r_stereo_sidebyside = {0, "r_stereo_sidebyside", "0", "side by side views (for those who can't afford glasses but can afford eye strain)"}; +cvar_t r_stereo_separation = {0, "r_stereo_separation", "4", "separation distance of eyes in the world (negative values are only useful for cross-eyed viewing)"}; +cvar_t r_stereo_sidebyside = {0, "r_stereo_sidebyside", "0", "side by side views for those who can't afford glasses but can afford eye strain (note: use a negative r_stereo_separation if you want cross-eyed viewing)"}; cvar_t r_stereo_redblue = {0, "r_stereo_redblue", "0", "red/blue anaglyph stereo glasses (note: most of these glasses are actually red/cyan, try that one too)"}; cvar_t r_stereo_redcyan = {0, "r_stereo_redcyan", "0", "red/cyan anaglyph stereo glasses, the kind given away at drive-in movies like Creature From The Black Lagoon In 3D"}; cvar_t r_stereo_redgreen = {0, "r_stereo_redgreen", "0", "red/green anaglyph stereo glasses (for those who don't mind yellow)"}; +cvar_t r_stereo_angle = {0, "r_stereo_angle", "0", "separation angle of eyes (makes the views look different directions, as an example, 90 gives a 90 degree separation where the views are 45 degrees left and 45 degrees right)"}; cvar_t scr_zoomwindow = {CVAR_SAVE, "scr_zoomwindow", "0", "displays a zoomed in overlay window"}; cvar_t scr_zoomwindow_viewsizex = {CVAR_SAVE, "scr_zoomwindow_viewsizex", "20", "horizontal viewsize of zoom window"}; cvar_t scr_zoomwindow_viewsizey = {CVAR_SAVE, "scr_zoomwindow_viewsizey", "20", "vertical viewsize of zoom window"}; cvar_t scr_zoomwindow_fov = {CVAR_SAVE, "scr_zoomwindow_fov", "20", "fov of zoom window"}; cvar_t scr_stipple = {0, "scr_stipple", "0", "interlacing-like stippling of the display"}; cvar_t scr_refresh = {0, "scr_refresh", "1", "allows you to completely shut off rendering for benchmarking purposes"}; +cvar_t shownetgraph = {CVAR_SAVE, "shownetgraph", "0", "shows a graph of packet sizes and other information, 0 = off, 1 = show client netgraph, 2 = show client and server netgraphs (when hosting a server)"}; int jpeg_supported = false; @@ -104,13 +108,17 @@ void SCR_CenterPrint(char *str) void SCR_DrawCenterString (void) { char *start; - int l; int x, y; int remaining; int color; + if(gamemode == GAME_NEXUIZ) + if(cl.intermission == 2) // in Nexuiz finale (voting screen), + if(sb_showscores) // make TAB hide vote messages + return; + // the finale prints the characters one at a time, except if printspeed is an absurdly high value - if (cl.intermission && scr_printspeed.value < 1000000) + if (cl.intermission && scr_printspeed.value > 0 && scr_printspeed.value < 1000000) remaining = (int)(scr_printspeed.value * (cl.time - scr_centertime_start)); else remaining = 9999; @@ -130,36 +138,25 @@ void SCR_DrawCenterString (void) do { // scan the number of characters on the line, not counting color codes - int chars = 0; - for (l=0 ; l= '0' && start[l+1] <= '9')) - l++; - else - chars++; - } + char *newline = strchr(start, '\n'); + int l = newline ? (newline - start) : (int)strlen(start); + int chars = COM_StringLengthNoColors(start, l, NULL); + x = (vid_conwidth.integer - chars*8)/2; if (l > 0) { if (remaining < l) l = remaining; - DrawQ_ColoredString(x, y, start, l, 8, 8, 1, 1, 1, 1, 0, &color); + DrawQ_String(x, y, start, l, 8, 8, 1, 1, 1, 1, 0, &color, false); remaining -= l; if (remaining <= 0) return; } - y += 8; - while (*start && *start != '\n') - start++; - - if (!*start) + if (!newline) break; - start++; // skip the \n + start = newline + 1; // skip the \n } while (1); } @@ -168,7 +165,8 @@ void SCR_CheckDrawCenterString (void) if (scr_center_lines > scr_erase_lines) scr_erase_lines = scr_center_lines; - scr_centertime_off -= cl.realframetime; + if (cl.time > cl.oldtime) + scr_centertime_off -= cl.time - cl.oldtime; // don't draw if this is a normal stats-screen intermission, // only if it is not an intermission, or a finale intermission @@ -182,6 +180,134 @@ void SCR_CheckDrawCenterString (void) SCR_DrawCenterString (); } +void SCR_DrawNetGraph_DrawGraph (int graphx, int graphy, int barwidth, int barheight, int bardivide, const char *label, float textsize, int packetcounter, int numparameters, const int **parameters, const float parametercolors[][4]) +{ + int j, k, x, y, index, offset, height; + // draw the bar graph itself + // advance the packet counter because it is the latest packet column being + // built up and should come last + packetcounter = (packetcounter + 1) % NETGRAPH_PACKETS; + for (j = 0;j < NETGRAPH_PACKETS;j++) + { + x = graphx + j * barwidth; + y = graphy + barheight; + index = (packetcounter + j) % NETGRAPH_PACKETS; + if (parameters[0][index] == NETGRAPH_LOSTPACKET) + DrawQ_Fill(x, y - barheight, barwidth, barheight, 1, 0, 0, 1, 0); + else if (parameters[0][index] == NETGRAPH_CHOKEDPACKET) + DrawQ_Fill(x, y - min(2, barheight), barwidth, min(2, barheight), 1, 1, 0, 1, 0); + else + { + offset = 0; + for (k = 0;k < numparameters;k++) + { + height = (parameters[k][index] + bardivide - 1) / bardivide; + height = min(height, barheight - offset); + offset += height; + if (height) + DrawQ_Fill(x, y - offset, barwidth, height, parametercolors[k][0], parametercolors[k][1], parametercolors[k][2], parametercolors[k][3], 0); + } + } + } +} + +const float netgraphcolors[3][4] = +{ + {1 , 0.5, 0 , 1}, + {1 , 1 , 1 , 1}, + {0 , 1 , 0 , 1}, +}; + +void SCR_DrawNetGraph_DrawConnection_Client (netconn_t *conn, int graphx, int graphy, int barwidth, int barheight, int bardivide, const char *labelincoming, int separator, const char *labeloutgoing, float textsize) +{ + int numparameters; + const int *parameters[3]; + // dim background + DrawQ_Fill(graphx , graphy, barwidth * NETGRAPH_PACKETS, barheight + textsize, 0, 0, 0, 0.5, 0); + DrawQ_Fill(graphx + barwidth * NETGRAPH_PACKETS + separator, graphy, barwidth * NETGRAPH_PACKETS, barheight + textsize, 0, 0, 0, 0.5, 0); + // draw the bar graphs + numparameters = 3; + parameters[0] = conn->incoming_unreliablesize; + parameters[1] = conn->incoming_reliablesize; + parameters[2] = conn->incoming_acksize; + SCR_DrawNetGraph_DrawGraph(graphx, graphy, barwidth, barheight, bardivide, labelincoming, textsize, conn->incoming_packetcounter, numparameters, parameters, netgraphcolors); + parameters[0] = conn->outgoing_unreliablesize; + parameters[1] = conn->outgoing_reliablesize; + parameters[2] = conn->outgoing_acksize; + SCR_DrawNetGraph_DrawGraph(graphx + barwidth * NETGRAPH_PACKETS + separator, graphy, barwidth, barheight, bardivide, labeloutgoing, textsize, conn->outgoing_packetcounter, numparameters, parameters, netgraphcolors); + // draw labels + DrawQ_String(graphx , graphy + barheight, labelincoming, 0, textsize, textsize, 1, 1, 1, 1, 0, NULL, false); + DrawQ_String(graphx + barwidth * NETGRAPH_PACKETS + separator, graphy + barheight, labeloutgoing, 0, textsize, textsize, 1, 1, 1, 1, 0, NULL, false); +} + +void SCR_DrawNetGraph_DrawConnection_Server (netconn_t *conn, int graphx, int graphy, int barwidth, int barheight, int bardivide, const char *labeloutgoing, int separator, const char *labelincoming, float textsize) +{ + int numparameters; + const int *parameters[3]; + // dim background + DrawQ_Fill(graphx , graphy, barwidth * NETGRAPH_PACKETS, barheight + textsize, 0, 0, 0, 0.5, 0); + DrawQ_Fill(graphx + barwidth * NETGRAPH_PACKETS + separator, graphy, barwidth * NETGRAPH_PACKETS, barheight + textsize, 0, 0, 0, 0.5, 0); + // draw the bar graphs + numparameters = 3; + parameters[0] = conn->outgoing_unreliablesize; + parameters[1] = conn->outgoing_reliablesize; + parameters[2] = conn->outgoing_acksize; + SCR_DrawNetGraph_DrawGraph(graphx , graphy, barwidth, barheight, bardivide, labeloutgoing, textsize, conn->outgoing_packetcounter, numparameters, parameters, netgraphcolors); + parameters[0] = conn->incoming_unreliablesize; + parameters[1] = conn->incoming_reliablesize; + parameters[2] = conn->incoming_acksize; + SCR_DrawNetGraph_DrawGraph(graphx + barwidth * NETGRAPH_PACKETS + separator, graphy, barwidth, barheight, bardivide, labelincoming, textsize, conn->incoming_packetcounter, numparameters, parameters, netgraphcolors); + // draw labels + DrawQ_String(graphx , graphy + barheight, labeloutgoing, 0, textsize, textsize, 1, 1, 1, 1, 0, NULL, false); + DrawQ_String(graphx + barwidth * NETGRAPH_PACKETS + separator, graphy + barheight, labelincoming, 0, textsize, textsize, 1, 1, 1, 1, 0, NULL, false); +} + +/* +============== +SCR_DrawNetGraph +============== +*/ +void SCR_DrawNetGraph (void) +{ + int i, separator1, separator2, barwidth, barheight, bardivide, netgraph_x, netgraph_y, textsize, index, netgraphsperrow; + + if (cls.state != ca_connected) + return; + if (!cls.netcon) + return; + if (!shownetgraph.integer) + return; + + separator1 = 2; + separator2 = 4; + textsize = 8; + barwidth = 1; + barheight = 50; + bardivide = 20; + + netgraphsperrow = (vid_conwidth.integer + separator2) / (barwidth * NETGRAPH_PACKETS * 2 + separator1 + separator2); + netgraphsperrow = max(netgraphsperrow, 1); + + index = 0; + netgraph_x = (vid_conwidth.integer + separator2) - (1 + (index % netgraphsperrow)) * (barwidth * NETGRAPH_PACKETS * 2 + separator1 + separator2); + netgraph_y = (vid_conheight.integer - 48 + separator2) - (1 + (index / netgraphsperrow)) * (barheight + textsize + separator2); + SCR_DrawNetGraph_DrawConnection_Client(cls.netcon, netgraph_x, netgraph_y, barwidth, barheight, bardivide, "incoming", separator1, "outgoing", textsize); + index++; + + if (sv.active && shownetgraph.integer >= 2) + { + for (i = 0;i < svs.maxclients;i++) + { + if (!svs.clients[i].netconnection) + continue; + netgraph_x = (vid_conwidth.integer + separator2) - (1 + (index % netgraphsperrow)) * (barwidth * NETGRAPH_PACKETS * 2 + separator1 + separator2); + netgraph_y = (vid_conheight.integer - 48 + separator2) - (1 + (index / netgraphsperrow)) * (barheight + textsize + separator2); + SCR_DrawNetGraph_DrawConnection_Server(svs.clients[i].netconnection, netgraph_x, netgraph_y, barwidth, barheight, bardivide, va("%s", svs.clients[i].name), separator1, "", textsize); + index++; + } + } +} + /* ============== SCR_DrawTurtle @@ -336,8 +462,8 @@ static int SCR_DrawQWDownload(int offset) len = (int)strlen(temp); x = (vid_conwidth.integer - len*size) / 2; y = vid_conheight.integer - size - offset; - DrawQ_Pic(0, y, NULL, vid_conwidth.integer, size, 0, 0, 0, 0.5, 0); - DrawQ_String(x, y, temp, len, size, size, 1, 1, 1, 1, 0); + DrawQ_Fill(0, y, vid_conwidth.integer, size, 0, 0, 0, 0.5, 0); + DrawQ_String(x, y, temp, len, size, size, 1, 1, 1, 1, 0, NULL, true); return 8; } @@ -367,8 +493,8 @@ static int SCR_DrawCurlDownload(int offset) { len = (int)strlen(addinfo); x = (vid_conwidth.integer - len*size) / 2; - DrawQ_Pic(0, y - size, NULL, vid_conwidth.integer, size, 1, 1, 1, 0.8, 0); - DrawQ_String(x, y - size, addinfo, len, size, size, 0, 0, 0, 1, 0); + DrawQ_Fill(0, y - size, vid_conwidth.integer, size, 1, 1, 1, 0.8, 0); + DrawQ_String(x, y - size, addinfo, len, size, size, 0, 0, 0, 1, 0, NULL, true); } for(i = 0; i != nDownloads; ++i) @@ -381,8 +507,8 @@ static int SCR_DrawCurlDownload(int offset) dpsnprintf(temp, sizeof(temp), "Downloading %s ... %5.1f%% @ %.1f KiB/s\n", downinfo[i].filename, 100.0 * downinfo[i].progress, downinfo[i].speed / 1024.0); len = (int)strlen(temp); x = (vid_conwidth.integer - len*size) / 2; - DrawQ_Pic(0, y + i * size, NULL, vid_conwidth.integer, size, 0, 0, 0, 0.8, 0); - DrawQ_String(x, y + i * size, temp, len, size, size, 1, 1, 1, 1, 0); + DrawQ_Fill(0, y + i * size, vid_conwidth.integer, size, 0, 0, 0, 0.8, 0); + DrawQ_String(x, y + i * size, temp, len, size, size, 1, 1, 1, 1, 0, NULL, true); } Z_Free(downinfo); @@ -532,14 +658,14 @@ void R_TimeReport_Frame(void) lines++; y = vid_conheight.integer - sb_lines - lines * 8; i = j = 0; - DrawQ_Pic(0, y, NULL, vid_conwidth.integer, lines * 8, 0, 0, 0, 0.5, 0); + DrawQ_Fill(0, y, vid_conwidth.integer, lines * 8, 0, 0, 0, 0.5, 0); while (r_speeds_string[i]) { j = i; while (r_speeds_string[i] && r_speeds_string[i] != '\n') i++; if (i - j > 0) - DrawQ_String(0, y, r_speeds_string + j, i - j, 8, 8, 1, 1, 1, 1, 0); + DrawQ_String(0, y, r_speeds_string + j, i - j, 8, 8, 1, 1, 1, 1, 0, NULL, true); if (r_speeds_string[i] == '\n') i++; y += 8; @@ -600,6 +726,12 @@ void SCR_SizeDown_f (void) Cvar_SetValue ("viewsize",scr_viewsize.value-10); } +void SCR_CaptureVideo_EndVideo(void); +void CL_Screen_Shutdown(void) +{ + SCR_CaptureVideo_EndVideo(); +} + void CL_Screen_Init(void) { Cvar_RegisterVariable (&scr_fov); @@ -621,6 +753,8 @@ void CL_Screen_Init(void) Cvar_RegisterVariable (&scr_screenshot_jpeg_quality); Cvar_RegisterVariable (&scr_screenshot_gammaboost); Cvar_RegisterVariable (&cl_capturevideo); + Cvar_RegisterVariable (&cl_capturevideo_width); + Cvar_RegisterVariable (&cl_capturevideo_height); Cvar_RegisterVariable (&cl_capturevideo_realtime); Cvar_RegisterVariable (&cl_capturevideo_fps); Cvar_RegisterVariable (&cl_capturevideo_number); @@ -630,12 +764,14 @@ void CL_Screen_Init(void) Cvar_RegisterVariable(&r_stereo_redblue); Cvar_RegisterVariable(&r_stereo_redcyan); Cvar_RegisterVariable(&r_stereo_redgreen); + Cvar_RegisterVariable(&r_stereo_angle); Cvar_RegisterVariable(&scr_zoomwindow); Cvar_RegisterVariable(&scr_zoomwindow_viewsizex); Cvar_RegisterVariable(&scr_zoomwindow_viewsizey); Cvar_RegisterVariable(&scr_zoomwindow_fov); Cvar_RegisterVariable(&scr_stipple); Cvar_RegisterVariable(&scr_refresh); + Cvar_RegisterVariable(&shownetgraph); Cmd_AddCommand ("sizeup",SCR_SizeUp_f, "increase view size (increases viewsize cvar)"); Cmd_AddCommand ("sizedown",SCR_SizeDown_f, "decrease view size (decreases viewsize cvar)"); @@ -809,57 +945,119 @@ static void SCR_CaptureVideo_RIFF_IndexEntry(const char *chunkfourcc, int chunks MSG_WriteLong(&cls.capturevideo.riffindexbuffer, chunksize); } -static void SCR_CaptureVideo_RIFF_Finish(void) +static void SCR_CaptureVideo_RIFF_Finish(qboolean final) { // close the "movi" list SCR_CaptureVideo_RIFF_Pop(); // write the idx1 chunk that we've been building while saving the frames - SCR_CaptureVideo_RIFF_Push("idx1", NULL); - SCR_CaptureVideo_RIFF_WriteBytes(cls.capturevideo.riffindexbuffer.data, cls.capturevideo.riffindexbuffer.cursize); - SCR_CaptureVideo_RIFF_Pop(); + if(final && cls.capturevideo.videofile_firstchunkframes_offset) + // TODO replace index creating by OpenDML ix##/##ix/indx chunk so it works for more than one AVI part too + { + SCR_CaptureVideo_RIFF_Push("idx1", NULL); + SCR_CaptureVideo_RIFF_WriteBytes(cls.capturevideo.riffindexbuffer.data, cls.capturevideo.riffindexbuffer.cursize); + SCR_CaptureVideo_RIFF_Pop(); + } cls.capturevideo.riffindexbuffer.cursize = 0; // pop the RIFF chunk itself while (cls.capturevideo.riffstacklevel > 0) SCR_CaptureVideo_RIFF_Pop(); SCR_CaptureVideo_RIFF_Flush(); + if(cls.capturevideo.videofile_firstchunkframes_offset) + { + Con_DPrintf("Finishing first chunk (%d frames)\n", cls.capturevideo.frame); + FS_Seek(cls.capturevideo.videofile, cls.capturevideo.videofile_firstchunkframes_offset, SEEK_SET); + SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.frame); + SCR_CaptureVideo_RIFF_Flush(); + FS_Seek(cls.capturevideo.videofile, 0, SEEK_END); + cls.capturevideo.videofile_firstchunkframes_offset = 0; + } + else + Con_DPrintf("Finishing another chunk (%d frames)\n", cls.capturevideo.frame); } static void SCR_CaptureVideo_RIFF_OverflowCheck(int framesize) { - fs_offset_t cursize; + fs_offset_t cursize, curfilesize; if (cls.capturevideo.riffstacklevel != 2) Sys_Error("SCR_CaptureVideo_RIFF_OverflowCheck: chunk stack leakage!\n"); // check where we are in the file SCR_CaptureVideo_RIFF_Flush(); cursize = SCR_CaptureVideo_RIFF_GetPosition() - cls.capturevideo.riffstackstartoffset[0]; + curfilesize = SCR_CaptureVideo_RIFF_GetPosition(); + // if this would overflow the windows limit of 1GB per RIFF chunk, we need // to close the current RIFF chunk and open another for future frames if (8 + cursize + framesize + cls.capturevideo.riffindexbuffer.cursize + 8 > 1<<30) { - SCR_CaptureVideo_RIFF_Finish(); + SCR_CaptureVideo_RIFF_Finish(false); // begin a new 1GB extended section of the AVI SCR_CaptureVideo_RIFF_Push("RIFF", "AVIX"); SCR_CaptureVideo_RIFF_Push("LIST", "movi"); } } +static void FindFraction(double val, int *num, int *denom, int denomMax) +{ + int i; + double bestdiff; + // initialize + bestdiff = fabs(val); + *num = 0; + *denom = 1; + + for(i = 1; i <= denomMax; ++i) + { + int inum = floor(0.5 + val * i); + double diff = fabs(val - inum / (double)i); + if(diff < bestdiff) + { + bestdiff = diff; + *num = inum; + *denom = i; + } + } +} + void SCR_CaptureVideo_BeginVideo(void) { - double gamma, g; - int width = vid.width, height = vid.height, x; + double gamma, g, aspect; + int width = cl_capturevideo_width.integer, height = cl_capturevideo_height.integer; + int n, d; unsigned int i; if (cls.capturevideo.active) return; memset(&cls.capturevideo, 0, sizeof(cls.capturevideo)); // soundrate is figured out on the first SoundFrame + + if(width == 0 && height != 0) + width = (int) (height * (double)vid.width / ((double)vid.height * vid_pixelheight.value)); // keep aspect + if(width != 0 && height == 0) + height = (int) (width * ((double)vid.height * vid_pixelheight.value) / (double)vid.width); // keep aspect + + if(width < 2 || width > vid.width) // can't scale up + width = vid.width; + if(height < 2 || height > vid.height) // can't scale up + height = vid.height; + + aspect = vid.width / (vid.height * vid_pixelheight.value); + + // ensure it's all even; if not, scale down a little + if(width % 1) + --width; + if(height % 1) + --height; + + cls.capturevideo.width = width; + cls.capturevideo.height = height; cls.capturevideo.active = true; - cls.capturevideo.starttime = Sys_DoubleTime(); + cls.capturevideo.starttime = realtime; cls.capturevideo.framerate = bound(1, cl_capturevideo_fps.value, 1000); cls.capturevideo.soundrate = S_GetSoundRate(); cls.capturevideo.frame = 0; cls.capturevideo.soundsampleframe = 0; cls.capturevideo.realtime = cl_capturevideo_realtime.integer != 0; - cls.capturevideo.buffer = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * (3+3+3) + 18); + cls.capturevideo.screenbuffer = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3); + cls.capturevideo.outbuffer = (unsigned char *)Mem_Alloc(tempmempool, width * height * (3+3+3) + 18); gamma = 1.0/scr_screenshot_gammaboost.value; dpsnprintf(cls.capturevideo.basename, sizeof(cls.capturevideo.basename), "video/dpvideo%03i", cl_capturevideo_number.integer); Cvar_SetValueQuick(&cl_capturevideo_number, cl_capturevideo_number.integer + 1); @@ -919,7 +1117,7 @@ Cr = R * .500 + G * -.419 + B * -.0813 + 128.; SCR_CaptureVideo_RIFF_Write32(0); // max bytes per second SCR_CaptureVideo_RIFF_Write32(0); // padding granularity SCR_CaptureVideo_RIFF_Write32(0x910); // flags (AVIF_HASINDEX | AVIF_ISINTERLEAVED | AVIF_TRUSTCKTYPE) - cls.capturevideo.videofile_totalframes_offset1 = SCR_CaptureVideo_RIFF_GetPosition(); + cls.capturevideo.videofile_firstchunkframes_offset = SCR_CaptureVideo_RIFF_GetPosition(); SCR_CaptureVideo_RIFF_Write32(0); // total frames SCR_CaptureVideo_RIFF_Write32(0); // initial frames if (cls.capturevideo.soundrate) @@ -943,13 +1141,11 @@ Cr = R * .500 + G * -.419 + B * -.0813 + 128.; SCR_CaptureVideo_RIFF_Write16(0); // language SCR_CaptureVideo_RIFF_Write32(0); // initial frames // find an ideal divisor for the framerate - for (x = 1;x < 1000;x++) - if (cls.capturevideo.framerate * x == floor(cls.capturevideo.framerate * x)) - break; - SCR_CaptureVideo_RIFF_Write32(x); // samples/second divisor - SCR_CaptureVideo_RIFF_Write32((int)(cls.capturevideo.framerate * x)); // samples/second multiplied by divisor + FindFraction(cls.capturevideo.framerate, &n, &d, 1000); + SCR_CaptureVideo_RIFF_Write32(d); // samples/second divisor + SCR_CaptureVideo_RIFF_Write32(n); // samples/second multiplied by divisor SCR_CaptureVideo_RIFF_Write32(0); // start - cls.capturevideo.videofile_totalframes_offset2 = SCR_CaptureVideo_RIFF_GetPosition(); + cls.capturevideo.videofile_totalframes_offset1 = SCR_CaptureVideo_RIFF_GetPosition(); SCR_CaptureVideo_RIFF_Write32(0); // length SCR_CaptureVideo_RIFF_Write32(width*height+(width/2)*(height/2)*2); // suggested buffer size SCR_CaptureVideo_RIFF_Write32(0); // quality @@ -973,6 +1169,27 @@ Cr = R * .500 + G * -.419 + B * -.0813 + 128.; SCR_CaptureVideo_RIFF_Write32(0); // color used SCR_CaptureVideo_RIFF_Write32(0); // color important SCR_CaptureVideo_RIFF_Pop(); + // extended format (aspect!) + SCR_CaptureVideo_RIFF_Push("vprp", NULL); + SCR_CaptureVideo_RIFF_Write32(0); // VideoFormatToken + SCR_CaptureVideo_RIFF_Write32(0); // VideoStandard + SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.framerate); // dwVerticalRefreshRate (bogus) + SCR_CaptureVideo_RIFF_Write32(width); // dwHTotalInT + SCR_CaptureVideo_RIFF_Write32(height); // dwVTotalInLines + FindFraction(aspect, &n, &d, 1000); + SCR_CaptureVideo_RIFF_Write32((n << 16) | d); // dwFrameAspectRatio // TODO a word + SCR_CaptureVideo_RIFF_Write32(width); // dwFrameWidthInPixels + SCR_CaptureVideo_RIFF_Write32(height); // dwFrameHeightInLines + SCR_CaptureVideo_RIFF_Write32(1); // nFieldPerFrame + SCR_CaptureVideo_RIFF_Write32(width); // CompressedBMWidth + SCR_CaptureVideo_RIFF_Write32(height); // CompressedBMHeight + SCR_CaptureVideo_RIFF_Write32(width); // ValidBMHeight + SCR_CaptureVideo_RIFF_Write32(height); // ValidBMWidth + SCR_CaptureVideo_RIFF_Write32(0); // ValidBMXOffset + SCR_CaptureVideo_RIFF_Write32(0); // ValidBMYOffset + SCR_CaptureVideo_RIFF_Write32(0); // ValidBMXOffsetInT + SCR_CaptureVideo_RIFF_Write32(0); // ValidBMYValidStartLine + SCR_CaptureVideo_RIFF_Pop(); SCR_CaptureVideo_RIFF_Pop(); if (cls.capturevideo.soundrate) { @@ -1009,6 +1226,15 @@ Cr = R * .500 + G * -.419 + B * -.0813 + 128.; SCR_CaptureVideo_RIFF_Pop(); SCR_CaptureVideo_RIFF_Pop(); } + + // extended header (for total #frames) + SCR_CaptureVideo_RIFF_Push("LIST", "odml"); + SCR_CaptureVideo_RIFF_Push("dmlh", NULL); + cls.capturevideo.videofile_totalframes_offset2 = SCR_CaptureVideo_RIFF_GetPosition(); + SCR_CaptureVideo_RIFF_Write32(0); + SCR_CaptureVideo_RIFF_Pop(); + SCR_CaptureVideo_RIFF_Pop(); + // close the AVI header list SCR_CaptureVideo_RIFF_Pop(); // software that produced this AVI video file @@ -1058,8 +1284,9 @@ void SCR_CaptureVideo_EndVideo(void) { case CAPTUREVIDEOFORMAT_AVI_I420: // close any open chunks - SCR_CaptureVideo_RIFF_Finish(); + SCR_CaptureVideo_RIFF_Finish(true); // go back and fix the video frames and audio samples fields + Con_DPrintf("Finishing capture (%d frames, %d audio frames)\n", cls.capturevideo.frame, cls.capturevideo.soundsampleframe); FS_Seek(cls.capturevideo.videofile, cls.capturevideo.videofile_totalframes_offset1, SEEK_SET); SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.frame); SCR_CaptureVideo_RIFF_Flush(); @@ -1080,10 +1307,16 @@ void SCR_CaptureVideo_EndVideo(void) cls.capturevideo.videofile = NULL; } - if (cls.capturevideo.buffer) + if (cls.capturevideo.screenbuffer) { - Mem_Free (cls.capturevideo.buffer); - cls.capturevideo.buffer = NULL; + Mem_Free (cls.capturevideo.screenbuffer); + cls.capturevideo.screenbuffer = NULL; + } + + if (cls.capturevideo.outbuffer) + { + Mem_Free (cls.capturevideo.outbuffer); + cls.capturevideo.outbuffer = NULL; } if (cls.capturevideo.riffindexbuffer.data) @@ -1138,9 +1371,55 @@ void SCR_CaptureVideo_ConvertFrame_RGB_to_I420_flip(int width, int height, unsig } } +static void SCR_ScaleDown(unsigned char *in, int inw, int inh, unsigned char *out, int outw, int outh) +{ + // TODO optimize this function + + int x, y; + float area; + + // memcpy is faster than me + if(inw == outw && inh == outh) + { + memcpy(out, in, 3 * inw * inh); + return; + } + + // otherwise: a box filter + area = (float)outw * (float)outh / (float)inw / (float)inh; + for(y = 0; y < outh; ++y) + { + float iny0 = y / (float)outh * inh; int iny0_i = floor(iny0); + float iny1 = (y+1) / (float)outh * inh; int iny1_i = ceil(iny1); + for(x = 0; x < outw; ++x) + { + float inx0 = x / (float)outw * inw; int inx0_i = floor(inx0); + float inx1 = (x+1) / (float)outw * inw; int inx1_i = ceil(inx1); + float r = 0, g = 0, b = 0; + int xx, yy; + + for(yy = iny0_i; yy < iny1_i; ++yy) + { + float ya = min(yy+1, iny1) - max(iny0, yy); + for(xx = inx0_i; xx < inx1_i; ++xx) + { + float a = ya * (min(xx+1, inx1) - max(inx0, xx)); + r += a * in[3*(xx + inw * yy)+0]; + g += a * in[3*(xx + inw * yy)+1]; + b += a * in[3*(xx + inw * yy)+2]; + } + } + + out[3*(x + outw * y)+0] = r * area; + out[3*(x + outw * y)+1] = g * area; + out[3*(x + outw * y)+2] = b * area; + } + } +} + qboolean SCR_CaptureVideo_VideoFrame(int newframenum) { - int x = 0, y = 0, width = vid.width, height = vid.height; + int x = 0, y = 0, width = cls.capturevideo.width, height = cls.capturevideo.height; unsigned char *in, *out; CHECKGLERROR //return SCR_ScreenShot(filename, cls.capturevideo.buffer, cls.capturevideo.buffer + vid.width * vid.height * 3, cls.capturevideo.buffer + vid.width * vid.height * 6, 0, 0, vid.width, vid.height, false, false, false, jpeg, true); @@ -1152,9 +1431,10 @@ qboolean SCR_CaptureVideo_VideoFrame(int newframenum) if (!cls.capturevideo.videofile) return false; // FIXME: width/height must be multiple of 2, enforce this? - qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cls.capturevideo.buffer);CHECKGLERROR - in = cls.capturevideo.buffer; - out = cls.capturevideo.buffer + width*height*3; + qglReadPixels (x, y, vid.width, vid.height, GL_RGB, GL_UNSIGNED_BYTE, cls.capturevideo.screenbuffer);CHECKGLERROR + SCR_ScaleDown (cls.capturevideo.screenbuffer, vid.width, vid.height, cls.capturevideo.outbuffer, width, height); + in = cls.capturevideo.outbuffer; + out = cls.capturevideo.outbuffer + width*height*3; SCR_CaptureVideo_ConvertFrame_RGB_to_I420_flip(width, height, in, out); x = width*height+(width/2)*(height/2)*2; SCR_CaptureVideo_RIFF_OverflowCheck(8 + x); @@ -1209,7 +1489,7 @@ void SCR_CaptureVideo(void) if (cls.capturevideo.realtime) { // preserve sound sync by duplicating frames when running slow - newframenum = (int)((Sys_DoubleTime() - cls.capturevideo.starttime) * cls.capturevideo.framerate); + newframenum = (int)((realtime - cls.capturevideo.starttime) * cls.capturevideo.framerate); } else newframenum = cls.capturevideo.frame + 1; @@ -1535,7 +1815,7 @@ void SCR_DrawScreen (void) // this it simply assumes the requested fov is the vertical fov // for a 4x3 display, if the ratio is not 4x3 this makes the fov // higher/lower according to the ratio - r_view.frustum_y = tan(scr_fov.value * cl.viewzoom * M_PI / 360.0) * (3.0/4.0); + r_view.frustum_y = tan(scr_fov.value * M_PI / 360.0) * (3.0/4.0) * cl.viewzoom; r_view.frustum_x = r_view.frustum_y * (float)r_view.width / (float)r_view.height / vid_pixelheight.value; r_view.frustum_x *= r_refdef.frustumscale_x; @@ -1555,7 +1835,7 @@ void SCR_DrawScreen (void) r_view.y = 0; r_view.z = 0; - r_view.frustum_y = tan(scr_zoomwindow_fov.value * cl.viewzoom * M_PI / 360.0) * (3.0/4.0); + r_view.frustum_y = tan(scr_zoomwindow_fov.value * M_PI / 360.0) * (3.0/4.0) * cl.viewzoom; r_view.frustum_x = r_view.frustum_y * vid_pixelheight.value * (float)r_view.width / (float)r_view.height; r_view.frustum_x *= r_refdef.frustumscale_x; @@ -1588,6 +1868,7 @@ void SCR_DrawScreen (void) SHOWLMP_drawall(); SCR_CheckDrawCenterString(); } + SCR_DrawNetGraph (); MR_Draw(); CL_DrawVideo(); R_Shadow_EditLights_DrawSelectedLightProperties(); @@ -1646,12 +1927,14 @@ void SCR_UpdateLoadingScreen (qboolean clear) y = (vid_conheight.integer - pic->height)/2; GL_Color(1,1,1,1); GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GL_DepthRange(0, 1); + GL_PolygonOffset(0, 0); GL_DepthTest(false); - R_Mesh_VertexPointer(vertex3f); - R_Mesh_ColorPointer(NULL); + R_Mesh_VertexPointer(vertex3f, 0, 0); + R_Mesh_ColorPointer(NULL, 0, 0); R_Mesh_ResetTextureState(); R_Mesh_TexBind(0, R_GetTexture(pic->tex)); - R_Mesh_TexCoordPointer(0, 2, texcoord2f); + R_Mesh_TexCoordPointer(0, 2, texcoord2f, 0, 0); vertex3f[2] = vertex3f[5] = vertex3f[8] = vertex3f[11] = 0; vertex3f[0] = vertex3f[9] = x; vertex3f[1] = vertex3f[4] = y; @@ -1664,14 +1947,14 @@ void SCR_UpdateLoadingScreen (qboolean clear) if (vid.stereobuffer) { qglDrawBuffer(GL_FRONT_LEFT); - R_Mesh_Draw(0, 4, 2, polygonelements); + R_Mesh_Draw(0, 4, 2, polygonelements, 0, 0); qglDrawBuffer(GL_FRONT_RIGHT); - R_Mesh_Draw(0, 4, 2, polygonelements); + R_Mesh_Draw(0, 4, 2, polygonelements, 0, 0); } else { qglDrawBuffer(GL_FRONT); - R_Mesh_Draw(0, 4, 2, polygonelements); + R_Mesh_Draw(0, 4, 2, polygonelements, 0, 0); } R_Mesh_Finish(); // refresh @@ -1691,15 +1974,6 @@ void CL_UpdateScreen(void) if (!scr_initialized || !con_initialized) return; // not initialized yet - // 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"); - } - conwidth = bound(320, vid_conwidth.value, 2048); conheight = bound(200, vid_conheight.value, 1536); if (vid_conwidth.value != conwidth) @@ -1787,11 +2061,13 @@ void CL_UpdateScreen(void) if (r_timereport_active) R_TimeReport("clear"); + qglDrawBuffer(GL_BACK); + if (vid.stereobuffer || r_stereo_redblue.integer || r_stereo_redgreen.integer || r_stereo_redcyan.integer || r_stereo_sidebyside.integer) { matrix4x4_t originalmatrix = r_view.matrix; matrix4x4_t offsetmatrix; - Matrix4x4_CreateTranslate(&offsetmatrix, 0, r_stereo_separation.value * -0.5f, 0); + Matrix4x4_CreateFromQuakeEntity(&offsetmatrix, 0, r_stereo_separation.value * 0.5f, 0, 0, r_stereo_angle.value * 0.5f, 0, 1); Matrix4x4_Concat(&r_view.matrix, &originalmatrix, &offsetmatrix); if (r_stereo_sidebyside.integer) @@ -1809,7 +2085,7 @@ void CL_UpdateScreen(void) SCR_DrawScreen(); - Matrix4x4_CreateTranslate(&offsetmatrix, 0, r_stereo_separation.value * 0.5f, 0); + Matrix4x4_CreateFromQuakeEntity(&offsetmatrix, 0, r_stereo_separation.value * -0.5f, 0, 0, r_stereo_angle.value * -0.5f, 0, 1); Matrix4x4_Concat(&r_view.matrix, &originalmatrix, &offsetmatrix); if (r_stereo_sidebyside.integer) @@ -1830,10 +2106,7 @@ void CL_UpdateScreen(void) r_view.matrix = originalmatrix; } else - { - qglDrawBuffer(GL_BACK); SCR_DrawScreen(); - } SCR_CaptureVideo();