]> icculus.org git repositories - divverent/darkplaces.git/blob - cl_screen.c
Fixed the "nosound" cvar. Thanks to Willis for having reported this bug
[divverent/darkplaces.git] / cl_screen.c
1
2 #include "quakedef.h"
3 #include "cl_video.h"
4 #include "image.h"
5 #include "jpeg.h"
6 #include "cl_collision.h"
7 #include "csprogs.h"
8
9 cvar_t scr_viewsize = {CVAR_SAVE, "viewsize","100", "how large the view should be, 110 disables inventory bar, 120 disables status bar"};
10 cvar_t scr_fov = {CVAR_SAVE, "fov","90", "field of vision, 1-170 degrees, default 90, some players use 110-130"};       // 1 - 170
11 cvar_t scr_conalpha = {CVAR_SAVE, "scr_conalpha", "1", "opacity of console background"};
12 cvar_t scr_conbrightness = {CVAR_SAVE, "scr_conbrightness", "1", "brightness of console background (0 = black, 1 = image)"};
13 cvar_t scr_conforcewhiledisconnected = {0, "scr_conforcewhiledisconnected", "1", "forces fullscreen console while disconnected"};
14 cvar_t scr_menuforcewhiledisconnected = {0, "scr_menuforcewhiledisconnected", "0", "forces menu while disconnected"};
15 cvar_t scr_centertime = {0, "scr_centertime","2", "how long centerprint messages show"};
16 cvar_t scr_showram = {CVAR_SAVE, "showram","1", "show ram icon if low on surface cache memory (not used)"};
17 cvar_t scr_showturtle = {CVAR_SAVE, "showturtle","0", "show turtle icon when framerate is too low (not used)"};
18 cvar_t scr_showpause = {CVAR_SAVE, "showpause","1", "show pause icon when game is paused"};
19 cvar_t scr_showbrand = {0, "showbrand","0", "shows gfx/brand.tga in a corner of the screen (different values select different positions, including centered)"};
20 cvar_t scr_printspeed = {0, "scr_printspeed","8", "speed of intermission printing (episode end texts)"};
21 cvar_t vid_conwidth = {CVAR_SAVE, "vid_conwidth", "640", "virtual width of 2D graphics system"};
22 cvar_t vid_conheight = {CVAR_SAVE, "vid_conheight", "480", "virtual height of 2D graphics system"};
23 cvar_t vid_pixelheight = {CVAR_SAVE, "vid_pixelheight", "1", "adjusts vertical field of vision to account for non-square pixels (1280x1024 on a CRT monitor for example)"};
24 cvar_t scr_screenshot_jpeg = {CVAR_SAVE, "scr_screenshot_jpeg","1", "save jpeg instead of targa"};
25 cvar_t scr_screenshot_jpeg_quality = {CVAR_SAVE, "scr_screenshot_jpeg_quality","0.9", "image quality of saved jpeg"};
26 cvar_t scr_screenshot_gammaboost = {CVAR_SAVE, "scr_screenshot_gammaboost","1", "gamma correction on saved screenshots and videos, 1.0 saves unmodified images"};
27 // scr_screenshot_name is defined in fs.c
28 cvar_t cl_capturevideo = {0, "cl_capturevideo", "0", "enables saving of video to a file or files (default is .tga files, if scr_screenshot_jpeg is on it saves .jpg files (VERY SLOW), if any rawrgb or rawyv12 are on it saves those formats instead, note that scr_screenshot_gammaboost affects the brightness of the output)"};
29 cvar_t cl_capturevideo_fps = {0, "cl_capturevideo_fps", "30", "how many frames per second to save (29.97 for NTSC, 30 for typical PC video, 15 can be useful)"};
30 cvar_t cl_capturevideo_rawrgb = {0, "cl_capturevideo_rawrgb", "0", "saves a single .rgb video file containing raw RGB images (you'll need special processing tools to encode this to something more useful)"};
31 cvar_t cl_capturevideo_rawyv12 = {0, "cl_capturevideo_rawyv12", "0", "saves a single .yv12 video file containing raw YV12 (luma plane, then half resolution chroma planes, first chroma blue then chroma red, this is the format used internally by many encoders, some tools can read it directly)"};
32 cvar_t cl_capturevideo_number = {CVAR_SAVE, "cl_capturevideo_number", "1", "number to append to video filename, incremented each time a capture begins"};
33 cvar_t r_letterbox = {0, "r_letterbox", "0", "reduces vertical height of view to simulate a letterboxed movie effect (can be used by mods for cutscenes)"};
34 cvar_t r_stereo_separation = {0, "r_stereo_separation", "4", "separation of eyes in the world (try negative values too)"};
35 cvar_t r_stereo_sidebyside = {0, "r_stereo_sidebyside", "0", "side by side views (for those who can't afford glasses but can afford eye strain)"};
36 cvar_t r_stereo_redblue = {0, "r_stereo_redblue", "0", "red/blue anaglyph stereo glasses (note: most of these glasses are actually red/cyan, try that one too)"};
37 cvar_t r_stereo_redcyan = {0, "r_stereo_redcyan", "0", "red/cyan anaglyph stereo glasses, the kind given away at drive-in movies like Creature From The Black Lagoon In 3D"};
38 cvar_t r_stereo_redgreen = {0, "r_stereo_redgreen", "0", "red/green anaglyph stereo glasses (for those who don't mind yellow)"};
39 cvar_t scr_zoomwindow = {CVAR_SAVE, "scr_zoomwindow", "0", "displays a zoomed in overlay window"};
40 cvar_t scr_zoomwindow_viewsizex = {CVAR_SAVE, "scr_zoomwindow_viewsizex", "20", "horizontal viewsize of zoom window"};
41 cvar_t scr_zoomwindow_viewsizey = {CVAR_SAVE, "scr_zoomwindow_viewsizey", "20", "vertical viewsize of zoom window"};
42 cvar_t scr_zoomwindow_fov = {CVAR_SAVE, "scr_zoomwindow_fov", "20", "fov of zoom window"};
43
44
45 int jpeg_supported = false;
46
47 qboolean        scr_initialized;                // ready to draw
48
49 float           scr_con_current;
50
51 extern int      con_vislines;
52
53 void DrawCrosshair(int num);
54 static void SCR_ScreenShot_f (void);
55 static void R_Envmap_f (void);
56
57 // backend
58 void R_ClearScreen(void);
59
60 /*
61 ===============================================================================
62
63 CENTER PRINTING
64
65 ===============================================================================
66 */
67
68 char            scr_centerstring[MAX_INPUTLINE];
69 float           scr_centertime_start;   // for slow victory printing
70 float           scr_centertime_off;
71 int                     scr_center_lines;
72 int                     scr_erase_lines;
73 int                     scr_erase_center;
74
75 /*
76 ==============
77 SCR_CenterPrint
78
79 Called for important messages that should stay in the center of the screen
80 for a few moments
81 ==============
82 */
83 void SCR_CenterPrint(char *str)
84 {
85         strlcpy (scr_centerstring, str, sizeof (scr_centerstring));
86         scr_centertime_off = scr_centertime.value;
87         scr_centertime_start = cl.time;
88
89 // count the number of lines for centering
90         scr_center_lines = 1;
91         while (*str)
92         {
93                 if (*str == '\n')
94                         scr_center_lines++;
95                 str++;
96         }
97 }
98
99
100 void SCR_DrawCenterString (void)
101 {
102         char    *start;
103         int             l;
104         int             x, y;
105         int             remaining;
106         int             color;
107
108 // the finale prints the characters one at a time
109         if (cl.intermission)
110                 remaining = (int)(scr_printspeed.value * (cl.time - scr_centertime_start));
111         else
112                 remaining = 9999;
113
114         scr_erase_center = 0;
115         start = scr_centerstring;
116
117         if (remaining < 1)
118                 return;
119
120         if (scr_center_lines <= 4)
121                 y = (int)(vid_conheight.integer*0.35);
122         else
123                 y = 48;
124
125         color = -1;
126         do
127         {
128                 // scan the number of characters on the line, not counting color codes
129                 int chars = 0;
130                 for (l=0 ; l<vid_conwidth.integer/8 ; l++)
131                 {
132                         if (start[l] == '\n' || !start[l])
133                                 break;
134                         // color codes add no visible characters, so don't count them
135                         if (start[l] == '^' && (start[l+1] >= '0' && start[l+1] <= '9'))
136                                 l++;
137                         else
138                                 chars++;
139                 }
140                 x = (vid_conwidth.integer - chars*8)/2;
141                 if (l > 0)
142                 {
143                         if (remaining < l)
144                                 l = remaining;
145                         DrawQ_ColoredString(x, y, start, l, 8, 8, 1, 1, 1, 1, 0, &color);
146                         remaining -= l;
147                         if (remaining <= 0)
148                                 return;
149                 }
150
151                 y += 8;
152
153                 while (*start && *start != '\n')
154                         start++;
155
156                 if (!*start)
157                         break;
158                 start++;                // skip the \n
159         } while (1);
160 }
161
162 void SCR_CheckDrawCenterString (void)
163 {
164         if (scr_center_lines > scr_erase_lines)
165                 scr_erase_lines = scr_center_lines;
166
167         scr_centertime_off -= cl.realframetime;
168
169         // don't draw if this is a normal stats-screen intermission,
170         // only if it is not an intermission, or a finale intermission
171         if (cl.intermission == 1)
172                 return;
173         if (scr_centertime_off <= 0 && !cl.intermission)
174                 return;
175         if (key_dest != key_game)
176                 return;
177
178         SCR_DrawCenterString ();
179 }
180
181 /*
182 ==============
183 SCR_DrawTurtle
184 ==============
185 */
186 void SCR_DrawTurtle (void)
187 {
188         static int      count;
189
190         if (cls.state != ca_connected)
191                 return;
192
193         if (!scr_showturtle.integer)
194                 return;
195
196         if (cl.realframetime < 0.1)
197         {
198                 count = 0;
199                 return;
200         }
201
202         count++;
203         if (count < 3)
204                 return;
205
206         DrawQ_Pic (0, 0, Draw_CachePic("gfx/turtle", true), 0, 0, 1, 1, 1, 1, 0);
207 }
208
209 /*
210 ==============
211 SCR_DrawNet
212 ==============
213 */
214 void SCR_DrawNet (void)
215 {
216         if (cls.state != ca_connected)
217                 return;
218         if (realtime - cl.last_received_message < 0.3)
219                 return;
220         if (cls.demoplayback)
221                 return;
222
223         DrawQ_Pic (64, 0, Draw_CachePic("gfx/net", true), 0, 0, 1, 1, 1, 1, 0);
224 }
225
226 /*
227 ==============
228 DrawPause
229 ==============
230 */
231 void SCR_DrawPause (void)
232 {
233         cachepic_t      *pic;
234
235         if (cls.state != ca_connected)
236                 return;
237
238         if (!scr_showpause.integer)             // turn off for screenshots
239                 return;
240
241         if (!cl.paused)
242                 return;
243
244         pic = Draw_CachePic ("gfx/pause", true);
245         DrawQ_Pic ((vid_conwidth.integer - pic->width)/2, (vid_conheight.integer - pic->height)/2, pic, 0, 0, 1, 1, 1, 1, 0);
246 }
247
248 /*
249 ==============
250 SCR_DrawBrand
251 ==============
252 */
253 void SCR_DrawBrand (void)
254 {
255         cachepic_t      *pic;
256         float           x, y;
257
258         if (!scr_showbrand.value)
259                 return;
260
261         pic = Draw_CachePic ("gfx/brand", true);
262
263         switch ((int)scr_showbrand.value)
264         {
265         case 1: // bottom left
266                 x = 0;
267                 y = vid_conheight.integer - pic->height;
268                 break;
269         case 2: // bottom centre
270                 x = (vid_conwidth.integer - pic->width) / 2;
271                 y = vid_conheight.integer - pic->height;
272                 break;
273         case 3: // bottom right
274                 x = vid_conwidth.integer - pic->width;
275                 y = vid_conheight.integer - pic->height;
276                 break;
277         case 4: // centre right
278                 x = vid_conwidth.integer - pic->width;
279                 y = (vid_conheight.integer - pic->height) / 2;
280                 break;
281         case 5: // top right
282                 x = vid_conwidth.integer - pic->width;
283                 y = 0;
284                 break;
285         case 6: // top centre
286                 x = (vid_conwidth.integer - pic->width) / 2;
287                 y = 0;
288                 break;
289         case 7: // top left
290                 x = 0;
291                 y = 0;
292                 break;
293         case 8: // centre left
294                 x = 0;
295                 y = (vid_conheight.integer - pic->height) / 2;
296                 break;
297         default:
298                 return;
299         }
300
301         DrawQ_Pic (x, y, pic, 0, 0, 1, 1, 1, 1, 0);
302 }
303
304 /*
305 ==============
306 SCR_DrawDownload
307 ==============
308 */
309 static void SCR_DrawDownload(void)
310 {
311         int len;
312         float x, y;
313         float size = 8;
314         char temp[256];
315         if (!cls.qw_downloadname[0])
316                 return;
317         dpsnprintf(temp, sizeof(temp), "Downloading %s ...  %3i%%\n", cls.qw_downloadname, cls.qw_downloadpercent);
318         len = (int)strlen(temp);
319         x = (vid_conwidth.integer - len*size) / 2;
320         y = vid_conheight.integer - size;
321         DrawQ_Pic(0, y, NULL, vid_conwidth.integer, size, 0, 0, 0, 0.5, 0);
322         DrawQ_String(x, y, temp, len, size, size, 1, 1, 1, 1, 0);
323 }
324
325 //=============================================================================
326
327
328 /*
329 ==================
330 SCR_SetUpToDrawConsole
331 ==================
332 */
333 void SCR_SetUpToDrawConsole (void)
334 {
335         // lines of console to display
336         float conlines;
337         static int framecounter = 0;
338
339         Con_CheckResize ();
340
341         if (scr_menuforcewhiledisconnected.integer && key_dest == key_game && cls.state == ca_disconnected)
342         {
343                 if (framecounter >= 2)
344                         MR_ToggleMenu_f();
345                 else
346                         framecounter++;
347         }
348         else
349                 framecounter = 0;
350
351         if (scr_conforcewhiledisconnected.integer && key_dest == key_game && cls.signon != SIGNONS)
352                 key_consoleactive |= KEY_CONSOLEACTIVE_FORCED;
353         else
354                 key_consoleactive &= ~KEY_CONSOLEACTIVE_FORCED;
355
356 // decide on the height of the console
357         if (key_consoleactive & KEY_CONSOLEACTIVE_USER)
358                 conlines = vid_conheight.integer/2;     // half screen
359         else
360                 conlines = 0;                           // none visible
361
362         scr_con_current = conlines;
363 }
364
365 /*
366 ==================
367 SCR_DrawConsole
368 ==================
369 */
370 void SCR_DrawConsole (void)
371 {
372         if (key_consoleactive & KEY_CONSOLEACTIVE_FORCED)
373         {
374                 // full screen
375                 Con_DrawConsole (vid_conheight.integer);
376         }
377         else if (scr_con_current)
378                 Con_DrawConsole ((int)scr_con_current);
379         else
380         {
381                 con_vislines = 0;
382                 if (key_dest == key_game || key_dest == key_message)
383                         Con_DrawNotify ();      // only draw notify in game
384         }
385 }
386
387 /*
388 ===============
389 SCR_BeginLoadingPlaque
390
391 ================
392 */
393 void SCR_BeginLoadingPlaque (void)
394 {
395         // save console log up to this point to log_file if it was set by configs
396         Log_Start();
397
398         Host_StartVideo();
399         S_StopAllSounds();
400         SCR_UpdateLoadingScreen();
401 }
402
403 //=============================================================================
404
405 char r_speeds_string[1024];
406 int speedstringcount, r_timereport_active;
407 double r_timereport_temp = 0, r_timereport_current = 0, r_timereport_start = 0;
408
409 void R_TimeReport(char *desc)
410 {
411         char tempbuf[256];
412         int length;
413         int t;
414
415         if (r_speeds.integer < 2 || !r_timereport_active)
416                 return;
417
418         CHECKGLERROR
419         qglFinish();CHECKGLERROR
420         r_timereport_temp = r_timereport_current;
421         r_timereport_current = Sys_DoubleTime();
422         t = (int) ((r_timereport_current - r_timereport_temp) * 1000000.0 + 0.5);
423
424         dpsnprintf(tempbuf, sizeof(tempbuf), "%8i %-11s", t, desc);
425         length = (int)strlen(tempbuf);
426         if (speedstringcount + length > (vid_conwidth.integer / 8))
427         {
428                 strlcat(r_speeds_string, "\n", sizeof(r_speeds_string));
429                 speedstringcount = 0;
430         }
431         strlcat(r_speeds_string, tempbuf, sizeof(r_speeds_string));
432         speedstringcount += length;
433 }
434
435 void R_TimeReport_Frame(void)
436 {
437         int i, j, lines, y;
438
439         if (r_speeds_string[0])
440         {
441                 if (r_timereport_active)
442                 {
443                         r_timereport_current = r_timereport_start;
444                         R_TimeReport("total");
445                 }
446
447                 if (r_speeds_string[strlen(r_speeds_string)-1] == '\n')
448                         r_speeds_string[strlen(r_speeds_string)-1] = 0;
449                 lines = 1;
450                 for (i = 0;r_speeds_string[i];i++)
451                         if (r_speeds_string[i] == '\n')
452                                 lines++;
453                 y = vid_conheight.integer - sb_lines - lines * 8;
454                 i = j = 0;
455                 DrawQ_Pic(0, y, NULL, vid_conwidth.integer, lines * 8, 0, 0, 0, 0.5, 0);
456                 while (r_speeds_string[i])
457                 {
458                         j = i;
459                         while (r_speeds_string[i] && r_speeds_string[i] != '\n')
460                                 i++;
461                         if (i - j > 0)
462                                 DrawQ_String(0, y, r_speeds_string + j, i - j, 8, 8, 1, 1, 1, 1, 0);
463                         if (r_speeds_string[i] == '\n')
464                                 i++;
465                         y += 8;
466                 }
467                 r_speeds_string[0] = 0;
468                 r_timereport_active = false;
469         }
470         if (r_speeds.integer && cls.signon == SIGNONS && cls.state == ca_connected)
471         {
472                 speedstringcount = 0;
473                 r_speeds_string[0] = 0;
474                 r_timereport_active = false;
475                 sprintf(r_speeds_string + strlen(r_speeds_string), "org:'%+8.2f %+8.2f %+8.2f' dir:'%+2.3f %+2.3f %+2.3f'\n", r_view.origin[0], r_view.origin[1], r_view.origin[2], r_view.forward[0], r_view.forward[1], r_view.forward[2]);
476                 sprintf(r_speeds_string + strlen(r_speeds_string), "%5i entities%6i surfaces%6i triangles%5i leafs%5i portals%6i particles\n", r_refdef.stats.entities, r_refdef.stats.entities_surfaces, r_refdef.stats.entities_triangles, r_refdef.stats.world_leafs, r_refdef.stats.world_portals, r_refdef.stats.particles);
477                 sprintf(r_speeds_string + strlen(r_speeds_string), "%4i lights%4i clears%4i scissored%7i light%7i shadow%7i dynamic\n", r_refdef.stats.lights, r_refdef.stats.lights_clears, r_refdef.stats.lights_scissored, r_refdef.stats.lights_lighttriangles, r_refdef.stats.lights_shadowtriangles, r_refdef.stats.lights_dynamicshadowtriangles);
478                 if (r_refdef.stats.bloom)
479                         sprintf(r_speeds_string + strlen(r_speeds_string), "rendered%6i meshes%8i triangles bloompixels%8i copied%8i drawn\n", r_refdef.stats.meshes, r_refdef.stats.meshes_elements / 3, r_refdef.stats.bloom_copypixels, r_refdef.stats.bloom_drawpixels);
480                 else
481                         sprintf(r_speeds_string + strlen(r_speeds_string), "rendered%6i meshes%8i triangles\n", r_refdef.stats.meshes, r_refdef.stats.meshes_elements / 3);
482
483                 memset(&r_refdef.stats, 0, sizeof(r_refdef.stats));
484
485                 if (r_speeds.integer >= 2)
486                 {
487                         r_timereport_active = true;
488                         r_timereport_start = r_timereport_current = Sys_DoubleTime();
489                 }
490         }
491 }
492
493 /*
494 =================
495 SCR_SizeUp_f
496
497 Keybinding command
498 =================
499 */
500 void SCR_SizeUp_f (void)
501 {
502         Cvar_SetValue ("viewsize",scr_viewsize.value+10);
503 }
504
505
506 /*
507 =================
508 SCR_SizeDown_f
509
510 Keybinding command
511 =================
512 */
513 void SCR_SizeDown_f (void)
514 {
515         Cvar_SetValue ("viewsize",scr_viewsize.value-10);
516 }
517
518 void CL_Screen_Init(void)
519 {
520         Cvar_RegisterVariable (&scr_fov);
521         Cvar_RegisterVariable (&scr_viewsize);
522         Cvar_RegisterVariable (&scr_conalpha);
523         Cvar_RegisterVariable (&scr_conbrightness);
524         Cvar_RegisterVariable (&scr_conforcewhiledisconnected);
525         Cvar_RegisterVariable (&scr_menuforcewhiledisconnected);
526         Cvar_RegisterVariable (&scr_showram);
527         Cvar_RegisterVariable (&scr_showturtle);
528         Cvar_RegisterVariable (&scr_showpause);
529         Cvar_RegisterVariable (&scr_showbrand);
530         Cvar_RegisterVariable (&scr_centertime);
531         Cvar_RegisterVariable (&scr_printspeed);
532         Cvar_RegisterVariable (&vid_conwidth);
533         Cvar_RegisterVariable (&vid_conheight);
534         Cvar_RegisterVariable (&vid_pixelheight);
535         Cvar_RegisterVariable (&scr_screenshot_jpeg);
536         Cvar_RegisterVariable (&scr_screenshot_jpeg_quality);
537         Cvar_RegisterVariable (&scr_screenshot_gammaboost);
538         Cvar_RegisterVariable (&cl_capturevideo);
539         Cvar_RegisterVariable (&cl_capturevideo_fps);
540         Cvar_RegisterVariable (&cl_capturevideo_rawrgb);
541         Cvar_RegisterVariable (&cl_capturevideo_rawyv12);
542         Cvar_RegisterVariable (&cl_capturevideo_number);
543         Cvar_RegisterVariable (&r_letterbox);
544         Cvar_RegisterVariable(&r_stereo_separation);
545         Cvar_RegisterVariable(&r_stereo_sidebyside);
546         Cvar_RegisterVariable(&r_stereo_redblue);
547         Cvar_RegisterVariable(&r_stereo_redcyan);
548         Cvar_RegisterVariable(&r_stereo_redgreen);
549         Cvar_RegisterVariable(&scr_zoomwindow);
550         Cvar_RegisterVariable(&scr_zoomwindow_viewsizex);
551         Cvar_RegisterVariable(&scr_zoomwindow_viewsizey);
552         Cvar_RegisterVariable(&scr_zoomwindow_fov);
553
554         Cmd_AddCommand ("sizeup",SCR_SizeUp_f, "increase view size (increases viewsize cvar)");
555         Cmd_AddCommand ("sizedown",SCR_SizeDown_f, "decrease view size (decreases viewsize cvar)");
556         Cmd_AddCommand ("screenshot",SCR_ScreenShot_f, "takes a screenshot of the next rendered frame");
557         Cmd_AddCommand ("envmap", R_Envmap_f, "render a cubemap (skybox) of the current scene");
558
559         scr_initialized = true;
560 }
561
562 /*
563 ==================
564 SCR_ScreenShot_f
565 ==================
566 */
567 void SCR_ScreenShot_f (void)
568 {
569         static int shotnumber;
570         static char oldname[MAX_QPATH];
571         char base[MAX_QPATH];
572         char filename[MAX_QPATH];
573         unsigned char *buffer1;
574         unsigned char *buffer2;
575         unsigned char *buffer3;
576         qboolean jpeg = (scr_screenshot_jpeg.integer != 0);
577
578         sprintf (base, "screenshots/%s", scr_screenshot_name.string);
579
580         if (strcmp (oldname, scr_screenshot_name.string))
581         {
582                 sprintf(oldname, "%s", scr_screenshot_name.string);
583                 shotnumber = 0;
584         }
585
586         // find a file name to save it to
587         for (;shotnumber < 1000000;shotnumber++)
588                 if (!FS_SysFileExists(va("%s/%s%06d.tga", fs_gamedir, base, shotnumber)) && !FS_SysFileExists(va("%s/%s%06d.jpg", fs_gamedir, base, shotnumber)))
589                         break;
590         if (shotnumber >= 1000000)
591         {
592                 Con_Print("SCR_ScreenShot_f: Couldn't create the image file\n");
593                 return;
594         }
595
596         sprintf(filename, "%s%06d.%s", base, shotnumber, jpeg ? "jpg" : "tga");
597
598         buffer1 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3);
599         buffer2 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3);
600         buffer3 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3 + 18);
601
602         if (SCR_ScreenShot (filename, buffer1, buffer2, buffer3, 0, 0, vid.width, vid.height, false, false, false, jpeg, true))
603                 Con_Printf("Wrote %s\n", filename);
604         else
605                 Con_Printf("unable to write %s\n", filename);
606
607         Mem_Free (buffer1);
608         Mem_Free (buffer2);
609         Mem_Free (buffer3);
610
611         shotnumber++;
612 }
613
614 void SCR_CaptureVideo_BeginVideo(void)
615 {
616         double gamma, g;
617         unsigned int i;
618         unsigned char out[44];
619         if (cls.capturevideo_active)
620                 return;
621         // soundrate is figured out on the first SoundFrame
622         cls.capturevideo_active = true;
623         cls.capturevideo_starttime = Sys_DoubleTime();
624         cls.capturevideo_framerate = bound(1, cl_capturevideo_fps.value, 1000);
625         cls.capturevideo_soundrate = 0;
626         cls.capturevideo_frame = 0;
627         cls.capturevideo_buffer = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * (3+3+3) + 18);
628         gamma = 1.0/scr_screenshot_gammaboost.value;
629         dpsnprintf(cls.capturevideo_basename, sizeof(cls.capturevideo_basename), "video/dpvideo%03i", cl_capturevideo_number.integer);
630         Cvar_SetValueQuick(&cl_capturevideo_number, cl_capturevideo_number.integer + 1);
631
632         /*
633         for (i = 0;i < 256;i++)
634         {
635                 unsigned char j = (unsigned char)bound(0, 255*pow(i/255.0, gamma), 255);
636                 cls.capturevideo_rgbgammatable[0][i] = j;
637                 cls.capturevideo_rgbgammatable[1][i] = j;
638                 cls.capturevideo_rgbgammatable[2][i] = j;
639         }
640         */
641 /*
642 R = Y + 1.4075 * (Cr - 128);
643 G = Y + -0.3455 * (Cb - 128) + -0.7169 * (Cr - 128);
644 B = Y + 1.7790 * (Cb - 128);
645 Y = R *  .299 + G *  .587 + B *  .114;
646 Cb = R * -.169 + G * -.332 + B *  .500 + 128.;
647 Cr = R *  .500 + G * -.419 + B * -.0813 + 128.;
648 */
649         for (i = 0;i < 256;i++)
650         {
651                 g = 255*pow(i/255.0, gamma);
652                 // Y weights from RGB
653                 cls.capturevideo_rgbtoyuvscaletable[0][0][i] = (short)(g *  0.299);
654                 cls.capturevideo_rgbtoyuvscaletable[0][1][i] = (short)(g *  0.587);
655                 cls.capturevideo_rgbtoyuvscaletable[0][2][i] = (short)(g *  0.114);
656                 // Cb weights from RGB
657                 cls.capturevideo_rgbtoyuvscaletable[1][0][i] = (short)(g * -0.169);
658                 cls.capturevideo_rgbtoyuvscaletable[1][1][i] = (short)(g * -0.332);
659                 cls.capturevideo_rgbtoyuvscaletable[1][2][i] = (short)(g *  0.500);
660                 // Cr weights from RGB
661                 cls.capturevideo_rgbtoyuvscaletable[2][0][i] = (short)(g *  0.500);
662                 cls.capturevideo_rgbtoyuvscaletable[2][1][i] = (short)(g * -0.419);
663                 cls.capturevideo_rgbtoyuvscaletable[2][2][i] = (short)(g * -0.0813);
664                 // range reduction of YCbCr to valid signal range
665                 cls.capturevideo_yuvnormalizetable[0][i] = 16 + i * (236-16) / 256;
666                 cls.capturevideo_yuvnormalizetable[1][i] = 16 + i * (240-16) / 256;
667                 cls.capturevideo_yuvnormalizetable[2][i] = 16 + i * (240-16) / 256;
668         }
669
670         if (cl_capturevideo_rawrgb.integer)
671         {
672                 cls.capturevideo_format = CAPTUREVIDEOFORMAT_RAWRGB;
673                 cls.capturevideo_videofile = FS_Open (va("%s.rgb", cls.capturevideo_basename), "wb", false, true);
674         }
675         else if (cl_capturevideo_rawyv12.integer)
676         {
677                 cls.capturevideo_format = CAPTUREVIDEOFORMAT_RAWYV12;
678                 cls.capturevideo_videofile = FS_Open (va("%s.yv12", cls.capturevideo_basename), "wb", false, true);
679         }
680         else if (scr_screenshot_jpeg.integer)
681         {
682                 cls.capturevideo_format = CAPTUREVIDEOFORMAT_JPEG;
683                 cls.capturevideo_videofile = NULL;
684         }
685         else
686         {
687                 cls.capturevideo_format = CAPTUREVIDEOFORMAT_TARGA;
688                 cls.capturevideo_videofile = NULL;
689         }
690
691         cls.capturevideo_soundfile = FS_Open (va("%s.wav", cls.capturevideo_basename), "wb", false, true);
692         if (cls.capturevideo_soundfile)
693         {
694                 // wave header will be filled out when video ends
695                 memset(out, 0, 44);
696                 FS_Write (cls.capturevideo_soundfile, out, 44);
697         }
698         else
699                 Con_Printf("Could not open video/dpvideo.wav for writing, sound capture disabled\n");
700 }
701
702 void SCR_CaptureVideo_EndVideo(void)
703 {
704         int i, n;
705         unsigned char out[44];
706         if (!cls.capturevideo_active)
707                 return;
708         cls.capturevideo_active = false;
709
710         if (cls.capturevideo_videofile)
711         {
712                 FS_Close(cls.capturevideo_videofile);
713                 cls.capturevideo_videofile = NULL;
714         }
715
716         // finish the wave file
717         if (cls.capturevideo_soundfile)
718         {
719                 i = (int)FS_Tell (cls.capturevideo_soundfile);
720                 //"RIFF", (int) unknown (chunk size), "WAVE",
721                 //"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
722                 //"data", (int) unknown (chunk size)
723                 memcpy (out, "RIFF****WAVEfmt \x10\x00\x00\x00\x01\x00\x02\x00********\x04\x00\x10\0data****", 44);
724                 // the length of the whole RIFF chunk
725                 n = i - 8;
726                 out[4] = (n) & 0xFF;
727                 out[5] = (n >> 8) & 0xFF;
728                 out[6] = (n >> 16) & 0xFF;
729                 out[7] = (n >> 24) & 0xFF;
730                 // rate
731                 n = cls.capturevideo_soundrate;
732                 out[24] = (n) & 0xFF;
733                 out[25] = (n >> 8) & 0xFF;
734                 out[26] = (n >> 16) & 0xFF;
735                 out[27] = (n >> 24) & 0xFF;
736                 // bytes per second (rate * channels * bytes per channel)
737                 n = cls.capturevideo_soundrate * 2 * 2;
738                 out[28] = (n) & 0xFF;
739                 out[29] = (n >> 8) & 0xFF;
740                 out[30] = (n >> 16) & 0xFF;
741                 out[31] = (n >> 24) & 0xFF;
742                 // the length of the data chunk
743                 n = i - 44;
744                 out[40] = (n) & 0xFF;
745                 out[41] = (n >> 8) & 0xFF;
746                 out[42] = (n >> 16) & 0xFF;
747                 out[43] = (n >> 24) & 0xFF;
748                 FS_Seek (cls.capturevideo_soundfile, 0, SEEK_SET);
749                 FS_Write (cls.capturevideo_soundfile, out, 44);
750                 FS_Close (cls.capturevideo_soundfile);
751                 cls.capturevideo_soundfile = NULL;
752         }
753
754         if (cls.capturevideo_buffer)
755         {
756                 Mem_Free (cls.capturevideo_buffer);
757                 cls.capturevideo_buffer = NULL;
758         }
759
760         cls.capturevideo_starttime = 0;
761         cls.capturevideo_framerate = 0;
762         cls.capturevideo_frame = 0;
763 }
764
765 qboolean SCR_CaptureVideo_VideoFrame(int newframenum)
766 {
767         int x = 0, y = 0, width = vid.width, height = vid.height;
768         unsigned char *b, *out;
769         char filename[32];
770         int outoffset = (width/2)*(height/2);
771         CHECKGLERROR
772         //return SCR_ScreenShot(filename, cls.capturevideo_buffer, cls.capturevideo_buffer + vid.width * vid.height * 3, cls.capturevideo_buffer + vid.width * vid.height * 6, 0, 0, vid.width, vid.height, false, false, false, jpeg, true);
773         // speed is critical here, so do saving as directly as possible
774         switch (cls.capturevideo_format)
775         {
776         case CAPTUREVIDEOFORMAT_RAWYV12:
777                 // if there's no videofile we have to just give up, and abort saving if there's no video or sound file
778                 if (!cls.capturevideo_videofile)
779                         return cls.capturevideo_soundfile != NULL;
780                 // FIXME: width/height must be multiple of 2, enforce this?
781                 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cls.capturevideo_buffer);CHECKGLERROR
782                 // process one line at a time, and CbCr every other line at 2 pixel intervals
783                 for (y = 0;y < height;y++)
784                 {
785                         // 1x1 Y
786                         for (b = cls.capturevideo_buffer + (height-1-y)*width*3, out = cls.capturevideo_buffer + width*height*3 + y*width, x = 0;x < width;x++, b += 3, out++)
787                                 *out = cls.capturevideo_yuvnormalizetable[0][cls.capturevideo_rgbtoyuvscaletable[0][0][b[0]] + cls.capturevideo_rgbtoyuvscaletable[0][1][b[1]] + cls.capturevideo_rgbtoyuvscaletable[0][2][b[2]]];
788                         if ((y & 1) == 0)
789                         {
790                                 // 2x2 Cb and Cr planes
791 #if 1
792                                 // low quality, no averaging
793                                 for (b = cls.capturevideo_buffer + (height-2-y)*width*3, out = cls.capturevideo_buffer + width*height*3 + width*height + (y/2)*(width/2), x = 0;x < width/2;x++, b += 6, out++)
794                                 {
795                                         // Cr
796                                         out[0        ] = cls.capturevideo_yuvnormalizetable[2][cls.capturevideo_rgbtoyuvscaletable[2][0][b[0]] + cls.capturevideo_rgbtoyuvscaletable[2][1][b[1]] + cls.capturevideo_rgbtoyuvscaletable[2][2][b[2]] + 128];
797                                         // Cb
798                                         out[outoffset] = cls.capturevideo_yuvnormalizetable[1][cls.capturevideo_rgbtoyuvscaletable[1][0][b[0]] + cls.capturevideo_rgbtoyuvscaletable[1][1][b[1]] + cls.capturevideo_rgbtoyuvscaletable[1][2][b[2]] + 128];
799                                 }
800 #else
801                                 // high quality, averaging
802                                 int inpitch = width*3;
803                                 for (b = cls.capturevideo_buffer + (height-2-y)*width*3, out = cls.capturevideo_buffer + width*height*3 + width*height + (y/2)*(width/2), x = 0;x < width/2;x++, b += 6, out++)
804                                 {
805                                         int blockr, blockg, blockb;
806                                         blockr = (b[0] + b[3] + b[inpitch+0] + b[inpitch+3]) >> 2;
807                                         blockg = (b[1] + b[4] + b[inpitch+1] + b[inpitch+4]) >> 2;
808                                         blockb = (b[2] + b[5] + b[inpitch+2] + b[inpitch+5]) >> 2;
809                                         // Cr
810                                         out[0        ] = cls.capturevideo_yuvnormalizetable[2][cls.capturevideo_rgbtoyuvscaletable[2][0][blockr] + cls.capturevideo_rgbtoyuvscaletable[2][1][blockg] + cls.capturevideo_rgbtoyuvscaletable[2][2][blockb] + 128];
811                                         // Cb
812                                         out[outoffset] = cls.capturevideo_yuvnormalizetable[1][cls.capturevideo_rgbtoyuvscaletable[1][0][blockr] + cls.capturevideo_rgbtoyuvscaletable[1][1][blockg] + cls.capturevideo_rgbtoyuvscaletable[1][2][blockb] + 128];
813                                 }
814 #endif
815                         }
816                 }
817                 for (;cls.capturevideo_frame < newframenum;cls.capturevideo_frame++)
818                         if (!FS_Write (cls.capturevideo_videofile, cls.capturevideo_buffer + width*height*3, width*height+(width/2)*(height/2)*2))
819                                 return false;
820                 return true;
821         case CAPTUREVIDEOFORMAT_RAWRGB:
822                 // if there's no videofile we have to just give up, and abort saving if there's no video or sound file
823                 if (!cls.capturevideo_videofile)
824                         return cls.capturevideo_soundfile != NULL;
825                 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cls.capturevideo_buffer);CHECKGLERROR
826                 for (;cls.capturevideo_frame < newframenum;cls.capturevideo_frame++)
827                         if (!FS_Write (cls.capturevideo_videofile, cls.capturevideo_buffer, width*height*3))
828                                 return false;
829                 return true;
830         case CAPTUREVIDEOFORMAT_JPEG:
831                 qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, cls.capturevideo_buffer);CHECKGLERROR
832                 for (;cls.capturevideo_frame < newframenum;cls.capturevideo_frame++)
833                 {
834                         sprintf(filename, "%s_%06d.jpg", cls.capturevideo_basename, cls.capturevideo_frame);
835                         if (!JPEG_SaveImage_preflipped (filename, width, height, cls.capturevideo_buffer))
836                                 return false;
837                 }
838                 return true;
839         case CAPTUREVIDEOFORMAT_TARGA:
840                 //return Image_WriteTGARGB_preflipped (filename, width, height, cls.capturevideo_buffer, cls.capturevideo_buffer + vid.width * vid.height * 3, );
841                 memset (cls.capturevideo_buffer, 0, 18);
842                 cls.capturevideo_buffer[2] = 2;         // uncompressed type
843                 cls.capturevideo_buffer[12] = (width >> 0) & 0xFF;
844                 cls.capturevideo_buffer[13] = (width >> 8) & 0xFF;
845                 cls.capturevideo_buffer[14] = (height >> 0) & 0xFF;
846                 cls.capturevideo_buffer[15] = (height >> 8) & 0xFF;
847                 cls.capturevideo_buffer[16] = 24;       // pixel size
848                 qglReadPixels (x, y, width, height, GL_BGR, GL_UNSIGNED_BYTE, cls.capturevideo_buffer + 18);CHECKGLERROR
849                 for (;cls.capturevideo_frame < newframenum;cls.capturevideo_frame++)
850                 {
851                         sprintf(filename, "%s_%06d.tga", cls.capturevideo_basename, cls.capturevideo_frame);
852                         if (!FS_WriteFile (filename, cls.capturevideo_buffer, width*height*3 + 18))
853                                 return false;
854                 }
855                 return true;
856         default:
857                 return false;
858         }
859 }
860
861 void SCR_CaptureVideo_SoundFrame(unsigned char *bufstereo16le, size_t length, int rate)
862 {
863         if (!cls.capturevideo_soundfile)
864                 return;
865         cls.capturevideo_soundrate = rate;
866         if (FS_Write (cls.capturevideo_soundfile, bufstereo16le, 4 * length) < (fs_offset_t)(4 * length))
867         {
868                 Cvar_SetValueQuick(&cl_capturevideo, 0);
869                 Con_Printf("video sound saving failed on frame %i, out of disk space? stopping video capture.\n", cls.capturevideo_frame);
870                 SCR_CaptureVideo_EndVideo();
871         }
872 }
873
874 void SCR_CaptureVideo(void)
875 {
876         int newframenum;
877         if (cl_capturevideo.integer && r_render.integer)
878         {
879                 if (!cls.capturevideo_active)
880                         SCR_CaptureVideo_BeginVideo();
881                 if (cls.capturevideo_framerate != cl_capturevideo_fps.value)
882                 {
883                         Con_Printf("You can not change the video framerate while recording a video.\n");
884                         Cvar_SetValueQuick(&cl_capturevideo_fps, cls.capturevideo_framerate);
885                 }
886 #if 0
887                 if (cls.capturevideo_soundfile)
888                 {
889                         // preserve sound sync by duplicating frames when running slow
890                         newframenum = (int)((Sys_DoubleTime() - cls.capturevideo_starttime) * cls.capturevideo_framerate);
891                 }
892                 else
893 #endif
894                         newframenum = cls.capturevideo_frame + 1;
895                 // if falling behind more than one second, stop
896                 if (newframenum - cls.capturevideo_frame > (int)ceil(cls.capturevideo_framerate))
897                 {
898                         Cvar_SetValueQuick(&cl_capturevideo, 0);
899                         Con_Printf("video saving failed on frame %i, your machine is too slow for this capture speed.\n", cls.capturevideo_frame);
900                         SCR_CaptureVideo_EndVideo();
901                         return;
902                 }
903                 // write frames
904                 if (!SCR_CaptureVideo_VideoFrame(newframenum))
905                 {
906                         Cvar_SetValueQuick(&cl_capturevideo, 0);
907                         Con_Printf("video saving failed on frame %i, out of disk space? stopping video capture.\n", cls.capturevideo_frame);
908                         SCR_CaptureVideo_EndVideo();
909                 }
910         }
911         else if (cls.capturevideo_active)
912                 SCR_CaptureVideo_EndVideo();
913 }
914
915 /*
916 ===============
917 R_Envmap_f
918
919 Grab six views for environment mapping tests
920 ===============
921 */
922 struct envmapinfo_s
923 {
924         float angles[3];
925         char *name;
926         qboolean flipx, flipy, flipdiagonaly;
927 }
928 envmapinfo[12] =
929 {
930         {{  0,   0, 0}, "rt", false, false, false},
931         {{  0, 270, 0}, "ft", false, false, false},
932         {{  0, 180, 0}, "lf", false, false, false},
933         {{  0,  90, 0}, "bk", false, false, false},
934         {{-90, 180, 0}, "up",  true,  true, false},
935         {{ 90, 180, 0}, "dn",  true,  true, false},
936
937         {{  0,   0, 0}, "px",  true,  true,  true},
938         {{  0,  90, 0}, "py", false,  true, false},
939         {{  0, 180, 0}, "nx", false, false,  true},
940         {{  0, 270, 0}, "ny",  true, false, false},
941         {{-90, 180, 0}, "pz", false, false,  true},
942         {{ 90, 180, 0}, "nz", false, false,  true}
943 };
944
945 static void R_Envmap_f (void)
946 {
947         int j, size;
948         char filename[MAX_QPATH], basename[MAX_QPATH];
949         unsigned char *buffer1;
950         unsigned char *buffer2;
951         unsigned char *buffer3;
952
953         if (Cmd_Argc() != 3)
954         {
955                 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");
956                 return;
957         }
958
959         strlcpy (basename, Cmd_Argv(1), sizeof (basename));
960         size = atoi(Cmd_Argv(2));
961         if (size != 128 && size != 256 && size != 512 && size != 1024)
962         {
963                 Con_Print("envmap: size must be one of 128, 256, 512, or 1024\n");
964                 return;
965         }
966         if (size > vid.width || size > vid.height)
967         {
968                 Con_Print("envmap: your resolution is not big enough to render that size\n");
969                 return;
970         }
971
972         r_refdef.envmap = true;
973
974         R_UpdateVariables();
975
976         r_view.x = 0;
977         r_view.y = 0;
978         r_view.z = 0;
979         r_view.width = size;
980         r_view.height = size;
981         r_view.depth = 1;
982
983         r_view.frustum_x = tan(90 * M_PI / 360.0);
984         r_view.frustum_y = tan(90 * M_PI / 360.0);
985
986         buffer1 = (unsigned char *)Mem_Alloc(tempmempool, size * size * 3);
987         buffer2 = (unsigned char *)Mem_Alloc(tempmempool, size * size * 3);
988         buffer3 = (unsigned char *)Mem_Alloc(tempmempool, size * size * 3 + 18);
989
990         for (j = 0;j < 12;j++)
991         {
992                 sprintf(filename, "env/%s%s.tga", basename, envmapinfo[j].name);
993                 Matrix4x4_CreateFromQuakeEntity(&r_view.matrix, r_view.origin[0], r_view.origin[1], r_view.origin[2], envmapinfo[j].angles[0], envmapinfo[j].angles[1], envmapinfo[j].angles[2], 1);
994                 R_ClearScreen();
995                 R_Mesh_Start();
996                 R_RenderView();
997                 R_Mesh_Finish();
998                 SCR_ScreenShot(filename, buffer1, buffer2, buffer3, 0, vid.height - (r_view.y + r_view.height), size, size, envmapinfo[j].flipx, envmapinfo[j].flipy, envmapinfo[j].flipdiagonaly, false, false);
999         }
1000
1001         Mem_Free (buffer1);
1002         Mem_Free (buffer2);
1003         Mem_Free (buffer3);
1004
1005         r_refdef.envmap = false;
1006 }
1007
1008 //=============================================================================
1009
1010 // LordHavoc: SHOWLMP stuff
1011 #define SHOWLMP_MAXLABELS 256
1012 typedef struct showlmp_s
1013 {
1014         qboolean        isactive;
1015         float           x;
1016         float           y;
1017         char            label[32];
1018         char            pic[128];
1019 }
1020 showlmp_t;
1021
1022 showlmp_t showlmp[SHOWLMP_MAXLABELS];
1023
1024 void SHOWLMP_decodehide(void)
1025 {
1026         int i;
1027         char *lmplabel;
1028         lmplabel = MSG_ReadString();
1029         for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1030                 if (showlmp[i].isactive && strcmp(showlmp[i].label, lmplabel) == 0)
1031                 {
1032                         showlmp[i].isactive = false;
1033                         return;
1034                 }
1035 }
1036
1037 void SHOWLMP_decodeshow(void)
1038 {
1039         int i, k;
1040         char lmplabel[256], picname[256];
1041         float x, y;
1042         strlcpy (lmplabel,MSG_ReadString(), sizeof (lmplabel));
1043         strlcpy (picname, MSG_ReadString(), sizeof (picname));
1044         if (gamemode == GAME_NEHAHRA) // LordHavoc: nasty old legacy junk
1045         {
1046                 x = MSG_ReadByte();
1047                 y = MSG_ReadByte();
1048         }
1049         else
1050         {
1051                 x = MSG_ReadShort();
1052                 y = MSG_ReadShort();
1053         }
1054         k = -1;
1055         for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1056                 if (showlmp[i].isactive)
1057                 {
1058                         if (strcmp(showlmp[i].label, lmplabel) == 0)
1059                         {
1060                                 k = i;
1061                                 break; // drop out to replace it
1062                         }
1063                 }
1064                 else if (k < 0) // find first empty one to replace
1065                         k = i;
1066         if (k < 0)
1067                 return; // none found to replace
1068         // change existing one
1069         showlmp[k].isactive = true;
1070         strlcpy (showlmp[k].label, lmplabel, sizeof (showlmp[k].label));
1071         strlcpy (showlmp[k].pic, picname, sizeof (showlmp[k].pic));
1072         showlmp[k].x = x;
1073         showlmp[k].y = y;
1074 }
1075
1076 void SHOWLMP_drawall(void)
1077 {
1078         int i;
1079         for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1080                 if (showlmp[i].isactive)
1081                         DrawQ_Pic(showlmp[i].x, showlmp[i].y, Draw_CachePic(showlmp[i].pic, true), 0, 0, 1, 1, 1, 1, 0);
1082 }
1083
1084 void SHOWLMP_clear(void)
1085 {
1086         int i;
1087         for (i = 0;i < SHOWLMP_MAXLABELS;i++)
1088                 showlmp[i].isactive = false;
1089 }
1090
1091 /*
1092 ==============================================================================
1093
1094                                                 SCREEN SHOTS
1095
1096 ==============================================================================
1097 */
1098
1099 qboolean SCR_ScreenShot(char *filename, unsigned char *buffer1, unsigned char *buffer2, unsigned char *buffer3, int x, int y, int width, int height, qboolean flipx, qboolean flipy, qboolean flipdiagonal, qboolean jpeg, qboolean gammacorrect)
1100 {
1101         int     indices[3] = {0,1,2};
1102         qboolean ret;
1103
1104         if (!r_render.integer)
1105                 return false;
1106
1107         CHECKGLERROR
1108         qglReadPixels (x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, buffer1);CHECKGLERROR
1109
1110         if (scr_screenshot_gammaboost.value != 1 && gammacorrect)
1111         {
1112                 int i;
1113                 double igamma = 1.0 / scr_screenshot_gammaboost.value;
1114                 unsigned char ramp[256];
1115                 for (i = 0;i < 256;i++)
1116                         ramp[i] = (unsigned char) (pow(i * (1.0 / 255.0), igamma) * 255.0);
1117                 for (i = 0;i < width*height*3;i++)
1118                         buffer1[i] = ramp[buffer1[i]];
1119         }
1120
1121         Image_CopyMux (buffer2, buffer1, width, height, flipx, flipy, flipdiagonal, 3, 3, indices);
1122
1123         if (jpeg)
1124                 ret = JPEG_SaveImage_preflipped (filename, width, height, buffer2);
1125         else
1126                 ret = Image_WriteTGARGB_preflipped (filename, width, height, buffer2, buffer3);
1127
1128         return ret;
1129 }
1130
1131 //=============================================================================
1132
1133 void R_ClearScreen(void)
1134 {
1135         // clear to black
1136         CHECKGLERROR
1137         if (r_refdef.fogenabled)
1138         {
1139                 qglClearColor(r_refdef.fogcolor[0],r_refdef.fogcolor[1],r_refdef.fogcolor[2],0);CHECKGLERROR
1140         }
1141         else
1142         {
1143                 qglClearColor(0,0,0,0);CHECKGLERROR
1144         }
1145         qglClearDepth(1);CHECKGLERROR
1146         if (gl_stencil)
1147         {
1148                 // LordHavoc: we use a stencil centered around 128 instead of 0,
1149                 // to avoid clamping interfering with strange shadow volume
1150                 // drawing orders
1151                 qglClearStencil(128);CHECKGLERROR
1152         }
1153         // clear the screen
1154         if (r_render.integer)
1155                 GL_Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | (gl_stencil ? GL_STENCIL_BUFFER_BIT : 0));
1156         // set dithering mode
1157         if (gl_dither.integer)
1158         {
1159                 qglEnable(GL_DITHER);CHECKGLERROR
1160         }
1161         else
1162         {
1163                 qglDisable(GL_DITHER);CHECKGLERROR
1164         }
1165 }
1166
1167 qboolean CL_VM_UpdateView (void);
1168 void SCR_DrawConsole (void);
1169 void R_Shadow_EditLights_DrawSelectedLightProperties(void);
1170
1171 int r_stereo_side;
1172
1173 void SCR_DrawScreen (void)
1174 {
1175         R_Mesh_Start();
1176
1177         if (r_timereport_active)
1178                 R_TimeReport("setup");
1179
1180         R_UpdateVariables();
1181
1182         if (cls.signon == SIGNONS)
1183         {
1184                 float size;
1185
1186                 size = scr_viewsize.value * (1.0 / 100.0);
1187                 size = min(size, 1);
1188
1189                 if (r_stereo_sidebyside.integer)
1190                 {
1191                         r_view.width = (int)(vid.width * size / 2.5);
1192                         r_view.height = (int)(vid.height * size / 2.5 * (1 - bound(0, r_letterbox.value, 100) / 100));
1193                         r_view.depth = 1;
1194                         r_view.x = (int)((vid.width - r_view.width * 2.5) * 0.5);
1195                         r_view.y = (int)((vid.height - r_view.height)/2);
1196                         r_view.z = 0;
1197                         if (r_stereo_side)
1198                                 r_view.x += (int)(r_view.width * 1.5);
1199                 }
1200                 else
1201                 {
1202                         r_view.width = (int)(vid.width * size);
1203                         r_view.height = (int)(vid.height * size * (1 - bound(0, r_letterbox.value, 100) / 100));
1204                         r_view.depth = 1;
1205                         r_view.x = (int)((vid.width - r_view.width)/2);
1206                         r_view.y = (int)((vid.height - r_view.height)/2);
1207                         r_view.z = 0;
1208                 }
1209
1210                 // LordHavoc: viewzoom (zoom in for sniper rifles, etc)
1211                 // LordHavoc: this is designed to produce widescreen fov values
1212                 // when the screen is wider than 4/3 width/height aspect, to do
1213                 // this it simply assumes the requested fov is the vertical fov
1214                 // for a 4x3 display, if the ratio is not 4x3 this makes the fov
1215                 // higher/lower according to the ratio
1216                 r_view.frustum_y = tan(scr_fov.value * cl.viewzoom * M_PI / 360.0) * (3.0/4.0);
1217                 r_view.frustum_x = r_view.frustum_y * (float)r_view.width / (float)r_view.height / vid_pixelheight.value;
1218
1219                 r_view.frustum_x *= r_refdef.frustumscale_x;
1220                 r_view.frustum_y *= r_refdef.frustumscale_y;
1221
1222                 if(!CL_VM_UpdateView())
1223                         R_RenderView();
1224                 else
1225                         SCR_DrawConsole();
1226
1227                 if (scr_zoomwindow.integer)
1228                 {
1229                         float sizex = bound(10, scr_zoomwindow_viewsizex.value, 100) / 100.0;
1230                         float sizey = bound(10, scr_zoomwindow_viewsizey.value, 100) / 100.0;
1231                         r_view.width = (int)(vid.width * sizex);
1232                         r_view.height = (int)(vid.height * sizey);
1233                         r_view.depth = 1;
1234                         r_view.x = (int)((vid.width - r_view.width)/2);
1235                         r_view.y = 0;
1236                         r_view.z = 0;
1237
1238                         r_view.frustum_y = tan(scr_zoomwindow_fov.value * cl.viewzoom * M_PI / 360.0) * (3.0/4.0);
1239                         r_view.frustum_x = r_view.frustum_y * vid_pixelheight.value * (float)r_view.width / (float)r_view.height;
1240
1241                         r_view.frustum_x *= r_refdef.frustumscale_x;
1242                         r_view.frustum_y *= r_refdef.frustumscale_y;
1243
1244                         if(!CL_VM_UpdateView())
1245                                 R_RenderView();
1246                 }
1247         }
1248
1249         if (!r_stereo_sidebyside.integer)
1250         {
1251                 r_view.width = vid.width;
1252                 r_view.height = vid.height;
1253                 r_view.depth = 1;
1254                 r_view.x = 0;
1255                 r_view.y = 0;
1256                 r_view.z = 0;
1257         }
1258
1259         // draw 2D stuff
1260         DrawQ_Begin();
1261
1262         //FIXME: force menu if nothing else to look at?
1263         //if (key_dest == key_game && cls.signon != SIGNONS && cls.state == ca_disconnected)
1264
1265         if (cls.signon == SIGNONS)
1266         {
1267                 SCR_DrawNet ();
1268                 SCR_DrawTurtle ();
1269                 SCR_DrawPause ();
1270                 if (!r_letterbox.value)
1271                         Sbar_Draw();
1272                 SHOWLMP_drawall();
1273                 SCR_CheckDrawCenterString();
1274         }
1275         MR_Draw();
1276         CL_DrawVideo();
1277         R_Shadow_EditLights_DrawSelectedLightProperties();
1278
1279         if(!csqc_loaded)
1280                 SCR_DrawConsole();
1281
1282         SCR_DrawBrand();
1283
1284         SCR_DrawDownload();
1285
1286         if (r_timereport_active)
1287                 R_TimeReport("2d");
1288
1289         if (cls.signon == SIGNONS)
1290                 R_TimeReport_Frame();
1291
1292         DrawQ_Finish();
1293
1294         R_DrawGamma();
1295
1296         R_Mesh_Finish();
1297
1298         if (r_timereport_active)
1299                 R_TimeReport("meshfinish");
1300 }
1301
1302 void SCR_UpdateLoadingScreen (void)
1303 {
1304         float x, y;
1305         cachepic_t *pic;
1306         float vertex3f[12];
1307         float texcoord2f[8];
1308         // don't do anything if not initialized yet
1309         if (vid_hidden)
1310                 return;
1311         CHECKGLERROR
1312         qglViewport(0, 0, vid.width, vid.height);CHECKGLERROR
1313         //qglDisable(GL_SCISSOR_TEST);CHECKGLERROR
1314         //qglDepthMask(1);CHECKGLERROR
1315         qglColorMask(1,1,1,1);CHECKGLERROR
1316         //qglClearColor(0,0,0,0);CHECKGLERROR
1317         //qglClear(GL_COLOR_BUFFER_BIT);CHECKGLERROR
1318         //qglCullFace(GL_FRONT);CHECKGLERROR
1319         //qglDisable(GL_CULL_FACE);CHECKGLERROR
1320         //R_ClearScreen();
1321         R_Textures_Frame();
1322         GL_SetupView_Mode_Ortho(0, 0, vid_conwidth.integer, vid_conheight.integer, -10, 100);
1323         R_Mesh_Start();
1324         R_Mesh_Matrix(&identitymatrix);
1325         // draw the loading plaque
1326         pic = Draw_CachePic("gfx/loading", true);
1327         x = (vid_conwidth.integer - pic->width)/2;
1328         y = (vid_conheight.integer - pic->height)/2;
1329         GL_Color(1,1,1,1);
1330         GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1331         GL_DepthTest(false);
1332         R_Mesh_VertexPointer(vertex3f);
1333         R_Mesh_ColorPointer(NULL);
1334         R_Mesh_ResetTextureState();
1335         R_Mesh_TexBind(0, R_GetTexture(pic->tex));
1336         R_Mesh_TexCoordPointer(0, 2, texcoord2f);
1337         vertex3f[2] = vertex3f[5] = vertex3f[8] = vertex3f[11] = 0;
1338         vertex3f[0] = vertex3f[9] = x;
1339         vertex3f[1] = vertex3f[4] = y;
1340         vertex3f[3] = vertex3f[6] = x + pic->width;
1341         vertex3f[7] = vertex3f[10] = y + pic->height;
1342         texcoord2f[0] = 0;texcoord2f[1] = 0;
1343         texcoord2f[2] = 1;texcoord2f[3] = 0;
1344         texcoord2f[4] = 1;texcoord2f[5] = 1;
1345         texcoord2f[6] = 0;texcoord2f[7] = 1;
1346         R_Mesh_Draw(0, 4, 2, polygonelements);
1347         R_Mesh_Finish();
1348         // refresh
1349         VID_Finish(false);
1350 }
1351
1352 void CL_UpdateScreen(void)
1353 {
1354         float conwidth, conheight;
1355
1356         if (vid_hidden)
1357                 return;
1358
1359         if (!scr_initialized || !con_initialized || vid_hidden)
1360                 return;                         // not initialized yet
1361
1362         // don't allow cheats in multiplayer
1363         if (!cl.islocalgame && cl.worldmodel)
1364         {
1365                 if (r_fullbright.integer != 0)
1366                         Cvar_Set ("r_fullbright", "0");
1367                 if (r_ambient.value != 0)
1368                         Cvar_Set ("r_ambient", "0");
1369         }
1370
1371         conwidth = bound(320, vid_conwidth.value, 2048);
1372         conheight = bound(200, vid_conheight.value, 1536);
1373         if (vid_conwidth.value != conwidth)
1374                 Cvar_SetValue("vid_conwidth", conwidth);
1375         if (vid_conheight.value != conheight)
1376                 Cvar_SetValue("vid_conheight", conheight);
1377
1378         // bound viewsize
1379         if (scr_viewsize.value < 30)
1380                 Cvar_Set ("viewsize","30");
1381         if (scr_viewsize.value > 120)
1382                 Cvar_Set ("viewsize","120");
1383
1384         // bound field of view
1385         if (scr_fov.value < 1)
1386                 Cvar_Set ("fov","1");
1387         if (scr_fov.value > 170)
1388                 Cvar_Set ("fov","170");
1389
1390         // validate r_textureunits cvar
1391         if (r_textureunits.integer > gl_textureunits)
1392                 Cvar_SetValueQuick(&r_textureunits, gl_textureunits);
1393         if (r_textureunits.integer < 1)
1394                 Cvar_SetValueQuick(&r_textureunits, 1);
1395
1396         // validate gl_combine cvar
1397         if (gl_combine.integer && !gl_combine_extension)
1398                 Cvar_SetValueQuick(&gl_combine, 0);
1399
1400         // intermission is always full screen
1401         if (cl.intermission)
1402                 sb_lines = 0;
1403         else
1404         {
1405                 if (scr_viewsize.value >= 120)
1406                         sb_lines = 0;           // no status bar at all
1407                 else if (scr_viewsize.value >= 110)
1408                         sb_lines = 24;          // no inventory
1409                 else
1410                         sb_lines = 24+16+8;
1411         }
1412
1413         r_view.colormask[0] = 1;
1414         r_view.colormask[1] = 1;
1415         r_view.colormask[2] = 1;
1416
1417         if (r_timereport_active)
1418                 R_TimeReport("other");
1419
1420         SCR_SetUpToDrawConsole();
1421
1422         if (r_timereport_active)
1423                 R_TimeReport("start");
1424
1425         CHECKGLERROR
1426         qglViewport(0, 0, vid.width, vid.height);CHECKGLERROR
1427         qglDisable(GL_SCISSOR_TEST);CHECKGLERROR
1428         qglDepthMask(1);CHECKGLERROR
1429         qglColorMask(1,1,1,1);CHECKGLERROR
1430         qglClearColor(0,0,0,0);CHECKGLERROR
1431         qglClear(GL_COLOR_BUFFER_BIT);CHECKGLERROR
1432
1433         if (r_timereport_active)
1434                 R_TimeReport("clear");
1435
1436         if (r_stereo_redblue.integer || r_stereo_redgreen.integer || r_stereo_redcyan.integer || r_stereo_sidebyside.integer)
1437         {
1438                 matrix4x4_t originalmatrix = r_view.matrix;
1439                 r_view.matrix.m[0][3] = originalmatrix.m[0][3] + r_stereo_separation.value * -0.5f * r_view.matrix.m[0][1];
1440                 r_view.matrix.m[1][3] = originalmatrix.m[1][3] + r_stereo_separation.value * -0.5f * r_view.matrix.m[1][1];
1441                 r_view.matrix.m[2][3] = originalmatrix.m[2][3] + r_stereo_separation.value * -0.5f * r_view.matrix.m[2][1];
1442
1443                 if (r_stereo_sidebyside.integer)
1444                         r_stereo_side = 0;
1445
1446                 if (r_stereo_redblue.integer || r_stereo_redgreen.integer || r_stereo_redcyan.integer)
1447                 {
1448                         r_view.colormask[0] = 1;
1449                         r_view.colormask[1] = 0;
1450                         r_view.colormask[2] = 0;
1451                 }
1452
1453                 SCR_DrawScreen();
1454
1455                 r_view.matrix.m[0][3] = originalmatrix.m[0][3] + r_stereo_separation.value * 0.5f * r_view.matrix.m[0][1];
1456                 r_view.matrix.m[1][3] = originalmatrix.m[1][3] + r_stereo_separation.value * 0.5f * r_view.matrix.m[1][1];
1457                 r_view.matrix.m[2][3] = originalmatrix.m[2][3] + r_stereo_separation.value * 0.5f * r_view.matrix.m[2][1];
1458
1459                 if (r_stereo_sidebyside.integer)
1460                         r_stereo_side = 1;
1461
1462                 if (r_stereo_redblue.integer || r_stereo_redgreen.integer || r_stereo_redcyan.integer)
1463                 {
1464                         r_view.colormask[0] = 0;
1465                         r_view.colormask[1] = r_stereo_redcyan.integer || r_stereo_redgreen.integer;
1466                         r_view.colormask[2] = r_stereo_redcyan.integer || r_stereo_redblue.integer;
1467                 }
1468
1469                 SCR_DrawScreen();
1470
1471                 r_view.matrix = originalmatrix;
1472         }
1473         else
1474                 SCR_DrawScreen();
1475
1476         SCR_CaptureVideo();
1477
1478         VID_Finish(true);
1479         if (r_timereport_active)
1480                 R_TimeReport("finish");
1481 }
1482
1483 void CL_Screen_NewMap(void)
1484 {
1485         SHOWLMP_clear();
1486 }