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 )
81 const char *first, *last;
85 if( !outcolor || *outcolor == -1 ) {
86 colorindex = _draw_color_default;
88 colorindex = *outcolor;
91 color = _draw_colors[colorindex];
96 len = min( maxlen, (signed) strlen( text ) );
100 // iterate until we get the next color tag or reach the end of the text part to draw
101 for( ; len && *last != _draw_color_tag ; len--, last++ )
103 // only draw the partial string if we have read anything
104 if( last != first ) {
106 DrawQ_String( x, y, first, last - first, scalex, scaley, basered * color[0], basegreen * color[1], baseblue * color[2], basealpha * color[3], flags );
107 // update x to be at the new start position
108 x += (last - first) * scalex;
109 // if we have reached the end, we have finished
117 if( len && '0' <= *last && *last <= '9' ) {
119 while( '0' <= *last && *last <= '9' && len ) {
120 colorindex = colorindex * 10 + *last - '0';
121 if( colorindex < _draw_colors_count ) {
130 color = _draw_colors[colorindex];
131 // we dont want to display the color tag and the color index
137 *outcolor = colorindex;
142 ===============================================================================
146 ===============================================================================
149 char scr_centerstring[1024];
150 float scr_centertime_start; // for slow victory printing
151 float scr_centertime_off;
152 int scr_center_lines;
154 int scr_erase_center;
160 Called for important messages that should stay in the center of the screen
164 void SCR_CenterPrint(char *str)
166 strlcpy (scr_centerstring, str, sizeof (scr_centerstring));
167 scr_centertime_off = scr_centertime.value;
168 scr_centertime_start = cl.time;
170 // count the number of lines for centering
171 scr_center_lines = 1;
181 void SCR_DrawCenterString (void)
188 // the finale prints the characters one at a time
190 remaining = scr_printspeed.value * (cl.time - scr_centertime_start);
194 scr_erase_center = 0;
195 start = scr_centerstring;
197 if (scr_center_lines <= 4)
198 y = vid_conheight.integer*0.35;
204 // scan the width of the line
205 for (l=0 ; l<vid_conwidth.integer/8 ; l++)
206 if (start[l] == '\n' || !start[l])
208 x = (vid_conwidth.integer - l*8)/2;
213 DrawQ_String(x, y, start, l, 8, 8, 1, 1, 1, 1, 0);
221 while (*start && *start != '\n')
226 start++; // skip the \n
230 void SCR_CheckDrawCenterString (void)
232 if (scr_center_lines > scr_erase_lines)
233 scr_erase_lines = scr_center_lines;
235 scr_centertime_off -= host_frametime;
237 // don't draw if this is a normal stats-screen intermission,
238 // only if it is not an intermission, or a finale intermission
239 if (cl.intermission == 1)
241 if (scr_centertime_off <= 0 && !cl.intermission)
243 if (key_dest != key_game)
246 SCR_DrawCenterString ();
254 void SCR_DrawTurtle (void)
258 if (cls.state != ca_connected)
261 if (!scr_showturtle.integer)
264 if (host_frametime < 0.1)
274 DrawQ_Pic (0, 0, "gfx/turtle", 0, 0, 1, 1, 1, 1, 0);
282 void SCR_DrawNet (void)
284 if (cls.state != ca_connected)
286 if (realtime - cl.last_received_message < 0.3)
288 if (cls.demoplayback)
291 DrawQ_Pic (64, 0, "gfx/net", 0, 0, 1, 1, 1, 1, 0);
299 void SCR_DrawPause (void)
303 if (cls.state != ca_connected)
306 if (!scr_showpause.integer) // turn off for screenshots
312 pic = Draw_CachePic ("gfx/pause", true);
313 DrawQ_Pic ((vid_conwidth.integer - pic->width)/2, (vid_conheight.integer - pic->height)/2, "gfx/pause", 0, 0, 1, 1, 1, 1, 0);
320 //=============================================================================
325 SCR_SetUpToDrawConsole
328 void SCR_SetUpToDrawConsole (void)
330 // lines of console to display
335 if (key_dest == key_game && cls.signon != SIGNONS && scr_conforcewhiledisconnected.integer)
336 key_consoleactive |= KEY_CONSOLEACTIVE_FORCED;
338 key_consoleactive &= ~KEY_CONSOLEACTIVE_FORCED;
340 // decide on the height of the console
341 if (key_consoleactive & KEY_CONSOLEACTIVE_USER)
342 conlines = vid_conheight.integer/2; // half screen
344 conlines = 0; // none visible
346 if (scr_conspeed.value)
348 if (scr_con_current > conlines)
350 scr_con_current -= scr_conspeed.value*host_realframetime;
351 if (scr_con_current < conlines)
352 scr_con_current = conlines;
355 else if (scr_con_current < conlines)
357 scr_con_current += scr_conspeed.value*host_realframetime;
358 if (scr_con_current > conlines)
359 scr_con_current = conlines;
363 scr_con_current = conlines;
371 void SCR_DrawConsole (void)
373 if (key_consoleactive & KEY_CONSOLEACTIVE_FORCED)
376 Con_DrawConsole (vid_conheight.integer);
378 else if (scr_con_current)
379 Con_DrawConsole (scr_con_current);
383 if (key_dest == key_game || key_dest == key_message)
384 Con_DrawNotify (); // only draw notify in game
390 SCR_BeginLoadingPlaque
394 void SCR_BeginLoadingPlaque (void)
398 SCR_UpdateLoadingScreen();
401 //=============================================================================
403 char r_speeds_string[1024];
404 int speedstringcount, r_timereport_active;
405 double r_timereport_temp = 0, r_timereport_current = 0, r_timereport_start = 0;
407 void R_TimeReport(char *desc)
413 if (!r_timereport_active || r_showtrispass)
416 r_timereport_temp = r_timereport_current;
417 r_timereport_current = Sys_DoubleTime();
418 t = (int) ((r_timereport_current - r_timereport_temp) * 1000000.0);
420 dpsnprintf(tempbuf, sizeof(tempbuf), "%8i %s", t, desc);
421 length = strlen(tempbuf);
423 tempbuf[length++] = ' ';
425 if (speedstringcount + length > (vid_conwidth.integer / 8))
427 strlcat(r_speeds_string, "\n", sizeof(r_speeds_string));
428 speedstringcount = 0;
430 // skip the space at the beginning if it's the first on the line
431 if (speedstringcount == 0)
433 strlcat(r_speeds_string, tempbuf + 1, sizeof(r_speeds_string));
434 speedstringcount = length - 1;
438 strlcat(r_speeds_string, tempbuf, sizeof(r_speeds_string));
439 speedstringcount += length;
443 void R_TimeReport_Start(void)
445 r_timereport_active = r_speeds.integer && cls.signon == SIGNONS && cls.state == ca_connected;
446 r_speeds_string[0] = 0;
447 if (r_timereport_active)
449 speedstringcount = 0;
450 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]);
451 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);
452 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);
453 sprintf(r_speeds_string + strlen(r_speeds_string), "%6i modeltris%6i meshs%6i meshtris\n", c_alias_polys, c_meshs, c_meshelements / 3);
454 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);
455 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);
456 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);
457 sprintf(r_speeds_string + strlen(r_speeds_string), "precomputed: %6i shadowmeshes%6i shadowtris\n", c_rtcached_shadowmeshes, c_rtcached_shadowtris);
474 c_rt_shadowmeshes = 0;
476 c_rt_lightmeshes = 0;
478 c_rtcached_shadowmeshes = 0;
479 c_rtcached_shadowtris = 0;
482 c_bloomcopypixels = 0;
484 c_bloomdrawpixels = 0;
486 r_timereport_start = Sys_DoubleTime();
490 void R_TimeReport_End(void)
492 r_timereport_current = r_timereport_start;
493 R_TimeReport("total");
495 if (r_timereport_active)
499 for (i = 0;r_speeds_string[i];i++)
500 if (r_speeds_string[i] == '\n')
502 y = vid_conheight.integer - sb_lines - lines * 8;
504 DrawQ_Fill(0, y, vid_conwidth.integer, lines * 8, 0, 0, 0, 0.5, 0);
505 while (r_speeds_string[i])
508 while (r_speeds_string[i] && r_speeds_string[i] != '\n')
511 DrawQ_String(0, y, r_speeds_string + j, i - j, 8, 8, 1, 1, 1, 1, 0);
512 if (r_speeds_string[i] == '\n')
526 void SCR_SizeUp_f (void)
528 Cvar_SetValue ("viewsize",scr_viewsize.value+10);
539 void SCR_SizeDown_f (void)
541 Cvar_SetValue ("viewsize",scr_viewsize.value-10);
544 void CL_Screen_Init(void)
546 Cvar_RegisterVariable (&scr_fov);
547 Cvar_RegisterVariable (&scr_viewsize);
548 Cvar_RegisterVariable (&scr_conspeed);
549 Cvar_RegisterVariable (&scr_conalpha);
550 Cvar_RegisterVariable (&scr_conbrightness);
551 Cvar_RegisterVariable (&scr_conforcewhiledisconnected);
552 Cvar_RegisterVariable (&scr_showram);
553 Cvar_RegisterVariable (&scr_showturtle);
554 Cvar_RegisterVariable (&scr_showpause);
555 Cvar_RegisterVariable (&scr_centertime);
556 Cvar_RegisterVariable (&scr_printspeed);
557 Cvar_RegisterVariable (&vid_conwidth);
558 Cvar_RegisterVariable (&vid_conheight);
559 Cvar_RegisterVariable (&vid_pixelaspect);
560 Cvar_RegisterVariable (&scr_screenshot_jpeg);
561 Cvar_RegisterVariable (&scr_screenshot_jpeg_quality);
562 Cvar_RegisterVariable (&scr_screenshot_gamma);
563 Cvar_RegisterVariable (&cl_capturevideo);
564 Cvar_RegisterVariable (&cl_capturevideo_sound);
565 Cvar_RegisterVariable (&cl_capturevideo_fps);
566 Cvar_RegisterVariable (&cl_capturevideo_rawrgb);
567 Cvar_RegisterVariable (&cl_capturevideo_rawyv12);
568 Cvar_RegisterVariable (&r_textshadow);
569 Cvar_RegisterVariable (&r_letterbox);
571 Cmd_AddCommand ("sizeup",SCR_SizeUp_f);
572 Cmd_AddCommand ("sizedown",SCR_SizeDown_f);
573 Cmd_AddCommand ("screenshot",SCR_ScreenShot_f);
574 Cmd_AddCommand ("envmap", R_Envmap_f);
576 scr_initialized = true;
579 void DrawQ_Clear(void)
581 r_refdef.drawqueuesize = 0;
584 static int picelements[6] = {0, 1, 2, 0, 2, 3};
585 void DrawQ_Pic(float x, float y, const char *picname, float width, float height, float red, float green, float blue, float alpha, int flags)
587 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);
590 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)
595 if (alpha < (1.0f / 255.0f))
598 len = strlen(string);
600 for (len = 0;len < maxlen && string[len];len++);
601 for (;len > 0 && string[0] == ' ';string++, x += scalex, len--);
602 for (;len > 0 && string[len - 1] == ' ';len--);
605 if (x >= vid_conwidth.integer || y >= vid_conheight.integer || x < (-scalex * len) || y < (-scaley))
607 size = sizeof(*dq) + ((len + 1 + 3) & ~3);
608 if (r_refdef.drawqueuesize + size > r_refdef.maxdrawqueuesize)
610 red = bound(0, red, 1);
611 green = bound(0, green, 1);
612 blue = bound(0, blue, 1);
613 alpha = bound(0, alpha, 1);
614 dq = (void *)(r_refdef.drawqueue + r_refdef.drawqueuesize);
616 dq->command = DRAWQUEUE_STRING;
618 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));
623 out = (char *)(dq + 1);
624 memcpy(out, string, len);
626 r_refdef.drawqueuesize += dq->size;
629 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)
631 if (r_textshadow.integer)
632 DrawQ_String_Real(x+scalex*0.25,y+scaley*0.25,string,maxlen,scalex,scaley,0,0,0,alpha*0.8,flags);
634 DrawQ_String_Real(x,y,string,maxlen,scalex,scaley,red,green,blue,alpha,flags);
639 void DrawQ_Fill (float x, float y, float w, float h, float red, float green, float blue, float alpha, int flags)
641 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);
644 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)
648 drawqueuemesh_t mesh;
649 memset(&mesh, 0, sizeof(mesh));
650 if (picname && picname[0])
652 pic = Draw_CachePic(picname, false);
656 height = pic->height;
657 mesh.texture = pic->tex;
659 mesh.num_triangles = 2;
660 mesh.num_vertices = 4;
661 mesh.data_element3i = picelements;
662 mesh.data_vertex3f = floats;
663 mesh.data_texcoord2f = floats + 12;
664 mesh.data_color4f = floats + 20;
665 memset(floats, 0, sizeof(floats));
666 mesh.data_vertex3f[0] = mesh.data_vertex3f[9] = x;
667 mesh.data_vertex3f[1] = mesh.data_vertex3f[4] = y;
668 mesh.data_vertex3f[3] = mesh.data_vertex3f[6] = x + width;
669 mesh.data_vertex3f[7] = mesh.data_vertex3f[10] = y + height;
670 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;
671 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;
672 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;
673 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;
674 DrawQ_Mesh (&mesh, flags);
677 void DrawQ_Mesh (drawqueuemesh_t *mesh, int flags)
684 size += sizeof(drawqueuemesh_t);
685 size += sizeof(int[3]) * mesh->num_triangles;
686 size += sizeof(float[3]) * mesh->num_vertices;
687 size += sizeof(float[2]) * mesh->num_vertices;
688 size += sizeof(float[4]) * mesh->num_vertices;
689 if (r_refdef.drawqueuesize + size > r_refdef.maxdrawqueuesize)
691 dq = (void *)(r_refdef.drawqueue + r_refdef.drawqueuesize);
693 dq->command = DRAWQUEUE_MESH;
700 p = (void *)(dq + 1);
701 m = p;p = (qbyte*)p + sizeof(drawqueuemesh_t);
702 m->num_triangles = mesh->num_triangles;
703 m->num_vertices = mesh->num_vertices;
704 m->texture = mesh->texture;
705 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]);
706 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]);
707 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]);
708 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]);
709 r_refdef.drawqueuesize += dq->size;
712 void DrawQ_SetClipArea(float x, float y, float width, float height)
715 if(r_refdef.drawqueuesize + (int)sizeof(*dq) > r_refdef.maxdrawqueuesize)
717 Con_DPrint("DrawQueue full !\n");
720 dq = (void*) (r_refdef.drawqueue + r_refdef.drawqueuesize);
721 dq->size = sizeof(*dq);
722 dq->command = DRAWQUEUE_SETCLIP;
730 r_refdef.drawqueuesize += dq->size;
733 void DrawQ_ResetClipArea(void)
736 if(r_refdef.drawqueuesize + (int)sizeof(*dq) > r_refdef.maxdrawqueuesize)
738 Con_DPrint("DrawQueue full !\n");
741 dq = (void*) (r_refdef.drawqueue + r_refdef.drawqueuesize);
742 dq->size = sizeof(*dq);
743 dq->command = DRAWQUEUE_RESETCLIP;
751 r_refdef.drawqueuesize += dq->size;
759 void SCR_ScreenShot_f (void)
761 static int shotnumber;
762 static char oldname[MAX_QPATH];
763 char base[MAX_QPATH];
764 char filename[MAX_QPATH];
768 qboolean jpeg = (scr_screenshot_jpeg.integer != 0);
770 sprintf (base, "screenshots/%s", scr_screenshot_name.string);
772 if (strcmp (oldname, scr_screenshot_name.string))
774 sprintf(oldname, "%s", scr_screenshot_name.string);
778 // find a file name to save it to
779 for (;shotnumber < 1000000;shotnumber++)
780 if (!FS_SysFileExists(va("%s/%s%06d.tga", fs_gamedir, base, shotnumber)) && !FS_SysFileExists(va("%s/%s%06d.jpg", fs_gamedir, base, shotnumber)))
782 if (shotnumber >= 1000000)
784 Con_Print("SCR_ScreenShot_f: Couldn't create the image file\n");
788 sprintf(filename, "%s%06d.%s", base, shotnumber, jpeg ? "jpg" : "tga");
790 buffer1 = Mem_Alloc(tempmempool, vid.width * vid.height * 3);
791 buffer2 = Mem_Alloc(tempmempool, vid.width * vid.height * 3);
792 buffer3 = Mem_Alloc(tempmempool, vid.width * vid.height * 3 + 18);
794 if (SCR_ScreenShot (filename, buffer1, buffer2, buffer3, 0, 0, vid.width, vid.height, false, false, false, jpeg, true))
795 Con_Printf("Wrote %s\n", filename);
797 Con_Printf("unable to write %s\n", filename);
806 typedef enum capturevideoformat_e
808 CAPTUREVIDEOFORMAT_TARGA,
809 CAPTUREVIDEOFORMAT_JPEG,
810 CAPTUREVIDEOFORMAT_RAWRGB,
811 CAPTUREVIDEOFORMAT_RAWYV12
813 capturevideoformat_t;
815 qboolean cl_capturevideo_active = false;
816 capturevideoformat_t cl_capturevideo_format;
817 static double cl_capturevideo_starttime = 0;
818 double cl_capturevideo_framerate = 0;
819 static int cl_capturevideo_soundrate = 0;
820 static int cl_capturevideo_frame = 0;
821 static qbyte *cl_capturevideo_buffer = NULL;
822 static qfile_t *cl_capturevideo_videofile = NULL;
823 qfile_t *cl_capturevideo_soundfile = NULL;
824 static short cl_capturevideo_rgbtoyuvscaletable[3][3][256];
825 static unsigned char cl_capturevideo_yuvnormalizetable[3][256];
826 //static unsigned char cl_capturevideo_rgbgammatable[3][256];
828 void SCR_CaptureVideo_BeginVideo(void)
833 if (cl_capturevideo_active)
835 // soundrate is figured out on the first SoundFrame
836 cl_capturevideo_active = true;
837 cl_capturevideo_starttime = Sys_DoubleTime();
838 cl_capturevideo_framerate = bound(1, cl_capturevideo_fps.value, 1000);
839 cl_capturevideo_soundrate = 0;
840 cl_capturevideo_frame = 0;
841 cl_capturevideo_buffer = Mem_Alloc(tempmempool, vid.width * vid.height * (3+3+3) + 18);
842 gamma = 1.0/scr_screenshot_gamma.value;
845 for (i = 0;i < 256;i++)
847 unsigned char j = (unsigned char)bound(0, 255*pow(i/255.0, gamma), 255);
848 cl_capturevideo_rgbgammatable[0][i] = j;
849 cl_capturevideo_rgbgammatable[1][i] = j;
850 cl_capturevideo_rgbgammatable[2][i] = j;
854 R = Y + 1.4075 * (Cr - 128);
855 G = Y + -0.3455 * (Cb - 128) + -0.7169 * (Cr - 128);
856 B = Y + 1.7790 * (Cb - 128);
857 Y = R * .299 + G * .587 + B * .114;
858 Cb = R * -.169 + G * -.332 + B * .500 + 128.;
859 Cr = R * .500 + G * -.419 + B * -.0813 + 128.;
861 for (i = 0;i < 256;i++)
863 g = 255*pow(i/255.0, gamma);
864 // Y weights from RGB
865 cl_capturevideo_rgbtoyuvscaletable[0][0][i] = (short)(g * 0.299);
866 cl_capturevideo_rgbtoyuvscaletable[0][1][i] = (short)(g * 0.587);
867 cl_capturevideo_rgbtoyuvscaletable[0][2][i] = (short)(g * 0.114);
868 // Cb weights from RGB
869 cl_capturevideo_rgbtoyuvscaletable[1][0][i] = (short)(g * -0.169);
870 cl_capturevideo_rgbtoyuvscaletable[1][1][i] = (short)(g * -0.332);
871 cl_capturevideo_rgbtoyuvscaletable[1][2][i] = (short)(g * 0.500);
872 // Cr weights from RGB
873 cl_capturevideo_rgbtoyuvscaletable[2][0][i] = (short)(g * 0.500);
874 cl_capturevideo_rgbtoyuvscaletable[2][1][i] = (short)(g * -0.419);
875 cl_capturevideo_rgbtoyuvscaletable[2][2][i] = (short)(g * -0.0813);
876 // range reduction of YCbCr to valid signal range
877 cl_capturevideo_yuvnormalizetable[0][i] = 16 + i * (236-16) / 256;
878 cl_capturevideo_yuvnormalizetable[1][i] = 16 + i * (240-16) / 256;
879 cl_capturevideo_yuvnormalizetable[2][i] = 16 + i * (240-16) / 256;
882 if (cl_capturevideo_rawrgb.integer)
884 cl_capturevideo_format = CAPTUREVIDEOFORMAT_RAWRGB;
885 cl_capturevideo_videofile = FS_Open ("video/dpvideo.rgb", "wb", false, true);
887 else if (cl_capturevideo_rawyv12.integer)
889 cl_capturevideo_format = CAPTUREVIDEOFORMAT_RAWYV12;
890 cl_capturevideo_videofile = FS_Open ("video/dpvideo.yv12", "wb", false, true);
892 else if (scr_screenshot_jpeg.integer)
894 cl_capturevideo_format = CAPTUREVIDEOFORMAT_JPEG;
895 cl_capturevideo_videofile = NULL;
899 cl_capturevideo_format = CAPTUREVIDEOFORMAT_TARGA;
900 cl_capturevideo_videofile = NULL;
903 if (cl_capturevideo_sound.integer)
905 cl_capturevideo_soundfile = FS_Open ("video/dpvideo.wav", "wb", false, true);
906 // wave header will be filled out when video ends
908 FS_Write (cl_capturevideo_soundfile, out, 44);
911 cl_capturevideo_soundfile = NULL;
914 void SCR_CaptureVideo_EndVideo(void)
918 if (!cl_capturevideo_active)
920 cl_capturevideo_active = false;
922 if (cl_capturevideo_videofile)
924 FS_Close(cl_capturevideo_videofile);
925 cl_capturevideo_videofile = NULL;
928 // finish the wave file
929 if (cl_capturevideo_soundfile)
931 i = FS_Tell (cl_capturevideo_soundfile);
932 //"RIFF", (int) unknown (chunk size), "WAVE",
933 //"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
934 //"data", (int) unknown (chunk size)
935 memcpy (out, "RIFF****WAVEfmt \x10\x00\x00\x00\x01\x00\x02\x00********\x04\x00\x10\0data****", 44);
936 // the length of the whole RIFF chunk
939 out[5] = (n >> 8) & 0xFF;
940 out[6] = (n >> 16) & 0xFF;
941 out[7] = (n >> 24) & 0xFF;
943 n = cl_capturevideo_soundrate;
944 out[24] = (n) & 0xFF;
945 out[25] = (n >> 8) & 0xFF;
946 out[26] = (n >> 16) & 0xFF;
947 out[27] = (n >> 24) & 0xFF;
948 // bytes per second (rate * channels * bytes per channel)
949 n = cl_capturevideo_soundrate * 2 * 2;
950 out[28] = (n) & 0xFF;
951 out[29] = (n >> 8) & 0xFF;
952 out[30] = (n >> 16) & 0xFF;
953 out[31] = (n >> 24) & 0xFF;
954 // the length of the data chunk
956 out[40] = (n) & 0xFF;
957 out[41] = (n >> 8) & 0xFF;
958 out[42] = (n >> 16) & 0xFF;
959 out[43] = (n >> 24) & 0xFF;
960 FS_Seek (cl_capturevideo_soundfile, 0, SEEK_SET);
961 FS_Write (cl_capturevideo_soundfile, out, 44);
962 FS_Close (cl_capturevideo_soundfile);
963 cl_capturevideo_soundfile = NULL;
966 if (cl_capturevideo_buffer)
968 Mem_Free (cl_capturevideo_buffer);
969 cl_capturevideo_buffer = NULL;
972 cl_capturevideo_starttime = 0;
973 cl_capturevideo_framerate = 0;
974 cl_capturevideo_frame = 0;
977 qboolean SCR_CaptureVideo_VideoFrame(int newframenum)
979 int x = 0, y = 0, width = vid.width, height = vid.height;
980 unsigned char *b, *out;
982 int outoffset = (width/2)*(height/2);
983 //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);
984 // speed is critical here, so do saving as directly as possible
985 switch (cl_capturevideo_format)
987 case CAPTUREVIDEOFORMAT_RAWYV12:
988 // FIXME: width/height must be multiple of 2, enforce this?
989 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cl_capturevideo_buffer);
991 // process one line at a time, and CbCr every other line at 2 pixel intervals
992 for (y = 0;y < height;y++)
995 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++)
996 *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]]];
999 // 2x2 Cb and Cr planes
1001 // low quality, no averaging
1002 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++)
1005 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];
1007 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];
1010 // high quality, averaging
1011 int inpitch = width*3;
1012 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++)
1014 int blockr, blockg, blockb;
1015 blockr = (b[0] + b[3] + b[inpitch+0] + b[inpitch+3]) >> 2;
1016 blockg = (b[1] + b[4] + b[inpitch+1] + b[inpitch+4]) >> 2;
1017 blockb = (b[2] + b[5] + b[inpitch+2] + b[inpitch+5]) >> 2;
1019 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];
1021 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];
1026 for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
1027 if (!FS_Write (cl_capturevideo_videofile, cl_capturevideo_buffer + width*height*3, width*height+(width/2)*(height/2)*2))
1030 case CAPTUREVIDEOFORMAT_RAWRGB:
1031 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cl_capturevideo_buffer);
1033 for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
1034 if (!FS_Write (cl_capturevideo_videofile, cl_capturevideo_buffer, width*height*3))
1037 case CAPTUREVIDEOFORMAT_JPEG:
1038 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cl_capturevideo_buffer);
1040 for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
1042 sprintf(filename, "video/dp%06d.jpg", cl_capturevideo_frame);
1043 if (!JPEG_SaveImage_preflipped (filename, width, height, cl_capturevideo_buffer))
1047 case CAPTUREVIDEOFORMAT_TARGA:
1048 //return Image_WriteTGARGB_preflipped (filename, width, height, cl_capturevideo_buffer, cl_capturevideo_buffer + vid.width * vid.height * 3, );
1049 memset (cl_capturevideo_buffer, 0, 18);
1050 cl_capturevideo_buffer[2] = 2; // uncompressed type
1051 cl_capturevideo_buffer[12] = (width >> 0) & 0xFF;
1052 cl_capturevideo_buffer[13] = (width >> 8) & 0xFF;
1053 cl_capturevideo_buffer[14] = (height >> 0) & 0xFF;
1054 cl_capturevideo_buffer[15] = (height >> 8) & 0xFF;
1055 cl_capturevideo_buffer[16] = 24; // pixel size
1056 qglReadPixels (x, y, width, height, GL_BGR, GL_UNSIGNED_BYTE, cl_capturevideo_buffer + 18);
1058 for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
1060 sprintf(filename, "video/dp%06d.tga", cl_capturevideo_frame);
1061 if (!FS_WriteFile (filename, cl_capturevideo_buffer, width*height*3 + 18))
1070 void SCR_CaptureVideo_SoundFrame(qbyte *bufstereo16le, size_t length, int rate)
1072 if (!cl_capturevideo_soundfile)
1074 cl_capturevideo_soundrate = rate;
1075 if (FS_Write (cl_capturevideo_soundfile, bufstereo16le, 4 * length) < 4 * length)
1077 Cvar_SetValueQuick(&cl_capturevideo, 0);
1078 Con_Printf("video sound saving failed on frame %i, out of disk space? stopping video capture.\n", cl_capturevideo_frame);
1079 SCR_CaptureVideo_EndVideo();
1083 void SCR_CaptureVideo(void)
1086 if (cl_capturevideo.integer && r_render.integer)
1088 if (!cl_capturevideo_active)
1089 SCR_CaptureVideo_BeginVideo();
1090 if (cl_capturevideo_framerate != cl_capturevideo_fps.value)
1092 Con_Printf("You can not change the video framerate while recording a video.\n");
1093 Cvar_SetValueQuick(&cl_capturevideo_fps, cl_capturevideo_framerate);
1095 if (cl_capturevideo_soundfile)
1097 // preserve sound sync by duplicating frames when running slow
1098 newframenum = (Sys_DoubleTime() - cl_capturevideo_starttime) * cl_capturevideo_framerate;
1101 newframenum = cl_capturevideo_frame + 1;
1102 // if falling behind more than one second, stop
1103 if (newframenum - cl_capturevideo_frame > (int)ceil(cl_capturevideo_framerate))
1105 Cvar_SetValueQuick(&cl_capturevideo, 0);
1106 Con_Printf("video saving failed on frame %i, your machine is too slow for this capture speed.\n", cl_capturevideo_frame);
1107 SCR_CaptureVideo_EndVideo();
1111 if (!SCR_CaptureVideo_VideoFrame(newframenum))
1113 Cvar_SetValueQuick(&cl_capturevideo, 0);
1114 Con_Printf("video saving failed on frame %i, out of disk space? stopping video capture.\n", cl_capturevideo_frame);
1115 SCR_CaptureVideo_EndVideo();
1118 else if (cl_capturevideo_active)
1119 SCR_CaptureVideo_EndVideo();
1126 Grab six views for environment mapping tests
1133 qboolean flipx, flipy, flipdiagonaly;
1137 {{ 0, 0, 0}, "rt", false, false, false},
1138 {{ 0, 270, 0}, "ft", false, false, false},
1139 {{ 0, 180, 0}, "lf", false, false, false},
1140 {{ 0, 90, 0}, "bk", false, false, false},
1141 {{-90, 180, 0}, "up", true, true, false},
1142 {{ 90, 180, 0}, "dn", true, true, false},
1144 {{ 0, 0, 0}, "px", true, true, true},
1145 {{ 0, 90, 0}, "py", false, true, false},
1146 {{ 0, 180, 0}, "nx", false, false, true},
1147 {{ 0, 270, 0}, "ny", true, false, false},
1148 {{-90, 180, 0}, "pz", false, false, true},
1149 {{ 90, 180, 0}, "nz", false, false, true}
1152 static void R_Envmap_f (void)
1155 char filename[256], basename[256];
1160 if (Cmd_Argc() != 3)
1162 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");
1166 strlcpy (basename, Cmd_Argv(1), sizeof (basename));
1167 size = atoi(Cmd_Argv(2));
1168 if (size != 128 && size != 256 && size != 512 && size != 1024)
1170 Con_Print("envmap: size must be one of 128, 256, 512, or 1024\n");
1173 if (size > vid.width || size > vid.height)
1175 Con_Print("envmap: your resolution is not big enough to render that size\n");
1183 r_refdef.width = size;
1184 r_refdef.height = size;
1186 r_refdef.fov_x = 90;
1187 r_refdef.fov_y = 90;
1189 buffer1 = Mem_Alloc(tempmempool, size * size * 3);
1190 buffer2 = Mem_Alloc(tempmempool, size * size * 3);
1191 buffer3 = Mem_Alloc(tempmempool, size * size * 3 + 18);
1193 for (j = 0;j < 12;j++)
1195 sprintf(filename, "env/%s%s.tga", basename, envmapinfo[j].name);
1196 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);
1201 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);
1211 //=============================================================================
1213 // LordHavoc: SHOWLMP stuff
1214 #define SHOWLMP_MAXLABELS 256
1215 typedef struct showlmp_s
1225 showlmp_t showlmp[SHOWLMP_MAXLABELS];
1227 void SHOWLMP_decodehide(void)
1231 lmplabel = MSG_ReadString();
1232 for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1233 if (showlmp[i].isactive && strcmp(showlmp[i].label, lmplabel) == 0)
1235 showlmp[i].isactive = false;
1240 void SHOWLMP_decodeshow(void)
1243 qbyte lmplabel[256], picname[256];
1245 strlcpy (lmplabel,MSG_ReadString(), sizeof (lmplabel));
1246 strlcpy (picname, MSG_ReadString(), sizeof (picname));
1247 if (gamemode == GAME_NEHAHRA) // LordHavoc: nasty old legacy junk
1254 x = MSG_ReadShort();
1255 y = MSG_ReadShort();
1258 for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1259 if (showlmp[i].isactive)
1261 if (strcmp(showlmp[i].label, lmplabel) == 0)
1264 break; // drop out to replace it
1267 else if (k < 0) // find first empty one to replace
1270 return; // none found to replace
1271 // change existing one
1272 showlmp[k].isactive = true;
1273 strlcpy (showlmp[k].label, lmplabel, sizeof (showlmp[k].label));
1274 strlcpy (showlmp[k].pic, picname, sizeof (showlmp[k].pic));
1279 void SHOWLMP_drawall(void)
1282 for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1283 if (showlmp[i].isactive)
1284 DrawQ_Pic(showlmp[i].x, showlmp[i].y, showlmp[i].pic, 0, 0, 1, 1, 1, 1, 0);
1287 void SHOWLMP_clear(void)
1290 for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1291 showlmp[i].isactive = false;
1294 void CL_SetupScreenSize(void)
1296 float conwidth, conheight;
1298 VID_UpdateGamma(false);
1300 conwidth = bound(320, vid_conwidth.value, 2048);
1301 conheight = bound(200, vid_conheight.value, 1536);
1302 if (vid_conwidth.value != conwidth)
1303 Cvar_SetValue("vid_conwidth", conwidth);
1304 if (vid_conheight.value != conheight)
1305 Cvar_SetValue("vid_conheight", conheight);
1307 vid_conwidth.integer = vid_conwidth.integer;
1308 vid_conheight.integer = vid_conheight.integer;
1310 SCR_SetUpToDrawConsole();
1313 extern void R_Shadow_EditLights_DrawSelectedLightProperties(void);
1314 void CL_UpdateScreen(void)
1316 if (!scr_initialized || !con_initialized || vid_hidden)
1317 return; // not initialized yet
1319 // don't allow cheats in multiplayer
1320 if (!cl.islocalgame && cl.worldmodel)
1322 if (r_fullbright.integer != 0)
1323 Cvar_Set ("r_fullbright", "0");
1324 if (r_ambient.value != 0)
1325 Cvar_Set ("r_ambient", "0");
1329 if (scr_viewsize.value < 30)
1330 Cvar_Set ("viewsize","30");
1331 if (scr_viewsize.value > 120)
1332 Cvar_Set ("viewsize","120");
1334 // bound field of view
1335 if (scr_fov.value < 1)
1336 Cvar_Set ("fov","1");
1337 if (scr_fov.value > 170)
1338 Cvar_Set ("fov","170");
1340 // intermission is always full screen
1341 if (cl.intermission)
1345 if (scr_viewsize.value >= 120)
1346 sb_lines = 0; // no status bar at all
1347 else if (scr_viewsize.value >= 110)
1348 sb_lines = 24; // no inventory
1353 r_refdef.colormask[0] = 1;
1354 r_refdef.colormask[1] = 1;
1355 r_refdef.colormask[2] = 1;
1359 if (cls.signon == SIGNONS)
1360 R_TimeReport("other");
1362 CL_SetupScreenSize();
1366 if (cls.signon == SIGNONS)
1367 R_TimeReport("setup");
1369 //FIXME: force menu if nothing else to look at?
1370 //if (key_dest == key_game && cls.signon != SIGNONS && cls.state == ca_disconnected)
1372 if (cls.signon == SIGNONS)
1377 if (!r_letterbox.value)
1380 SCR_CheckDrawCenterString();
1386 if (cls.signon == SIGNONS)
1390 R_TimeReport_Start();
1392 R_Shadow_EditLights_DrawSelectedLightProperties();
1399 void CL_Screen_NewMap(void)