Add async videos update

This commit is contained in:
Wojtek Figat
2024-05-08 12:35:18 +02:00
parent e51d2dda00
commit b91f51fb46
5 changed files with 116 additions and 87 deletions

View File

@@ -4,6 +4,7 @@
#include "VideoBackendMF.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/TaskGraph.h"
#include "Engine/Core/Log.h"
#include "Engine/Engine/Time.h"
#include "Engine/Audio/Types.h"
@@ -26,7 +27,7 @@ struct VideoPlayerMF
TimeSpan Time;
};
namespace
namespace MF
{
Array<VideoBackendPlayer*> Players;
@@ -346,6 +347,82 @@ namespace
// True if run out of samples and failed to get frame for the current time
return samplesLeft == 0;
}
void UpdatePlayer(int32 index)
{
PROFILE_CPU();
auto& player = *Players[index];
ZoneText(player.DebugUrl, player.DebugUrlLen);
auto& playerMF = player.GetBackendState<VideoPlayerMF>();
// Skip paused player
if (!playerMF.Playing && !playerMF.Seek)
return;
bool useTimeScale = true;
#if USE_EDITOR
if (!Editor::IsPlayMode)
useTimeScale = false;
#endif
TimeSpan dt = useTimeScale ? Time::Update.DeltaTime : Time::Update.UnscaledDeltaTime;
// Update playback time
if (playerMF.FirstFrame)
{
playerMF.FirstFrame = 0;
playerMF.Seek = 1;
}
else if (playerMF.Playing)
{
playerMF.Time += dt;
}
if (playerMF.Time > player.Duration)
{
if (playerMF.Loop)
{
// Loop
playerMF.Time.Ticks %= player.Duration.Ticks;
playerMF.Seek = 1;
}
else
{
// End
playerMF.Time = player.Duration;
}
}
// Update current position
SEEK_START:
if (playerMF.Seek)
{
// Reset cached frames timings
player.VideoFrameDuration = player.AudioBufferDuration = TimeSpan::Zero();
playerMF.Seek = 0;
PROPVARIANT var;
PropVariantInit(&var);
var.vt = VT_I8;
var.hVal.QuadPart = playerMF.Time.Ticks;
PROFILE_CPU_NAMED("SetCurrentPosition");
playerMF.SourceReader->SetCurrentPosition(GUID_NULL, var);
// Note:
// SetCurrentPosition method does not guarantee exact seeking.
// The accuracy of the seek depends on the media content.
// If the media content contains a video stream, the SetCurrentPosition method typically seeks to the nearest key frame before the desired position.
// After seeking, the application should call ReadSample and advance to the desired position.
}
// Update streams
if (ReadStream(player, playerMF, MF_SOURCE_READER_FIRST_VIDEO_STREAM, dt))
{
// Failed to pick a valid sample so try again with seeking
playerMF.Seek = 1;
goto SEEK_START;
}
if (player.AudioInfo.BitDepth != 0)
ReadStream(player, playerMF, MF_SOURCE_READER_FIRST_AUDIO_STREAM, dt);
}
}
bool VideoBackendMF::Player_Create(const VideoBackendPlayerInfo& info, VideoBackendPlayer& player)
@@ -377,8 +454,8 @@ bool VideoBackendMF::Player_Create(const VideoBackendPlayerInfo& info, VideoBack
playerMF.SourceReader = sourceReader;
// Read media info
if (Configure(player, playerMF, MF_SOURCE_READER_FIRST_VIDEO_STREAM) ||
Configure(player, playerMF, MF_SOURCE_READER_FIRST_AUDIO_STREAM))
if (MF::Configure(player, playerMF, MF_SOURCE_READER_FIRST_VIDEO_STREAM) ||
MF::Configure(player, playerMF, MF_SOURCE_READER_FIRST_AUDIO_STREAM))
return true;
PROPVARIANT var;
hr = sourceReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &var);
@@ -393,7 +470,7 @@ bool VideoBackendMF::Player_Create(const VideoBackendPlayerInfo& info, VideoBack
playerMF.Loop = info.Loop;
playerMF.FirstFrame = 1;
player.Created(info);
Players.Add(&player);
MF::Players.Add(&player);
return false;
}
@@ -404,7 +481,7 @@ void VideoBackendMF::Player_Destroy(VideoBackendPlayer& player)
player.ReleaseResources();
auto& playerMF = player.GetBackendState<VideoPlayerMF>();
playerMF.SourceReader->Release();
Players.Remove(&player);
MF::Players.Remove(&player);
player = VideoBackendPlayer();
}
@@ -482,85 +559,12 @@ bool VideoBackendMF::Base_Init()
return false;
}
void VideoBackendMF::Base_Update()
void VideoBackendMF::Base_Update(TaskGraph* graph)
{
PROFILE_CPU();
// TODO: use async Task Graph to update videos
for (auto* e : Players)
{
auto& player = *e;
auto& playerMF = player.GetBackendState<VideoPlayerMF>();
// Skip paused player
if (!playerMF.Playing && !playerMF.Seek)
continue;
bool useTimeScale = true;
#if USE_EDITOR
if (!Editor::IsPlayMode)
useTimeScale = false;
#endif
TimeSpan dt = useTimeScale ? Time::Update.DeltaTime : Time::Update.UnscaledDeltaTime;
// Update playback time
if (playerMF.FirstFrame)
{
playerMF.FirstFrame = 0;
playerMF.Seek = 1;
}
else if (playerMF.Playing)
{
playerMF.Time += dt;
}
if (playerMF.Time > player.Duration)
{
if (playerMF.Loop)
{
// Loop
playerMF.Time.Ticks %= player.Duration.Ticks;
playerMF.Seek = 1;
}
else
{
// End
playerMF.Time = player.Duration;
}
}
// Update current position
int32 seeks = 0;
SEEK_START:
if (playerMF.Seek)
{
// Reset cached frames timings
player.VideoFrameDuration = player.AudioBufferDuration = TimeSpan::Zero();
seeks++;
playerMF.Seek = 0;
PROPVARIANT var;
PropVariantInit(&var);
var.vt = VT_I8;
var.hVal.QuadPart = playerMF.Time.Ticks;
PROFILE_CPU_NAMED("SetCurrentPosition");
playerMF.SourceReader->SetCurrentPosition(GUID_NULL, var);
// Note:
// SetCurrentPosition method does not guarantee exact seeking.
// The accuracy of the seek depends on the media content.
// If the media content contains a video stream, the SetCurrentPosition method typically seeks to the nearest key frame before the desired position.
// After seeking, the application should call ReadSample and advance to the desired position.
}
// Update streams
if (ReadStream(player, playerMF, MF_SOURCE_READER_FIRST_VIDEO_STREAM, dt))
{
// Failed to pick a valid sample so try again with seeking
playerMF.Seek = 1;
goto SEEK_START;
}
if (player.AudioInfo.BitDepth != 0)
ReadStream(player, playerMF, MF_SOURCE_READER_FIRST_AUDIO_STREAM, dt);
}
// Schedule work to update all videos models in async
Function<void(int32)> job;
job.Bind(MF::UpdatePlayer);
graph->DispatchJob(job, MF::Players.Count());
}
void VideoBackendMF::Base_Dispose()

