diff --git a/Source/Engine/Video/MF/VideoBackendMF.cpp b/Source/Engine/Video/MF/VideoBackendMF.cpp index 6f0225041..207f9b72c 100644 --- a/Source/Engine/Video/MF/VideoBackendMF.cpp +++ b/Source/Engine/Video/MF/VideoBackendMF.cpp @@ -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 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(); + + // 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(); 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(); - - // 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 job; + job.Bind(MF::UpdatePlayer); + graph->DispatchJob(job, MF::Players.Count()); } void VideoBackendMF::Base_Dispose() diff --git a/Source/Engine/Video/MF/VideoBackendMF.h b/Source/Engine/Video/MF/VideoBackendMF.h index 7b97008de..da3f6044e 100644 --- a/Source/Engine/Video/MF/VideoBackendMF.h +++ b/Source/Engine/Video/MF/VideoBackendMF.h @@ -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; }; diff --git a/Source/Engine/Video/Video.cpp b/Source/Engine/Video/Video.cpp index 185b96739..2da3d2b4d 100644 --- a/Source/Engine/Video/Video.cpp +++ b/Source/Engine/Video/Video.cpp @@ -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(); + 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) diff --git a/Source/Engine/Video/Video.h b/Source/Engine/Video/Video.h index 08c0edf8b..46c757015 100644 --- a/Source/Engine/Video/Video.h +++ b/Source/Engine/Video/Video.h @@ -10,5 +10,6 @@ class Video { public: + static class TaskGraphSystem* System; static bool CreatePlayerBackend(const VideoBackendPlayerInfo& info, VideoBackendPlayer& player); }; diff --git a/Source/Engine/Video/VideoBackend.h b/Source/Engine/Video/VideoBackend.h index 74d50affb..9829483b6 100644 --- a/Source/Engine/Video/VideoBackend.h +++ b/Source/Engine/Video/VideoBackend.h @@ -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; };