2020-06-27 09:48:55 +00:00
/*
* * screenjob . cpp
* *
* * Generic asynchronous screen display
* *
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* * Copyright 2020 Christoph Oelckers
* * All rights reserved .
* *
* * Redistribution and use in source and binary forms , with or without
* * modification , are permitted provided that the following conditions
* * are met :
* *
* * 1. Redistributions of source code must retain the above copyright
* * notice , this list of conditions and the following disclaimer .
* * 2. Redistributions in binary form must reproduce the above copyright
* * notice , this list of conditions and the following disclaimer in the
* * documentation and / or other materials provided with the distribution .
* * 3. The name of the author may not be used to endorse or promote products
* * derived from this software without specific prior written permission .
* *
* * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ` ` AS IS ' ' AND ANY EXPRESS OR
* * IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES
* * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED .
* * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT , INDIRECT ,
* * INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT
* * NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE ,
* * DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY
* * THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF
* * THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
* * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* *
*/
2020-06-20 07:46:41 +00:00
# include "types.h"
# include "build.h"
# include "screenjob.h"
2020-06-27 09:48:55 +00:00
# include "i_time.h"
# include "v_2ddrawer.h"
2020-06-27 22:32:28 +00:00
# include "animlib.h"
# include "v_draw.h"
# include "s_soundinternal.h"
# include "animtexture.h"
2020-07-19 10:48:31 +00:00
# include "gamestate.h"
2020-10-04 16:31:48 +00:00
# include "razemenu.h"
2020-07-26 16:02:24 +00:00
# include "raze_sound.h"
2020-07-29 21:18:08 +00:00
# include "SmackerDecoder.h"
2020-07-23 20:26:07 +00:00
# include "movie/playmve.h"
2020-07-28 19:05:14 +00:00
# include "gamecontrol.h"
2020-09-10 15:54:27 +00:00
# include <vpx/vpx_decoder.h>
# include <vpx/vp8dx.h>
2020-09-05 09:58:19 +00:00
# include "raze_music.h"
2020-06-20 07:46:41 +00:00
2020-06-27 09:48:55 +00:00
IMPLEMENT_CLASS ( DScreenJob , true , false )
2020-06-28 20:17:27 +00:00
IMPLEMENT_CLASS ( DImageScreen , true , false )
2020-07-29 21:18:08 +00:00
int DBlackScreen : : Frame ( uint64_t clock , bool skiprequest )
{
int span = int ( clock / 1'000'000 ) ;
twod - > ClearScreen ( ) ;
return span < wait ? 1 : - 1 ;
}
2020-06-28 20:17:27 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int DImageScreen : : Frame ( uint64_t clock , bool skiprequest )
{
if ( tilenum > 0 )
{
tex = tileGetTexture ( tilenum , true ) ;
}
2020-09-04 19:17:24 +00:00
if ( ! tex )
{
twod - > ClearScreen ( ) ;
return 0 ;
}
2020-06-28 20:17:27 +00:00
int span = int ( clock / 1'000'000 ) ;
twod - > ClearScreen ( ) ;
2020-10-13 21:29:12 +00:00
DrawTexture ( twod , tex , 0 , 0 , DTA_FullscreenEx , FSMode_ScaleToFit43 , DTA_LegacyRenderStyle , STYLE_Normal , DTA_TranslationIndex , trans , TAG_DONE ) ;
2020-06-28 20:17:27 +00:00
// Only end after having faded out.
2020-07-29 21:18:08 +00:00
return skiprequest ? - 1 : span > waittime ? 0 : 1 ;
2020-06-28 20:17:27 +00:00
}
2020-06-27 22:32:28 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2020-06-27 09:48:55 +00:00
2020-06-27 22:32:28 +00:00
class DAnmPlayer : public DScreenJob
2020-06-20 07:46:41 +00:00
{
2020-06-27 22:32:28 +00:00
// This doesn't need its own class type
anim_t anim ;
TArray < uint8_t > buffer ;
int numframes = 0 ;
int curframe = 1 ;
int frametime = 0 ;
2020-08-19 15:23:18 +00:00
int nextframetime = 0 ;
2020-06-27 22:32:28 +00:00
AnimTextures animtex ;
const AnimSound * animSnd ;
const int * frameTicks ;
2020-08-17 22:04:48 +00:00
bool nostopsound ;
2020-06-27 22:32:28 +00:00
public :
bool isvalid ( ) { return numframes > 0 ; }
2020-08-17 22:04:48 +00:00
DAnmPlayer ( FileReader & fr , const AnimSound * ans , const int * frameticks , bool nosoundcutoff )
: animSnd ( ans ) , frameTicks ( frameticks ) , nostopsound ( nosoundcutoff )
2020-06-20 07:46:41 +00:00
{
2020-06-27 22:32:28 +00:00
buffer = fr . ReadPadded ( 1 ) ;
fr . Close ( ) ;
if ( ANIM_LoadAnim ( & anim , buffer . Data ( ) , buffer . Size ( ) - 1 ) < 0 )
2020-06-20 07:46:41 +00:00
{
2020-06-27 22:32:28 +00:00
return ;
}
numframes = ANIM_NumFrames ( & anim ) ;
animtex . SetSize ( AnimTexture : : Paletted , 320 , 200 ) ;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int Frame ( uint64_t clock , bool skiprequest ) override
{
2020-08-19 15:23:18 +00:00
int currentclock = int ( clock * 120 / 1'000'000'000 ) ;
2020-06-27 22:32:28 +00:00
2020-08-19 15:23:18 +00:00
if ( currentclock < nextframetime - 1 )
2020-06-27 22:32:28 +00:00
{
twod - > ClearScreen ( ) ;
2020-08-14 19:01:27 +00:00
DrawTexture ( twod , animtex . GetFrame ( ) , 0 , 0 , DTA_FullscreenEx , FSMode_ScaleToFit43 , DTA_Masked , false , TAG_DONE ) ;
2020-08-17 22:04:48 +00:00
if ( skiprequest & & ! nostopsound ) soundEngine - > StopAllChannels ( ) ;
2020-06-27 22:32:28 +00:00
return skiprequest ? - 1 : 1 ;
}
animtex . SetFrame ( ANIM_GetPalette ( & anim ) , ANIM_DrawFrame ( & anim , curframe ) ) ;
2020-08-19 15:23:18 +00:00
frametime = currentclock ;
2020-06-27 22:32:28 +00:00
twod - > ClearScreen ( ) ;
2020-08-14 19:01:27 +00:00
DrawTexture ( twod , animtex . GetFrame ( ) , 0 , 0 , DTA_FullscreenEx , FSMode_ScaleToFit43 , DTA_Masked , false , TAG_DONE ) ;
2020-06-27 22:32:28 +00:00
int delay = 20 ;
if ( frameTicks )
{
2020-08-15 11:04:15 +00:00
if ( curframe = = 1 ) delay = frameTicks [ 0 ] ;
2020-09-18 20:49:51 +00:00
else if ( curframe < numframes - 2 ) delay = frameTicks [ 1 ] ;
2020-06-27 22:32:28 +00:00
else delay = frameTicks [ 2 ] ;
}
2020-08-19 15:23:18 +00:00
nextframetime + = delay ;
2020-06-27 22:32:28 +00:00
2020-08-14 20:31:54 +00:00
if ( animSnd ) for ( int i = 0 ; animSnd [ i ] . framenum > = 0 ; i + + )
2020-06-27 22:32:28 +00:00
{
if ( animSnd [ i ] . framenum = = curframe )
2020-06-20 07:46:41 +00:00
{
2020-06-27 22:32:28 +00:00
int sound = animSnd [ i ] . soundnum ;
if ( sound = = - 1 )
soundEngine - > StopAllChannels ( ) ;
2020-07-28 19:05:14 +00:00
else if ( SoundEnabled ( ) )
2020-06-28 12:42:31 +00:00
soundEngine - > StartSound ( SOURCE_None , nullptr , nullptr , CHAN_AUTO , CHANF_UI , sound , 1.f , ATTN_NONE ) ;
2020-06-20 07:46:41 +00:00
}
}
2020-09-05 16:14:50 +00:00
if ( ! skiprequest & & ! nostopsound & & curframe = = numframes & & soundEngine - > GetSoundPlayingInfo ( SOURCE_None , nullptr , - 1 ) ) return 1 ;
2020-06-27 22:32:28 +00:00
curframe + + ;
2020-08-17 22:04:48 +00:00
if ( skiprequest & & ! nostopsound ) soundEngine - > StopAllChannels ( ) ;
2020-06-27 22:32:28 +00:00
return skiprequest ? - 1 : curframe < numframes ? 1 : 0 ;
}
void OnDestroy ( ) override
{
buffer . Reset ( ) ;
animtex . Clean ( ) ;
}
} ;
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2020-07-23 20:26:07 +00:00
class DMvePlayer : public DScreenJob
{
InterplayDecoder decoder ;
bool failed = false ;
public :
bool isvalid ( ) { return ! failed ; }
2020-08-11 17:52:54 +00:00
DMvePlayer ( FileReader & fr ) : decoder ( SoundEnabled ( ) )
2020-07-23 20:26:07 +00:00
{
failed = ! decoder . Open ( fr ) ;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int Frame ( uint64_t clock , bool skiprequest ) override
{
if ( failed ) return - 1 ;
bool playon = decoder . RunFrame ( clock ) ;
twod - > ClearScreen ( ) ;
2020-08-14 19:01:27 +00:00
DrawTexture ( twod , decoder . animTex ( ) . GetFrame ( ) , 0 , 0 , DTA_FullscreenEx , FSMode_ScaleToFit43 , TAG_DONE ) ;
2020-07-23 20:26:07 +00:00
2020-07-24 17:05:34 +00:00
return skiprequest ? - 1 : playon ? 1 : 0 ;
2020-07-23 20:26:07 +00:00
}
void OnDestroy ( ) override
{
decoder . Close ( ) ;
}
} ;
2020-09-05 09:58:19 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class DVpxPlayer : public DScreenJob
{
bool failed = false ;
FileReader fr ;
AnimTextures animtex ;
const AnimSound * animSnd ;
2020-09-10 15:54:27 +00:00
unsigned width , height ;
TArray < uint8_t > Pic ;
TArray < uint8_t > readBuf ;
vpx_codec_ctx_t codec { } ;
vpx_codec_iter_t iter = nullptr ;
2020-09-05 09:58:19 +00:00
uint32_t convnumer ;
uint32_t convdenom ;
2020-09-10 15:54:27 +00:00
uint64_t nsecsperframe ;
2020-09-05 09:58:19 +00:00
uint64_t nextframetime ;
2020-09-10 15:54:27 +00:00
int decstate = 0 ;
2020-09-05 09:58:19 +00:00
int framenum = 0 ;
2020-09-10 15:54:27 +00:00
int numframes ;
2020-09-05 09:58:19 +00:00
int lastsoundframe = - 1 ;
public :
int soundtrack = - 1 ;
public :
bool isvalid ( ) { return ! failed ; }
DVpxPlayer ( FileReader & fr_ , const AnimSound * animSnd_ , int origframedelay )
{
fr = std : : move ( fr_ ) ;
animSnd = animSnd_ ;
2020-09-10 15:54:27 +00:00
if ( ! ReadIVFHeader ( origframedelay ) )
2020-09-05 09:58:19 +00:00
{
2020-09-10 15:54:27 +00:00
// We should never get here, because any file failing this has been eliminated before this constructor got called.
Printf ( PRINT_BOLD , " Failed reading IVF header \n " ) ;
2020-09-05 09:58:19 +00:00
failed = true ;
}
2020-09-10 15:54:27 +00:00
Pic . Resize ( width * height * 4 ) ;
// Todo: Support VP9 as well?
vpx_codec_dec_cfg_t cfg = { 1 , width , height } ;
if ( vpx_codec_dec_init ( & codec , & vpx_codec_vp8_dx_algo , & cfg , 0 ) )
2020-09-05 09:58:19 +00:00
{
Printf ( PRINT_BOLD , " Error initializing VPX codec. \n " ) ;
failed = true ;
}
2020-09-10 15:54:27 +00:00
}
2020-09-05 09:58:19 +00:00
2020-09-10 15:54:27 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool ReadIVFHeader ( int origframedelay )
{
// IVF format: http://wiki.multimedia.cx/index.php?title=IVF
uint32_t magic ; fr . Read ( & magic , 4 ) ; // do not byte swap!
if ( magic ! = MAKE_ID ( ' D ' , ' K ' , ' I ' , ' F ' ) ) return false ;
uint16_t version = fr . ReadUInt16 ( ) ;
if ( version ! = 0 ) return false ;
uint16_t length = fr . ReadUInt16 ( ) ;
if ( length ! = 32 ) return false ;
fr . Read ( & magic , 4 ) ;
if ( magic ! = MAKE_ID ( ' V ' , ' P ' , ' 8 ' , ' 0 ' ) ) return false ;
width = fr . ReadUInt16 ( ) ;
height = fr . ReadUInt16 ( ) ;
uint32_t fpsdenominator = fr . ReadUInt32 ( ) ;
uint32_t fpsnumerator = fr . ReadUInt32 ( ) ;
numframes = fr . ReadUInt32 ( ) ;
if ( numframes = = 0 ) return false ;
fr . Seek ( 4 , FileReader : : SeekCur ) ;
if ( fpsdenominator > 1000 | | fpsnumerator = = 0 | | fpsdenominator = = 0 )
{
// default to 30 fps if the header does not provide useful info.
fpsdenominator = 30 ;
fpsnumerator = 1 ;
}
2020-09-05 09:58:19 +00:00
2020-09-10 15:54:27 +00:00
convnumer = 120 * fpsnumerator ;
convdenom = fpsdenominator * origframedelay ;
nsecsperframe = int64_t ( fpsnumerator ) * 1'000'000'000 / fpsdenominator ;
2020-09-05 09:58:19 +00:00
nextframetime = 0 ;
2020-09-10 15:54:27 +00:00
return true ;
2020-09-05 09:58:19 +00:00
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2020-09-10 15:54:27 +00:00
bool ReadFrame ( )
{
int corrupted = 0 ;
int framesize = fr . ReadInt32 ( ) ;
fr . Seek ( 8 , FileReader : : SeekCur ) ;
if ( framesize = = 0 ) return false ;
readBuf . Resize ( framesize ) ;
if ( fr . Read ( readBuf . Data ( ) , framesize ) ! = framesize ) return false ;
if ( vpx_codec_decode ( & codec , readBuf . Data ( ) , readBuf . Size ( ) , NULL , 0 ) ! = VPX_CODEC_OK ) return false ;
if ( vpx_codec_control ( & codec , VP8D_GET_FRAME_CORRUPTED , & corrupted ) ! = VPX_CODEC_OK ) return false ;
return true ;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
vpx_image_t * GetFrameData ( )
{
vpx_image_t * img ;
do
{
if ( decstate = = 0 ) // first time / begin
{
if ( ! ReadFrame ( ) ) return nullptr ;
decstate = 1 ;
}
img = vpx_codec_get_frame ( & codec , & iter ) ;
if ( img = = nullptr )
{
decstate = 0 ;
iter = nullptr ;
}
} while ( img = = nullptr ) ;
return img - > d_w = = width & & img - > d_h = = height ? img : nullptr ;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void SetPixel ( uint8_t * dest , uint8_t y , uint8_t u , uint8_t v )
{
dest [ 0 ] = y ;
dest [ 1 ] = u ;
dest [ 2 ] = v ;
}
bool CreateNextFrame ( )
{
auto img = GetFrameData ( ) ;
if ( ! img ) return false ;
uint8_t const * const yplane = img - > planes [ VPX_PLANE_Y ] ;
uint8_t const * const uplane = img - > planes [ VPX_PLANE_U ] ;
uint8_t const * const vplane = img - > planes [ VPX_PLANE_V ] ;
const int ystride = img - > stride [ VPX_PLANE_Y ] ;
const int ustride = img - > stride [ VPX_PLANE_U ] ;
const int vstride = img - > stride [ VPX_PLANE_V ] ;
for ( unsigned int y = 0 ; y < height ; y + = 2 )
{
unsigned int y1 = y + 1 ;
unsigned int wy = width * y ;
unsigned int wy1 = width * y1 ;
for ( unsigned int x = 0 ; x < width ; x + = 2 )
{
uint8_t u = uplane [ ustride * ( y > > 1 ) + ( x > > 1 ) ] ;
uint8_t v = vplane [ vstride * ( y > > 1 ) + ( x > > 1 ) ] ;
SetPixel ( & Pic [ ( wy + x ) < < 2 ] , yplane [ ystride * y + x ] , u , v ) ;
SetPixel ( & Pic [ ( wy + x + 1 ) < < 2 ] , yplane [ ystride * y + x + 1 ] , u , v ) ;
SetPixel ( & Pic [ ( wy1 + x ) < < 2 ] , yplane [ ystride * y1 + x ] , u , v ) ;
SetPixel ( & Pic [ ( wy1 + x + 1 ) < < 2 ] , yplane [ ystride * y1 + x + 1 ] , u , v ) ;
}
}
return true ;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2020-09-05 09:58:19 +00:00
int Frame ( uint64_t clock , bool skiprequest ) override
{
if ( clock = = 0 )
{
if ( soundtrack > 0 )
{
Mus_Play ( nullptr , fileSystem . GetFileFullName ( soundtrack , false ) , false ) ;
}
2020-09-10 15:54:27 +00:00
animtex . SetSize ( AnimTexture : : YUV , width , height ) ;
2020-09-05 09:58:19 +00:00
}
bool stop = false ;
if ( clock > nextframetime )
{
2020-09-10 15:54:27 +00:00
nextframetime + = nsecsperframe ;
2020-09-05 09:58:19 +00:00
2020-09-10 15:54:27 +00:00
if ( ! CreateNextFrame ( ) )
2020-09-05 09:58:19 +00:00
{
2020-09-10 15:54:27 +00:00
Printf ( PRINT_BOLD , " Failed reading next frame \n " ) ;
2020-09-05 09:58:19 +00:00
stop = true ;
}
2020-09-10 15:54:27 +00:00
else
2020-09-05 09:58:19 +00:00
{
2020-09-10 15:54:27 +00:00
animtex . SetFrame ( nullptr , Pic . Data ( ) ) ;
2020-09-05 09:58:19 +00:00
}
framenum + + ;
2020-09-10 15:54:27 +00:00
if ( framenum > = numframes ) stop = true ;
2020-09-05 09:58:19 +00:00
2021-01-04 12:34:55 +00:00
int soundframe = convdenom ? Scale ( framenum , convnumer , convdenom ) : framenum ;
2020-09-05 09:58:19 +00:00
if ( soundframe > lastsoundframe )
{
if ( animSnd & & soundtrack = = - 1 ) for ( int i = 0 ; animSnd [ i ] . framenum > = 0 ; i + + )
{
if ( animSnd [ i ] . framenum = = soundframe )
{
int sound = animSnd [ i ] . soundnum ;
if ( sound = = - 1 )
soundEngine - > StopAllChannels ( ) ;
else if ( SoundEnabled ( ) )
soundEngine - > StartSound ( SOURCE_None , nullptr , nullptr , CHAN_AUTO , CHANF_UI , sound , 1.f , ATTN_NONE ) ;
}
}
lastsoundframe = soundframe ;
}
}
DrawTexture ( twod , animtex . GetFrame ( ) , 0 , 0 , DTA_FullscreenEx , FSMode_ScaleToFit , TAG_DONE ) ;
if ( stop | | skiprequest ) Mus_Stop ( ) ;
if ( stop ) return 0 ;
return skiprequest ? - 1 : 1 ;
}
void OnDestroy ( ) override
{
2020-09-10 15:54:27 +00:00
vpx_codec_destroy ( & codec ) ;
2020-09-05 09:58:19 +00:00
}
} ;
2020-07-29 21:18:08 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2020-10-24 22:46:39 +00:00
struct AudioData
{
int hFx ;
SmackerAudioInfo inf ;
int16_t samples [ 6000 * 20 ] ; // must be a multiple of the stream buffer size and larger than the initial chunk of audio
int nWrite ;
int nRead ;
} ;
2020-07-29 21:18:08 +00:00
class DSmkPlayer : public DScreenJob
{
SmackerHandle hSMK { } ;
2020-10-24 22:46:39 +00:00
int numAudioTracks ;
AudioData adata ;
2020-07-29 21:18:08 +00:00
uint32_t nWidth , nHeight ;
uint8_t palette [ 768 ] ;
AnimTextures animtex ;
TArray < uint8_t > pFrame ;
2020-10-24 22:46:39 +00:00
TArray < uint8_t > audioBuffer ;
2020-07-29 21:18:08 +00:00
int nFrameRate ;
int nFrames ;
bool fullscreenScale ;
uint64_t nFrameNs ;
int nFrame = 0 ;
const AnimSound * animSnd ;
FString filename ;
2020-10-24 22:46:39 +00:00
SoundStream * stream = nullptr ;
2020-07-29 21:18:08 +00:00
public :
bool isvalid ( ) { return hSMK . isValid ; }
2020-10-24 22:46:39 +00:00
static bool StreamCallbackFunc ( SoundStream * stream , void * buff , int len , void * userdata )
{
DSmkPlayer * pId = ( DSmkPlayer * ) userdata ;
memcpy ( buff , & pId - > adata . samples [ pId - > adata . nRead ] , len ) ;
pId - > adata . nRead + = len / 2 ;
if ( pId - > adata . nRead > = countof ( pId - > adata . samples ) ) pId - > adata . nRead = 0 ;
return true ;
}
void copy8bitSamples ( unsigned count )
{
for ( unsigned i = 0 ; i < count ; i + + )
{
adata . samples [ adata . nWrite ] = ( audioBuffer [ i ] - 128 ) < < 8 ;
if ( + + adata . nWrite > = countof ( adata . samples ) ) adata . nWrite = 0 ;
}
}
void copy16bitSamples ( unsigned count )
{
auto ptr = ( uint16_t * ) audioBuffer . Data ( ) ;
for ( unsigned i = 0 ; i < count / 2 ; i + + )
{
adata . samples [ adata . nWrite ] = * ptr + + ;
if ( + + adata . nWrite > = countof ( adata . samples ) ) adata . nWrite = 0 ;
}
}
2020-07-29 21:18:08 +00:00
DSmkPlayer ( const char * fn , const AnimSound * ans , bool fixedviewport )
{
hSMK = Smacker_Open ( fn ) ;
if ( ! hSMK . isValid )
{
return ;
}
Smacker_GetFrameSize ( hSMK , nWidth , nHeight ) ;
pFrame . Resize ( nWidth * nHeight + std : : max ( nWidth , nHeight ) ) ;
nFrameRate = Smacker_GetFrameRate ( hSMK ) ;
nFrameNs = 1'000'000'000 / nFrameRate ;
nFrames = Smacker_GetNumFrames ( hSMK ) ;
Smacker_GetPalette ( hSMK , palette ) ;
fullscreenScale = ( ! fixedviewport | | ( nWidth < = 320 & & nHeight < = 200 ) | | nWidth > = 640 | | nHeight > = 480 ) ;
2020-10-24 22:46:39 +00:00
bool hassound = false ;
numAudioTracks = Smacker_GetNumAudioTracks ( hSMK ) ;
if ( numAudioTracks )
{
adata . nWrite = 0 ;
adata . nRead = 0 ;
adata . inf = Smacker_GetAudioTrackDetails ( hSMK , 0 ) ;
if ( adata . inf . idealBufferSize > 0 )
{
audioBuffer . Resize ( adata . inf . idealBufferSize ) ;
auto read = Smacker_GetAudioData ( hSMK , 0 , ( int16_t * ) audioBuffer . Data ( ) ) ;
if ( adata . inf . bitsPerSample = = 8 ) copy8bitSamples ( read ) ;
else copy16bitSamples ( read ) ;
animSnd = nullptr ;
hassound = true ;
}
}
if ( ! hassound )
{
adata . inf = { } ;
animSnd = ans ;
}
2020-07-29 21:18:08 +00:00
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int Frame ( uint64_t clock , bool skiprequest ) override
{
int frame = clock / nFrameNs ;
if ( clock = = 0 )
{
animtex . SetSize ( AnimTexture : : Paletted , nWidth , nHeight ) ;
}
twod - > ClearScreen ( ) ;
if ( frame > nFrame )
{
Smacker_GetPalette ( hSMK , palette ) ;
Smacker_GetFrame ( hSMK , pFrame . Data ( ) ) ;
animtex . SetFrame ( palette , pFrame . Data ( ) ) ;
2020-10-24 22:46:39 +00:00
if ( numAudioTracks )
{
auto read = Smacker_GetAudioData ( hSMK , 0 , ( int16_t * ) audioBuffer . Data ( ) ) ;
if ( adata . inf . bitsPerSample = = 8 ) copy8bitSamples ( read ) ;
else copy16bitSamples ( read ) ;
if ( ! stream & & read ) // the sound may not start in the first frame, but the stream cannot start without any sound data present.
stream = S_CreateCustomStream ( 6000 , adata . inf . sampleRate , adata . inf . nChannels , StreamCallbackFunc , this ) ;
}
2020-07-29 21:18:08 +00:00
}
if ( fullscreenScale )
{
2020-08-14 19:01:27 +00:00
DrawTexture ( twod , animtex . GetFrame ( ) , 0 , 0 , DTA_FullscreenEx , FSMode_ScaleToFit43 , TAG_DONE ) ;
2020-07-29 21:18:08 +00:00
}
else
{
DrawTexture ( twod , animtex . GetFrame ( ) , 320 , 240 , DTA_VirtualWidth , 640 , DTA_VirtualHeight , 480 , DTA_CenterOffset , true , TAG_DONE ) ;
}
if ( frame > nFrame )
{
nFrame + + ;
Smacker_GetNextFrame ( hSMK ) ;
2020-10-24 22:46:39 +00:00
if ( animSnd ) for ( int i = 0 ; animSnd [ i ] . framenum > = 0 ; i + + )
2020-07-29 21:18:08 +00:00
{
if ( animSnd [ i ] . framenum = = nFrame )
{
int sound = animSnd [ i ] . soundnum ;
if ( sound = = - 1 )
soundEngine - > StopAllChannels ( ) ;
2020-08-11 17:52:54 +00:00
else if ( SoundEnabled ( ) )
2020-07-29 21:18:08 +00:00
soundEngine - > StartSound ( SOURCE_None , nullptr , nullptr , CHAN_AUTO , CHANF_UI , sound , 1.f , ATTN_NONE ) ;
}
}
}
return skiprequest ? - 1 : nFrame < nFrames ? 1 : 0 ;
}
void OnDestroy ( ) override
{
Smacker_Close ( hSMK ) ;
2020-10-24 22:46:39 +00:00
if ( stream ) S_StopCustomStream ( stream ) ;
2020-07-29 21:18:08 +00:00
soundEngine - > StopAllChannels ( ) ;
2020-08-11 23:32:05 +00:00
animtex . Clean ( ) ;
2020-07-29 21:18:08 +00:00
}
} ;
2020-07-23 20:26:07 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2020-08-17 22:04:48 +00:00
DScreenJob * PlayVideo ( const char * filename , const AnimSound * ans , const int * frameticks , bool nosoundcutoff )
2020-06-27 22:32:28 +00:00
{
2020-07-01 20:27:38 +00:00
auto nothing = [ ] ( ) - > DScreenJob * { return Create < DScreenJob > ( ) ; } ;
2020-06-28 19:38:25 +00:00
if ( ! filename )
2020-06-27 22:32:28 +00:00
{
2020-07-01 20:27:38 +00:00
return nothing ( ) ;
2020-06-27 22:32:28 +00:00
}
2020-09-05 09:58:19 +00:00
FileReader fr ;
// first try as .ivf - but only if sounds are provided - the decoder is video only.
if ( ans )
{
auto fn = StripExtension ( filename ) ;
DefaultExtension ( fn , " .ivf " ) ;
fr = fileSystem . OpenFileReader ( fn ) ;
}
if ( ! fr . isOpen ( ) ) fr = fileSystem . OpenFileReader ( filename ) ;
2020-06-27 22:32:28 +00:00
if ( ! fr . isOpen ( ) )
{
2020-07-29 21:18:08 +00:00
int nLen = strlen ( filename ) ;
// Strip the drive letter and retry.
if ( nLen > = 3 & & isalpha ( filename [ 0 ] ) & & filename [ 1 ] = = ' : ' & & filename [ 2 ] = = ' / ' )
{
filename + = 3 ;
fr = fileSystem . OpenFileReader ( filename ) ;
}
2020-08-10 11:15:17 +00:00
if ( ! fr . isOpen ( ) )
{
2020-09-05 09:58:19 +00:00
Printf ( PRINT_BOLD , " %s: Unable to open video \n " , filename ) ;
2020-08-10 11:15:17 +00:00
return nothing ( ) ;
}
2020-06-27 22:32:28 +00:00
}
char id [ 20 ] = { } ;
fr . Read ( & id , 20 ) ;
fr . Seek ( - 20 , FileReader : : SeekCur ) ;
if ( ! memcmp ( id , " LPF " , 4 ) )
{
2020-08-17 22:04:48 +00:00
auto anm = Create < DAnmPlayer > ( fr , ans , frameticks , nosoundcutoff ) ;
2020-06-27 22:32:28 +00:00
if ( ! anm - > isvalid ( ) )
{
2020-09-05 09:58:19 +00:00
Printf ( PRINT_BOLD , " %s: invalid ANM file. \n " , filename ) ;
2020-06-27 22:32:28 +00:00
anm - > Destroy ( ) ;
2020-07-29 21:18:08 +00:00
return nothing ( ) ;
2020-06-27 22:32:28 +00:00
}
2020-06-28 19:38:25 +00:00
return anm ;
2020-06-27 22:32:28 +00:00
}
else if ( ! memcmp ( id , " SMK2 " , 4 ) )
{
2020-07-29 21:18:08 +00:00
fr . Close ( ) ;
auto anm = Create < DSmkPlayer > ( filename , ans , true ) ; // Fixme: Handle Blood's video scaling behavior more intelligently.
if ( ! anm - > isvalid ( ) )
{
2020-09-05 09:58:19 +00:00
Printf ( PRINT_BOLD , " %s: invalid SMK file. \n " , filename ) ;
2020-07-29 21:18:08 +00:00
anm - > Destroy ( ) ;
return nothing ( ) ;
}
return anm ;
2020-06-27 22:32:28 +00:00
}
2020-07-23 20:26:07 +00:00
else if ( ! memcmp ( id , " Interplay MVE File " , 18 ) )
2020-06-27 22:32:28 +00:00
{
2020-07-23 20:26:07 +00:00
auto anm = Create < DMvePlayer > ( fr ) ;
if ( ! anm - > isvalid ( ) )
{
anm - > Destroy ( ) ;
return nothing ( ) ;
}
return anm ;
2020-06-27 22:32:28 +00:00
}
2020-09-05 09:58:19 +00:00
else if ( ! memcmp ( id , " DKIF \0 \0 \0 VP80 " , 12 ) )
{
auto anm = Create < DVpxPlayer > ( fr , ans , frameticks ? frameticks [ 1 ] : 0 ) ;
if ( ! anm - > isvalid ( ) )
{
anm - > Destroy ( ) ;
return nothing ( ) ;
}
anm - > soundtrack = LookupMusic ( filename , true ) ;
return anm ;
}
2020-06-27 22:32:28 +00:00
// add more formats here.
else
{
2020-09-05 09:58:19 +00:00
Printf ( PRINT_BOLD , " %s: Unknown video format \n " , filename ) ;
2020-06-20 07:46:41 +00:00
}
2020-07-01 20:27:38 +00:00
return nothing ( ) ;
2020-06-20 07:46:41 +00:00
}
2020-06-27 22:32:28 +00:00
2020-07-19 09:57:00 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class ScreenJobRunner
{
enum
{
State_Clear ,
State_Run ,
State_Fadeout
} ;
TArray < JobDesc > jobs ;
CompletionFunc completion ;
int index = - 1 ;
float screenfade ;
bool clearbefore ;
2020-09-03 22:20:32 +00:00
int64_t startTime = - 1 ;
int64_t lastTime = - 1 ;
2020-07-19 09:57:00 +00:00
int actionState ;
int terminateState ;
2020-09-05 14:21:53 +00:00
uint64_t clock = 0 ;
2020-07-19 09:57:00 +00:00
public :
ScreenJobRunner ( JobDesc * jobs_ , int count , CompletionFunc completion_ , bool clearbefore_ )
: completion ( std : : move ( completion_ ) ) , clearbefore ( clearbefore_ )
{
jobs . Resize ( count ) ;
memcpy ( jobs . Data ( ) , jobs_ , count * sizeof ( JobDesc ) ) ;
// Release all jobs from the garbage collector - the code as it is cannot deal with them getting collected. This should be removed later once the GC is working.
for ( int i = 0 ; i < count ; i + + )
{
jobs [ i ] . job - > Release ( ) ;
}
AdvanceJob ( false ) ;
}
2020-07-19 10:48:31 +00:00
~ ScreenJobRunner ( )
{
DeleteJobs ( ) ;
}
void DeleteJobs ( )
{
for ( auto & job : jobs )
{
job . job - > ObjectFlags | = OF_YesReallyDelete ;
delete job . job ;
}
jobs . Clear ( ) ;
}
2020-07-19 09:57:00 +00:00
void AdvanceJob ( bool skip )
{
2020-07-29 21:18:08 +00:00
if ( index > = 0 )
{
if ( jobs [ index ] . postAction ) jobs [ index ] . postAction ( ) ;
jobs [ index ] . job - > Destroy ( ) ;
}
2020-07-19 09:57:00 +00:00
index + + ;
2020-07-29 21:18:08 +00:00
while ( index < jobs . Size ( ) & & ( jobs [ index ] . job = = nullptr | | ( skip & & jobs [ index ] . ignoreifskipped ) ) )
{
if ( jobs [ index ] . job ! = nullptr ) jobs [ index ] . job - > Destroy ( ) ;
index + + ;
}
2020-07-19 09:57:00 +00:00
actionState = clearbefore ? State_Clear : State_Run ;
if ( index < jobs . Size ( ) ) screenfade = jobs [ index ] . job - > fadestyle & DScreenJob : : fadein ? 0.f : 1.f ;
2020-09-05 15:37:37 +00:00
lastTime = startTime = - 1 ;
clock = 0 ;
2020-07-26 17:55:06 +00:00
inputState . ClearAllInput ( ) ;
2020-07-19 09:57:00 +00:00
}
int DisplayFrame ( )
{
auto & job = jobs [ index ] ;
2020-09-05 18:31:45 +00:00
auto now = I_GetTimeNS ( ) ;
2020-08-21 20:30:51 +00:00
bool processed = job . job - > ProcessInput ( ) ;
2020-09-05 14:21:53 +00:00
if ( startTime = = - 1 )
{
lastTime = startTime = now ;
}
else if ( ! M_Active ( ) )
2020-07-21 22:42:50 +00:00
{
2020-09-05 14:21:53 +00:00
clock + = now - lastTime ;
if ( clock = = 0 ) clock = 1 ;
2020-07-21 22:42:50 +00:00
}
2020-10-13 21:36:27 +00:00
bool skiprequest = clock > 100'000'000 & & inputState . CheckAllInput ( ) & & ! processed ;
2020-07-21 22:42:50 +00:00
lastTime = now ;
2020-09-16 17:11:36 +00:00
if ( screenfade < 1.f & & ! M_Active ( ) )
2020-07-19 09:57:00 +00:00
{
float ms = ( clock / 1'000'000 ) / job . job - > fadetime ;
screenfade = clamp ( ms , 0.f , 1.f ) ;
2020-09-16 17:11:36 +00:00
twod - > SetScreenFade ( screenfade ) ;
2020-07-19 09:57:00 +00:00
job . job - > fadestate = DScreenJob : : fadein ;
}
2020-09-16 17:11:36 +00:00
else
{
job . job - > fadestate = DScreenJob : : visible ;
screenfade = 1.f ;
}
2020-07-19 09:57:00 +00:00
job . job - > SetClock ( clock ) ;
int state = job . job - > Frame ( clock , skiprequest ) ;
2020-09-05 14:21:53 +00:00
clock = job . job - > GetClock ( ) ;
if ( clock = = 0 ) clock = 1 ;
2020-07-19 09:57:00 +00:00
return state ;
}
int FadeoutFrame ( )
{
2020-09-05 18:31:45 +00:00
auto now = I_GetTimeNS ( ) ;
2020-07-21 22:42:50 +00:00
2020-09-11 18:55:58 +00:00
if ( startTime = = - 1 )
{
lastTime = startTime = now ;
}
else if ( ! M_Active ( ) )
2020-07-21 22:42:50 +00:00
{
2020-09-05 14:21:53 +00:00
clock + = now - lastTime ;
if ( clock = = 0 ) clock = 1 ;
2020-07-21 22:42:50 +00:00
}
lastTime = now ;
2020-07-19 09:57:00 +00:00
float ms = ( clock / 1'000'000 ) / jobs [ index ] . job - > fadetime ;
float screenfade2 = clamp ( screenfade - ms , 0.f , 1.f ) ;
2020-07-21 22:42:50 +00:00
if ( ! M_Active ( ) ) twod - > SetScreenFade ( screenfade2 ) ;
2020-07-19 09:57:00 +00:00
if ( screenfade2 < = 0.f )
{
twod - > Unlock ( ) ; // must unlock before displaying.
return 0 ;
}
return 1 ;
}
bool RunFrame ( )
{
if ( index > = jobs . Size ( ) )
{
2020-07-19 10:48:31 +00:00
DeleteJobs ( ) ;
2020-07-19 09:57:00 +00:00
twod - > SetScreenFade ( 1 ) ;
2020-09-05 13:59:32 +00:00
twod - > ClearScreen ( ) ; // This must not leave the 2d buffer empty.
2020-07-19 09:57:00 +00:00
if ( completion ) completion ( false ) ;
return false ;
}
if ( actionState = = State_Clear )
{
actionState = State_Run ;
twod - > ClearScreen ( ) ;
}
else if ( actionState = = State_Run )
{
terminateState = DisplayFrame ( ) ;
if ( terminateState < 1 )
{
// Must lock before displaying.
if ( jobs [ index ] . job - > fadestyle & DScreenJob : : fadeout )
{
twod - > Lock ( ) ;
2020-09-11 18:55:58 +00:00
startTime = - 1 ;
clock = 0 ;
2020-07-19 09:57:00 +00:00
jobs [ index ] . job - > fadestate = DScreenJob : : fadeout ;
2021-04-07 17:39:48 +00:00
gamestate = GS_INTRO ; // block menu and console during fadeout - this can cause timing problems.
2020-07-19 09:57:00 +00:00
actionState = State_Fadeout ;
}
else
{
AdvanceJob ( terminateState < 0 ) ;
}
}
}
else if ( actionState = = State_Fadeout )
{
int ended = FadeoutFrame ( ) ;
if ( ended < 1 )
{
AdvanceJob ( terminateState < 0 ) ;
}
}
return true ;
}
} ;
2020-07-19 10:48:31 +00:00
ScreenJobRunner * runner ;
2020-07-21 22:42:50 +00:00
void RunScreenJob ( JobDesc * jobs , int count , CompletionFunc completion , bool clearbefore , bool blockingui )
2020-07-19 09:57:00 +00:00
{
2020-07-19 10:48:31 +00:00
assert ( completion ! = nullptr ) ;
2020-08-07 20:00:43 +00:00
videoclearFade ( ) ;
2020-07-19 10:48:31 +00:00
if ( count )
2020-07-19 09:57:00 +00:00
{
2020-07-19 10:48:31 +00:00
runner = new ScreenJobRunner ( jobs , count , completion , clearbefore ) ;
2020-09-05 13:43:34 +00:00
gameaction = blockingui ? ga_intro : ga_intermission ;
}
else
{
completion ( false ) ;
2020-07-19 09:57:00 +00:00
}
}
2020-07-19 10:48:31 +00:00
void DeleteScreenJob ( )
2020-07-19 09:57:00 +00:00
{
2020-07-19 10:48:31 +00:00
if ( runner )
2020-07-19 09:57:00 +00:00
{
2020-07-19 10:48:31 +00:00
delete runner ;
runner = nullptr ;
2020-07-19 09:57:00 +00:00
}
2021-04-07 17:39:48 +00:00
twod - > SetScreenFade ( 1 ) ;
2020-07-19 10:48:31 +00:00
}
2020-07-19 09:57:00 +00:00
2020-07-19 10:48:31 +00:00
void RunScreenJobFrame ( )
{
// we cannot recover from this because we have no completion callback to call.
2020-09-04 18:46:44 +00:00
if ( ! runner )
{
// We can get here before a gameaction has been processed. In that case just draw a black screen and wait.
if ( gameaction = = ga_nothing ) I_Error ( " Trying to run a non-existent screen job " ) ;
twod - > ClearScreen ( ) ;
return ;
}
2020-07-19 10:48:31 +00:00
auto res = runner - > RunFrame ( ) ;
2020-07-26 10:43:32 +00:00
if ( ! res )
{
2020-09-04 18:46:44 +00:00
assert ( ( gamestate ! = GS_INTERMISSION & & gamestate ! = GS_INTRO ) | | gameaction ! = ga_nothing ) ;
2020-07-26 10:43:32 +00:00
DeleteScreenJob ( ) ;
}
2020-07-19 09:57:00 +00:00
}