5 #include "cl_collision.h"
7 cvar_t scr_viewsize = {CVAR_SAVE, "viewsize","100"};
8 cvar_t scr_fov = {CVAR_SAVE, "fov","90"}; // 1 - 170
9 cvar_t scr_conspeed = {CVAR_SAVE, "scr_conspeed","900"}; // LordHavoc: quake used 300
10 cvar_t scr_conalpha = {CVAR_SAVE, "scr_conalpha", "1"};
11 cvar_t scr_conbrightness = {CVAR_SAVE, "scr_conbrightness", "0.2"};
12 cvar_t scr_conforcewhiledisconnected = {CVAR_SAVE, "scr_conforcewhiledisconnected", "1"};
13 cvar_t scr_centertime = {0, "scr_centertime","2"};
14 cvar_t scr_showram = {CVAR_SAVE, "showram","1"};
15 cvar_t scr_showturtle = {CVAR_SAVE, "showturtle","0"};
16 cvar_t scr_showpause = {CVAR_SAVE, "showpause","1"};
17 cvar_t scr_printspeed = {0, "scr_printspeed","8"};
18 cvar_t vid_conwidth = {CVAR_SAVE, "vid_conwidth", "640"};
19 cvar_t vid_conheight = {CVAR_SAVE, "vid_conheight", "480"};
20 cvar_t vid_pixelaspect = {CVAR_SAVE, "vid_pixelaspect", "1"};
21 cvar_t scr_screenshot_jpeg = {CVAR_SAVE, "scr_screenshot_jpeg","0"};
22 cvar_t scr_screenshot_jpeg_quality = {CVAR_SAVE, "scr_screenshot_jpeg_quality","0.9"};
23 cvar_t scr_screenshot_gamma = {CVAR_SAVE, "scr_screenshot_gamma","2.2"};
24 cvar_t scr_screenshot_name = {0, "scr_screenshot_name","dp"};
25 cvar_t cl_capturevideo = {0, "cl_capturevideo", "0"};
26 cvar_t cl_capturevideo_sound = {0, "cl_capturevideo_sound", "0"};
27 cvar_t cl_capturevideo_fps = {0, "cl_capturevideo_fps", "30"};
28 cvar_t cl_capturevideo_rawrgb = {0, "cl_capturevideo_rawrgb", "0"};
29 cvar_t cl_capturevideo_rawyv12 = {0, "cl_capturevideo_rawyv12", "0"};
30 cvar_t r_textshadow = {0, "r_textshadow", "0"};
31 cvar_t r_letterbox = {0, "r_letterbox", "0"};
33 int jpeg_supported = false;
35 qboolean scr_initialized; // ready to draw
37 float scr_con_current;
39 extern int con_vislines;
41 void DrawCrosshair(int num);
42 static void SCR_ScreenShot_f (void);
43 static void R_Envmap_f (void);
46 void R_ClearScreen(void);
49 static vec4_t _draw_colors[] =
52 // LordHavoc: why on earth is cyan before magenta in Quake3?
53 // LordHavoc: note: Doom3 uses white for [0] and [7]
54 {0.0, 0.0, 0.0, 1.0}, // black
55 {1.0, 0.0, 0.0, 1.0}, // red
56 {0.0, 1.0, 0.0, 1.0}, // green
57 {1.0, 1.0, 0.0, 1.0}, // yellow
58 {0.0, 0.0, 1.0, 1.0}, // blue
59 {0.0, 1.0, 1.0, 1.0}, // cyan
60 {1.0, 0.0, 1.0, 1.0}, // magenta
61 {1.0, 1.0, 1.0, 1.0} // white
62 // Black's color table
63 //{1.0, 1.0, 1.0, 1.0},
64 //{1.0, 0.0, 0.0, 1.0},
65 //{0.0, 1.0, 0.0, 1.0},
66 //{0.0, 0.0, 1.0, 1.0},
67 //{1.0, 1.0, 0.0, 1.0},
68 //{0.0, 1.0, 1.0, 1.0},
69 //{1.0, 0.0, 1.0, 1.0},
70 //{0.1, 0.1, 0.1, 1.0}
73 #define _draw_colors_count (sizeof(_draw_colors) / sizeof(vec4_t))
74 #define _draw_color_tag '^'
75 #define _draw_color_default 7
77 // color is read and changed in the end
78 void DrawQ_ColoredString( float x, float y, const char *text, int maxlen, float scalex, float scaley, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor )
83 const char *start, *current;
85 if( !outcolor || *outcolor == -1 ) {
86 colorindex = _draw_color_default;
88 colorindex = *outcolor;
90 color = _draw_colors[colorindex];
95 len = min( maxlen, (signed) strlen( text ) );
97 start = current = text;
99 // check for color control char
100 if( *current == _draw_color_tag ) {
107 // display the tag char?
108 if( *current == _draw_color_tag ) {
109 // only display one of the two
114 } else if( '0' <= *current && *current <= '9' ) {
117 colorindex = colorindex * 10 + (*current - '0');
118 // only read as long as it makes a valid index
119 if( colorindex >= _draw_colors_count ) {
120 // undo the last operation
126 } while( len > 0 && '0' <= *current && *current <= '9' );
128 color = _draw_colors[colorindex];
129 // we jump over the color tag
133 // go on and read normal text in until the next control char
134 while( len > 0 && *current != _draw_color_tag ) {
139 if( start != current ) {
141 DrawQ_String( x, y, start, current - start, scalex, scaley, basered * color[0], basegreen * color[1], baseblue * color[2], basealpha * color[3], flags );
142 // update x to be at the new start position
143 x += (current - start) * scalex;
144 // set start accordingly
149 // return the last colorindex
151 *outcolor = colorindex;
156 ===============================================================================
160 ===============================================================================
163 char scr_centerstring[1024];
164 float scr_centertime_start; // for slow victory printing
165 float scr_centertime_off;
166 int scr_center_lines;
168 int scr_erase_center;
174 Called for important messages that should stay in the center of the screen
178 void SCR_CenterPrint(char *str)
180 strlcpy (scr_centerstring, str, sizeof (scr_centerstring));
181 scr_centertime_off = scr_centertime.value;
182 scr_centertime_start = cl.time;
184 // count the number of lines for centering
185 scr_center_lines = 1;
195 void SCR_DrawCenterString (void)
202 // the finale prints the characters one at a time
204 remaining = scr_printspeed.value * (cl.time - scr_centertime_start);
208 scr_erase_center = 0;
209 start = scr_centerstring;
211 if (scr_center_lines <= 4)
212 y = vid_conheight.integer*0.35;
218 // scan the width of the line
219 for (l=0 ; l<vid_conwidth.integer/8 ; l++)
220 if (start[l] == '\n' || !start[l])
222 x = (vid_conwidth.integer - l*8)/2;
227 DrawQ_String(x, y, start, l, 8, 8, 1, 1, 1, 1, 0);
235 while (*start && *start != '\n')
240 start++; // skip the \n
244 void SCR_CheckDrawCenterString (void)
246 if (scr_center_lines > scr_erase_lines)
247 scr_erase_lines = scr_center_lines;
249 scr_centertime_off -= host_frametime;
251 // don't draw if this is a normal stats-screen intermission,
252 // only if it is not an intermission, or a finale intermission
253 if (cl.intermission == 1)
255 if (scr_centertime_off <= 0 && !cl.intermission)
257 if (key_dest != key_game)
260 SCR_DrawCenterString ();
268 void SCR_DrawTurtle (void)
272 if (cls.state != ca_connected)
275 if (!scr_showturtle.integer)
278 if (host_frametime < 0.1)
288 DrawQ_Pic (0, 0, "gfx/turtle", 0, 0, 1, 1, 1, 1, 0);
296 void SCR_DrawNet (void)
298 if (cls.state != ca_connected)
300 if (realtime - cl.last_received_message < 0.3)
302 if (cls.demoplayback)
305 DrawQ_Pic (64, 0, "gfx/net", 0, 0, 1, 1, 1, 1, 0);
313 void SCR_DrawPause (void)
317 if (cls.state != ca_connected)
320 if (!scr_showpause.integer) // turn off for screenshots
326 pic = Draw_CachePic ("gfx/pause", true);
327 DrawQ_Pic ((vid_conwidth.integer - pic->width)/2, (vid_conheight.integer - pic->height)/2, "gfx/pause", 0, 0, 1, 1, 1, 1, 0);
334 //=============================================================================
339 SCR_SetUpToDrawConsole
342 void SCR_SetUpToDrawConsole (void)
344 // lines of console to display
349 if (key_dest == key_game && cls.signon != SIGNONS && scr_conforcewhiledisconnected.integer)
350 key_consoleactive |= KEY_CONSOLEACTIVE_FORCED;
352 key_consoleactive &= ~KEY_CONSOLEACTIVE_FORCED;
354 // decide on the height of the console
355 if (key_consoleactive & KEY_CONSOLEACTIVE_USER)
356 conlines = vid_conheight.integer/2; // half screen
358 conlines = 0; // none visible
360 if (scr_conspeed.value)
362 if (scr_con_current > conlines)
364 scr_con_current -= scr_conspeed.value*host_realframetime;
365 if (scr_con_current < conlines)
366 scr_con_current = conlines;
369 else if (scr_con_current < conlines)
371 scr_con_current += scr_conspeed.value*host_realframetime;
372 if (scr_con_current > conlines)
373 scr_con_current = conlines;
377 scr_con_current = conlines;
385 void SCR_DrawConsole (void)
387 if (key_consoleactive & KEY_CONSOLEACTIVE_FORCED)
390 Con_DrawConsole (vid_conheight.integer);
392 else if (scr_con_current)
393 Con_DrawConsole (scr_con_current);
397 if (key_dest == key_game || key_dest == key_message)
398 Con_DrawNotify (); // only draw notify in game
404 SCR_BeginLoadingPlaque
408 void SCR_BeginLoadingPlaque (void)
412 SCR_UpdateLoadingScreen();
415 //=============================================================================
417 char r_speeds_string[1024];
418 int speedstringcount, r_timereport_active;
419 double r_timereport_temp = 0, r_timereport_current = 0, r_timereport_start = 0;
421 void R_TimeReport(char *desc)
427 if (!r_timereport_active || r_showtrispass)
430 r_timereport_temp = r_timereport_current;
431 r_timereport_current = Sys_DoubleTime();
432 t = (int) ((r_timereport_current - r_timereport_temp) * 1000000.0);
434 dpsnprintf(tempbuf, sizeof(tempbuf), "%8i %s", t, desc);
435 length = strlen(tempbuf);
437 tempbuf[length++] = ' ';
439 if (speedstringcount + length > (vid_conwidth.integer / 8))
441 strlcat(r_speeds_string, "\n", sizeof(r_speeds_string));
442 speedstringcount = 0;
444 // skip the space at the beginning if it's the first on the line
445 if (speedstringcount == 0)
447 strlcat(r_speeds_string, tempbuf + 1, sizeof(r_speeds_string));
448 speedstringcount = length - 1;
452 strlcat(r_speeds_string, tempbuf, sizeof(r_speeds_string));
453 speedstringcount += length;
457 void R_TimeReport_Start(void)
459 r_timereport_active = r_speeds.integer && cls.signon == SIGNONS && cls.state == ca_connected;
460 r_speeds_string[0] = 0;
461 if (r_timereport_active)
463 speedstringcount = 0;
464 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]);
465 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);
466 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);
467 sprintf(r_speeds_string + strlen(r_speeds_string), "%6i modeltris%6i meshs%6i meshtris\n", c_alias_polys, c_meshs, c_meshelements / 3);
468 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);
469 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);
470 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);
471 sprintf(r_speeds_string + strlen(r_speeds_string), "precomputed: %6i shadowmeshes%6i shadowtris\n", c_rtcached_shadowmeshes, c_rtcached_shadowtris);
488 c_rt_shadowmeshes = 0;
490 c_rt_lightmeshes = 0;
492 c_rtcached_shadowmeshes = 0;
493 c_rtcached_shadowtris = 0;
496 c_bloomcopypixels = 0;
498 c_bloomdrawpixels = 0;
500 r_timereport_start = Sys_DoubleTime();
504 void R_TimeReport_End(void)
506 r_timereport_current = r_timereport_start;
507 R_TimeReport("total");
509 if (r_timereport_active)
513 for (i = 0;r_speeds_string[i];i++)
514 if (r_speeds_string[i] == '\n')
516 y = vid_conheight.integer - sb_lines - lines * 8;
518 DrawQ_Fill(0, y, vid_conwidth.integer, lines * 8, 0, 0, 0, 0.5, 0);
519 while (r_speeds_string[i])
522 while (r_speeds_string[i] && r_speeds_string[i] != '\n')
525 DrawQ_String(0, y, r_speeds_string + j, i - j, 8, 8, 1, 1, 1, 1, 0);
526 if (r_speeds_string[i] == '\n')
540 void SCR_SizeUp_f (void)
542 Cvar_SetValue ("viewsize",scr_viewsize.value+10);
553 void SCR_SizeDown_f (void)
555 Cvar_SetValue ("viewsize",scr_viewsize.value-10);
558 void CL_Screen_Init(void)
560 Cvar_RegisterVariable (&scr_fov);
561 Cvar_RegisterVariable (&scr_viewsize);
562 Cvar_RegisterVariable (&scr_conspeed);
563 Cvar_RegisterVariable (&scr_conalpha);
564 Cvar_RegisterVariable (&scr_conbrightness);
565 Cvar_RegisterVariable (&scr_conforcewhiledisconnected);
566 Cvar_RegisterVariable (&scr_showram);
567 Cvar_RegisterVariable (&scr_showturtle);
568 Cvar_RegisterVariable (&scr_showpause);
569 Cvar_RegisterVariable (&scr_centertime);
570 Cvar_RegisterVariable (&scr_printspeed);
571 Cvar_RegisterVariable (&vid_conwidth);
572 Cvar_RegisterVariable (&vid_conheight);
573 Cvar_RegisterVariable (&vid_pixelaspect);
574 Cvar_RegisterVariable (&scr_screenshot_jpeg);
575 Cvar_RegisterVariable (&scr_screenshot_jpeg_quality);
576 Cvar_RegisterVariable (&scr_screenshot_gamma);
577 Cvar_RegisterVariable (&cl_capturevideo);
578 Cvar_RegisterVariable (&cl_capturevideo_sound);
579 Cvar_RegisterVariable (&cl_capturevideo_fps);
580 Cvar_RegisterVariable (&cl_capturevideo_rawrgb);
581 Cvar_RegisterVariable (&cl_capturevideo_rawyv12);
582 Cvar_RegisterVariable (&r_textshadow);
583 Cvar_RegisterVariable (&r_letterbox);
585 Cmd_AddCommand ("sizeup",SCR_SizeUp_f);
586 Cmd_AddCommand ("sizedown",SCR_SizeDown_f);
587 Cmd_AddCommand ("screenshot",SCR_ScreenShot_f);
588 Cmd_AddCommand ("envmap", R_Envmap_f);
590 scr_initialized = true;
593 void DrawQ_Clear(void)
595 r_refdef.drawqueuesize = 0;
598 static int picelements[6] = {0, 1, 2, 0, 2, 3};
599 void DrawQ_Pic(float x, float y, const char *picname, float width, float height, float red, float green, float blue, float alpha, int flags)
601 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);
604 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)
609 if (alpha < (1.0f / 255.0f))
612 len = strlen(string);
614 for (len = 0;len < maxlen && string[len];len++);
615 for (;len > 0 && string[0] == ' ';string++, x += scalex, len--);
616 for (;len > 0 && string[len - 1] == ' ';len--);
619 if (x >= vid_conwidth.integer || y >= vid_conheight.integer || x < (-scalex * len) || y < (-scaley))
621 size = sizeof(*dq) + ((len + 1 + 3) & ~3);
622 if (r_refdef.drawqueuesize + size > r_refdef.maxdrawqueuesize)
624 red = bound(0, red, 1);
625 green = bound(0, green, 1);
626 blue = bound(0, blue, 1);
627 alpha = bound(0, alpha, 1);
628 dq = (void *)(r_refdef.drawqueue + r_refdef.drawqueuesize);
630 dq->command = DRAWQUEUE_STRING;
632 dq->color = ((unsigned int) (red * 255.0f) << 24) | ((unsigned int) (green * 255.0f) << 16) | ((unsigned int) (blue * 255.0f) << 8) | ((unsigned int) (alpha * 255.0f));
637 out = (char *)(dq + 1);
638 memcpy(out, string, len);
640 r_refdef.drawqueuesize += dq->size;
643 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)
645 if (r_textshadow.integer)
646 DrawQ_String_Real(x+scalex*0.25,y+scaley*0.25,string,maxlen,scalex,scaley,0,0,0,alpha*0.8,flags);
648 DrawQ_String_Real(x,y,string,maxlen,scalex,scaley,red,green,blue,alpha,flags);
653 void DrawQ_Fill (float x, float y, float w, float h, float red, float green, float blue, float alpha, int flags)
655 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);
658 void DrawQ_SuperPic(float x, float y, const char *picname, float width, float height, float s1, float t1, float r1, float g1, float b1, float a1, float s2, float t2, float r2, float g2, float b2, float a2, float s3, float t3, float r3, float g3, float b3, float a3, float s4, float t4, float r4, float g4, float b4, float a4, int flags)
662 drawqueuemesh_t mesh;
663 memset(&mesh, 0, sizeof(mesh));
664 if (picname && picname[0])
666 pic = Draw_CachePic(picname, false);
670 height = pic->height;
671 mesh.texture = pic->tex;
673 mesh.num_triangles = 2;
674 mesh.num_vertices = 4;
675 mesh.data_element3i = picelements;
676 mesh.data_vertex3f = floats;
677 mesh.data_texcoord2f = floats + 12;
678 mesh.data_color4f = floats + 20;
679 memset(floats, 0, sizeof(floats));
680 mesh.data_vertex3f[0] = mesh.data_vertex3f[9] = x;
681 mesh.data_vertex3f[1] = mesh.data_vertex3f[4] = y;
682 mesh.data_vertex3f[3] = mesh.data_vertex3f[6] = x + width;
683 mesh.data_vertex3f[7] = mesh.data_vertex3f[10] = y + height;
684 mesh.data_texcoord2f[0] = s1;mesh.data_texcoord2f[1] = t1;mesh.data_color4f[ 0] = r1;mesh.data_color4f[ 1] = g1;mesh.data_color4f[ 2] = b1;mesh.data_color4f[ 3] = a1;
685 mesh.data_texcoord2f[2] = s2;mesh.data_texcoord2f[3] = t2;mesh.data_color4f[ 4] = r2;mesh.data_color4f[ 5] = g2;mesh.data_color4f[ 6] = b2;mesh.data_color4f[ 7] = a2;
686 mesh.data_texcoord2f[4] = s4;mesh.data_texcoord2f[5] = t4;mesh.data_color4f[ 8] = r4;mesh.data_color4f[ 9] = g4;mesh.data_color4f[10] = b4;mesh.data_color4f[11] = a4;
687 mesh.data_texcoord2f[6] = s3;mesh.data_texcoord2f[7] = t3;mesh.data_color4f[12] = r3;mesh.data_color4f[13] = g3;mesh.data_color4f[14] = b3;mesh.data_color4f[15] = a3;
688 DrawQ_Mesh (&mesh, flags);
691 void DrawQ_Mesh (drawqueuemesh_t *mesh, int flags)
698 size += sizeof(drawqueuemesh_t);
699 size += sizeof(int[3]) * mesh->num_triangles;
700 size += sizeof(float[3]) * mesh->num_vertices;
701 size += sizeof(float[2]) * mesh->num_vertices;
702 size += sizeof(float[4]) * mesh->num_vertices;
703 if (r_refdef.drawqueuesize + size > r_refdef.maxdrawqueuesize)
705 dq = (void *)(r_refdef.drawqueue + r_refdef.drawqueuesize);
707 dq->command = DRAWQUEUE_MESH;
714 p = (void *)(dq + 1);
715 m = p;p = (qbyte*)p + sizeof(drawqueuemesh_t);
716 m->num_triangles = mesh->num_triangles;
717 m->num_vertices = mesh->num_vertices;
718 m->texture = mesh->texture;
719 m->data_element3i = p;memcpy(m->data_element3i , mesh->data_element3i , m->num_triangles * sizeof(int[3]));p = (qbyte*)p + m->num_triangles * sizeof(int[3]);
720 m->data_vertex3f = p;memcpy(m->data_vertex3f , mesh->data_vertex3f , m->num_vertices * sizeof(float[3]));p = (qbyte*)p + m->num_vertices * sizeof(float[3]);
721 m->data_texcoord2f = p;memcpy(m->data_texcoord2f, mesh->data_texcoord2f, m->num_vertices * sizeof(float[2]));p = (qbyte*)p + m->num_vertices * sizeof(float[2]);
722 m->data_color4f = p;memcpy(m->data_color4f , mesh->data_color4f , m->num_vertices * sizeof(float[4]));p = (qbyte*)p + m->num_vertices * sizeof(float[4]);
723 r_refdef.drawqueuesize += dq->size;
726 void DrawQ_SetClipArea(float x, float y, float width, float height)
729 if(r_refdef.drawqueuesize + (int)sizeof(*dq) > r_refdef.maxdrawqueuesize)
731 Con_DPrint("DrawQueue full !\n");
734 dq = (void*) (r_refdef.drawqueue + r_refdef.drawqueuesize);
735 dq->size = sizeof(*dq);
736 dq->command = DRAWQUEUE_SETCLIP;
744 r_refdef.drawqueuesize += dq->size;
747 void DrawQ_ResetClipArea(void)
750 if(r_refdef.drawqueuesize + (int)sizeof(*dq) > r_refdef.maxdrawqueuesize)
752 Con_DPrint("DrawQueue full !\n");
755 dq = (void*) (r_refdef.drawqueue + r_refdef.drawqueuesize);
756 dq->size = sizeof(*dq);
757 dq->command = DRAWQUEUE_RESETCLIP;
765 r_refdef.drawqueuesize += dq->size;
773 void SCR_ScreenShot_f (void)
775 static int shotnumber;
776 static char oldname[MAX_QPATH];
777 char base[MAX_QPATH];
778 char filename[MAX_QPATH];
782 qboolean jpeg = (scr_screenshot_jpeg.integer != 0);
784 sprintf (base, "screenshots/%s", scr_screenshot_name.string);
786 if (strcmp (oldname, scr_screenshot_name.string))
788 sprintf(oldname, "%s", scr_screenshot_name.string);
792 // find a file name to save it to
793 for (;shotnumber < 1000000;shotnumber++)
794 if (!FS_SysFileExists(va("%s/%s%06d.tga", fs_gamedir, base, shotnumber)) && !FS_SysFileExists(va("%s/%s%06d.jpg", fs_gamedir, base, shotnumber)))
796 if (shotnumber >= 1000000)
798 Con_Print("SCR_ScreenShot_f: Couldn't create the image file\n");
802 sprintf(filename, "%s%06d.%s", base, shotnumber, jpeg ? "jpg" : "tga");
804 buffer1 = Mem_Alloc(tempmempool, vid.width * vid.height * 3);
805 buffer2 = Mem_Alloc(tempmempool, vid.width * vid.height * 3);
806 buffer3 = Mem_Alloc(tempmempool, vid.width * vid.height * 3 + 18);
808 if (SCR_ScreenShot (filename, buffer1, buffer2, buffer3, 0, 0, vid.width, vid.height, false, false, false, jpeg, true))
809 Con_Printf("Wrote %s\n", filename);
811 Con_Printf("unable to write %s\n", filename);
820 typedef enum capturevideoformat_e
822 CAPTUREVIDEOFORMAT_TARGA,
823 CAPTUREVIDEOFORMAT_JPEG,
824 CAPTUREVIDEOFORMAT_RAWRGB,
825 CAPTUREVIDEOFORMAT_RAWYV12
827 capturevideoformat_t;
829 qboolean cl_capturevideo_active = false;
830 capturevideoformat_t cl_capturevideo_format;
831 static double cl_capturevideo_starttime = 0;
832 double cl_capturevideo_framerate = 0;
833 static int cl_capturevideo_soundrate = 0;
834 static int cl_capturevideo_frame = 0;
835 static qbyte *cl_capturevideo_buffer = NULL;
836 static qfile_t *cl_capturevideo_videofile = NULL;
837 qfile_t *cl_capturevideo_soundfile = NULL;
838 static short cl_capturevideo_rgbtoyuvscaletable[3][3][256];
839 static unsigned char cl_capturevideo_yuvnormalizetable[3][256];
840 //static unsigned char cl_capturevideo_rgbgammatable[3][256];
842 void SCR_CaptureVideo_BeginVideo(void)
847 if (cl_capturevideo_active)
849 // soundrate is figured out on the first SoundFrame
850 cl_capturevideo_active = true;
851 cl_capturevideo_starttime = Sys_DoubleTime();
852 cl_capturevideo_framerate = bound(1, cl_capturevideo_fps.value, 1000);
853 cl_capturevideo_soundrate = 0;
854 cl_capturevideo_frame = 0;
855 cl_capturevideo_buffer = Mem_Alloc(tempmempool, vid.width * vid.height * (3+3+3) + 18);
856 gamma = 1.0/scr_screenshot_gamma.value;
859 for (i = 0;i < 256;i++)
861 unsigned char j = (unsigned char)bound(0, 255*pow(i/255.0, gamma), 255);
862 cl_capturevideo_rgbgammatable[0][i] = j;
863 cl_capturevideo_rgbgammatable[1][i] = j;
864 cl_capturevideo_rgbgammatable[2][i] = j;
868 R = Y + 1.4075 * (Cr - 128);
869 G = Y + -0.3455 * (Cb - 128) + -0.7169 * (Cr - 128);
870 B = Y + 1.7790 * (Cb - 128);
871 Y = R * .299 + G * .587 + B * .114;
872 Cb = R * -.169 + G * -.332 + B * .500 + 128.;
873 Cr = R * .500 + G * -.419 + B * -.0813 + 128.;
875 for (i = 0;i < 256;i++)
877 g = 255*pow(i/255.0, gamma);
878 // Y weights from RGB
879 cl_capturevideo_rgbtoyuvscaletable[0][0][i] = (short)(g * 0.299);
880 cl_capturevideo_rgbtoyuvscaletable[0][1][i] = (short)(g * 0.587);
881 cl_capturevideo_rgbtoyuvscaletable[0][2][i] = (short)(g * 0.114);
882 // Cb weights from RGB
883 cl_capturevideo_rgbtoyuvscaletable[1][0][i] = (short)(g * -0.169);
884 cl_capturevideo_rgbtoyuvscaletable[1][1][i] = (short)(g * -0.332);
885 cl_capturevideo_rgbtoyuvscaletable[1][2][i] = (short)(g * 0.500);
886 // Cr weights from RGB
887 cl_capturevideo_rgbtoyuvscaletable[2][0][i] = (short)(g * 0.500);
888 cl_capturevideo_rgbtoyuvscaletable[2][1][i] = (short)(g * -0.419);
889 cl_capturevideo_rgbtoyuvscaletable[2][2][i] = (short)(g * -0.0813);
890 // range reduction of YCbCr to valid signal range
891 cl_capturevideo_yuvnormalizetable[0][i] = 16 + i * (236-16) / 256;
892 cl_capturevideo_yuvnormalizetable[1][i] = 16 + i * (240-16) / 256;
893 cl_capturevideo_yuvnormalizetable[2][i] = 16 + i * (240-16) / 256;
896 if (cl_capturevideo_rawrgb.integer)
898 cl_capturevideo_format = CAPTUREVIDEOFORMAT_RAWRGB;
899 cl_capturevideo_videofile = FS_Open ("video/dpvideo.rgb", "wb", false, true);
901 else if (cl_capturevideo_rawyv12.integer)
903 cl_capturevideo_format = CAPTUREVIDEOFORMAT_RAWYV12;
904 cl_capturevideo_videofile = FS_Open ("video/dpvideo.yv12", "wb", false, true);
906 else if (scr_screenshot_jpeg.integer)
908 cl_capturevideo_format = CAPTUREVIDEOFORMAT_JPEG;
909 cl_capturevideo_videofile = NULL;
913 cl_capturevideo_format = CAPTUREVIDEOFORMAT_TARGA;
914 cl_capturevideo_videofile = NULL;
917 if (cl_capturevideo_sound.integer)
919 cl_capturevideo_soundfile = FS_Open ("video/dpvideo.wav", "wb", false, true);
920 // wave header will be filled out when video ends
922 FS_Write (cl_capturevideo_soundfile, out, 44);
925 cl_capturevideo_soundfile = NULL;
928 void SCR_CaptureVideo_EndVideo(void)
932 if (!cl_capturevideo_active)
934 cl_capturevideo_active = false;
936 if (cl_capturevideo_videofile)
938 FS_Close(cl_capturevideo_videofile);
939 cl_capturevideo_videofile = NULL;
942 // finish the wave file
943 if (cl_capturevideo_soundfile)
945 i = FS_Tell (cl_capturevideo_soundfile);
946 //"RIFF", (int) unknown (chunk size), "WAVE",
947 //"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
948 //"data", (int) unknown (chunk size)
949 memcpy (out, "RIFF****WAVEfmt \x10\x00\x00\x00\x01\x00\x02\x00********\x04\x00\x10\0data****", 44);
950 // the length of the whole RIFF chunk
953 out[5] = (n >> 8) & 0xFF;
954 out[6] = (n >> 16) & 0xFF;
955 out[7] = (n >> 24) & 0xFF;
957 n = cl_capturevideo_soundrate;
958 out[24] = (n) & 0xFF;
959 out[25] = (n >> 8) & 0xFF;
960 out[26] = (n >> 16) & 0xFF;
961 out[27] = (n >> 24) & 0xFF;
962 // bytes per second (rate * channels * bytes per channel)
963 n = cl_capturevideo_soundrate * 2 * 2;
964 out[28] = (n) & 0xFF;
965 out[29] = (n >> 8) & 0xFF;
966 out[30] = (n >> 16) & 0xFF;
967 out[31] = (n >> 24) & 0xFF;
968 // the length of the data chunk
970 out[40] = (n) & 0xFF;
971 out[41] = (n >> 8) & 0xFF;
972 out[42] = (n >> 16) & 0xFF;
973 out[43] = (n >> 24) & 0xFF;
974 FS_Seek (cl_capturevideo_soundfile, 0, SEEK_SET);
975 FS_Write (cl_capturevideo_soundfile, out, 44);
976 FS_Close (cl_capturevideo_soundfile);
977 cl_capturevideo_soundfile = NULL;
980 if (cl_capturevideo_buffer)
982 Mem_Free (cl_capturevideo_buffer);
983 cl_capturevideo_buffer = NULL;
986 cl_capturevideo_starttime = 0;
987 cl_capturevideo_framerate = 0;
988 cl_capturevideo_frame = 0;
991 qboolean SCR_CaptureVideo_VideoFrame(int newframenum)
993 int x = 0, y = 0, width = vid.width, height = vid.height;
994 unsigned char *b, *out;
996 int outoffset = (width/2)*(height/2);
997 //return SCR_ScreenShot(filename, cl_capturevideo_buffer, cl_capturevideo_buffer + vid.width * vid.height * 3, cl_capturevideo_buffer + vid.width * vid.height * 6, 0, 0, vid.width, vid.height, false, false, false, jpeg, true);
998 // speed is critical here, so do saving as directly as possible
999 switch (cl_capturevideo_format)
1001 case CAPTUREVIDEOFORMAT_RAWYV12:
1002 // FIXME: width/height must be multiple of 2, enforce this?
1003 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cl_capturevideo_buffer);
1005 // process one line at a time, and CbCr every other line at 2 pixel intervals
1006 for (y = 0;y < height;y++)
1009 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++)
1010 *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]]];
1013 // 2x2 Cb and Cr planes
1015 // low quality, no averaging
1016 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++)
1019 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];
1021 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];
1024 // high quality, averaging
1025 int inpitch = width*3;
1026 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++)
1028 int blockr, blockg, blockb;
1029 blockr = (b[0] + b[3] + b[inpitch+0] + b[inpitch+3]) >> 2;
1030 blockg = (b[1] + b[4] + b[inpitch+1] + b[inpitch+4]) >> 2;
1031 blockb = (b[2] + b[5] + b[inpitch+2] + b[inpitch+5]) >> 2;
1033 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];
1035 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];
1040 for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
1041 if (!FS_Write (cl_capturevideo_videofile, cl_capturevideo_buffer + width*height*3, width*height+(width/2)*(height/2)*2))
1044 case CAPTUREVIDEOFORMAT_RAWRGB:
1045 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cl_capturevideo_buffer);
1047 for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
1048 if (!FS_Write (cl_capturevideo_videofile, cl_capturevideo_buffer, width*height*3))
1051 case CAPTUREVIDEOFORMAT_JPEG:
1052 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cl_capturevideo_buffer);
1054 for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
1056 sprintf(filename, "video/dp%06d.jpg", cl_capturevideo_frame);
1057 if (!JPEG_SaveImage_preflipped (filename, width, height, cl_capturevideo_buffer))
1061 case CAPTUREVIDEOFORMAT_TARGA:
1062 //return Image_WriteTGARGB_preflipped (filename, width, height, cl_capturevideo_buffer, cl_capturevideo_buffer + vid.width * vid.height * 3, );
1063 memset (cl_capturevideo_buffer, 0, 18);
1064 cl_capturevideo_buffer[2] = 2; // uncompressed type
1065 cl_capturevideo_buffer[12] = (width >> 0) & 0xFF;
1066 cl_capturevideo_buffer[13] = (width >> 8) & 0xFF;
1067 cl_capturevideo_buffer[14] = (height >> 0) & 0xFF;
1068 cl_capturevideo_buffer[15] = (height >> 8) & 0xFF;
1069 cl_capturevideo_buffer[16] = 24; // pixel size
1070 qglReadPixels (x, y, width, height, GL_BGR, GL_UNSIGNED_BYTE, cl_capturevideo_buffer + 18);
1072 for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
1074 sprintf(filename, "video/dp%06d.tga", cl_capturevideo_frame);
1075 if (!FS_WriteFile (filename, cl_capturevideo_buffer, width*height*3 + 18))
1084 void SCR_CaptureVideo_SoundFrame(qbyte *bufstereo16le, size_t length, int rate)
1086 if (!cl_capturevideo_soundfile)
1088 cl_capturevideo_soundrate = rate;
1089 if (FS_Write (cl_capturevideo_soundfile, bufstereo16le, 4 * length) < 4 * length)
1091 Cvar_SetValueQuick(&cl_capturevideo, 0);
1092 Con_Printf("video sound saving failed on frame %i, out of disk space? stopping video capture.\n", cl_capturevideo_frame);
1093 SCR_CaptureVideo_EndVideo();
1097 void SCR_CaptureVideo(void)
1100 if (cl_capturevideo.integer && r_render.integer)
1102 if (!cl_capturevideo_active)
1103 SCR_CaptureVideo_BeginVideo();
1104 if (cl_capturevideo_framerate != cl_capturevideo_fps.value)
1106 Con_Printf("You can not change the video framerate while recording a video.\n");
1107 Cvar_SetValueQuick(&cl_capturevideo_fps, cl_capturevideo_framerate);
1109 if (cl_capturevideo_soundfile)
1111 // preserve sound sync by duplicating frames when running slow
1112 newframenum = (Sys_DoubleTime() - cl_capturevideo_starttime) * cl_capturevideo_framerate;
1115 newframenum = cl_capturevideo_frame + 1;
1116 // if falling behind more than one second, stop
1117 if (newframenum - cl_capturevideo_frame > (int)ceil(cl_capturevideo_framerate))
1119 Cvar_SetValueQuick(&cl_capturevideo, 0);
1120 Con_Printf("video saving failed on frame %i, your machine is too slow for this capture speed.\n", cl_capturevideo_frame);
1121 SCR_CaptureVideo_EndVideo();
1125 if (!SCR_CaptureVideo_VideoFrame(newframenum))
1127 Cvar_SetValueQuick(&cl_capturevideo, 0);
1128 Con_Printf("video saving failed on frame %i, out of disk space? stopping video capture.\n", cl_capturevideo_frame);
1129 SCR_CaptureVideo_EndVideo();
1132 else if (cl_capturevideo_active)
1133 SCR_CaptureVideo_EndVideo();
1140 Grab six views for environment mapping tests
1147 qboolean flipx, flipy, flipdiagonaly;
1151 {{ 0, 0, 0}, "rt", false, false, false},
1152 {{ 0, 270, 0}, "ft", false, false, false},
1153 {{ 0, 180, 0}, "lf", false, false, false},
1154 {{ 0, 90, 0}, "bk", false, false, false},
1155 {{-90, 180, 0}, "up", true, true, false},
1156 {{ 90, 180, 0}, "dn", true, true, false},
1158 {{ 0, 0, 0}, "px", true, true, true},
1159 {{ 0, 90, 0}, "py", false, true, false},
1160 {{ 0, 180, 0}, "nx", false, false, true},
1161 {{ 0, 270, 0}, "ny", true, false, false},
1162 {{-90, 180, 0}, "pz", false, false, true},
1163 {{ 90, 180, 0}, "nz", false, false, true}
1166 static void R_Envmap_f (void)
1169 char filename[256], basename[256];
1174 if (Cmd_Argc() != 3)
1176 Con_Print("envmap <basename> <size>: save out 6 cubic environment map images, usable with loadsky, note that size must one of 128, 256, 512, or 1024 and can't be bigger than your current resolution\n");
1180 strlcpy (basename, Cmd_Argv(1), sizeof (basename));
1181 size = atoi(Cmd_Argv(2));
1182 if (size != 128 && size != 256 && size != 512 && size != 1024)
1184 Con_Print("envmap: size must be one of 128, 256, 512, or 1024\n");
1187 if (size > vid.width || size > vid.height)
1189 Con_Print("envmap: your resolution is not big enough to render that size\n");
1197 r_refdef.width = size;
1198 r_refdef.height = size;
1200 r_refdef.fov_x = 90;
1201 r_refdef.fov_y = 90;
1203 buffer1 = Mem_Alloc(tempmempool, size * size * 3);
1204 buffer2 = Mem_Alloc(tempmempool, size * size * 3);
1205 buffer3 = Mem_Alloc(tempmempool, size * size * 3 + 18);
1207 for (j = 0;j < 12;j++)
1209 sprintf(filename, "env/%s%s.tga", basename, envmapinfo[j].name);
1210 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);
1215 SCR_ScreenShot(filename, buffer1, buffer2, buffer3, 0, vid.height - (r_refdef.y + r_refdef.height), size, size, envmapinfo[j].flipx, envmapinfo[j].flipy, envmapinfo[j].flipdiagonaly, false, false);
1225 //=============================================================================
1227 // LordHavoc: SHOWLMP stuff
1228 #define SHOWLMP_MAXLABELS 256
1229 typedef struct showlmp_s
1239 showlmp_t showlmp[SHOWLMP_MAXLABELS];
1241 void SHOWLMP_decodehide(void)
1245 lmplabel = MSG_ReadString();
1246 for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1247 if (showlmp[i].isactive && strcmp(showlmp[i].label, lmplabel) == 0)
1249 showlmp[i].isactive = false;
1254 void SHOWLMP_decodeshow(void)
1257 qbyte lmplabel[256], picname[256];
1259 strlcpy (lmplabel,MSG_ReadString(), sizeof (lmplabel));
1260 strlcpy (picname, MSG_ReadString(), sizeof (picname));
1261 if (gamemode == GAME_NEHAHRA) // LordHavoc: nasty old legacy junk
1268 x = MSG_ReadShort();
1269 y = MSG_ReadShort();
1272 for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1273 if (showlmp[i].isactive)
1275 if (strcmp(showlmp[i].label, lmplabel) == 0)
1278 break; // drop out to replace it
1281 else if (k < 0) // find first empty one to replace
1284 return; // none found to replace
1285 // change existing one
1286 showlmp[k].isactive = true;
1287 strlcpy (showlmp[k].label, lmplabel, sizeof (showlmp[k].label));
1288 strlcpy (showlmp[k].pic, picname, sizeof (showlmp[k].pic));
1293 void SHOWLMP_drawall(void)
1296 for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1297 if (showlmp[i].isactive)
1298 DrawQ_Pic(showlmp[i].x, showlmp[i].y, showlmp[i].pic, 0, 0, 1, 1, 1, 1, 0);
1301 void SHOWLMP_clear(void)
1304 for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1305 showlmp[i].isactive = false;
1308 void CL_SetupScreenSize(void)
1310 float conwidth, conheight;
1312 VID_UpdateGamma(false);
1314 conwidth = bound(320, vid_conwidth.value, 2048);
1315 conheight = bound(200, vid_conheight.value, 1536);
1316 if (vid_conwidth.value != conwidth)
1317 Cvar_SetValue("vid_conwidth", conwidth);
1318 if (vid_conheight.value != conheight)
1319 Cvar_SetValue("vid_conheight", conheight);
1321 vid_conwidth.integer = vid_conwidth.integer;
1322 vid_conheight.integer = vid_conheight.integer;
1324 SCR_SetUpToDrawConsole();
1327 extern void R_Shadow_EditLights_DrawSelectedLightProperties(void);
1328 void CL_UpdateScreen(void)
1330 if (!scr_initialized || !con_initialized || vid_hidden)
1331 return; // not initialized yet
1333 // don't allow cheats in multiplayer
1334 if (!cl.islocalgame && cl.worldmodel)
1336 if (r_fullbright.integer != 0)
1337 Cvar_Set ("r_fullbright", "0");
1338 if (r_ambient.value != 0)
1339 Cvar_Set ("r_ambient", "0");
1343 if (scr_viewsize.value < 30)
1344 Cvar_Set ("viewsize","30");
1345 if (scr_viewsize.value > 120)
1346 Cvar_Set ("viewsize","120");
1348 // bound field of view
1349 if (scr_fov.value < 1)
1350 Cvar_Set ("fov","1");
1351 if (scr_fov.value > 170)
1352 Cvar_Set ("fov","170");
1354 // intermission is always full screen
1355 if (cl.intermission)
1359 if (scr_viewsize.value >= 120)
1360 sb_lines = 0; // no status bar at all
1361 else if (scr_viewsize.value >= 110)
1362 sb_lines = 24; // no inventory
1367 r_refdef.colormask[0] = 1;
1368 r_refdef.colormask[1] = 1;
1369 r_refdef.colormask[2] = 1;
1373 if (cls.signon == SIGNONS)
1374 R_TimeReport("other");
1376 CL_SetupScreenSize();
1380 if (cls.signon == SIGNONS)
1381 R_TimeReport("setup");
1383 //FIXME: force menu if nothing else to look at?
1384 //if (key_dest == key_game && cls.signon != SIGNONS && cls.state == ca_disconnected)
1386 if (cls.signon == SIGNONS)
1391 if (!r_letterbox.value)
1394 SCR_CheckDrawCenterString();
1400 if (cls.signon == SIGNONS)
1404 R_TimeReport_Start();
1406 R_Shadow_EditLights_DrawSelectedLightProperties();
1413 void CL_Screen_NewMap(void)