]> git.xonotic.org Git - xonotic/darkplaces.git/blob - cl_video.c
fix a warning
[xonotic/darkplaces.git] / cl_video.c
1
2 #include "quakedef.h"
3 #include "cl_dyntexture.h"
4 #include "cl_video.h"
5 #include "dpvsimpledecode.h"
6
7 // cvars
8 cvar_t cl_video_subtitles = {CVAR_SAVE, "cl_video_subtitles", "0", "show subtitles for videos (if they are presented)"};
9 cvar_t cl_video_subtitles_lines = {CVAR_SAVE, "cl_video_subtitles_lines", "4", "how many lines to occupy for subtitles"};
10 cvar_t cl_video_subtitles_textsize = {CVAR_SAVE, "cl_video_subtitles_textsize", "16", "textsize for subtitles"};
11 cvar_t cl_video_scale = {CVAR_SAVE, "cl_video_scale", "1", "scale of video, 1 = fullscreen, 0.75 - 3/4 of screen etc."};
12 cvar_t cl_video_brightness = {CVAR_SAVE, "cl_video_brightness", "1", "brightness of video, 1 = fullbright, 0.75 - 3/4 etc."};
13
14 // constants (and semi-constants)
15 static int  cl_videormask;
16 static int  cl_videobmask;
17 static int  cl_videogmask;
18 static int      cl_videobytesperpixel;
19
20 static int cl_num_videos;
21 static clvideo_t cl_videos[ MAXCLVIDEOS ];
22 static rtexturepool_t *cl_videotexturepool;
23
24 static clvideo_t *FindUnusedVid( void )
25 {
26         int i;
27         for( i = 1 ; i < MAXCLVIDEOS ; i++ )
28                 if( cl_videos[ i ].state == CLVIDEO_UNUSED )
29                         return &cl_videos[ i ];
30         return NULL;
31 }
32
33 static qboolean OpenStream( clvideo_t * video )
34 {
35         char *errorstring;
36         video->stream = dpvsimpledecode_open( video->filename, &errorstring);
37         if (!video->stream )
38         {
39                 Con_Printf("unable to open \"%s\", error: %s\n", video->filename, errorstring);
40                 return false;
41         }
42         return true;
43 }
44
45 static void VideoUpdateCallback(rtexture_t *rt, void *data) {
46         clvideo_t *video = (clvideo_t *) data;
47         R_UpdateTexture( video->cpif.tex, (unsigned char *)video->imagedata, 0, 0, video->cpif.width, video->cpif.height );
48 }
49
50 static void LinkVideoTexture( clvideo_t *video ) {
51         video->cpif.tex = R_LoadTexture2D( cl_videotexturepool, video->cpif.name,
52                 video->cpif.width, video->cpif.height, NULL, TEXTYPE_BGRA, TEXF_PERSISTENT | TEXF_ALLOWUPDATES, NULL );
53         R_MakeTextureDynamic( video->cpif.tex, VideoUpdateCallback, video );
54         CL_LinkDynTexture( video->cpif.name, video->cpif.tex );
55 }
56
57 static void UnlinkVideoTexture( clvideo_t *video ) {
58         CL_UnlinkDynTexture( video->cpif.name );
59         // free the texture
60         R_FreeTexture( video->cpif.tex );
61         // free the image data
62         Mem_Free( video->imagedata );
63 }
64
65 static void SuspendVideo( clvideo_t * video )
66 {
67         if( video->suspended )
68                 return;
69         video->suspended = true;
70         UnlinkVideoTexture( video );
71         // if we are in firstframe mode, also close the stream
72         if( video->state == CLVIDEO_FIRSTFRAME )
73                 dpvsimpledecode_close( video->stream );
74 }
75
76 static qboolean WakeVideo( clvideo_t * video )
77 {
78         if( !video->suspended )
79                 return true;
80         video->suspended = false;
81
82         if( video->state == CLVIDEO_FIRSTFRAME )
83                 if( !OpenStream( video ) ) {
84                         video->state = CLVIDEO_UNUSED;
85                         return false;
86                 }
87
88         video->imagedata = Mem_Alloc( cls.permanentmempool, video->cpif.width * video->cpif.height * cl_videobytesperpixel );
89         LinkVideoTexture( video );
90
91         // update starttime
92         video->starttime += realtime - video->lasttime;
93
94         return true;
95 }
96
97 static void LoadSubtitles( clvideo_t *video, const char *subtitlesfile )
98 {
99         char *subtitle_text;
100         const char *data;
101         float subtime, sublen;
102         int numsubs = 0;
103
104         if (gamemode == GAME_BLOODOMNICIDE)
105         {
106                 char overridename[MAX_QPATH];
107                 cvar_t *langcvar;
108
109                 langcvar = Cvar_FindVar("language");
110                 subtitle_text = NULL;
111                 if (langcvar)
112                 {
113                         dpsnprintf(overridename, sizeof(overridename), "script/locale/%s/%s", langcvar->string, subtitlesfile);
114                         subtitle_text = (char *)FS_LoadFile(overridename, cls.permanentmempool, false, NULL);
115                 }
116                 if (!subtitle_text)
117                         subtitle_text = (char *)FS_LoadFile(subtitlesfile, cls.permanentmempool, false, NULL);
118         }
119         else
120         {
121                 subtitle_text = (char *)FS_LoadFile(subtitlesfile, cls.permanentmempool, false, NULL);
122         }
123         if (!subtitle_text)
124         {
125                 Con_DPrintf( "LoadSubtitles: can't open subtitle file '%s'!\n", subtitlesfile );
126                 return;
127         }
128
129         // parse subtitle_text
130         // line is: x y "text" where
131         //    x - start time
132         //    y - seconds last (if 0 - last thru next sub, if negative - last to next sub - this amount of seconds)
133
134         data = subtitle_text;
135         for (;;)
136         {
137                 if (!COM_ParseToken_QuakeC(&data, false))
138                         break;
139                 subtime = atof( com_token );
140                 if (!COM_ParseToken_QuakeC(&data, false))
141                         break;
142                 sublen = atof( com_token );
143                 if (!COM_ParseToken_QuakeC(&data, false))
144                         break;
145                 if (!com_token[0])
146                         continue;
147                 // check limits
148                 if (video->subtitles == CLVIDEO_MAX_SUBTITLES)
149                 {
150                         Con_Printf("WARNING: CLVIDEO_MAX_SUBTITLES = %i reached when reading subtitles from '%s'\n", CLVIDEO_MAX_SUBTITLES, subtitlesfile);
151                         break;  
152                 }
153                 // add a sub
154                 video->subtitle_text[numsubs] = (char *) Mem_Alloc(cls.permanentmempool, strlen(com_token) + 1);
155                 memcpy(video->subtitle_text[numsubs], com_token, strlen(com_token) + 1);
156                 video->subtitle_start[numsubs] = subtime;
157                 video->subtitle_end[numsubs] = sublen;
158                 if (numsubs > 0) // make true len for prev sub, autofix overlapping subtitles
159                 {
160                         if (video->subtitle_end[numsubs-1] <= 0)
161                                 video->subtitle_end[numsubs-1] = max(video->subtitle_start[numsubs-1], video->subtitle_start[numsubs] + video->subtitle_end[numsubs-1]);
162                         else
163                                 video->subtitle_end[numsubs-1] = min(video->subtitle_start[numsubs-1] + video->subtitle_end[numsubs-1], video->subtitle_start[numsubs]);
164                 }
165                 numsubs++;
166                 // todo: check timing for consistency?
167         }
168         if (numsubs > 0) // make true len for prev sub, autofix overlapping subtitles
169         {
170                 if (video->subtitle_end[numsubs-1] <= 0)
171                         video->subtitle_end[numsubs-1] = 99999999; // fixme: make it end when video ends?
172                 else
173                         video->subtitle_end[numsubs-1] = video->subtitle_start[numsubs-1] + video->subtitle_end[numsubs-1];
174         }
175         Z_Free( subtitle_text );
176         video->subtitles = numsubs;
177 /*
178         Con_Printf( "video->subtitles: %i\n", video->subtitles );
179         for (numsubs = 0; numsubs < video->subtitles; numsubs++)
180                 Con_Printf( "  %03.2f %03.2f : %s\n", video->subtitle_start[numsubs], video->subtitle_end[numsubs], video->subtitle_text[numsubs] );
181 */
182 }
183
184 static clvideo_t* OpenVideo( clvideo_t *video, const char *filename, const char *name, int owner, const char *subtitlesfile )
185 {
186         strlcpy( video->filename, filename, sizeof(video->filename) );
187         video->ownertag = owner;
188         if( strncmp( name, CLVIDEOPREFIX, sizeof( CLVIDEOPREFIX ) - 1 ) )
189                 return NULL;
190         strlcpy( video->cpif.name, name, sizeof(video->cpif.name) );
191
192         if( !OpenStream( video ) )
193                 return NULL;
194
195         video->state = CLVIDEO_FIRSTFRAME;
196         video->framenum = -1;
197         video->framerate = dpvsimpledecode_getframerate( video->stream );
198         video->lasttime = realtime;
199         video->subtitles = 0;
200
201         video->cpif.width = dpvsimpledecode_getwidth( video->stream );
202         video->cpif.height = dpvsimpledecode_getheight( video->stream );
203         video->imagedata = Mem_Alloc( cls.permanentmempool, video->cpif.width * video->cpif.height * cl_videobytesperpixel );
204         LinkVideoTexture( video );
205
206         // VorteX: load simple subtitle_text file
207         if (subtitlesfile[0])
208                 LoadSubtitles( video, subtitlesfile );
209
210         return video;
211 }
212
213 clvideo_t* CL_OpenVideo( const char *filename, const char *name, int owner, const char *subtitlesfile )
214 {
215         clvideo_t *video;
216         // sanity check
217         if( !name || !*name || strncmp( name, CLVIDEOPREFIX, sizeof( CLVIDEOPREFIX ) - 1 ) != 0 ) {
218                 Con_DPrintf( "CL_OpenVideo: Bad video texture name '%s'!\n", name );
219                 return NULL;
220         }
221
222         video = FindUnusedVid();
223         if( !video ) {
224                 Con_Printf( "CL_OpenVideo: unable to open video \"%s\" - video limit reached\n", filename );
225                 return NULL;
226         }
227         video = OpenVideo( video, filename, name, owner, subtitlesfile );
228         // expand the active range to include the new entry
229         if (video) {
230                 cl_num_videos = max(cl_num_videos, (int)(video - cl_videos) + 1);
231         }
232         return video;
233 }
234
235 static clvideo_t* CL_GetVideoBySlot( int slot )
236 {
237         clvideo_t *video = &cl_videos[ slot ];
238
239         if( video->suspended )
240         {
241                 if( !WakeVideo( video ) )
242                         return NULL;
243                 else if( video->state == CLVIDEO_RESETONWAKEUP )
244                         video->framenum = -1;
245         }
246
247         video->lasttime = realtime;
248
249         return video;
250 }
251
252 clvideo_t *CL_GetVideoByName( const char *name )
253 {
254         int i;
255
256         for( i = 0 ; i < cl_num_videos ; i++ )
257                 if( cl_videos[ i ].state != CLVIDEO_UNUSED
258                         &&      !strcmp( cl_videos[ i ].cpif.name , name ) )
259                         break;
260         if( i != cl_num_videos )
261                 return CL_GetVideoBySlot( i );
262         else
263                 return NULL;
264 }
265
266 void CL_SetVideoState( clvideo_t *video, clvideostate_t state )
267 {
268         if( !video )
269                 return;
270
271         video->lasttime = realtime;
272         video->state = state;
273         if( state == CLVIDEO_FIRSTFRAME )
274                 CL_RestartVideo( video );
275 }
276
277 void CL_RestartVideo( clvideo_t *video )
278 {
279         if( !video )
280                 return;
281
282         video->starttime = video->lasttime = realtime;
283         video->framenum = -1;
284
285         dpvsimpledecode_close( video->stream );
286         if( !OpenStream( video ) )
287                 video->state = CLVIDEO_UNUSED;
288 }
289
290 void CL_CloseVideo( clvideo_t * video )
291 {
292         int i;
293
294         if( !video || video->state == CLVIDEO_UNUSED )
295                 return;
296
297         if( !video->suspended || video->state != CLVIDEO_FIRSTFRAME )
298                 dpvsimpledecode_close( video->stream );
299         if( !video->suspended )
300                 UnlinkVideoTexture( video );
301         if (video->subtitles)
302         {
303                 for (i = 0; i < video->subtitles; i++)
304                         Z_Free( video->subtitle_text[i] );
305                 video->subtitles = 0;
306         }
307
308         video->state = CLVIDEO_UNUSED;
309 }
310
311 static void VideoFrame( clvideo_t *video )
312 {
313         int destframe;
314
315         if( video->state == CLVIDEO_FIRSTFRAME )
316                 destframe = 0;
317         else
318                 destframe = (int)((realtime - video->starttime) * video->framerate);
319         if( destframe < 0 )
320                 destframe = 0;
321         if( video->framenum < destframe ) {
322                 do {
323                         video->framenum++;
324                         if( dpvsimpledecode_video( video->stream, video->imagedata, cl_videormask,
325                                 cl_videogmask, cl_videobmask, cl_videobytesperpixel,
326                                 cl_videobytesperpixel * video->cpif.width )
327                                 ) { // finished?
328                                 CL_RestartVideo( video );
329                                 if( video->state == CLVIDEO_PLAY )
330                                                 video->state = CLVIDEO_FIRSTFRAME;
331                                 return;
332                         }
333                 } while( video->framenum < destframe );
334                 R_MarkDirtyTexture( video->cpif.tex );
335         }
336 }
337
338 void CL_Video_Frame( void ) // update all videos
339 {
340         int i;
341         clvideo_t *video;
342
343         if (!cl_num_videos)
344                 return;
345
346         for( video = cl_videos, i = 0 ; i < cl_num_videos ; video++, i++ )
347                 if( video->state != CLVIDEO_UNUSED && !video->suspended )
348                 {
349                         if( realtime - video->lasttime > CLTHRESHOLD )
350                                 SuspendVideo( video );
351                         else if( video->state == CLVIDEO_PAUSE )
352                                 video->starttime = realtime - video->framenum * video->framerate;
353                         else
354                                 VideoFrame( video );
355                 }
356
357         if( cl_videos->state == CLVIDEO_FIRSTFRAME )
358                 CL_VideoStop();
359
360         // reduce range to exclude unnecessary entries
361         while (cl_num_videos > 0 && cl_videos[cl_num_videos-1].state == CLVIDEO_UNUSED)
362                 cl_num_videos--;
363 }
364
365 void CL_Video_Shutdown( void )
366 {
367         int i;
368         for( i = 0 ; i < cl_num_videos ; i++ )
369                 CL_CloseVideo( &cl_videos[ i ] );
370 }
371
372 void CL_PurgeOwner( int owner )
373 {
374         int i;
375         for( i = 0 ; i < cl_num_videos ; i++ )
376                 if( cl_videos[ i ].ownertag == owner )
377                         CL_CloseVideo( &cl_videos[ i ] );
378 }
379
380 typedef struct
381 {
382         dp_font_t *font;
383         float x;
384         float y;
385         float width;
386         float height;
387         float alignment; // 0 = left, 0.5 = center, 1 = right
388         float fontsize;
389         float textalpha;
390 }
391 cl_video_subtitle_info_t;
392
393 float CL_DrawVideo_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
394 {
395         cl_video_subtitle_info_t *si = (cl_video_subtitle_info_t *) passthrough;
396
397         if(w == NULL)
398                 return si->fontsize * si->font->maxwidth;
399         if(maxWidth >= 0)
400                 return DrawQ_TextWidth_UntilWidth(w, length, si->fontsize, si->fontsize, false, si->font, -maxWidth); // -maxWidth: we want at least one char
401         else if(maxWidth == -1)
402                 return DrawQ_TextWidth(w, *length, si->fontsize, si->fontsize, false, si->font);
403         else
404                 return 0;
405 }
406
407 int CL_DrawVideo_DisplaySubtitleLine(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
408 {
409         cl_video_subtitle_info_t *si = (cl_video_subtitle_info_t *) passthrough;
410
411         int x = (int) (si->x + (si->width - width) * si->alignment);
412         if (length > 0)
413                 DrawQ_String(x, si->y, line, length, si->fontsize, si->fontsize, 1.0, 1.0, 1.0, si->textalpha, 0, NULL, false, si->font);
414         si->y += si->fontsize;
415         return 1;
416 }
417
418 int cl_videoplaying = false; // old, but still supported
419
420 void CL_DrawVideo(void)
421 {
422         clvideo_t *video;
423         float videotime;
424         cl_video_subtitle_info_t si;
425         int i;
426
427         if (!cl_videoplaying)
428                 return;
429
430         video = CL_GetVideoBySlot( 0 );
431
432         // fix cvars
433         if (cl_video_scale.value <= 0 || cl_video_scale.value > 1)
434                 Cvar_SetValueQuick( &cl_video_scale, 1);
435         if (cl_video_brightness.value <= 0 || cl_video_brightness.value > 10)
436                 Cvar_SetValueQuick( &cl_video_brightness, 1);
437
438 #if 0
439         // enable video-only polygon stipple (of global stipple is not active)
440         if (qglPolygonStipple && !scr_stipple.integer)
441         {
442                 GLubyte stipple[128];
443                 int i, s, width, parts;
444         
445                 s = 1;
446                 parts = (s & 007);
447                 width = (s & 070) >> 3;
448                 qglEnable(GL_POLYGON_STIPPLE);CHECKGLERROR // 0x0B42
449                 for(i = 0; i < 128; ++i)
450                 {
451                         int line = i/4;
452                         stipple[i] = ((line >> width) & ((1 << parts) - 1)) ? 0x00 : 0xFF;
453                 }
454                 qglPolygonStipple(stipple);CHECKGLERROR
455         }
456 #endif
457
458         // draw video
459         if (cl_video_scale.value == 1)
460                 DrawQ_Pic(0, 0, &video->cpif, vid_conwidth.integer, vid_conheight.integer, cl_video_brightness.value, cl_video_brightness.value, cl_video_brightness.value, 1, 0);
461         else
462         {
463                 DrawQ_Fill(0, 0, vid_conwidth.integer, vid_conheight.integer, 0, 0, 0, 1, 0);
464                 DrawQ_Pic((int)(vid_conwidth.integer * (1 - cl_video_scale.value) * 0.5), (int)(vid_conheight.integer * (1 - cl_video_scale.value) * 0.5), &video->cpif, (int)(vid_conwidth.integer * cl_video_scale.value), (int)(vid_conheight.integer * cl_video_scale.value), cl_video_brightness.value, cl_video_brightness.value, cl_video_brightness.value, 1, 0);
465         }
466
467         
468 #if 0
469         // disable video-only stipple
470         if (qglPolygonStipple && !scr_stipple.integer)
471         {
472                 qglDisable(GL_POLYGON_STIPPLE);CHECKGLERROR
473         }
474 #endif
475
476         // VorteX: draw subtitle_text
477         if (!video->subtitles || !cl_video_subtitles.integer)
478                 return;
479
480         // find current subtitle
481         videotime = realtime - video->starttime;
482         for (i = 0; i < video->subtitles; i++)
483         {
484                 if (videotime >= video->subtitle_start[i] && videotime <= video->subtitle_end[i])
485                 {
486                         // found, draw it
487                         si.font = FONT_NOTIFY;
488                         si.x = vid_conwidth.integer * 0.1;
489                         si.y = vid_conheight.integer - (max(1, cl_video_subtitles_lines.value) * cl_video_subtitles_textsize.value);
490                         si.width = vid_conwidth.integer * 0.8;
491                         si.height = max(1, cl_video_subtitles_lines.integer) * cl_video_subtitles_textsize.value;
492                         si.alignment = 0.5;
493                         si.fontsize = cl_video_subtitles_textsize.value;
494                         si.textalpha = min(1, (videotime - video->subtitle_start[i])/0.5) * min(1, ((video->subtitle_end[i] - videotime)/0.3)); // fade in and fade out
495                         COM_Wordwrap(video->subtitle_text[i], strlen(video->subtitle_text[i]), 0, si.width, CL_DrawVideo_WordWidthFunc, &si, CL_DrawVideo_DisplaySubtitleLine, &si);
496                         break;
497                 }
498         }
499 }
500
501 void CL_VideoStart(char *filename, const char *subtitlesfile)
502 {
503         Host_StartVideo();
504
505         if( cl_videos->state != CLVIDEO_UNUSED )
506                 CL_CloseVideo( cl_videos );
507         // already contains video/
508         if( !OpenVideo( cl_videos, filename, va( CLDYNTEXTUREPREFIX "%s", filename ), 0, subtitlesfile ) )
509                 return;
510         // expand the active range to include the new entry
511         cl_num_videos = max(cl_num_videos, 1);
512
513         cl_videoplaying = true;
514
515         CL_SetVideoState( cl_videos, CLVIDEO_PLAY );
516         CL_RestartVideo( cl_videos );
517 }
518
519 void CL_Video_KeyEvent( int key, int ascii, qboolean down ) 
520 {
521         // only react to up events, to allow the user to delay the abortion point if it suddenly becomes interesting..
522         if( !down ) {
523                 if( key == K_ESCAPE || key == K_ENTER || key == K_SPACE ) {
524                         CL_VideoStop();
525                 }
526         }
527 }
528
529 void CL_VideoStop(void)
530 {
531         cl_videoplaying = false;
532
533         CL_CloseVideo( cl_videos );
534 }
535
536 static void CL_PlayVideo_f(void)
537 {
538         char name[MAX_QPATH], subtitlesfile[MAX_QPATH];
539
540         Host_StartVideo();
541
542         if (Cmd_Argc() < 2)
543         {
544                 Con_Print("usage: playvideo <videoname> [custom_subtitles_file]\nplays video named video/<videoname>.dpv\nif custom subtitles file is not presented\nit tries video/<videoname>.sub");
545                 return;
546         }
547
548         dpsnprintf(name, sizeof(name), "video/%s.dpv", Cmd_Argv(1));
549         if ( Cmd_Argc() > 2)
550                 CL_VideoStart(name, Cmd_Argv(2));
551         else
552         {
553                 dpsnprintf(subtitlesfile, sizeof(subtitlesfile), "video/%s.dpsubs", Cmd_Argv(1));
554                 CL_VideoStart(name, subtitlesfile);
555         }
556 }
557
558 static void CL_StopVideo_f(void)
559 {
560         CL_VideoStop();
561 }
562
563 static void cl_video_start( void )
564 {
565         int i;
566         clvideo_t *video;
567
568         cl_videotexturepool = R_AllocTexturePool();
569
570         for( video = cl_videos, i = 0 ; i < cl_num_videos ; i++, video++ )
571                 if( video->state != CLVIDEO_UNUSED && !video->suspended )
572                         LinkVideoTexture( video );
573 }
574
575 static void cl_video_shutdown( void )
576 {
577         // TODO: unlink video textures?
578         R_FreeTexturePool( &cl_videotexturepool );
579 }
580
581 static void cl_video_newmap( void )
582 {
583 }
584
585 void CL_Video_Init( void )
586 {
587         union
588         {
589                 unsigned char b[4];
590                 unsigned int i;
591         }
592         bgra;
593
594         cl_num_videos = 0;
595         cl_videobytesperpixel = 4;
596
597         // set masks in an endian-independent way (as they really represent bytes)
598         bgra.i = 0;bgra.b[0] = 0xFF;cl_videobmask = bgra.i;
599         bgra.i = 0;bgra.b[1] = 0xFF;cl_videogmask = bgra.i;
600         bgra.i = 0;bgra.b[2] = 0xFF;cl_videormask = bgra.i;
601
602         Cmd_AddCommand( "playvideo", CL_PlayVideo_f, "play a .dpv video file" );
603         Cmd_AddCommand( "stopvideo", CL_StopVideo_f, "stop playing a .dpv video file" );
604
605         Cvar_RegisterVariable(&cl_video_subtitles);
606         Cvar_RegisterVariable(&cl_video_subtitles_lines);
607         Cvar_RegisterVariable(&cl_video_subtitles_textsize);
608         Cvar_RegisterVariable(&cl_video_scale);
609         Cvar_RegisterVariable(&cl_video_brightness);
610
611         R_RegisterModule( "CL_Video", cl_video_start, cl_video_shutdown, cl_video_newmap );
612 }
613