From ab74efb83fa720932f8d890c2684ae658fa8652c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 9 Dec 2024 23:37:16 +0300 Subject: [PATCH 1/3] common: render_api: update render api definitions from the engine repository --- common/render_api.h | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/common/render_api.h b/common/render_api.h index 32b48a0db..f3d2967c2 100644 --- a/common/render_api.h +++ b/common/render_api.h @@ -162,6 +162,21 @@ typedef struct decallist_s modelstate_t studio_state; // studio decals only } decallist_t; +enum movie_parms_e +{ + AVI_PARM_LAST = 0, // marker for SetParm to end parse parsing arguments + AVI_RENDER_TEXNUM, // (int) sets texture to draw into, if 0 will draw to screen + AVI_RENDER_X, // (int) when set to screen, sets position where to draw + AVI_RENDER_Y, + AVI_RENDER_W, // (int) sets texture or screen width + AVI_RENDER_H, // set to -1 to draw full screen + AVI_REWIND, // no argument, rewind playback to the beginning + AVI_ENTNUM, // (int) entity number, -1 for no spatialization + AVI_VOLUME, // (int) volume from 0 to 255 + AVI_ATTN, +}; + +struct movie_state_s; struct ref_viewpass_s; typedef struct render_api_s @@ -198,16 +213,16 @@ typedef struct render_api_s void (*R_EntityRemoveDecals)( struct model_s *mod ); // remove all the decals from specified entity (BSP only) // AVIkit support - void *(*AVI_LoadVideo)( const char *filename, qboolean load_audio ); - int (*AVI_GetVideoInfo)( void *Avi, int *xres, int *yres, float *duration ); // a1ba: changed longs to int - int (*AVI_GetVideoFrameNumber)( void *Avi, float time ); - byte *(*AVI_GetVideoFrame)( void *Avi, int frame ); + struct movie_state_s *(*AVI_LoadVideo)( const char *filename, qboolean load_audio ); + int (*AVI_GetVideoInfo)( struct movie_state_s *Avi, int *xres, int *yres, float *duration ); // a1ba: changed longs to int + int (*AVI_GetVideoFrameNumber)( struct movie_state_s *Avi, float time ); + byte *(*AVI_GetVideoFrame)( struct movie_state_s *Avi, int frame ); void (*AVI_UploadRawFrame)( int texture, int cols, int rows, int width, int height, const byte *data ); - void (*AVI_FreeVideo)( void *Avi ); - int (*AVI_IsActive)( void *Avi ); - void (*AVI_StreamSound)( void *Avi, int entnum, float fvol, float attn, float synctime ); - void (*AVI_Reserved0)( void ); // for potential interface expansion without broken compatibility - void (*AVI_Reserved1)( void ); + void (*AVI_FreeVideo)( struct movie_state_s *Avi ); + int (*AVI_IsActive)( struct movie_state_s *Avi ); + void (*AVI_StreamSound)( struct movie_state_s *Avi, int entnum, float fvol, float attn, float synctime ); + qboolean (*AVI_Think)( struct movie_state_s *Avi ); + qboolean (*AVI_SetParm)( struct movie_state_s *Avi, enum movie_parms_e parm, ... ); // glState related calls (must use this instead of normal gl-calls to prevent de-synchornize local states between engine and the client) void (*GL_Bind)( int tmu, unsigned int texnum ); From c1468c8635ff5c7049d2d013a4d8bed026f20411 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 9 Dec 2024 23:37:49 +0300 Subject: [PATCH 2/3] server: pass real filename in UTIL_PrecacheMovie because engine checks for file existence Not sure how it even worked in original Xash3D. --- server/util.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/server/util.cpp b/server/util.cpp index 57fb8b46d..fc1bba3a8 100644 --- a/server/util.cpp +++ b/server/util.cpp @@ -2451,16 +2451,19 @@ unsigned short UTIL_PrecacheMovie( string_t iString, int allow_sound ) unsigned short UTIL_PrecacheMovie( const char *s, int allow_sound ) { int iCompare; - char path[64], temp[64]; + char path[128]; - Q_snprintf( path, sizeof( path ), "media/%s", s ); - Q_snprintf( temp, sizeof( temp ), "%s%s", allow_sound ? "*" : "", s ); + if( Q_snprintf( path, sizeof( path ), "media/%s", s ) < 0 ) + { + ALERT( at_console, "Error: video (%s) name too long\n", path ); + return 0; + } // verify file exists // g-cont. idea! use COMPARE_FILE_TIME instead of LOAD_FILE_FOR_ME if( COMPARE_FILE_TIME( path, path, &iCompare )) { - return g_engfuncs.pfnPrecacheGeneric( temp ); + return g_engfuncs.pfnPrecacheGeneric( path ); } ALERT( at_console, "Warning: video (%s) not found!\n", path ); From d911a8e62be2447748d51c1435ee33eb5e5e3d64 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 9 Dec 2024 23:41:03 +0300 Subject: [PATCH 3/3] client: basic adaption of movie playback to the new API --- client/enginecallback.h | 8 +++-- client/render/gl_local.h | 6 +++- client/render/gl_movie.cpp | 73 +++++++++++++++++++------------------- client/render/gl_scene.cpp | 29 +++++++++------ 4 files changed, 66 insertions(+), 50 deletions(-) diff --git a/client/enginecallback.h b/client/enginecallback.h index aa80b28f1..9b9e0f9db 100644 --- a/client/enginecallback.h +++ b/client/enginecallback.h @@ -115,9 +115,11 @@ inline void PlaySound( int iSound, float vol ) { gEngfuncs.pfnPlaySoundByIndex( #define FREE_CINEMATIC (*gRenderfuncs.AVI_FreeVideo) #define CIN_IS_ACTIVE (*gRenderfuncs.AVI_IsActive) #define CIN_GET_VIDEO_INFO (*gRenderfuncs.AVI_GetVideoInfo) -#define CIN_GET_FRAME_NUMBER (*gRenderfuncs.AVI_GetVideoFrameNumber) -#define CIN_GET_FRAMEDATA (*gRenderfuncs.AVI_GetVideoFrame) -#define CIN_UPDATE_SOUND (*gRenderfuncs.AVI_StreamSound) +// #define CIN_GET_FRAME_NUMBER (*gRenderfuncs.AVI_GetVideoFrameNumber) +// #define CIN_GET_FRAMEDATA (*gRenderfuncs.AVI_GetVideoFrame) +// #define CIN_UPDATE_SOUND (*gRenderfuncs.AVI_StreamSound) +#define CIN_THINK (*gRenderfuncs.AVI_Think) +#define CIN_SET_PARM (*gRenderfuncs.AVI_SetParm) // glcommands #define GL_SelectTexture (*gRenderfuncs.GL_SelectTexture) diff --git a/client/render/gl_local.h b/client/render/gl_local.h index e501c1862..d3becccec 100644 --- a/client/render/gl_local.h +++ b/client/render/gl_local.h @@ -229,9 +229,13 @@ typedef struct gl_fbo_s typedef struct gl_movie_s { char name[32]; - void *state; + struct movie_state_s *state; float length; // total cinematic length int xres, yres; // size of cinematic + + bool finished; + bool sound_set; + bool texture_set; } gl_movie_t; typedef struct gl_texbuffer_s diff --git a/client/render/gl_movie.cpp b/client/render/gl_movie.cpp index a10476be0..462ab6991 100644 --- a/client/render/gl_movie.cpp +++ b/client/render/gl_movie.cpp @@ -26,13 +26,9 @@ int R_PrecacheCinematic( const char *cinname ) if( !cinname || !*cinname ) return -1; - if( *cinname == '*' ) - { - cinname++; - } - // not AVI file - if( Q_stricmp( UTIL_FileExtension( cinname ), "avi" )) + const char *ext = UTIL_FileExtension( cinname ); + if( Q_stricmp( ext, "avi" ) && Q_stricmp( ext, "webm" )) // with ffmpeg we don't really have a limit here return -1; int i; @@ -68,7 +64,13 @@ int R_PrecacheCinematic( const char *cinname ) } ALERT( at_console, "Loading cinematic %s [%s]\n", cinname, "sound" ); - tr.cinematics[i].state = OPEN_CINEMATIC( tr.cinematics[i].name, true ); + + // FIXME: engine is hardcoded to load file in media/ folder, must be fixed on engine side + const char *p = tr.cinematics[i].name; + if( !Q_strnicmp( p, "media/", 6 )) + p += 6; + + tr.cinematics[i].state = (movie_state_s *)OPEN_CINEMATIC( p, true ); // grab info about movie if( tr.cinematics[i].state != NULL ) @@ -79,6 +81,8 @@ int R_PrecacheCinematic( const char *cinname ) void R_InitCinematics( void ) { + // a1ba: this function is useless lmao + // it's called before WORLD_HAS_MOVIES bit set const char *name, *ext; // make sure what we have texture to draw cinematics @@ -162,6 +166,7 @@ void R_UpdateCinematic( const msurface_t *surf ) if( cinhandle >= 0 && es->cintexturenum <= 0 ) es->cintexturenum = R_AllocateCinematicTexture( TF_NOMIPMAP ); + // a1ba: isn't this kinda stupid? If movie isn't active anymore, we will never draw movie on it again if( cinhandle == -1 || es->cintexturenum <= 0 || CIN_IS_ACTIVE( tr.cinematics[cinhandle].state ) == false ) { // cinematic textures limit exceeded, so remove SURF_MOVIE flag @@ -170,28 +175,18 @@ void R_UpdateCinematic( const msurface_t *surf ) } gl_movie_t *cin = &tr.cinematics[cinhandle]; - float cin_time; - - if( FBitSet( RI->currententity->curstate.iuser1, CF_LOOPED_MOVIE )) - { - // advances cinematic time - cin_time = fmod( RI->currententity->curstate.fuser2, cin->length ); - } - else - { - cin_time = RI->currententity->curstate.fuser2; - } - // read the next frame - int cin_frame = CIN_GET_FRAME_NUMBER( cin->state, cin_time ); + if( cin->finished ) + return; - // upload the new frame - if( cin_frame != es->checkcount ) + if( !cin->texture_set ) { - GL_SelectTexture( GL_TEXTURE0 ); // doesn't matter. select 0-th unit just as default - byte *raw = CIN_GET_FRAMEDATA( cin->state, cin_frame ); - CIN_UPLOAD_FRAME( tr.cinTextures[es->cintexturenum-1], cin->xres, cin->yres, cin->xres, cin->yres, raw ); - es->checkcount = cin_frame; + CIN_SET_PARM( cin->state, + AVI_RENDER_TEXNUM, tr.cinTextures[es->cintexturenum-1], + AVI_RENDER_W, cin->xres, + AVI_RENDER_H, cin->yres, + AVI_PARM_LAST ); + cin->texture_set = true; } } @@ -208,18 +203,24 @@ void R_UpdateCinSound( cl_entity_t *e ) return; gl_movie_t *cin = &tr.cinematics[cinhandle]; - float cin_time; - if( FBitSet( e->curstate.iuser1, CF_LOOPED_MOVIE )) + if( cin->finished ) + return; + + if( !cin->sound_set ) { - // advances cinematic time - cin_time = fmod( e->curstate.fuser2, cin->length ); + CIN_SET_PARM( cin->state, + AVI_ENTNUM, e->index, + AVI_VOLUME, static_cast( VOL_NORM * 255 ), + AVI_ATTN, ATTN_NORM, + AVI_PARM_LAST ); + cin->sound_set = true; } - else + + if( !CIN_THINK( cin->state )) // TODO: make a video manager that will call this each frame { - cin_time = e->curstate.fuser2; + if( FBitSet( RI->currententity->curstate.iuser1, CF_LOOPED_MOVIE )) + CIN_SET_PARM( cin->state, AVI_REWIND, AVI_PARM_LAST ); + else cin->finished = true; } - - // stream avi sound - CIN_UPDATE_SOUND( cin->state, e->index, VOL_NORM, ATTN_IDLE, cin_time ); -} \ No newline at end of file +} diff --git a/client/render/gl_scene.cpp b/client/render/gl_scene.cpp index e7149f345..1c933a7ae 100644 --- a/client/render/gl_scene.cpp +++ b/client/render/gl_scene.cpp @@ -496,19 +496,28 @@ static bool R_HandleLightEntity(cl_entity_t *ent) return true; } + // TODO: move this to a common function in gl_movie.cpp gl_movie_t *cin = &tr.cinematics[hCin]; - // advances cinematic time - float cin_time = fmod(entity.GetCinTime(), cin->length); - - // read the next frame - int cin_frame = CIN_GET_FRAME_NUMBER(cin->state, cin_time); - if (cin_frame != dlight->lastframe) + if( !cin->finished ) { - // upload the new frame - byte *raw = CIN_GET_FRAMEDATA(cin->state, cin_frame); - CIN_UPLOAD_FRAME(tr.cinTextures[dlight->cinTexturenum - 1], cin->xres, cin->yres, cin->xres, cin->yres, raw); - dlight->lastframe = cin_frame; + if( !cin->texture_set ) + { + CIN_SET_PARM( cin->state, AVI_RENDER_TEXNUM, tr.cinTextures[dlight->cinTexturenum - 1], + AVI_RENDER_W, cin->xres, + AVI_RENDER_H, cin->yres, + AVI_PARM_LAST ); + cin->texture_set = true; + } + + // running think here because we're usually thinking with audio, but dlight doesn't have audio + + if( !CIN_THINK( cin->state )); // probably should be moved to some kind of global manager that will tick each frame + { + if( FBitSet( RI->currententity->curstate.iuser1, CF_LOOPED_MOVIE )) + CIN_SET_PARM( cin->state, AVI_REWIND, AVI_PARM_LAST ); + else cin->finished = true; + } } if (entity.DisableShadows())