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 string_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 STRING_COLORS_COUNT (sizeof(string_colors) / sizeof(vec4_t))
75 // color is read and changed in the end
76 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 *start, *current;
83 if( !outcolor || *outcolor == -1 ) {
84 colorindex = STRING_COLOR_DEFAULT;
86 colorindex = *outcolor;
88 color = string_colors[colorindex];
93 len = min( maxlen, (signed) strlen( text ) );
95 start = current = text;
97 // check for color control char
98 if( *current == STRING_COLOR_TAG ) {
105 // display the tag char?
106 if( *current == STRING_COLOR_TAG ) {
107 // only display one of the two
112 } else if( '0' <= *current && *current <= '9' ) {
115 colorindex = colorindex * 10 + (*current - '0');
116 // only read as long as it makes a valid index
117 if( colorindex >= STRING_COLORS_COUNT ) {
118 // undo the last operation
124 } while( len > 0 && '0' <= *current && *current <= '9' );
126 color = string_colors[colorindex];
127 // we jump over the color tag
131 // go on and read normal text in until the next control char
132 while( len > 0 && *current != STRING_COLOR_TAG ) {
137 if( start != current ) {
139 DrawQ_String( x, y, start, current - start, scalex, scaley, basered * color[0], basegreen * color[1], baseblue * color[2], basealpha * color[3], flags );
140 // update x to be at the new start position
141 x += (current - start) * scalex;
142 // set start accordingly
147 // return the last colorindex
149 *outcolor = colorindex;
154 ===============================================================================
158 ===============================================================================
161 char scr_centerstring[1024];
162 float scr_centertime_start; // for slow victory printing
163 float scr_centertime_off;
164 int scr_center_lines;
166 int scr_erase_center;
172 Called for important messages that should stay in the center of the screen
176 void SCR_CenterPrint(char *str)
178 strlcpy (scr_centerstring, str, sizeof (scr_centerstring));
179 scr_centertime_off = scr_centertime.value;
180 scr_centertime_start = cl.time;
182 // count the number of lines for centering
183 scr_center_lines = 1;
193 void SCR_DrawCenterString (void)
200 // the finale prints the characters one at a time
202 remaining = scr_printspeed.value * (cl.time - scr_centertime_start);
206 scr_erase_center = 0;
207 start = scr_centerstring;
209 if (scr_center_lines <= 4)
210 y = vid_conheight.integer*0.35;
216 // scan the width of the line
217 for (l=0 ; l<vid_conwidth.integer/8 ; l++)
218 if (start[l] == '\n' || !start[l])
220 x = (vid_conwidth.integer - l*8)/2;
225 DrawQ_String(x, y, start, l, 8, 8, 1, 1, 1, 1, 0);
233 while (*start && *start != '\n')
238 start++; // skip the \n
242 void SCR_CheckDrawCenterString (void)
244 if (scr_center_lines > scr_erase_lines)
245 scr_erase_lines = scr_center_lines;
247 scr_centertime_off -= host_frametime;
249 // don't draw if this is a normal stats-screen intermission,
250 // only if it is not an intermission, or a finale intermission
251 if (cl.intermission == 1)
253 if (scr_centertime_off <= 0 && !cl.intermission)
255 if (key_dest != key_game)
258 SCR_DrawCenterString ();
266 void SCR_DrawTurtle (void)
270 if (cls.state != ca_connected)
273 if (!scr_showturtle.integer)
276 if (host_frametime < 0.1)
286 DrawQ_Pic (0, 0, "gfx/turtle", 0, 0, 1, 1, 1, 1, 0);
294 void SCR_DrawNet (void)
296 if (cls.state != ca_connected)
298 if (realtime - cl.last_received_message < 0.3)
300 if (cls.demoplayback)
303 DrawQ_Pic (64, 0, "gfx/net", 0, 0, 1, 1, 1, 1, 0);
311 void SCR_DrawPause (void)
315 if (cls.state != ca_connected)
318 if (!scr_showpause.integer) // turn off for screenshots
324 pic = Draw_CachePic ("gfx/pause", true);
325 DrawQ_Pic ((vid_conwidth.integer - pic->width)/2, (vid_conheight.integer - pic->height)/2, "gfx/pause", 0, 0, 1, 1, 1, 1, 0);
332 //=============================================================================
337 SCR_SetUpToDrawConsole
340 void SCR_SetUpToDrawConsole (void)
342 // lines of console to display
347 if (key_dest == key_game && cls.signon != SIGNONS && scr_conforcewhiledisconnected.integer)
348 key_consoleactive |= KEY_CONSOLEACTIVE_FORCED;
350 key_consoleactive &= ~KEY_CONSOLEACTIVE_FORCED;
352 // decide on the height of the console
353 if (key_consoleactive & KEY_CONSOLEACTIVE_USER)
354 conlines = vid_conheight.integer/2; // half screen
356 conlines = 0; // none visible
358 if (scr_conspeed.value)
360 if (scr_con_current > conlines)
362 scr_con_current -= scr_conspeed.value*host_realframetime;
363 if (scr_con_current < conlines)
364 scr_con_current = conlines;
367 else if (scr_con_current < conlines)
369 scr_con_current += scr_conspeed.value*host_realframetime;
370 if (scr_con_current > conlines)
371 scr_con_current = conlines;
375 scr_con_current = conlines;
383 void SCR_DrawConsole (void)
385 if (key_consoleactive & KEY_CONSOLEACTIVE_FORCED)
388 Con_DrawConsole (vid_conheight.integer);
390 else if (scr_con_current)
391 Con_DrawConsole (scr_con_current);
395 if (key_dest == key_game || key_dest == key_message)
396 Con_DrawNotify (); // only draw notify in game
402 SCR_BeginLoadingPlaque
406 void SCR_BeginLoadingPlaque (void)
410 SCR_UpdateLoadingScreen();
413 //=============================================================================
415 char r_speeds_string[1024];
416 int speedstringcount, r_timereport_active;
417 double r_timereport_temp = 0, r_timereport_current = 0, r_timereport_start = 0;
419 void R_TimeReport(char *desc)
425 if (!r_timereport_active || r_showtrispass)
428 r_timereport_temp = r_timereport_current;
429 r_timereport_current = Sys_DoubleTime();
430 t = (int) ((r_timereport_current - r_timereport_temp) * 1000000.0);
432 dpsnprintf(tempbuf, sizeof(tempbuf), "%8i %s", t, desc);
433 length = strlen(tempbuf);
435 tempbuf[length++] = ' ';
437 if (speedstringcount + length > (vid_conwidth.integer / 8))
439 strlcat(r_speeds_string, "\n", sizeof(r_speeds_string));
440 speedstringcount = 0;
442 // skip the space at the beginning if it's the first on the line
443 if (speedstringcount == 0)
445 strlcat(r_speeds_string, tempbuf + 1, sizeof(r_speeds_string));
446 speedstringcount = length - 1;
450 strlcat(r_speeds_string, tempbuf, sizeof(r_speeds_string));
451 speedstringcount += length;
455 void R_TimeReport_Start(void)
457 r_timereport_active = r_speeds.integer && cls.signon == SIGNONS && cls.state == ca_connected;
458 r_speeds_string[0] = 0;
459 if (r_timereport_active)
461 speedstringcount = 0;
462 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]);
463 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);
464 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);
465 sprintf(r_speeds_string + strlen(r_speeds_string), "%6i modeltris%6i meshs%6i meshtris\n", c_alias_polys, c_meshs, c_meshelements / 3);
466 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);
467 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);
468 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);
469 sprintf(r_speeds_string + strlen(r_speeds_string), "precomputed: %6i shadowmeshes%6i shadowtris\n", c_rtcached_shadowmeshes, c_rtcached_shadowtris);
486 c_rt_shadowmeshes = 0;
488 c_rt_lightmeshes = 0;
490 c_rtcached_shadowmeshes = 0;
491 c_rtcached_shadowtris = 0;
494 c_bloomcopypixels = 0;
496 c_bloomdrawpixels = 0;
498 r_timereport_start = Sys_DoubleTime();
502 void R_TimeReport_End(void)
504 r_timereport_current = r_timereport_start;
505 R_TimeReport("total");
507 if (r_timereport_active)
511 for (i = 0;r_speeds_string[i];i++)
512 if (r_speeds_string[i] == '\n')
514 y = vid_conheight.integer - sb_lines - lines * 8;
516 DrawQ_Fill(0, y, vid_conwidth.integer, lines * 8, 0, 0, 0, 0.5, 0);
517 while (r_speeds_string[i])
520 while (r_speeds_string[i] && r_speeds_string[i] != '\n')
523 DrawQ_String(0, y, r_speeds_string + j, i - j, 8, 8, 1, 1, 1, 1, 0);
524 if (r_speeds_string[i] == '\n')
538 void SCR_SizeUp_f (void)
540 Cvar_SetValue ("viewsize",scr_viewsize.value+10);
551 void SCR_SizeDown_f (void)
553 Cvar_SetValue ("viewsize",scr_viewsize.value-10);
556 void CL_Screen_Init(void)
558 Cvar_RegisterVariable (&scr_fov);
559 Cvar_RegisterVariable (&scr_viewsize);
560 Cvar_RegisterVariable (&scr_conspeed);
561 Cvar_RegisterVariable (&scr_conalpha);
562 Cvar_RegisterVariable (&scr_conbrightness);
563 Cvar_RegisterVariable (&scr_conforcewhiledisconnected);
564 Cvar_RegisterVariable (&scr_showram);
565 Cvar_RegisterVariable (&scr_showturtle);
566 Cvar_RegisterVariable (&scr_showpause);
567 Cvar_RegisterVariable (&scr_centertime);
568 Cvar_RegisterVariable (&scr_printspeed);
569 Cvar_RegisterVariable (&vid_conwidth);
570 Cvar_RegisterVariable (&vid_conheight);
571 Cvar_RegisterVariable (&vid_pixelaspect);
572 Cvar_RegisterVariable (&scr_screenshot_jpeg);
573 Cvar_RegisterVariable (&scr_screenshot_jpeg_quality);
574 Cvar_RegisterVariable (&scr_screenshot_gamma);
575 Cvar_RegisterVariable (&cl_capturevideo);
576 Cvar_RegisterVariable (&cl_capturevideo_sound);
577 Cvar_RegisterVariable (&cl_capturevideo_fps);
578 Cvar_RegisterVariable (&cl_capturevideo_rawrgb);
579 Cvar_RegisterVariable (&cl_capturevideo_rawyv12);
580 Cvar_RegisterVariable (&r_textshadow);
581 Cvar_RegisterVariable (&r_letterbox);
583 Cmd_AddCommand ("sizeup",SCR_SizeUp_f);
584 Cmd_AddCommand ("sizedown",SCR_SizeDown_f);
585 Cmd_AddCommand ("screenshot",SCR_ScreenShot_f);
586 Cmd_AddCommand ("envmap", R_Envmap_f);
588 scr_initialized = true;
591 void DrawQ_Clear(void)
593 r_refdef.drawqueuesize = 0;
596 static int picelements[6] = {0, 1, 2, 0, 2, 3};
597 void DrawQ_Pic(float x, float y, const char *picname, float width, float height, float red, float green, float blue, float alpha, int flags)
599 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);
602 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)
607 if (alpha < (1.0f / 255.0f))
610 len = strlen(string);
612 for (len = 0;len < maxlen && string[len];len++);
613 for (;len > 0 && string[0] == ' ';string++, x += scalex, len--);
614 for (;len > 0 && string[len - 1] == ' ';len--);
617 if (x >= vid_conwidth.integer || y >= vid_conheight.integer || x < (-scalex * len) || y < (-scaley))
619 size = sizeof(*dq) + ((len + 1 + 3) & ~3);
620 if (r_refdef.drawqueuesize + size > r_refdef.maxdrawqueuesize)
622 red = bound(0, red, 1);
623 green = bound(0, green, 1);
624 blue = bound(0, blue, 1);
625 alpha = bound(0, alpha, 1);
626 dq = (void *)(r_refdef.drawqueue + r_refdef.drawqueuesize);
628 dq->command = DRAWQUEUE_STRING;
630 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));
635 out = (char *)(dq + 1);
636 memcpy(out, string, len);
638 r_refdef.drawqueuesize += dq->size;
641 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)
643 if (r_textshadow.integer)
644 DrawQ_String_Real(x+scalex*0.25,y+scaley*0.25,string,maxlen,scalex,scaley,0,0,0,alpha*0.8,flags);
646 DrawQ_String_Real(x,y,string,maxlen,scalex,scaley,red,green,blue,alpha,flags);
651 void DrawQ_Fill (float x, float y, float w, float h, float red, float green, float blue, float alpha, int flags)
653 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);
656 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)
660 drawqueuemesh_t mesh;
661 memset(&mesh, 0, sizeof(mesh));
662 if (picname && picname[0])
664 pic = Draw_CachePic(picname, false);
668 height = pic->height;
669 mesh.texture = pic->tex;
671 mesh.num_triangles = 2;
672 mesh.num_vertices = 4;
673 mesh.data_element3i = picelements;
674 mesh.data_vertex3f = floats;
675 mesh.data_texcoord2f = floats + 12;
676 mesh.data_color4f = floats + 20;
677 memset(floats, 0, sizeof(floats));
678 mesh.data_vertex3f[0] = mesh.data_vertex3f[9] = x;
679 mesh.data_vertex3f[1] = mesh.data_vertex3f[4] = y;
680 mesh.data_vertex3f[3] = mesh.data_vertex3f[6] = x + width;
681 mesh.data_vertex3f[7] = mesh.data_vertex3f[10] = y + height;
682 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;
683 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;
684 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;
685 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;
686 DrawQ_Mesh (&mesh, flags);
689 void DrawQ_Mesh (drawqueuemesh_t *mesh, int flags)
696 size += sizeof(drawqueuemesh_t);
697 size += sizeof(int[3]) * mesh->num_triangles;
698 size += sizeof(float[3]) * mesh->num_vertices;
699 size += sizeof(float[2]) * mesh->num_vertices;
700 size += sizeof(float[4]) * mesh->num_vertices;
701 if (r_refdef.drawqueuesize + size > r_refdef.maxdrawqueuesize)
703 dq = (void *)(r_refdef.drawqueue + r_refdef.drawqueuesize);
705 dq->command = DRAWQUEUE_MESH;
712 p = (void *)(dq + 1);
713 m = p;p = (qbyte*)p + sizeof(drawqueuemesh_t);
714 m->num_triangles = mesh->num_triangles;
715 m->num_vertices = mesh->num_vertices;
716 m->texture = mesh->texture;
717 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]);
718 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]);
719 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]);
720 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]);
721 r_refdef.drawqueuesize += dq->size;
724 void DrawQ_SetClipArea(float x, float y, float width, float height)
727 if(r_refdef.drawqueuesize + (int)sizeof(*dq) > r_refdef.maxdrawqueuesize)
729 Con_DPrint("DrawQueue full !\n");
732 dq = (void*) (r_refdef.drawqueue + r_refdef.drawqueuesize);
733 dq->size = sizeof(*dq);
734 dq->command = DRAWQUEUE_SETCLIP;
742 r_refdef.drawqueuesize += dq->size;
745 void DrawQ_ResetClipArea(void)
748 if(r_refdef.drawqueuesize + (int)sizeof(*dq) > r_refdef.maxdrawqueuesize)
750 Con_DPrint("DrawQueue full !\n");
753 dq = (void*) (r_refdef.drawqueue + r_refdef.drawqueuesize);
754 dq->size = sizeof(*dq);
755 dq->command = DRAWQUEUE_RESETCLIP;
763 r_refdef.drawqueuesize += dq->size;
771 void SCR_ScreenShot_f (void)
773 static int shotnumber;
774 static char oldname[MAX_QPATH];
775 char base[MAX_QPATH];
776 char filename[MAX_QPATH];
780 qboolean jpeg = (scr_screenshot_jpeg.integer != 0);
782 sprintf (base, "screenshots/%s", scr_screenshot_name.string);
784 if (strcmp (oldname, scr_screenshot_name.string))
786 sprintf(oldname, "%s", scr_screenshot_name.string);
790 // find a file name to save it to
791 for (;shotnumber < 1000000;shotnumber++)
792 if (!FS_SysFileExists(va("%s/%s%06d.tga", fs_gamedir, base, shotnumber)) && !FS_SysFileExists(va("%s/%s%06d.jpg", fs_gamedir, base, shotnumber)))
794 if (shotnumber >= 1000000)
796 Con_Print("SCR_ScreenShot_f: Couldn't create the image file\n");
800 sprintf(filename, "%s%06d.%s", base, shotnumber, jpeg ? "jpg" : "tga");
802 buffer1 = Mem_Alloc(tempmempool, vid.width * vid.height * 3);
803 buffer2 = Mem_Alloc(tempmempool, vid.width * vid.height * 3);
804 buffer3 = Mem_Alloc(tempmempool, vid.width * vid.height * 3 + 18);
806 if (SCR_ScreenShot (filename, buffer1, buffer2, buffer3, 0, 0, vid.width, vid.height, false, false, false, jpeg, true))
807 Con_Printf("Wrote %s\n", filename);
809 Con_Printf("unable to write %s\n", filename);
818 typedef enum capturevideoformat_e
820 CAPTUREVIDEOFORMAT_TARGA,
821 CAPTUREVIDEOFORMAT_JPEG,
822 CAPTUREVIDEOFORMAT_RAWRGB,
823 CAPTUREVIDEOFORMAT_RAWYV12
825 capturevideoformat_t;
827 qboolean cl_capturevideo_active = false;
828 capturevideoformat_t cl_capturevideo_format;
829 static double cl_capturevideo_starttime = 0;
830 double cl_capturevideo_framerate = 0;
831 static int cl_capturevideo_soundrate = 0;
832 static int cl_capturevideo_frame = 0;
833 static qbyte *cl_capturevideo_buffer = NULL;
834 static qfile_t *cl_capturevideo_videofile = NULL;
835 qfile_t *cl_capturevideo_soundfile = NULL;
836 static short cl_capturevideo_rgbtoyuvscaletable[3][3][256];
837 static unsigned char cl_capturevideo_yuvnormalizetable[3][256];
838 //static unsigned char cl_capturevideo_rgbgammatable[3][256];
840 void SCR_CaptureVideo_BeginVideo(void)
845 if (cl_capturevideo_active)
847 // soundrate is figured out on the first SoundFrame
848 cl_capturevideo_active = true;
849 cl_capturevideo_starttime = Sys_DoubleTime();
850 cl_capturevideo_framerate = bound(1, cl_capturevideo_fps.value, 1000);
851 cl_capturevideo_soundrate = 0;
852 cl_capturevideo_frame = 0;
853 cl_capturevideo_buffer = Mem_Alloc(tempmempool, vid.width * vid.height * (3+3+3) + 18);
854 gamma = 1.0/scr_screenshot_gamma.value;
857 for (i = 0;i < 256;i++)
859 unsigned char j = (unsigned char)bound(0, 255*pow(i/255.0, gamma), 255);
860 cl_capturevideo_rgbgammatable[0][i] = j;
861 cl_capturevideo_rgbgammatable[1][i] = j;
862 cl_capturevideo_rgbgammatable[2][i] = j;
866 R = Y + 1.4075 * (Cr - 128);
867 G = Y + -0.3455 * (Cb - 128) + -0.7169 * (Cr - 128);
868 B = Y + 1.7790 * (Cb - 128);
869 Y = R * .299 + G * .587 + B * .114;
870 Cb = R * -.169 + G * -.332 + B * .500 + 128.;
871 Cr = R * .500 + G * -.419 + B * -.0813 + 128.;
873 for (i = 0;i < 256;i++)
875 g = 255*pow(i/255.0, gamma);
876 // Y weights from RGB
877 cl_capturevideo_rgbtoyuvscaletable[0][0][i] = (short)(g * 0.299);
878 cl_capturevideo_rgbtoyuvscaletable[0][1][i] = (short)(g * 0.587);
879 cl_capturevideo_rgbtoyuvscaletable[0][2][i] = (short)(g * 0.114);
880 // Cb weights from RGB
881 cl_capturevideo_rgbtoyuvscaletable[1][0][i] = (short)(g * -0.169);
882 cl_capturevideo_rgbtoyuvscaletable[1][1][i] = (short)(g * -0.332);
883 cl_capturevideo_rgbtoyuvscaletable[1][2][i] = (short)(g * 0.500);
884 // Cr weights from RGB
885 cl_capturevideo_rgbtoyuvscaletable[2][0][i] = (short)(g * 0.500);
886 cl_capturevideo_rgbtoyuvscaletable[2][1][i] = (short)(g * -0.419);
887 cl_capturevideo_rgbtoyuvscaletable[2][2][i] = (short)(g * -0.0813);
888 // range reduction of YCbCr to valid signal range
889 cl_capturevideo_yuvnormalizetable[0][i] = 16 + i * (236-16) / 256;
890 cl_capturevideo_yuvnormalizetable[1][i] = 16 + i * (240-16) / 256;
891 cl_capturevideo_yuvnormalizetable[2][i] = 16 + i * (240-16) / 256;
894 if (cl_capturevideo_rawrgb.integer)
896 cl_capturevideo_format = CAPTUREVIDEOFORMAT_RAWRGB;
897 cl_capturevideo_videofile = FS_Open ("video/dpvideo.rgb", "wb", false, true);
899 else if (cl_capturevideo_rawyv12.integer)
901 cl_capturevideo_format = CAPTUREVIDEOFORMAT_RAWYV12;
902 cl_capturevideo_videofile = FS_Open ("video/dpvideo.yv12", "wb", false, true);
904 else if (scr_screenshot_jpeg.integer)
906 cl_capturevideo_format = CAPTUREVIDEOFORMAT_JPEG;
907 cl_capturevideo_videofile = NULL;
911 cl_capturevideo_format = CAPTUREVIDEOFORMAT_TARGA;
912 cl_capturevideo_videofile = NULL;
915 if (cl_capturevideo_sound.integer)
917 cl_capturevideo_soundfile = FS_Open ("video/dpvideo.wav", "wb", false, true);
918 // wave header will be filled out when video ends
920 FS_Write (cl_capturevideo_soundfile, out, 44);
923 cl_capturevideo_soundfile = NULL;
926 void SCR_CaptureVideo_EndVideo(void)
930 if (!cl_capturevideo_active)
932 cl_capturevideo_active = false;
934 if (cl_capturevideo_videofile)
936 FS_Close(cl_capturevideo_videofile);
937 cl_capturevideo_videofile = NULL;
940 // finish the wave file
941 if (cl_capturevideo_soundfile)
943 i = FS_Tell (cl_capturevideo_soundfile);
944 //"RIFF", (int) unknown (chunk size), "WAVE",
945 //"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
946 //"data", (int) unknown (chunk size)
947 memcpy (out, "RIFF****WAVEfmt \x10\x00\x00\x00\x01\x00\x02\x00********\x04\x00\x10\0data****", 44);
948 // the length of the whole RIFF chunk
951 out[5] = (n >> 8) & 0xFF;
952 out[6] = (n >> 16) & 0xFF;
953 out[7] = (n >> 24) & 0xFF;
955 n = cl_capturevideo_soundrate;
956 out[24] = (n) & 0xFF;
957 out[25] = (n >> 8) & 0xFF;
958 out[26] = (n >> 16) & 0xFF;
959 out[27] = (n >> 24) & 0xFF;
960 // bytes per second (rate * channels * bytes per channel)
961 n = cl_capturevideo_soundrate * 2 * 2;
962 out[28] = (n) & 0xFF;
963 out[29] = (n >> 8) & 0xFF;
964 out[30] = (n >> 16) & 0xFF;
965 out[31] = (n >> 24) & 0xFF;
966 // the length of the data chunk
968 out[40] = (n) & 0xFF;
969 out[41] = (n >> 8) & 0xFF;
970 out[42] = (n >> 16) & 0xFF;
971 out[43] = (n >> 24) & 0xFF;
972 FS_Seek (cl_capturevideo_soundfile, 0, SEEK_SET);
973 FS_Write (cl_capturevideo_soundfile, out, 44);
974 FS_Close (cl_capturevideo_soundfile);
975 cl_capturevideo_soundfile = NULL;
978 if (cl_capturevideo_buffer)
980 Mem_Free (cl_capturevideo_buffer);
981 cl_capturevideo_buffer = NULL;
984 cl_capturevideo_starttime = 0;
985 cl_capturevideo_framerate = 0;
986 cl_capturevideo_frame = 0;
989 qboolean SCR_CaptureVideo_VideoFrame(int newframenum)
991 int x = 0, y = 0, width = vid.width, height = vid.height;
992 unsigned char *b, *out;
994 int outoffset = (width/2)*(height/2);
995 //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);
996 // speed is critical here, so do saving as directly as possible
997 switch (cl_capturevideo_format)
999 case CAPTUREVIDEOFORMAT_RAWYV12:
1000 // FIXME: width/height must be multiple of 2, enforce this?
1001 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cl_capturevideo_buffer);
1003 // process one line at a time, and CbCr every other line at 2 pixel intervals
1004 for (y = 0;y < height;y++)
1007 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++)
1008 *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]]];
1011 // 2x2 Cb and Cr planes
1013 // low quality, no averaging
1014 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++)
1017 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];
1019 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];
1022 // high quality, averaging
1023 int inpitch = width*3;
1024 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++)
1026 int blockr, blockg, blockb;
1027 blockr = (b[0] + b[3] + b[inpitch+0] + b[inpitch+3]) >> 2;
1028 blockg = (b[1] + b[4] + b[inpitch+1] + b[inpitch+4]) >> 2;
1029 blockb = (b[2] + b[5] + b[inpitch+2] + b[inpitch+5]) >> 2;
1031 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];
1033 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];
1038 for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
1039 if (!FS_Write (cl_capturevideo_videofile, cl_capturevideo_buffer + width*height*3, width*height+(width/2)*(height/2)*2))
1042 case CAPTUREVIDEOFORMAT_RAWRGB:
1043 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cl_capturevideo_buffer);
1045 for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
1046 if (!FS_Write (cl_capturevideo_videofile, cl_capturevideo_buffer, width*height*3))
1049 case CAPTUREVIDEOFORMAT_JPEG:
1050 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cl_capturevideo_buffer);
1052 for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
1054 sprintf(filename, "video/dp%06d.jpg", cl_capturevideo_frame);
1055 if (!JPEG_SaveImage_preflipped (filename, width, height, cl_capturevideo_buffer))
1059 case CAPTUREVIDEOFORMAT_TARGA:
1060 //return Image_WriteTGARGB_preflipped (filename, width, height, cl_capturevideo_buffer, cl_capturevideo_buffer + vid.width * vid.height * 3, );
1061 memset (cl_capturevideo_buffer, 0, 18);
1062 cl_capturevideo_buffer[2] = 2; // uncompressed type
1063 cl_capturevideo_buffer[12] = (width >> 0) & 0xFF;
1064 cl_capturevideo_buffer[13] = (width >> 8) & 0xFF;
1065 cl_capturevideo_buffer[14] = (height >> 0) & 0xFF;
1066 cl_capturevideo_buffer[15] = (height >> 8) & 0xFF;
1067 cl_capturevideo_buffer[16] = 24; // pixel size
1068 qglReadPixels (x, y, width, height, GL_BGR, GL_UNSIGNED_BYTE, cl_capturevideo_buffer + 18);
1070 for (;cl_capturevideo_frame < newframenum;cl_capturevideo_frame++)
1072 sprintf(filename, "video/dp%06d.tga", cl_capturevideo_frame);
1073 if (!FS_WriteFile (filename, cl_capturevideo_buffer, width*height*3 + 18))
1082 void SCR_CaptureVideo_SoundFrame(qbyte *bufstereo16le, size_t length, int rate)
1084 if (!cl_capturevideo_soundfile)
1086 cl_capturevideo_soundrate = rate;
1087 if (FS_Write (cl_capturevideo_soundfile, bufstereo16le, 4 * length) < 4 * length)
1089 Cvar_SetValueQuick(&cl_capturevideo, 0);
1090 Con_Printf("video sound saving failed on frame %i, out of disk space? stopping video capture.\n", cl_capturevideo_frame);
1091 SCR_CaptureVideo_EndVideo();
1095 void SCR_CaptureVideo(void)
1098 if (cl_capturevideo.integer && r_render.integer)
1100 if (!cl_capturevideo_active)
1101 SCR_CaptureVideo_BeginVideo();
1102 if (cl_capturevideo_framerate != cl_capturevideo_fps.value)
1104 Con_Printf("You can not change the video framerate while recording a video.\n");
1105 Cvar_SetValueQuick(&cl_capturevideo_fps, cl_capturevideo_framerate);
1107 if (cl_capturevideo_soundfile)
1109 // preserve sound sync by duplicating frames when running slow
1110 newframenum = (Sys_DoubleTime() - cl_capturevideo_starttime) * cl_capturevideo_framerate;
1113 newframenum = cl_capturevideo_frame + 1;
1114 // if falling behind more than one second, stop
1115 if (newframenum - cl_capturevideo_frame > (int)ceil(cl_capturevideo_framerate))
1117 Cvar_SetValueQuick(&cl_capturevideo, 0);
1118 Con_Printf("video saving failed on frame %i, your machine is too slow for this capture speed.\n", cl_capturevideo_frame);
1119 SCR_CaptureVideo_EndVideo();
1123 if (!SCR_CaptureVideo_VideoFrame(newframenum))
1125 Cvar_SetValueQuick(&cl_capturevideo, 0);
1126 Con_Printf("video saving failed on frame %i, out of disk space? stopping video capture.\n", cl_capturevideo_frame);
1127 SCR_CaptureVideo_EndVideo();
1130 else if (cl_capturevideo_active)
1131 SCR_CaptureVideo_EndVideo();
1138 Grab six views for environment mapping tests
1145 qboolean flipx, flipy, flipdiagonaly;
1149 {{ 0, 0, 0}, "rt", false, false, false},
1150 {{ 0, 270, 0}, "ft", false, false, false},
1151 {{ 0, 180, 0}, "lf", false, false, false},
1152 {{ 0, 90, 0}, "bk", false, false, false},
1153 {{-90, 180, 0}, "up", true, true, false},
1154 {{ 90, 180, 0}, "dn", true, true, false},
1156 {{ 0, 0, 0}, "px", true, true, true},
1157 {{ 0, 90, 0}, "py", false, true, false},
1158 {{ 0, 180, 0}, "nx", false, false, true},
1159 {{ 0, 270, 0}, "ny", true, false, false},
1160 {{-90, 180, 0}, "pz", false, false, true},
1161 {{ 90, 180, 0}, "nz", false, false, true}
1164 static void R_Envmap_f (void)
1167 char filename[256], basename[256];
1172 if (Cmd_Argc() != 3)
1174 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");
1178 strlcpy (basename, Cmd_Argv(1), sizeof (basename));
1179 size = atoi(Cmd_Argv(2));
1180 if (size != 128 && size != 256 && size != 512 && size != 1024)
1182 Con_Print("envmap: size must be one of 128, 256, 512, or 1024\n");
1185 if (size > vid.width || size > vid.height)
1187 Con_Print("envmap: your resolution is not big enough to render that size\n");
1195 r_refdef.width = size;
1196 r_refdef.height = size;
1198 r_refdef.fov_x = 90;
1199 r_refdef.fov_y = 90;
1201 buffer1 = Mem_Alloc(tempmempool, size * size * 3);
1202 buffer2 = Mem_Alloc(tempmempool, size * size * 3);
1203 buffer3 = Mem_Alloc(tempmempool, size * size * 3 + 18);
1205 for (j = 0;j < 12;j++)
1207 sprintf(filename, "env/%s%s.tga", basename, envmapinfo[j].name);
1208 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);
1213 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);
1223 //=============================================================================
1225 // LordHavoc: SHOWLMP stuff
1226 #define SHOWLMP_MAXLABELS 256
1227 typedef struct showlmp_s
1237 showlmp_t showlmp[SHOWLMP_MAXLABELS];
1239 void SHOWLMP_decodehide(void)
1243 lmplabel = MSG_ReadString();
1244 for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1245 if (showlmp[i].isactive && strcmp(showlmp[i].label, lmplabel) == 0)
1247 showlmp[i].isactive = false;
1252 void SHOWLMP_decodeshow(void)
1255 qbyte lmplabel[256], picname[256];
1257 strlcpy (lmplabel,MSG_ReadString(), sizeof (lmplabel));
1258 strlcpy (picname, MSG_ReadString(), sizeof (picname));
1259 if (gamemode == GAME_NEHAHRA) // LordHavoc: nasty old legacy junk
1266 x = MSG_ReadShort();
1267 y = MSG_ReadShort();
1270 for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1271 if (showlmp[i].isactive)
1273 if (strcmp(showlmp[i].label, lmplabel) == 0)
1276 break; // drop out to replace it
1279 else if (k < 0) // find first empty one to replace
1282 return; // none found to replace
1283 // change existing one
1284 showlmp[k].isactive = true;
1285 strlcpy (showlmp[k].label, lmplabel, sizeof (showlmp[k].label));
1286 strlcpy (showlmp[k].pic, picname, sizeof (showlmp[k].pic));
1291 void SHOWLMP_drawall(void)
1294 for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1295 if (showlmp[i].isactive)
1296 DrawQ_Pic(showlmp[i].x, showlmp[i].y, showlmp[i].pic, 0, 0, 1, 1, 1, 1, 0);
1299 void SHOWLMP_clear(void)
1302 for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1303 showlmp[i].isactive = false;
1306 void CL_SetupScreenSize(void)
1308 float conwidth, conheight;
1310 VID_UpdateGamma(false);
1312 conwidth = bound(320, vid_conwidth.value, 2048);
1313 conheight = bound(200, vid_conheight.value, 1536);
1314 if (vid_conwidth.value != conwidth)
1315 Cvar_SetValue("vid_conwidth", conwidth);
1316 if (vid_conheight.value != conheight)
1317 Cvar_SetValue("vid_conheight", conheight);
1319 vid_conwidth.integer = vid_conwidth.integer;
1320 vid_conheight.integer = vid_conheight.integer;
1322 SCR_SetUpToDrawConsole();
1325 extern void R_Shadow_EditLights_DrawSelectedLightProperties(void);
1326 void CL_UpdateScreen(void)
1328 if (!scr_initialized || !con_initialized || vid_hidden)
1329 return; // not initialized yet
1331 // don't allow cheats in multiplayer
1332 if (!cl.islocalgame && cl.worldmodel)
1334 if (r_fullbright.integer != 0)
1335 Cvar_Set ("r_fullbright", "0");
1336 if (r_ambient.value != 0)
1337 Cvar_Set ("r_ambient", "0");
1341 if (scr_viewsize.value < 30)
1342 Cvar_Set ("viewsize","30");
1343 if (scr_viewsize.value > 120)
1344 Cvar_Set ("viewsize","120");
1346 // bound field of view
1347 if (scr_fov.value < 1)
1348 Cvar_Set ("fov","1");
1349 if (scr_fov.value > 170)
1350 Cvar_Set ("fov","170");
1352 // intermission is always full screen
1353 if (cl.intermission)
1357 if (scr_viewsize.value >= 120)
1358 sb_lines = 0; // no status bar at all
1359 else if (scr_viewsize.value >= 110)
1360 sb_lines = 24; // no inventory
1365 r_refdef.colormask[0] = 1;
1366 r_refdef.colormask[1] = 1;
1367 r_refdef.colormask[2] = 1;
1371 if (cls.signon == SIGNONS)
1372 R_TimeReport("other");
1374 CL_SetupScreenSize();
1378 if (cls.signon == SIGNONS)
1379 R_TimeReport("setup");
1381 //FIXME: force menu if nothing else to look at?
1382 //if (key_dest == key_game && cls.signon != SIGNONS && cls.state == ca_disconnected)
1384 if (cls.signon == SIGNONS)
1389 if (!r_letterbox.value)
1392 SCR_CheckDrawCenterString();
1398 if (cls.signon == SIGNONS)
1402 R_TimeReport_Start();
1404 R_Shadow_EditLights_DrawSelectedLightProperties();
1411 void CL_Screen_NewMap(void)