View File

@@ -23,7 +23,7 @@ public:
TimeSpan Player_GetTime(const VideoBackendPlayer& player) override;
const Char* Base_Name() override;
bool Base_Init() override;
void Base_Update() override;
void Base_Update(TaskGraph* graph) override;
void Base_Dispose() override;
};

View File

@@ -6,6 +6,7 @@
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Quaternion.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Engine/Engine.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/GPUBuffer.h"
@@ -16,6 +17,7 @@
#include "Engine/Graphics/Shaders/GPUShader.h"
#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Scripting/Enums.h"
#include "Engine/Threading/TaskGraph.h"
#if VIDEO_API_MF
#include "MF/VideoBackendMF.h"
#endif
@@ -108,6 +110,12 @@ protected:
}
};
class VideoSystem : public TaskGraphSystem
{
public:
void Execute(TaskGraph* graph) override;
};
class VideoService : public EngineService
{
public:
@@ -128,13 +136,15 @@ public:
Backends[index] = backend;
}
bool Init() override;
void Update() override;
void Dispose() override;
};
VideoService VideoServiceInstance;
TaskGraphSystem* Video::System = nullptr;
void VideoService::Update()
void VideoSystem::Execute(TaskGraph* graph)
{
PROFILE_CPU_NAMED("Video.Update");
@@ -142,10 +152,22 @@ void VideoService::Update()
for (VideoBackend*& backend : VideoServiceInstance.Backends)
{
if (backend)
backend->Base_Update();
backend->Base_Update(graph);
}
}
bool VideoService::Init()
{
Video::System = New<VideoSystem>();
Engine::UpdateGraph->AddSystem(Video::System);
return false;
}
void VideoService::Update()
{
PROFILE_CPU_NAMED("Video.Update");
}
void VideoService::Dispose()
{
PROFILE_CPU_NAMED("Video.Dispose");
@@ -159,6 +181,8 @@ void VideoService::Dispose()
backend = nullptr;
}
}
SAFE_DELETE(Video::System);
}
bool Video::CreatePlayerBackend(const VideoBackendPlayerInfo& info, VideoBackendPlayer& player)

View File

@@ -10,5 +10,6 @@
class Video
{
public:
static class TaskGraphSystem* System;
static bool CreatePlayerBackend(const VideoBackendPlayerInfo& info, VideoBackendPlayer& player);
};

View File

@@ -38,6 +38,6 @@ public:
// Base
virtual const Char* Base_Name() = 0;
virtual bool Base_Init() = 0;
virtual void Base_Update() = 0;
virtual void Base_Update(class TaskGraph* graph) = 0;
virtual void Base_Dispose() = 0;
};