X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fdarkplaces.git;a=blobdiff_plain;f=cl_screen.c;h=09f56f8ac8833e47a0539d884fafdef707a0cf34;hp=4d0d7709f19ba99dc12684399f251e6eff86ee28;hb=0144ec3bdff2b7e8397be064cb7850a1ece7bce9;hpb=3ae5a27bc289ce5c7be2955c2865239317db0295 diff --git a/cl_screen.c b/cl_screen.c index 4d0d7709..09f56f8a 100644 --- a/cl_screen.c +++ b/cl_screen.c @@ -30,6 +30,8 @@ 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"}; @@ -48,6 +50,7 @@ cvar_t scr_stipple = {0, "scr_stipple", "0", "interlacing-like stippling of the 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)"}; +#define AVI_MASTER_INDEX_SIZE 640 // GB ought to be enough for anyone int jpeg_supported = false; @@ -106,11 +109,14 @@ void SCR_CenterPrint(char *str) void SCR_DrawCenterString (void) { char *start; - int l; int x, y; int remaining; int color; + if(cl.intermission == 2) // in finale, + if(sb_showscores) // make TAB hide the finale message (sb_showscores overrides finale in sbar.c) + return; + // the finale prints the characters one at a time, except if printspeed is an absurdly high value if (cl.intermission && scr_printspeed.value > 0 && scr_printspeed.value < 1000000) remaining = (int)(scr_printspeed.value * (cl.time - scr_centertime_start)); @@ -132,17 +138,10 @@ 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) { @@ -153,15 +152,11 @@ void SCR_DrawCenterString (void) 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); } @@ -606,7 +601,6 @@ void SCR_BeginLoadingPlaque (void) Log_Start(); Host_StartVideo(); - S_StopAllSounds(); SCR_UpdateLoadingScreen(false); } @@ -689,7 +683,8 @@ void R_TimeReport_Frame(void) if (loc) sprintf(r_speeds_string + strlen(r_speeds_string), "Location: %s\n", loc->name); sprintf(r_speeds_string + strlen(r_speeds_string), "org:'%+8.2f %+8.2f %+8.2f' dir:'%+2.3f %+2.3f %+2.3f'\n", r_view.origin[0], r_view.origin[1], r_view.origin[2], r_view.forward[0], r_view.forward[1], r_view.forward[2]); - sprintf(r_speeds_string + strlen(r_speeds_string), "%5i entities%6i surfaces%6i triangles%5i leafs%5i portals%6i particles\n", r_refdef.stats.entities, r_refdef.stats.entities_surfaces, r_refdef.stats.entities_triangles, r_refdef.stats.world_leafs, r_refdef.stats.world_portals, r_refdef.stats.particles); + sprintf(r_speeds_string + strlen(r_speeds_string), "%7i surfaces%7i triangles %5i entities (%7i surfaces%7i triangles)\n", r_refdef.stats.world_surfaces, r_refdef.stats.world_triangles, r_refdef.stats.entities, r_refdef.stats.entities_surfaces, r_refdef.stats.entities_triangles); + sprintf(r_speeds_string + strlen(r_speeds_string), "%5ileafs%5i portals%6i particles%6i decals\n", r_refdef.stats.world_leafs, r_refdef.stats.world_portals, r_refdef.stats.particles, r_refdef.stats.decals); sprintf(r_speeds_string + strlen(r_speeds_string), "%4i lights%4i clears%4i scissored%7i light%7i shadow%7i dynamic\n", r_refdef.stats.lights, r_refdef.stats.lights_clears, r_refdef.stats.lights_scissored, r_refdef.stats.lights_lighttriangles, r_refdef.stats.lights_shadowtriangles, r_refdef.stats.lights_dynamicshadowtriangles); if (r_refdef.stats.bloom) sprintf(r_speeds_string + strlen(r_speeds_string), "rendered%6i meshes%8i triangles bloompixels%8i copied%8i drawn\n", r_refdef.stats.meshes, r_refdef.stats.meshes_elements / 3, r_refdef.stats.bloom_copypixels, r_refdef.stats.bloom_drawpixels); @@ -731,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); @@ -752,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); @@ -919,72 +922,200 @@ static void SCR_CaptureVideo_RIFF_Pop(void) } } -static void SCR_CaptureVideo_RIFF_IndexEntry(const char *chunkfourcc, int chunksize, int flags) +static void GrowBuf(sizebuf_t *buf, int extralen) { - if (cls.capturevideo.riffstacklevel != 2) - Sys_Error("SCR_Capturevideo_RIFF_IndexEntry: RIFF stack level is %i (should be 2)\n", cls.capturevideo.riffstacklevel); - if (cls.capturevideo.riffindexbuffer.cursize + 16 > cls.capturevideo.riffindexbuffer.maxsize) + if(buf->cursize + extralen > buf->maxsize) { - int oldsize = cls.capturevideo.riffindexbuffer.maxsize; + int oldsize = buf->maxsize; unsigned char *olddata; - olddata = cls.capturevideo.riffindexbuffer.data; - cls.capturevideo.riffindexbuffer.maxsize = max(cls.capturevideo.riffindexbuffer.maxsize * 2, 4096); - cls.capturevideo.riffindexbuffer.data = Mem_Alloc(tempmempool, cls.capturevideo.riffindexbuffer.maxsize); - if (olddata) + olddata = buf->data; + buf->maxsize = max(buf->maxsize * 2, 4096); + buf->data = Mem_Alloc(tempmempool, buf->maxsize); + if(olddata) { - memcpy(cls.capturevideo.riffindexbuffer.data, olddata, oldsize); + memcpy(buf->data, olddata, oldsize); Mem_Free(olddata); } } +} + +static void SCR_CaptureVideo_RIFF_IndexEntry(const char *chunkfourcc, int chunksize, int flags) +{ + if (cls.capturevideo.riffstacklevel != 2) + Sys_Error("SCR_Capturevideo_RIFF_IndexEntry: RIFF stack level is %i (should be 2)\n", cls.capturevideo.riffstacklevel); + GrowBuf(&cls.capturevideo.riffindexbuffer, 16); + SCR_CaptureVideo_RIFF_Flush(); MSG_WriteUnterminatedString(&cls.capturevideo.riffindexbuffer, chunkfourcc); MSG_WriteLong(&cls.capturevideo.riffindexbuffer, flags); MSG_WriteLong(&cls.capturevideo.riffindexbuffer, (int)FS_Tell(cls.capturevideo.videofile) - cls.capturevideo.riffstackstartoffset[1]); MSG_WriteLong(&cls.capturevideo.riffindexbuffer, chunksize); } -static void SCR_CaptureVideo_RIFF_Finish(void) +static void SCR_CaptureVideo_RIFF_MakeIxChunk(const char *fcc, const char *dwChunkId, fs_offset_t masteridx_counter, int *masteridx_count, fs_offset_t masteridx_start) { - // close the "movi" list + int nMatching; + int i; + fs_offset_t ix = SCR_CaptureVideo_RIFF_GetPosition(); + fs_offset_t pos; + + if(*masteridx_count >= AVI_MASTER_INDEX_SIZE) + return; + + nMatching = 0; // go through index and enumerate them + for(i = 0; i < cls.capturevideo.riffindexbuffer.cursize; i += 16) + if(!memcmp(cls.capturevideo.riffindexbuffer.data + i, dwChunkId, 4)) + ++nMatching; + + SCR_CaptureVideo_RIFF_Push(fcc, NULL); + SCR_CaptureVideo_RIFF_Write16(2); // wLongsPerEntry + SCR_CaptureVideo_RIFF_Write16(0x0100); // bIndexType=1, bIndexSubType=0 + SCR_CaptureVideo_RIFF_Write32(nMatching); // nEntriesInUse + SCR_CaptureVideo_RIFF_WriteFourCC(dwChunkId); // dwChunkId + SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.videofile_ix_movistart & (fs_offset_t) 0xFFFFFFFFu); + SCR_CaptureVideo_RIFF_Write32(((fs_offset_t) cls.capturevideo.videofile_ix_movistart) >> 32); + SCR_CaptureVideo_RIFF_Write32(0); // dwReserved + + for(i = 0; i < cls.capturevideo.riffindexbuffer.cursize; i += 16) + if(!memcmp(cls.capturevideo.riffindexbuffer.data + i, dwChunkId, 4)) + { + unsigned int *p = (unsigned int *) (cls.capturevideo.riffindexbuffer.data + i); + unsigned int flags = p[1]; + unsigned int rpos = p[2]; + unsigned int size = p[3]; + size &= ~0x80000000; + if(!(flags & 0x10)) // no keyframe? + size |= 0x80000000; + SCR_CaptureVideo_RIFF_Write32(rpos + 8); + SCR_CaptureVideo_RIFF_Write32(size); + } + 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); + pos = SCR_CaptureVideo_RIFF_GetPosition(); + SCR_CaptureVideo_RIFF_Flush(); + + FS_Seek(cls.capturevideo.videofile, masteridx_start + 16 * *masteridx_count, SEEK_SET); + SCR_CaptureVideo_RIFF_Write32(ix & (fs_offset_t) 0xFFFFFFFFu); + SCR_CaptureVideo_RIFF_Write32(((fs_offset_t) ix) >> 32); + SCR_CaptureVideo_RIFF_Write32(pos - ix); + SCR_CaptureVideo_RIFF_Write32(nMatching); + SCR_CaptureVideo_RIFF_Flush(); + + FS_Seek(cls.capturevideo.videofile, masteridx_counter, SEEK_SET); + SCR_CaptureVideo_RIFF_Write32(++*masteridx_count); + SCR_CaptureVideo_RIFF_Flush(); + + FS_Seek(cls.capturevideo.videofile, 0, SEEK_END); +} + +static void SCR_CaptureVideo_RIFF_Finish(qboolean final) +{ + // close the "movi" list SCR_CaptureVideo_RIFF_Pop(); + if(cls.capturevideo.videofile_ix_master_video_inuse_offset) + SCR_CaptureVideo_RIFF_MakeIxChunk("ix00", "00dc", cls.capturevideo.videofile_ix_master_video_inuse_offset, &cls.capturevideo.videofile_ix_master_video_inuse, cls.capturevideo.videofile_ix_master_video_start_offset); + if(cls.capturevideo.videofile_ix_master_audio_inuse_offset) + SCR_CaptureVideo_RIFF_MakeIxChunk("ix01", "01wb", cls.capturevideo.videofile_ix_master_audio_inuse_offset, &cls.capturevideo.videofile_ix_master_audio_inuse, cls.capturevideo.videofile_ix_master_audio_start_offset); + // write the idx1 chunk that we've been building while saving the frames (for old style players) + 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) + if (8 + cursize + framesize + cls.capturevideo.riffindexbuffer.cursize + 8 + cls.capturevideo.riffindexbuffer.cursize + 64 > 1<<30) // note that the Ix buffer takes less space... I just don't dare to / 2 here now... sorry, maybe later { - 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"); + cls.capturevideo.videofile_ix_movistart = cls.capturevideo.riffstackstartoffset[1]; + } +} + +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 = realtime; cls.capturevideo.framerate = bound(1, cl_capturevideo_fps.value, 1000); @@ -992,7 +1123,8 @@ void SCR_CaptureVideo_BeginVideo(void) 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 * 4); + cls.capturevideo.outbuffer = (unsigned char *)Mem_Alloc(tempmempool, width * height * (4+4) + 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); @@ -1052,7 +1184,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) @@ -1076,13 +1208,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 @@ -1106,6 +1236,41 @@ 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(); + // master index + SCR_CaptureVideo_RIFF_Push("indx", NULL); + SCR_CaptureVideo_RIFF_Write16(4); // wLongsPerEntry + SCR_CaptureVideo_RIFF_Write16(0); // bIndexSubType=0, bIndexType=0 + cls.capturevideo.videofile_ix_master_video_inuse_offset = SCR_CaptureVideo_RIFF_GetPosition(); + SCR_CaptureVideo_RIFF_Write32(0); // nEntriesInUse + SCR_CaptureVideo_RIFF_WriteFourCC("00dc"); // dwChunkId + SCR_CaptureVideo_RIFF_Write32(0); // dwReserved1 + SCR_CaptureVideo_RIFF_Write32(0); // dwReserved2 + SCR_CaptureVideo_RIFF_Write32(0); // dwReserved3 + cls.capturevideo.videofile_ix_master_video_start_offset = SCR_CaptureVideo_RIFF_GetPosition(); + for(i = 0; i < AVI_MASTER_INDEX_SIZE * 4; ++i) + SCR_CaptureVideo_RIFF_Write32(0); // fill up later + 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((int)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) { @@ -1140,8 +1305,33 @@ Cr = R * .500 + G * -.419 + B * -.0813 + 128.; SCR_CaptureVideo_RIFF_Write16(16); // bits per sample SCR_CaptureVideo_RIFF_Write16(0); // size SCR_CaptureVideo_RIFF_Pop(); + // master index + SCR_CaptureVideo_RIFF_Push("indx", NULL); + SCR_CaptureVideo_RIFF_Write16(4); // wLongsPerEntry + SCR_CaptureVideo_RIFF_Write16(0); // bIndexSubType=0, bIndexType=0 + cls.capturevideo.videofile_ix_master_audio_inuse_offset = SCR_CaptureVideo_RIFF_GetPosition(); + SCR_CaptureVideo_RIFF_Write32(0); // nEntriesInUse + SCR_CaptureVideo_RIFF_WriteFourCC("01wb"); // dwChunkId + SCR_CaptureVideo_RIFF_Write32(0); // dwReserved1 + SCR_CaptureVideo_RIFF_Write32(0); // dwReserved2 + SCR_CaptureVideo_RIFF_Write32(0); // dwReserved3 + cls.capturevideo.videofile_ix_master_audio_start_offset = SCR_CaptureVideo_RIFF_GetPosition(); + for(i = 0; i < AVI_MASTER_INDEX_SIZE * 4; ++i) + SCR_CaptureVideo_RIFF_Write32(0); // fill up later + SCR_CaptureVideo_RIFF_Pop(); SCR_CaptureVideo_RIFF_Pop(); } + + cls.capturevideo.videofile_ix_master_audio_inuse = cls.capturevideo.videofile_ix_master_video_inuse = 0; + + // 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 @@ -1165,6 +1355,7 @@ Cr = R * .500 + G * -.419 + B * -.0813 + 128.; SCR_CaptureVideo_RIFF_Pop(); // begin the actual video section now SCR_CaptureVideo_RIFF_Push("LIST", "movi"); + cls.capturevideo.videofile_ix_movistart = cls.capturevideo.riffstackstartoffset[1]; // we're done with the headers now... SCR_CaptureVideo_RIFF_Flush(); if (cls.capturevideo.riffstacklevel != 2) @@ -1191,8 +1382,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(); @@ -1213,10 +1405,16 @@ void SCR_CaptureVideo_EndVideo(void) cls.capturevideo.videofile = NULL; } - if (cls.capturevideo.buffer) + if (cls.capturevideo.screenbuffer) + { + Mem_Free (cls.capturevideo.screenbuffer); + cls.capturevideo.screenbuffer = NULL; + } + + if (cls.capturevideo.outbuffer) { - Mem_Free (cls.capturevideo.buffer); - cls.capturevideo.buffer = NULL; + Mem_Free (cls.capturevideo.outbuffer); + cls.capturevideo.outbuffer = NULL; } if (cls.capturevideo.riffindexbuffer.data) @@ -1228,52 +1426,93 @@ void SCR_CaptureVideo_EndVideo(void) memset(&cls.capturevideo, 0, sizeof(cls.capturevideo)); } -// converts from RGB24 to I420 colorspace (identical to YV12 except chroma plane order is reversed), this colorspace is handled by the Intel(r) 4:2:0 codec on Windows -void SCR_CaptureVideo_ConvertFrame_RGB_to_I420_flip(int width, int height, unsigned char *instart, unsigned char *outstart) +// converts from BGRA32 to I420 colorspace (identical to YV12 except chroma plane order is reversed), this colorspace is handled by the Intel(r) 4:2:0 codec on Windows +void SCR_CaptureVideo_ConvertFrame_BGRA_to_I420_flip(int width, int height, unsigned char *instart, unsigned char *outstart) { int x, y; + int blockr, blockg, blockb; int outoffset = (width/2)*(height/2); unsigned char *b, *out; // 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 = instart + (height-1-y)*width*3, out = outstart + y*width, x = 0;x < width;x++, b += 3, out++) - *out = cls.capturevideo.yuvnormalizetable[0][cls.capturevideo.rgbtoyuvscaletable[0][0][b[0]] + cls.capturevideo.rgbtoyuvscaletable[0][1][b[1]] + cls.capturevideo.rgbtoyuvscaletable[0][2][b[2]]]; + for (b = instart + (height-1-y)*width*4, out = outstart + y*width, x = 0;x < width;x++, b += 4, out++) + { + blockr = b[2]; + blockg = b[1]; + blockb = b[0]; + *out = cls.capturevideo.yuvnormalizetable[0][cls.capturevideo.rgbtoyuvscaletable[0][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[0][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[0][2][blockb]]; + } if ((y & 1) == 0) { // 2x2 Cr and Cb planes -#if 0 - // low quality, no averaging - for (b = instart + (height-2-y)*width*3, out = outstart + width*height + (y/2)*(width/2), x = 0;x < width/2;x++, b += 6, out++) + int inpitch = width*4; + for (b = instart + (height-2-y)*width*4, out = outstart + width*height + (y/2)*(width/2), x = 0;x < width/2;x++, b += 8, out++) { - // Cr - out[0 ] = cls.capturevideo.yuvnormalizetable[1][cls.capturevideo.rgbtoyuvscaletable[1][0][b[0]] + cls.capturevideo.rgbtoyuvscaletable[1][1][b[1]] + cls.capturevideo.rgbtoyuvscaletable[1][2][b[2]] + 128]; - // Cb - out[outoffset] = cls.capturevideo.yuvnormalizetable[2][cls.capturevideo.rgbtoyuvscaletable[2][0][b[0]] + cls.capturevideo.rgbtoyuvscaletable[2][1][b[1]] + cls.capturevideo.rgbtoyuvscaletable[2][2][b[2]] + 128]; - } -#else - // high quality, averaging - int inpitch = width*3; - for (b = instart + (height-2-y)*width*3, out = outstart + width*height + (y/2)*(width/2), x = 0;x < width/2;x++, b += 6, out++) - { - 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; + blockr = (b[2] + b[6] + b[inpitch+2] + b[inpitch+6]) >> 2; + blockg = (b[1] + b[5] + b[inpitch+1] + b[inpitch+5]) >> 2; + blockb = (b[0] + b[4] + b[inpitch+0] + b[inpitch+4]) >> 2; // Cr out[0 ] = cls.capturevideo.yuvnormalizetable[1][cls.capturevideo.rgbtoyuvscaletable[1][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[1][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[1][2][blockb] + 128]; // Cb out[outoffset] = cls.capturevideo.yuvnormalizetable[2][cls.capturevideo.rgbtoyuvscaletable[2][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[2][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[2][2][blockb] + 128]; } -#endif + } + } +} + +static void SCR_ScaleDownBGRA(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, 4 * 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, alpha = 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[4*(xx + inw * yy)+0]; + g += a * in[4*(xx + inw * yy)+1]; + b += a * in[4*(xx + inw * yy)+2]; + alpha += a * in[4*(xx + inw * yy)+3]; + } + } + + out[4*(x + outw * y)+0] = r * area; + out[4*(x + outw * y)+1] = g * area; + out[4*(x + outw * y)+2] = b * area; + out[4*(x + outw * y)+3] = alpha * 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); @@ -1285,10 +1524,11 @@ 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; - SCR_CaptureVideo_ConvertFrame_RGB_to_I420_flip(width, height, in, out); + qglReadPixels (x, y, vid.width, vid.height, GL_BGRA, GL_UNSIGNED_BYTE, cls.capturevideo.screenbuffer);CHECKGLERROR + SCR_ScaleDownBGRA (cls.capturevideo.screenbuffer, vid.width, vid.height, cls.capturevideo.outbuffer, width, height); + in = cls.capturevideo.outbuffer; + out = cls.capturevideo.outbuffer + width*height*4; + SCR_CaptureVideo_ConvertFrame_BGRA_to_I420_flip(width, height, in, out); x = width*height+(width/2)*(height/2)*2; SCR_CaptureVideo_RIFF_OverflowCheck(8 + x); for (;cls.capturevideo.frame < newframenum;cls.capturevideo.frame++) @@ -1434,6 +1674,7 @@ static void R_Envmap_f (void) r_view.width = size; r_view.height = size; r_view.depth = 1; + r_view.useperspective = true; r_view.frustum_x = tan(90 * M_PI / 360.0); r_view.frustum_y = tan(90 * M_PI / 360.0); @@ -1462,36 +1703,22 @@ static void R_Envmap_f (void) //============================================================================= -// LordHavoc: SHOWLMP stuff -#define SHOWLMP_MAXLABELS 256 -typedef struct showlmp_s -{ - qboolean isactive; - float x; - float y; - char label[32]; - char pic[128]; -} -showlmp_t; - -showlmp_t showlmp[SHOWLMP_MAXLABELS]; - void SHOWLMP_decodehide(void) { int i; char *lmplabel; lmplabel = MSG_ReadString(); - for (i = 0;i < SHOWLMP_MAXLABELS;i++) - if (showlmp[i].isactive && strcmp(showlmp[i].label, lmplabel) == 0) + for (i = 0;i < cl.num_showlmps;i++) + if (cl.showlmps[i].isactive && strcmp(cl.showlmps[i].label, lmplabel) == 0) { - showlmp[i].isactive = false; + cl.showlmps[i].isactive = false; return; } } void SHOWLMP_decodeshow(void) { - int i, k; + int k; char lmplabel[256], picname[256]; float x, y; strlcpy (lmplabel,MSG_ReadString(), sizeof (lmplabel)); @@ -1506,41 +1733,37 @@ void SHOWLMP_decodeshow(void) x = MSG_ReadShort(); y = MSG_ReadShort(); } - k = -1; - for (i = 0;i < SHOWLMP_MAXLABELS;i++) - if (showlmp[i].isactive) - { - if (strcmp(showlmp[i].label, lmplabel) == 0) - { - k = i; - break; // drop out to replace it - } - } - else if (k < 0) // find first empty one to replace - k = i; - if (k < 0) - return; // none found to replace - // change existing one - showlmp[k].isactive = true; - strlcpy (showlmp[k].label, lmplabel, sizeof (showlmp[k].label)); - strlcpy (showlmp[k].pic, picname, sizeof (showlmp[k].pic)); - showlmp[k].x = x; - showlmp[k].y = y; + if (!cl.showlmps || cl.num_showlmps >= cl.max_showlmps) + { + showlmp_t *oldshowlmps = cl.showlmps; + cl.max_showlmps += 16; + cl.showlmps = Mem_Alloc(cls.levelmempool, cl.max_showlmps * sizeof(showlmp_t)); + if (cl.num_showlmps) + memcpy(cl.showlmps, oldshowlmps, cl.num_showlmps * sizeof(showlmp_t)); + if (oldshowlmps) + Mem_Free(oldshowlmps); + } + for (k = 0;k < cl.max_showlmps;k++) + if (cl.showlmps[k].isactive && !strcmp(cl.showlmps[k].label, lmplabel)) + break; + if (k == cl.max_showlmps) + for (k = 0;k < cl.max_showlmps;k++) + if (!cl.showlmps[k].isactive) + break; + cl.showlmps[k].isactive = true; + strlcpy (cl.showlmps[k].label, lmplabel, sizeof (cl.showlmps[k].label)); + strlcpy (cl.showlmps[k].pic, picname, sizeof (cl.showlmps[k].pic)); + cl.showlmps[k].x = x; + cl.showlmps[k].y = y; + cl.num_showlmps = max(cl.num_showlmps, k + 1); } void SHOWLMP_drawall(void) { int i; - for (i = 0;i < SHOWLMP_MAXLABELS;i++) - if (showlmp[i].isactive) - DrawQ_Pic(showlmp[i].x, showlmp[i].y, Draw_CachePic(showlmp[i].pic, true), 0, 0, 1, 1, 1, 1, 0); -} - -void SHOWLMP_clear(void) -{ - int i; - for (i = 0;i < SHOWLMP_MAXLABELS;i++) - showlmp[i].isactive = false; + for (i = 0;i < cl.num_showlmps;i++) + if (cl.showlmps[i].isactive) + DrawQ_Pic(cl.showlmps[i].x, cl.showlmps[i].y, Draw_CachePic(cl.showlmps[i].pic, true), 0, 0, 1, 1, 1, 1, 0); } /* @@ -1560,7 +1783,7 @@ qboolean SCR_ScreenShot(char *filename, unsigned char *buffer1, unsigned char *b return false; CHECKGLERROR - qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, buffer1);CHECKGLERROR + qglReadPixels (x, y, width, height, jpeg ? GL_RGB : GL_BGR, GL_UNSIGNED_BYTE, buffer1);CHECKGLERROR if (scr_screenshot_gammaboost.value != 1 && gammacorrect) { @@ -1578,7 +1801,7 @@ qboolean SCR_ScreenShot(char *filename, unsigned char *buffer1, unsigned char *b if (jpeg) ret = JPEG_SaveImage_preflipped (filename, width, height, buffer2); else - ret = Image_WriteTGARGB_preflipped (filename, width, height, buffer2, buffer3); + ret = Image_WriteTGABGR_preflipped (filename, width, height, buffer2, buffer3); return ret; } @@ -1630,10 +1853,14 @@ void SCR_DrawScreen (void) R_Mesh_Start(); if (r_timereport_active) - R_TimeReport("setup"); + R_TimeReport("screensetup"); R_UpdateVariables(); + // Quake uses clockwise winding, so these are swapped + r_view.cullface_front = GL_BACK; + r_view.cullface_back = GL_FRONT; + if (cls.signon == SIGNONS) { float size; @@ -1668,6 +1895,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.useperspective = true; 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; @@ -1688,6 +1916,7 @@ void SCR_DrawScreen (void) r_view.y = 0; r_view.z = 0; + r_view.useperspective = true; 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; @@ -1707,6 +1936,7 @@ void SCR_DrawScreen (void) r_view.x = 0; r_view.y = 0; r_view.z = 0; + r_view.useperspective = false; } // draw 2D stuff @@ -1781,6 +2011,7 @@ void SCR_UpdateLoadingScreen (qboolean clear) 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, 0, 0); R_Mesh_ColorPointer(NULL, 0, 0); @@ -1886,7 +2117,8 @@ void CL_UpdateScreen(void) qglDepthMask(1);CHECKGLERROR qglColorMask(1,1,1,1);CHECKGLERROR qglClearColor(0,0,0,0);CHECKGLERROR - qglClear(GL_COLOR_BUFFER_BIT);CHECKGLERROR + R_ClearScreen(); + r_view.clear = false; if(scr_stipple.integer) { @@ -1911,7 +2143,7 @@ void CL_UpdateScreen(void) qglDisable(GL_POLYGON_STIPPLE); if (r_timereport_active) - R_TimeReport("clear"); + R_TimeReport("screenclear"); qglDrawBuffer(GL_BACK); @@ -1969,5 +2201,4 @@ void CL_UpdateScreen(void) void CL_Screen_NewMap(void) { - SHOWLMP_clear(); }