This commit is contained in:
2025-07-14 22:24:27 +08:00
parent daacc18ecf
commit 4af19ef574
7722 changed files with 72086 additions and 0 deletions

View File

@ -0,0 +1,72 @@
{
"FileVersion": 3,
"Version": 10,
"VersionName": "v2.05",
"FriendlyName": "DirectVideo Android",
"Description": "Android video playback using Vulkan",
"Category": "Rendering",
"CreatedBy": "Joe Marshall",
"CreatedByURL": "",
"DocsURL": "",
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/342ae80f4bc04e4aa232991047532ff2",
"SupportURL": "https://joemarshall.github.io/directvideo/",
"EngineVersion": "5.3.0",
"CanContainContent": true,
"Installed": true,
"SupportedTargetPlatforms": [
"Win64",
"Android"
],
"Modules": [
{
"Name": "AndroidVulkanVideo",
"Type": "Runtime",
"LoadingPhase": "PostConfigInit",
"PlatformAllowList": [
"Android"
],
"TargetAllowList": [
"Game",
"Client"
]
},
{
"Name": "VulkanVideoMeshComponent",
"Type": "RuntimeNoCommandlet",
"LoadingPhase": "PostEngineInit",
"PlatformAllowList": [
"Android"
],
"TargetAllowList": [
"Game",
"Editor",
"Client",
"Server"
]
},
{
"Name": "AndroidVulkanVideoFactory",
"Type": "RuntimeNoCommandlet",
"LoadingPhase": "PostEngineInit",
"PlatformAllowList": [
"Android"
],
"TargetAllowList": [
"Game",
"Editor",
"Client",
"Server"
]
},
{
"Name": "AndroidVulkanVideoFactory",
"Type": "Editor",
"LoadingPhase": "PostEngineInit"
},
{
"Name": "VulkanVideoMeshComponent",
"Type": "Editor",
"LoadingPhase": "PostEngineInit"
}
]
}

BIN
Plugins/AndroidVulkanVideo_5.3/Resources/Icon128.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,88 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Build AndroidVulkanVideo module
// ------------------------------------------------
using UnrealBuildTool;
using System.IO;
using System;
public class AndroidVulkanVideo : ModuleRules
{
public AndroidVulkanVideo(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
CppStandard = CppStandardVersion.Cpp17;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"Projects"
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject", "Engine","RHI","VulkanRHI","AudioExtensions","MediaUtils"
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
"Media"
}
);
PrivateIncludePathModuleNames.AddRange(
new string[] {
"Media","MediaUtils"
});
if (Target.Platform == UnrealTargetPlatform.Android)
{
PrivateDependencyModuleNames.AddRange(new string[] { "Launch" });
// AdditionalPropertiesForReceipt.Add("AndroidPlugin", Path.Combine(ModuleDirectory, "Copy_Lib_Android.xml"));
AddEngineThirdPartyPrivateStaticDependencies(Target, "Vulkan");
PublicSystemLibraries.Add("libmediandk");
string overridelib_src_path = Path.GetFullPath("../overridelib/BuildOverrideLib.xml", ModuleDirectory);
string overridelib_built_path = Path.GetFullPath("./overridelib_built/InstallOverrideLib.xml",ModuleDirectory);
if(File.Exists(overridelib_src_path)){
// build vulkan override layer from scratch if we have code
AdditionalPropertiesForReceipt.Add("AndroidPlugin", overridelib_src_path);
}else if(File.Exists(overridelib_built_path)){
// otherwise copy pre-built vulkan override layer .so into project
AdditionalPropertiesForReceipt.Add("AndroidPlugin", overridelib_built_path);
}else{
// ERROR
throw new InvalidOperationException("Need "+overridelib_built_path+" to build");
}
}
bPrecompile = true;
}
}

View File

@ -0,0 +1,716 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// The main IMediaPlayer implementation. This is a
// shim which provides Unreal interfaces, and then
// defers everything vulkan related to the main
// AndroidVulkanVideoImpl class.
// ------------------------------------------------
#include "AndroidVulkanMediaPlayer.h"
#include "AndroidVulkanTextureSample.h"
#include "IVulkanImpl.h"
#include "VideoMediaSampleHolder.h"
#include "UnrealArchiveFileSource.h"
#include "UnrealAudioOut.h"
#include "HAL/FileManager.h"
#include "HAL/PlatformFilemanager.h"
#include "IPlatformFilePak.h"
#include "Misc/ConfigCacheIni.h" // for GConfig access
#include "UnrealLogging.h"
#define LOCTEXT_NAMESPACE "FAndroidVulkanVideoModule"
#include <dlfcn.h>
static void *implDLL = NULL;
// logging object passed to impl
static UnrealLogger logger;
typedef IVulkanImpl *(*CreateImplType)();
typedef void (*DestroyImplType)(IVulkanImpl *);
static CreateImplType createImpl = NULL;
static DestroyImplType destroyImpl = NULL;
FAndroidVulkanMediaPlayer::FAndroidVulkanMediaPlayer(IMediaEventSink &InEventSink)
: SampleQueue(MakeShared<FVideoMediaSampleHolder, ESPMode::ThreadSafe>()),
EventSink(InEventSink)
{
if (implDLL == NULL)
{
implDLL = dlopen("libVkLayer_OverrideLib.so", RTLD_NOW | RTLD_LOCAL);
createImpl = (CreateImplType)(dlsym(implDLL, "createImpl"));
destroyImpl = (DestroyImplType)(dlsym(implDLL, "destroyImpl"));
}
impl = createImpl();
impl->setDataCallback(this);
impl->setLogger(&logger);
impl->setLooping(Looping);
// get log format bitmask from defaultEngine.ini
int32 LogVisibility;
if (GConfig->GetInt(TEXT("DirectVideo"), TEXT("LogBitmask"), LogVisibility, GEngineIni))
{
logger.SetLogVisibilityBitmask((int64)LogVisibility);
}
else
{
logger.SetLogVisibilityBitmask(ILogger::LogTypes::ALL_LOGS_BITMASK);
}
// get output format from defaultEngine.ini
FString OutFormat;
EMediaTextureSampleFormat SampleFormat = EMediaTextureSampleFormat::CharBGR10A2;
if (GConfig->GetString(TEXT("DirectVideo"), TEXT("OutputFormat"), OutFormat, GEngineIni))
{
if (OutFormat.Equals(TEXT("CharBGR10A2"), ESearchCase::IgnoreCase))
{
SampleFormat = EMediaTextureSampleFormat::CharBGR10A2;
}
else if (OutFormat.Equals(TEXT("CharBGRA"), ESearchCase::IgnoreCase))
{
SampleFormat = EMediaTextureSampleFormat::CharBGRA;
}
else if (OutFormat.Equals(TEXT("CharRGBA"), ESearchCase::IgnoreCase))
{
SampleFormat = EMediaTextureSampleFormat::CharRGBA;
}
else if (OutFormat.Equals(TEXT("RGBA16"), ESearchCase::IgnoreCase))
{
SampleFormat = EMediaTextureSampleFormat::RGBA16;
}
else if (OutFormat.Equals(TEXT("FloatRGB"), ESearchCase::IgnoreCase))
{
SampleFormat = EMediaTextureSampleFormat::FloatRGB;
}
else if (OutFormat.Equals(TEXT("FloatRGBA"), ESearchCase::IgnoreCase))
{
SampleFormat = EMediaTextureSampleFormat::FloatRGBA;
}
else
{
UE_LOGFMT(LogDirectVideo, Error, "Bad sample format in defaultEngine.ini");
}
}
AndroidVulkanTextureSample::SetVideoFormat(SampleFormat);
AudioOut = new UnrealAudioOut(NULL);
impl->setAudioOut(AudioOut);
PlayState = EMediaState::Closed;
CurInfo.Empty();
DelegateEnterBackground = FCoreDelegates::ApplicationWillEnterBackgroundDelegate.AddRaw(
this, &FAndroidVulkanMediaPlayer::OnEnterBackground);
DelegateEnterForeground = FCoreDelegates::ApplicationHasEnteredForegroundDelegate.AddRaw(
this, &FAndroidVulkanMediaPlayer::OnEnterForeground);
SeekIndex = 0;
SentBlankFrame = false;
Seeking = false;
}
FAndroidVulkanMediaPlayer::~FAndroidVulkanMediaPlayer()
{
Close();
SentBlankFrame = true;
if (DelegateEnterBackground.IsValid())
{
FCoreDelegates::ApplicationWillEnterBackgroundDelegate.Remove(DelegateEnterBackground);
}
if (DelegateEnterForeground.IsValid())
{
FCoreDelegates::ApplicationHasEnteredForegroundDelegate.Remove(DelegateEnterForeground);
}
destroyImpl(impl);
impl = NULL;
delete AudioOut;
AudioOut = NULL;
}
// IMediaPlayer
// --------------------------------
void FAndroidVulkanMediaPlayer::Close()
{
SentBlankFrame = false;
if (PlayState != EMediaState::Closed)
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Media player close called");
SampleQueue->FlushSamples();
VideoSamplePool.ReleaseEverything();
SeekIndex = 0;
Seeking = false;
EventSink.ReceiveMediaEvent(EMediaEvent::TracksChanged);
EventSink.ReceiveMediaEvent(EMediaEvent::MediaClosed);
PlayState = EMediaState::Closed;
// close the impl last because it will
// kill all the texture images that we release in the pool above
impl->close();
}
}
FString FAndroidVulkanMediaPlayer::GetInfo() const
{
return CurInfo;
}
FGuid FAndroidVulkanMediaPlayer::GetPlayerPluginGUID() const
{
static FGuid OurGUID(0x9bf2d7c6, 0xb2b84d26, 0xb6ae5a3a, 0xc9883569);
return OurGUID;
}
FString FAndroidVulkanMediaPlayer::GetStats() const
{
return TEXT("Not implemented");
}
FString FAndroidVulkanMediaPlayer::GetUrl() const
{
return VideoURL;
}
bool FAndroidVulkanMediaPlayer::Open(const FString &Url, const IMediaOptions *Options)
{
Close();
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Try open Url {0}", Url);
SentBlankFrame = false;
bool started = false;
FString fullPath = Url;
if (fullPath.StartsWith("file://"))
{
fullPath = fullPath.RightChop(7);
}
if (fullPath.Contains("://"))
{
// a (non-file url)
started = impl->startVideoURL(TCHAR_TO_UTF8(*fullPath), false);
}
else
{
// a file path - check if it is local or not
if (fullPath.StartsWith("./"))
{
fullPath = FPaths::ProjectContentDir() + fullPath.RightChop(2);
}
FPaths::NormalizeFilename(fullPath);
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Start video {0}", fullPath);
IAndroidPlatformFile &PlatformFile = IAndroidPlatformFile::GetPlatformPhysical();
if (PlatformFile.FileExists(*fullPath))
{
int64 FileOffset = PlatformFile.FileStartOffset(*fullPath);
int64 FileSize = PlatformFile.FileSize(*fullPath);
FString FileRootPath = PlatformFile.FileRootPath(*fullPath);
UE_LOGFMT(LogDirectVideo, VeryVerbose, "File exists: {0} {1} {2} {3}", FileRootPath,
FileOffset, FileSize, fullPath);
started =
impl->startVideoFile(TCHAR_TO_UTF8(*FileRootPath), FileOffset, FileSize, false);
}
else
{
// check if the file exists in an archive / encrypted etc.
FPakPlatformFile *PakPlatformFile =
(FPakPlatformFile *)(FPlatformFileManager::Get().FindPlatformFile(
FPakPlatformFile::GetTypeName()));
TRefCountPtr<FPakFile> PakFile;
FPakEntry FileEntry;
if (PakPlatformFile != nullptr &&
PakPlatformFile->FindFileInPakFiles(*fullPath, &PakFile, &FileEntry))
{
// we have the file in a pak file
// use a custom media data source to read it
// n.b. if pak file is uncompressed and the file isn't encrypted we could skip this
// step but this should work whatever
TSharedRef<FArchive, ESPMode::ThreadSafe> Archive =
MakeShareable(IFileManager::Get().CreateFileReader(*fullPath));
UnrealArchiveFileSource *fs = new UnrealArchiveFileSource(Archive);
started = impl->startVideoCustomSource(fs, false);
}
}
}
if (started)
{
PlayState = EMediaState::Stopped;
EventSink.ReceiveMediaEvent(EMediaEvent::TracksChanged);
EventSink.ReceiveMediaEvent(EMediaEvent::MediaOpened);
UE_LOGFMT(LogDirectVideo, Verbose, "Opening {0}", *Url);
SeekIndex = 0;
Seeking = false;
}
else
{
// couldn't open
UE_LOGFMT(LogDirectVideo, Error, "Can't find video file {0}", *Url);
}
return started;
}
bool FAndroidVulkanMediaPlayer::Open(const TSharedRef<FArchive, ESPMode::ThreadSafe> &Archive,
const FString &OriginalUrl, const IMediaOptions *Options)
{
UE_LOGFMT(LogDirectVideo, Error, "Opening archive {0} not supported", *OriginalUrl);
return false;
}
void FAndroidVulkanMediaPlayer::SetGuid(const FGuid &Guid)
{
PlayerGUID = Guid;
}
void FAndroidVulkanMediaPlayer::TickFetch(FTimespan DeltaTime, FTimespan Timecode)
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "TickFetch");
}
void FAndroidVulkanMediaPlayer::TickInput(FTimespan DeltaTime, FTimespan Timecode)
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "TickInput start");
/* int64_t presTimeNs = impl->getTimeNS();
SampleQueue->SetLastTimeStamp(
FMediaTimeStamp(DeltaTime,
#if UE_VERSION_OLDER_THAN(5, 3, 0)
static_cast<int64>(SeekIndex) << 32));
#else
FMediaTimeStamp::MakeSequenceIndex(SeekIndex, 0)));
#endif
*/
HasVideoThisFrame = false;
VideoSamplePool.Tick();
if (!SentBlankFrame && impl->numVideoTracks() == 0)
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "blank frame");
SentBlankFrame = true;
auto textureSample = VideoSamplePool.AcquireShared();
textureSample->InitNoVideo();
SampleQueue->AddVideo(textureSample);
}
UE_LOGFMT(LogDirectVideo, VeryVerbose, "TickInput dt:{0} tc {1} num {2}", DeltaTime.GetTicks(),
Timecode.GetTicks(), SampleQueue->NumVideoSamples());
}
bool FAndroidVulkanMediaPlayer::GetPlayerFeatureFlag(EFeatureFlag flag) const
{
switch (flag)
{
case EFeatureFlag::PlayerUsesInternalFlushOnSeek:
return true;
case EFeatureFlag::AlwaysPullNewestVideoFrame:
return true;
case EFeatureFlag::UsePlaybackTimingV2:
return true;
case EFeatureFlag::IsTrackSwitchSeamless:
return true;
case EFeatureFlag::UseRealtimeWithVideoOnly:
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Feature flag query");
return true;
default:
return false;
}
}
// IMediaControl
// --------------------------------
bool FAndroidVulkanMediaPlayer::CanControl(EMediaControl Control) const
{
switch (Control)
{
case EMediaControl::Pause:
return PlayState == EMediaState::Playing;
case EMediaControl::Resume:
return PlayState == EMediaState::Stopped;
case EMediaControl::Seek:
return PlayState != EMediaState::Closed;
}
return false;
}
FTimespan FAndroidVulkanMediaPlayer::GetDuration() const
{
int64_t timeNS = impl->getDurationNS();
if (timeNS >= 0 && PlayState != EMediaState::Closed)
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Get Duration {0}", int64(timeNS));
return FTimespan(timeNS / 100LL);
}
else
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Get Duration empty");
return FTimespan::MaxValue();
}
}
float FAndroidVulkanMediaPlayer::GetRate() const
{
if (PlayState == EMediaState::Playing)
{
return impl->getRate();
}
else
{
return 0.0;
}
}
EMediaState FAndroidVulkanMediaPlayer::GetState() const
{
return PlayState;
}
EMediaStatus FAndroidVulkanMediaPlayer::GetStatus() const
{
// not supported yet
return EMediaStatus::None;
}
TRangeSet<float> FAndroidVulkanMediaPlayer::GetSupportedRates(EMediaRateThinning Thinning) const
{
TRangeSet<float> Retval;
if (impl->numAudioTracks() > 0)
{
Retval.Add(TRange<float>(0.0f));
Retval.Add(TRange<float>(1.0f));
}
else
{
Retval.Add(TRange<float>(TRange<float>::BoundsType::Inclusive(0.0f),
TRange<float>::BoundsType::Inclusive(10.0f)));
}
return Retval;
}
FTimespan FAndroidVulkanMediaPlayer::GetTime() const
{
int64_t timeNS = impl->getTimeNS();
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Get Time {0}", int64(timeNS));
if (timeNS >= 0)
{
return FTimespan(timeNS / 100LL);
}
return FTimespan::Zero();
}
bool FAndroidVulkanMediaPlayer::IsLooping() const
{
return Looping;
}
bool FAndroidVulkanMediaPlayer::Seek(const FTimespan &Time)
{
UE_LOGFMT(LogDirectVideo, Verbose, "Seek index:{0}", SeekIndex);
Seeking = true;
int64_t ticks = Time.GetTicks();
int64_t nanoseconds = ticks * 100LL;
impl->seek(nanoseconds);
return true;
}
bool FAndroidVulkanMediaPlayer::SetLooping(bool Loop)
{
Looping = Loop;
impl->setLooping(Looping);
return true;
}
bool FAndroidVulkanMediaPlayer::SetRate(float Rate)
{
bool retval = false;
int iInitialState = (int)PlayState;
switch (PlayState)
{
case EMediaState::Playing:
if (Rate == 0.0)
{
impl->setPlaying(false);
PlayState = EMediaState::Stopped;
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackSuspended);
retval = true;
}
else
{
return impl->setRate(Rate);
retval = true;
}
break;
case EMediaState::Stopped:
if (Rate == 0.0)
{
retval = true;
}
else if (Rate > 0.0)
{
impl->setPlaying(true);
PlayState = EMediaState::Playing;
retval = impl->setRate(Rate);
if (retval)
{
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackResumed);
}
}
break;
};
int iFinalState = (int)PlayState;
UE_LOGFMT(LogDirectVideo, Verbose, "Set rate {0} {1} state:{2}->{3}", Rate, retval,
iInitialState, iFinalState);
// return false;
return retval;
}
bool FAndroidVulkanMediaPlayer::SetNativeVolume(float Volume)
{
return AudioOut->setVolume(Volume);
}
// IMediaTracks
// --------------------------------
bool FAndroidVulkanMediaPlayer::GetAudioTrackFormat(int32 TrackIndex, int32 FormatIndex,
FMediaAudioTrackFormat &OutFormat) const
{
if (FormatIndex != 0 || PlayState == EMediaState::Closed)
{
return false;
}
int32 bitsPerSample;
int32 channels;
int32 rate;
if (!impl->getAudioTrackFormat(TrackIndex, &bitsPerSample, &channels, &rate))
{
return false;
}
OutFormat.BitsPerSample = bitsPerSample;
OutFormat.NumChannels = channels;
OutFormat.SampleRate = rate;
OutFormat.TypeName = TEXT("Native");
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Get audio trackformat {0} {1} {2} {3}", TrackIndex,
bitsPerSample, channels, rate);
return true;
}
int32 FAndroidVulkanMediaPlayer::GetNumTracks(EMediaTrackType TrackType) const
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Get num tracks type: {0}", int(TrackType));
// TODO: support audio / video only
switch (TrackType)
{
case EMediaTrackType::Audio:
return impl->numAudioTracks();
case EMediaTrackType::Video:
return impl->numVideoTracks();
default:
return 0;
}
}
int32 FAndroidVulkanMediaPlayer::GetNumTrackFormats(EMediaTrackType TrackType,
int32 TrackIndex) const
{
if (TrackIndex >= GetNumTracks(TrackType))
{
return 0;
}
switch (TrackType)
{
case EMediaTrackType::Audio:
return 1;
case EMediaTrackType::Video:
return 1;
default:
return 0;
}
}
int32 FAndroidVulkanMediaPlayer::GetSelectedTrack(EMediaTrackType TrackType) const
{
if (TrackType == EMediaTrackType::Audio)
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Returning no selected audio track");
return INDEX_NONE;
}
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Returning selected track type {0}", int(TrackType));
return 0;
}
FText FAndroidVulkanMediaPlayer::GetTrackDisplayName(EMediaTrackType TrackType,
int32 TrackIndex) const
{
// TODO: pass through display names
return FText::Format(LOCTEXT("TrackName", "Track {0} type {1}"), (int)TrackType, TrackIndex);
}
int32 FAndroidVulkanMediaPlayer::GetTrackFormat(EMediaTrackType TrackType, int32 TrackIndex) const
{
return 0;
}
FString FAndroidVulkanMediaPlayer::GetTrackLanguage(EMediaTrackType TrackType,
int32 TrackIndex) const
{
// TODO - pass language through
return TEXT("");
}
FString FAndroidVulkanMediaPlayer::GetTrackName(EMediaTrackType TrackType, int32 TrackIndex) const
{
if (TrackIndex == 0 && PlayState != EMediaState::Closed)
{
return TEXT("TRACK");
}
else
{
return TEXT("");
}
}
bool FAndroidVulkanMediaPlayer::GetVideoTrackFormat(int32 TrackIndex, int32 FormatIndex,
FMediaVideoTrackFormat &OutFormat) const
{
if (FormatIndex != 0 || PlayState == EMediaState::Closed)
{
return false;
}
int w = 0, h = 0;
float frameRate = 0;
if (!impl->getVideoTrackFormat(TrackIndex, &w, &h, &frameRate))
{
return false;
}
OutFormat.Dim.X = w;
OutFormat.Dim.Y = h;
OutFormat.FrameRate = frameRate;
OutFormat.FrameRates = TRange<float>(frameRate);
OutFormat.TypeName = TEXT("Vulkan Video Frame");
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Got out video format");
return true;
}
bool FAndroidVulkanMediaPlayer::SelectTrack(EMediaTrackType TrackType, int32 TrackIndex)
{
if (TrackType == EMediaTrackType::Audio)
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Not selecting audio track");
return false;
}
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Selecting track type {0} = {0}", int(TrackType),
TrackIndex);
return true;
// return TrackIndex == 0 && PlayState != EMediaState::Closed;
}
bool FAndroidVulkanMediaPlayer::SetTrackFormat(EMediaTrackType TrackType, int32 TrackIndex,
int32 FormatIndex)
{
// todo: support multiple formats / track
return TrackIndex == 0 && FormatIndex == 0 && PlayState != EMediaState::Closed;
}
IMediaSamples &FAndroidVulkanMediaPlayer::GetSamples()
{
return *SampleQueue.Get();
}
void FAndroidVulkanMediaPlayer::onVideoFrame(void *frameHwBuffer, int w, int h, int64_t presTimeNs)
{
if (PlayState == EMediaState::Closed)
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Releasing frame as we are closed");
impl->releaseFrame(frameHwBuffer);
return;
}
if (HasVideoThisFrame)
{
impl->releaseFrame(frameHwBuffer);
return;
}
HasVideoThisFrame = true;
auto textureSample = VideoSamplePool.AcquireShared();
textureSample->Init(impl, frameHwBuffer, w, h,
FMediaTimeStamp(FTimespan(presTimeNs / 100LL),
FMediaTimeStamp::MakeSequenceIndex(SeekIndex, 0)));
SampleQueue->AddVideo(textureSample);
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Add video sample");
// UE_LOGFMT(LogDirectVideo, VeryVerbose, "Video samples {0}",
// SampleQueue->NumVideoSamples());
// NB: this needs to happen after the sample is in the queue, so that
// the queue has something in when media player facade samples it for playback
// time
if (Seeking)
{
SeekIndex += 1;
UE_LOGFMT(LogDirectVideo, Verbose, "Seek completed {0}", SeekIndex);
Seeking = false;
EventSink.ReceiveMediaEvent(EMediaEvent::SeekCompleted);
}
}
void FAndroidVulkanMediaPlayer::OnEnterBackground()
{
// going into backgroud - if playing, pause implementation player
if (PlayState == EMediaState::Playing)
{
impl->setPlaying(false);
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackSuspended);
UE_LOGFMT(LogDirectVideo, Verbose, "Enter suspend");
}
}
void FAndroidVulkanMediaPlayer::OnEnterForeground()
{
// back into foreground - if playing, start
if (PlayState == EMediaState::Playing)
{
impl->setPlaying(true);
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackResumed);
UE_LOGFMT(LogDirectVideo, Verbose, "Enter foreground");
}
}
void FAndroidVulkanMediaPlayer::ProcessVideoSamples()
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Process video samples");
}
void *FAndroidVulkanMediaPlayer::getVkDeviceProcAddr(const char *name)
{
void *result = static_cast<void *>(
(static_cast<struct IVulkanDynamicRHI *>(GDynamicRHI))->RHIGetVkDeviceProcAddr(name));
return result;
}
VkDevice FAndroidVulkanMediaPlayer::getVkDevice()
{
IVulkanDynamicRHI *rhi = static_cast<struct IVulkanDynamicRHI *>(GDynamicRHI);
if (rhi == NULL)
{
return VK_NULL_HANDLE;
}
return rhi->RHIGetVkDevice();
}
const VkAllocationCallbacks *FAndroidVulkanMediaPlayer::getVkAllocationCallbacks()
{
IVulkanDynamicRHI *rhi = static_cast<struct IVulkanDynamicRHI *>(GDynamicRHI);
if (rhi == NULL)
{
return NULL;
}
return rhi->RHIGetVkAllocationCallbacks();
}
VkPhysicalDevice FAndroidVulkanMediaPlayer::getNativePhysicalDevice()
{
return static_cast<VkPhysicalDevice>(GDynamicRHI->RHIGetNativePhysicalDevice());
}
void FAndroidVulkanMediaPlayer::onPlaybackEnd(bool looping)
{
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackEndReached);
if (!looping)
{
PlayState = EMediaState::Stopped;
EventSink.ReceiveMediaEvent(EMediaEvent::PlaybackSuspended);
}
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,399 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Vulkan texture sample implementation.
//
// Actual texture handling is handled in
// FRenderOnRHIThreadCommand::Execute via calls to
// IVulkanImpl
// ------------------------------------------------
#include "AndroidVulkanTextureSample.h"
#include "UnrealLogging.h"
#include "Android/AndroidApplication.h"
#include "IVulkanDynamicRHI.h"
#include "IVulkanImpl.h"
#include "SceneUtils.h"
#include "RenderingThread.h"
void AndroidVulkanTextureSample::FRenderOnRHIThreadCommand(const FTexture2DRHIRef &DstTexture)
{
FScopeLock Lock(&AccessLock);
if (Impl == NULL)
return;
IVulkanDynamicRHI *RHI = static_cast<struct IVulkanDynamicRHI *>(GDynamicRHI);
FVulkanRHIImageViewInfo TexInfo = RHI->RHIGetImageViewInfo(DstTexture.GetReference());
VkCommandBuffer cmdBuffer = RHI->RHIGetActiveVkCommandBuffer();
// 2) impl class
// 3) VkImageView etc. from InDstTexture
int w = TexInfo.Width;
int h = TexInfo.Height;
int Samples = DstTexture->GetDesc().NumSamples;
Impl->renderFrameToImageView(cmdBuffer, FrameHWImage, TexInfo.ImageView, TexInfo.Image,
TexInfo.Format, w, h, Samples);
}
void AndroidVulkanTextureSample::FRenderMeshOnRHIThreadCommand(const FTexture2DRHIRef &DstTexture,
void *MeshID, int Samples)
{
FScopeLock Lock(&AccessLock);
if (Impl == NULL)
return;
IVulkanDynamicRHI *RHI = static_cast<struct IVulkanDynamicRHI *>(GDynamicRHI);
FVulkanRHIImageViewInfo TexInfo = RHI->RHIGetImageViewInfo(DstTexture.GetReference());
VkCommandBuffer cmdBuffer = RHI->RHIGetActiveVkCommandBuffer();
int w = TexInfo.Width;
int h = TexInfo.Height;
int DrawViews = DstTexture->GetDesc().ArraySize;
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Render in existing pass: Samples: {0} views: {1}",
Samples, DrawViews);
// render to upside down viewport for consistency with unreal rendering
Impl->renderFrameToMeshInExistingPass(cmdBuffer, FrameHWImage, TexInfo.Image, 0, h, w, -h,
Samples, DrawViews, MeshID);
}
void AndroidVulkanTextureSample::FUpdateMeshOnRHIThreadCommand(
const FTexture2DRHIRef &DstTexture, const TArray<VertexData> &PositionAndUV,
const TArray<uint32> &Indices, const ShaderModelMatrix &Matrix, void *MeshID)
{
FScopeLock Lock(&AccessLock);
if (Impl == NULL)
return;
IVulkanDynamicRHI *RHI = static_cast<struct IVulkanDynamicRHI *>(GDynamicRHI);
VkCommandBuffer cmdBuffer = RHI->RHIGetActiveVkCommandBuffer();
Impl->loadMesh(cmdBuffer, PositionAndUV.GetData(), PositionAndUV.Num(), Indices.GetData(),
Indices.Num(), MeshID);
Impl->updateModelMatrix(cmdBuffer, Matrix, MeshID);
}
void AndroidVulkanTextureSample::FUpdateMatricesOnRHIThreadCommand(
const FTexture2DRHIRef &DstTexture, const ShaderViewMatrices &Matrices, void *MeshID)
{
FScopeLock Lock(&AccessLock);
if (Impl == NULL)
return;
IVulkanDynamicRHI *RHI = static_cast<struct IVulkanDynamicRHI *>(GDynamicRHI);
FVulkanRHIImageViewInfo TexInfo = RHI->RHIGetImageViewInfo(DstTexture.GetReference());
VkCommandBuffer cmdBuffer = RHI->RHIGetActiveVkCommandBuffer();
Impl->updateViewMatrices(cmdBuffer, Matrices, TexInfo.Image);
}
void AndroidVulkanTextureSample::FInitMeshRenderingCommand(const FTexture2DRHIRef &DstTexture,
void *MeshID, int Samples)
{
FScopeLock Lock(&AccessLock);
IVulkanDynamicRHI *RHI = static_cast<struct IVulkanDynamicRHI *>(GDynamicRHI);
FVulkanRHIImageViewInfo TexInfo = RHI->RHIGetImageViewInfo(DstTexture.GetReference());
VkCommandBuffer cmdBuffer = RHI->RHIGetActiveVkCommandBuffer();
int w = TexInfo.Width;
int h = TexInfo.Height;
int DrawViews = DstTexture->GetDesc().ArraySize;
Impl->initializeFrameObjectsOutsidePass(cmdBuffer, FrameHWImage, TexInfo.Image, w, h, MeshID,
Samples, DrawViews);
}
AndroidVulkanTextureSample::AndroidVulkanTextureSample()
{
Impl = NULL;
FrameHWImage = NULL;
NumSamples = 1;
static const auto NumSamplesVar =
IConsoleManager::Get().FindConsoleVariable(TEXT("r.MSAACount"));
static const auto AAType =
IConsoleManager::Get().FindConsoleVariable(TEXT("r.Mobile.AntiAliasing"));
if (AAType != NULL && NumSamplesVar != NULL)
{
EAntiAliasingMethod MobileAntiAliasing = EAntiAliasingMethod(AAType->GetInt());
if (MobileAntiAliasing == EAntiAliasingMethod::AAM_MSAA)
{
NumSamples = NumSamplesVar->GetInt();
}
}
}
void AndroidVulkanTextureSample::InitNoVideo()
{
FScopeLock Lock(&AccessLock);
Clear();
this->Dimension = FIntPoint(2, 2);
this->FrameHWImage = NULL;
this->SampleTime = FMediaTimeStamp(FTimespan::Zero());
IsEmptyTexture = true;
}
void AndroidVulkanTextureSample::Init(IVulkanImpl *impl, void *hwImage, int w, int h,
FMediaTimeStamp sampleTime)
{
FScopeLock Lock(&AccessLock);
Clear();
this->Impl = impl;
this->Dimension = FIntPoint(w, h);
this->FrameHWImage = hwImage;
this->SampleTime = sampleTime;
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Create texture sample {0} {1}x{2} {3} ", this,
Dimension.X, Dimension.Y, sampleTime.Time);
IsEmptyTexture = false;
}
AndroidVulkanTextureSample::~AndroidVulkanTextureSample()
{
}
FIntPoint AndroidVulkanTextureSample::GetDim() const
{
return Dimension;
}
FIntPoint AndroidVulkanTextureSample::GetOutputDim() const
{
return Dimension;
}
uint32 AndroidVulkanTextureSample::GetStride() const
{
return Dimension.X;
}
FRHITexture *AndroidVulkanTextureSample::GetTexture() const
{
return NULL;
}
IMediaTextureSampleConverter *AndroidVulkanTextureSample::GetMediaTextureSampleConverter()
{
return this;
}
bool AndroidVulkanTextureSample::Convert(FTexture2DRHIRef &InDstTexture,
const FConversionHints &Hints)
{
FRHICommandListImmediate &RHICmdList = FRHICommandListExecutor::GetImmediateCommandList();
return ConvertInternal(RHICmdList, InDstTexture, Hints);
}
bool AndroidVulkanTextureSample::ConvertInternal(FRHICommandListImmediate &RHICmdList,
FTexture2DRHIRef &InDstTexture,
const FConversionHints &Hints)
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "ConvertInternal");
FScopeLock Lock(&AccessLock);
if (IsEmptyTexture)
{
// just let texture clear itself to clear colour
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Getting clear texture");
Shown = true;
return true;
}
else
{
if (Impl == NULL)
{
return false;
}
UE_LOGFMT(LogDirectVideo, VeryVerbose, "doing texture conversion");
FTexture2DRHIRef CopyInDstTexture = InDstTexture;
RHICmdList.EnqueueLambda(TEXT("ConvertTexture"),
([this, CopyInDstTexture](FRHICommandList &RHICommandList) {
this->FRenderOnRHIThreadCommand(CopyInDstTexture);
}));
if (!Fence.IsValid())
{
Fence = RHICreateGPUFence(TEXT("VVRFrameFence"));
}
Fence->Clear();
RHICmdList.WriteGPUFence(Fence);
Shown = true;
return true;
}
}
bool AndroidVulkanTextureSample::InitFrameForMeshRendering(FTexture2DRHIRef InDstTexture,
FRHICommandList &RHICmdList,
void *MeshID)
{
FScopeLock Lock(&AccessLock);
if (Impl == NULL)
{
return false;
}
if (IsEmptyTexture)
{
// TODO: no frame, do nothing?
return true;
}
else
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "init frame for rendering");
RHICmdList.EnqueueLambda(
TEXT("InitFrame"), ([this, InDstTexture, MeshID](FRHICommandList &RHICommandList) {
this->FInitMeshRenderingCommand(InDstTexture, MeshID, NumSamples);
}));
return true;
}
}
bool AndroidVulkanTextureSample::UpdateMesh(FTexture2DRHIRef InDstTexture,
FRHICommandList &RHICmdList,
TArray<VertexData> PositionAndUV,
TArray<uint32> Indices, ShaderModelMatrix Matrix,
void *MeshID)
{
if (Impl != NULL && !Impl->hasMesh(MeshID))
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "setting texture mesh");
RHICmdList.EnqueueLambda(TEXT("SetMesh"), ([this, InDstTexture, PositionAndUV, Indices,
Matrix, MeshID](FRHICommandList &RHICmdList) {
this->FUpdateMeshOnRHIThreadCommand(
InDstTexture, PositionAndUV, Indices, Matrix, MeshID);
}));
return true;
}
else
{
return false;
}
}
bool AndroidVulkanTextureSample::UpdateViewMatrices(FTexture2DRHIRef InDstTexture,
FRHICommandList &RHICmdList,
ShaderViewMatrices Matrices, void *MeshID)
{
// in impl, we need to
// a) load data to mesh uniform buffers if not already loaded
// then b) render to that mesh
FScopeLock Lock(&AccessLock);
if (Impl == NULL)
{
return false;
}
UE_LOGFMT(LogDirectVideo, VeryVerbose, "updating matrices");
RHICmdList.EnqueueLambda(TEXT("UpdateMatrices"),
([this, InDstTexture, Matrices, MeshID](FRHICommandList &RHICmdList) {
this->FUpdateMatricesOnRHIThreadCommand(InDstTexture, Matrices,
MeshID);
}));
return true;
}
bool AndroidVulkanTextureSample::RenderToMesh(FTexture2DRHIRef InDstTexture,
FRHICommandList &RHICmdList, void *MeshID)
{
// in impl, we need to
// a) load data to mesh uniform buffers if not already loaded
// then b) render to that mesh
FScopeLock Lock(&AccessLock);
if (IsEmptyTexture)
{
// need to render to a clear colour...
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Getting clear texture");
Shown = true;
return true;
}
else
{
if (Impl == NULL)
{
return false;
}
UE_LOGFMT(LogDirectVideo, VeryVerbose, "writing texture mesh Immediate:{0},{1},{2}",
RHICmdList.IsImmediate(), RHICmdList.Bypass(), RHICmdList.IsExecuting());
RHICmdList.EnqueueLambda(
TEXT("RenderMesh"), ([this, InDstTexture, MeshID](FRHICommandList &RHICmdList) {
this->FRenderMeshOnRHIThreadCommand(InDstTexture, MeshID, NumSamples);
}));
if (!Fence.IsValid())
{
Fence = RHICreateGPUFence(TEXT("VVRFrameFence"));
}
Fence->Clear();
RHICmdList.WriteGPUFence(Fence);
Shown = true;
return true;
}
}
FMediaTimeStamp AndroidVulkanTextureSample::GetTime() const
{
return SampleTime;
}
void AndroidVulkanTextureSample::InitializePoolable()
{
Clear();
}
void AndroidVulkanTextureSample::ClearIfShown()
{
if (Shown && IsReadyForReuse())
{
Clear();
}
}
bool AndroidVulkanTextureSample::IsReadyForReuse()
{
FScopeLock Lock(&AccessLock);
if (this->FrameHWImage != NULL && Fence.IsValid() && !Fence->Poll())
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Fence not set yet");
return false;
}
return true;
}
void AndroidVulkanTextureSample::Clear()
{
FScopeLock Lock(&AccessLock);
if (this->Impl != NULL && this->FrameHWImage != NULL)
{
UE_LOG(LogDirectVideo, VeryVerbose, TEXT("Release texture sample %x"), this->FrameHWImage);
this->Impl->releaseFrame(this->FrameHWImage);
}
this->FrameHWImage = NULL;
this->Impl = NULL;
this->Shown = false;
}
void AndroidVulkanTextureSample::ShutdownPoolable()
{
bool needsWait = false;
{
FScopeLock Lock(&AccessLock);
if (this->FrameHWImage != NULL && Fence.IsValid() && !Fence->Poll())
{
needsWait = true;
}
}
if (needsWait)
{
UE_LOG(LogDirectVideo, VeryVerbose, TEXT("Shutdown poolable %x"), this->FrameHWImage);
if (IsInGameThread())
{
FlushRenderingCommands();
}
}
Clear();
}
void AndroidVulkanTextureSample::ImplDeleted()
{
FScopeLock Lock(&AccessLock);
this->Impl = NULL;
}
EMediaTextureSampleFormat AndroidVulkanTextureSample::VideoTextureFormat =
EMediaTextureSampleFormat::CharBGR10A2;

View File

@ -0,0 +1,71 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Vulkan video player module. Handles starting up
// the correct vulkan extensions, and enabling
// the override layer which we use to enable
// SamplerYCbCrConversion feature properly.
// ------------------------------------------------
#include "AndroidVulkanVideo.h"
// #include "Core.h"
#include "Modules/ModuleManager.h"
// #include "Interfaces/IPluginManager.h"
#if PLATFORM_ANDROID
#include "AndroidVulkanMediaPlayer.h"
#include "IVulkanDynamicRHI.h"
#endif
#define LOCTEXT_NAMESPACE "FAndroidVulkanVideoModule"
class FAndroidVulkanVideoModule : public IAndroidVulkanVideoModule
{
void StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is
// specified in the .uplugin file per-module
// Get the base directory of this plugin
// FString BaseDir = IPluginManager::Get().FindPlugin("AndroidVulkanVideo")->GetBaseDir();
//
#if PLATFORM_ANDROID
// setup vulkan extensions that we need
const TArray<const ANSICHAR *> DeviceExtensions = {
"VK_KHR_external_memory",
"VK_ANDROID_external_memory_android_hardware_buffer",
"VK_KHR_sampler_ycbcr_conversion",
"VK_KHR_bind_memory2",
"VK_EXT_queue_family_foreign",
"VK_KHR_dedicated_allocation",
"VK_KHR_get_physical_device_properties2"};
const TArray<const ANSICHAR *> DeviceLayers = {};
const TArray<const ANSICHAR *> InstanceExtensions = {
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME};
const TArray<const ANSICHAR *> InstanceLayers = {"VkLayer_OverrideLib"};
IVulkanDynamicRHI::AddEnabledDeviceExtensionsAndLayers(DeviceExtensions, DeviceLayers);
IVulkanDynamicRHI::AddEnabledInstanceExtensionsAndLayers(InstanceExtensions,
InstanceLayers);
#endif
}
void ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that
// support dynamic reloading, we call this function before unloading the module.
// Free the dll handle
}
TSharedPtr<IMediaPlayer, ESPMode::ThreadSafe> CreatePlayer(IMediaEventSink &EventSink)
{
return TSharedPtr<IMediaPlayer, ESPMode::ThreadSafe>(
new FAndroidVulkanMediaPlayer(EventSink));
}
};
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FAndroidVulkanVideoModule, AndroidVulkanVideo)

View File

@ -0,0 +1,38 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// interface for audio output classes. This is used
// so that AndroidVulkanVideoImpl doesn't need to
// know about Unreal, so I can keep Unreal and
// Android code separate.
// ------------------------------------------------
#pragma once
#include <chrono>
class IAudioOut
{
public:
virtual ~IAudioOut() {};
virtual void init(int sampleRate, int channels) = 0;
virtual void initSilent() = 0;
virtual void close() = 0;
virtual void sendBuffer(uint8_t *buf, int bufSize, int64_t presentationTimeNS,
bool reset = false) = 0;
virtual int64_t getPresentationTimeNS() = 0; // current presentation time
virtual bool setVolume(float level) = 0;
virtual void setPlaying(bool playing) = 0;
virtual bool setRate(float rate) = 0;
virtual int64_t getQueuedTimeNS() = 0;
virtual void onSeek(int64_t newPresentationTimeNS, bool resetAudio) = 0;
virtual void onHasVideoTime(int64_t newPresentationTimeNS) = 0;
typedef std::chrono::nanoseconds NsDuration;
typedef std::chrono::time_point<std::chrono::steady_clock, NsDuration> NsTime;
virtual NsTime getWaitTimeForPresentationTime(int64_t presentationTimeNS,
int64_t maxDuration) = 0;
};

View File

@ -0,0 +1,19 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// A custom file source - used for reading encrypted
// or compressed video files.
// ------------------------------------------------
#pragma once
#include <cstdint>
class ICustomMediaFileSource
{
public:
virtual ~ICustomMediaFileSource() {};
virtual int64_t getAvailableSize(uint64_t offset) = 0;
virtual int64_t getSize() = 0;
virtual int64_t readAt(uint64_t offset, void *buffer, uint64_t size) = 0;
};

View File

@ -0,0 +1,21 @@
#pragma once
#include <cinttypes>
class ILogger
{
public:
virtual void LogWithType(int64_t type, const char *fmt, ...) = 0;
enum LogTypes
{
ERROR = 1 << 0,
TEXTURE_IMPL = 1 << 1,
TEXTURE_IMPL_VV = 1 << 2,
DECODER = 1 << 3,
DECODER_VV = 1 << 4,
VULKAN_DYNAMIC = 1 << 5,
VULKAN_DYNAMIC_VV = 1 << 6,
LOGTYPE_MAX = 1 << 7,
ALL_LOGS_BITMASK = 0xff
};
};

View File

@ -0,0 +1,82 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Interface used by Android specific implementation class
// This does all handling of Android hardware
// buffers, Vulkan texture images etc.
//
// It lives in libVkLayer_OverrideLib.so
// along with a vulkan override layer to handle
// initialisation of the Vulkan device.
//
// ------------------------------------------------
#pragma once
#include "IAndroidVulkanVideoAVCallback.h"
#include "IAudioOut.h"
#include "ICustomMediaFileSource.h"
#include <vulkan/vulkan.h>
class ILogger;
#include "IVulkanVertexData.h"
class IVulkanImpl
{
public:
virtual bool initializeFrameObjectsOutsidePass(VkCommandBuffer cmdBuffer, void *frameImage,
VkImage outImage, int w, int h, void *meshID,
int samples, int drawViews) = 0;
virtual bool renderFrameToMeshInExistingPass(VkCommandBuffer cmdBuffer, void *frameImage,
VkImage outImage, int x, int y, int w, int h,
int samples, int drawViews, void *meshID) = 0;
virtual bool updateViewMatrices(VkCommandBuffer cmdBuffer, const ShaderViewMatrices &matrices,
VkImage targetImage) = 0;
virtual bool updateModelMatrix(VkCommandBuffer cmdBuffer, const ShaderModelMatrix &matrix,
void *meshID) = 0;
virtual bool loadMesh(VkCommandBuffer cmdBuffer, const VertexData *positionAndUV,
int vertexCount, const uint32_t *indices, int indexCount,
void *meshID) = 0;
virtual bool hasMesh(void *meshID) const = 0;
virtual bool unloadMesh(void *meshID) = 0;
virtual bool renderFrameToImageView(VkCommandBuffer cmdBuffer, void *frameImage,
VkImageView view, VkImage image, VkFormat format, int w,
int h, int samples) = 0;
virtual void releaseFrame(void *frameHwBuffer) = 0;
virtual void setDataCallback(IAndroidVulkanVideoAVCallback *cb) = 0;
virtual void setLogger(ILogger *logger) = 0;
virtual bool startVideoCustomSource(ICustomMediaFileSource *source, bool autoplay) = 0;
virtual bool startVideoURL(char *videoURL, bool autoplay) = 0;
virtual bool startVideoFile(char *fname, int64_t offset, int64_t length, bool autoplay) = 0;
virtual bool hasVideo(int *width, int *height) = 0;
virtual bool getVideoTrackFormat(int idx, int *width, int *height, float *fps) = 0;
virtual bool getAudioTrackFormat(int idx, int *bitsPerSample, int *channels, int *rate) = 0;
// set audio out handler - typically has to be done once per
// run, as it is re-initialized per video
virtual void setAudioOut(IAudioOut *audioOut) = 0;
// set the output texture
virtual void setOutputImageView(VkImageView view, VkImage image, VkFormat format, int w, int h,
int samples) = 0;
virtual void close() = 0;
virtual void setPlaying(bool playing) = 0;
virtual bool getPlaying() const = 0;
virtual void seek(int64_t nsTimestamp) = 0;
virtual bool setRate(float rate) = 0;
virtual float getRate() const = 0;
virtual int64_t getTimeNS() const = 0;
virtual int64_t getDurationNS() const = 0;
virtual int numAudioTracks() const = 0;
virtual int numVideoTracks() const = 0;
virtual void setLooping(bool looping) = 0;
virtual bool getLooping() const = 0;
};

View File

@ -0,0 +1,46 @@
#include "UnrealArchiveFileSource.h"
UnrealArchiveFileSource::UnrealArchiveFileSource(
const TSharedRef<FArchive, ESPMode::ThreadSafe> inArchive)
: archive(inArchive)
{
}
int64_t UnrealArchiveFileSource::getAvailableSize(uint64_t offset)
{
FScopeLock lockCS(&threadLock);
return getSize() - offset;
}
int64_t UnrealArchiveFileSource::getSize()
{
FScopeLock lockCS(&threadLock);
return archive->TotalSize();
}
int64_t UnrealArchiveFileSource::readAt(uint64_t position, void *buffer, uint64_t readSize)
{
FScopeLock lockCS(&threadLock);
int64_t fileSize = archive->TotalSize();
if (fileSize < position)
{
return -1; // EOF
}
int64_t curPos = archive->Tell();
if (curPos != position)
{
archive->Seek(position);
}
if (position + readSize > fileSize)
{
readSize = fileSize - position;
}
if (readSize > 0)
{
archive->Serialize(buffer, readSize);
}
return readSize;
}

View File

@ -0,0 +1,25 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Read files from Unreal archive
// ------------------------------------------------
#pragma once
#include "ICustomMediaFileSource.h"
#include "CoreMinimal.h"
class UnrealArchiveFileSource : public ICustomMediaFileSource
{
public:
explicit UnrealArchiveFileSource(const TSharedRef<FArchive, ESPMode::ThreadSafe> inArchive);
virtual ~UnrealArchiveFileSource() override {};
virtual int64_t getAvailableSize(uint64_t offset) override;
virtual int64_t getSize() override;
virtual int64_t readAt(uint64_t offset, void *buffer, uint64_t size) override;
private:
TSharedRef<FArchive, ESPMode::ThreadSafe> archive;
FCriticalSection threadLock;
};

View File

@ -0,0 +1,423 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Unreal audio output implementation.
// ------------------------------------------------
#include "UnrealAudioOut.h"
#include "AudioDevice.h"
#include "Components/ActorComponent.h"
#include "Components/AudioComponent.h"
#include "Runtime/Engine/Classes/Sound/AudioSettings.h"
#include <time.h>
#include "Engine/engine.h"
#include "UnrealLogging.h"
static int64_t _getNSTime()
{
#if PLATFORM_ANDROID
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return static_cast<int64_t>(ts.tv_nsec) + (1000000000LL * static_cast<int64_t>(ts.tv_sec));
#else
return -1;
#endif
}
static int64_t _scaleTime(int64_t val, float scalar)
{
double dt = double(val) * double(scalar);
return int64_t(dt);
}
void USoundWaveProceduralWithTiming::ResetAudio()
{
USoundWaveProcedural::ResetAudio();
QueueSilence(4096);
}
void USoundWaveProceduralWithTiming::QueueSilence(int64_t bytes)
{
UE_LOG(LogDirectVideo, VeryVerbose, TEXT("Queue Silence %d"), bytes);
uint8_t silence[4096];
memset(silence, 0, 4096);
int64_t count = 4096;
while (bytes > 0)
{
if (bytes < 4096)
count = bytes;
bytes -= count;
QueueAudio(silence, count);
}
}
int32 USoundWaveProceduralWithTiming::GeneratePCMData(uint8 *PCMData, const int32 SamplesNeeded)
{
lastBufSendTime = _getNSTime();
if (!paused)
{
return USoundWaveProcedural::GeneratePCMData(PCMData, SamplesNeeded);
}
else
{
memset(PCMData, 0, SampleByteSize * SamplesNeeded);
return SamplesNeeded;
}
}
UnrealAudioOut::UnrealAudioOut(UActorComponent *pOwner)
: audioSender(NULL), audioComponent(NULL), hasTimeOffset(false), presentationTimeOffset(0),
pausedPresentationTime(-1), numChannels(0), sampleRate(0), playbackRate(1.0f),
afterSeek(false), isPlaying(false), owner(pOwner)
{
}
void UnrealAudioOut::close()
{
afterSeek = false;
if (audioComponent.IsValid())
{
audioComponent->Stop();
audioComponent->SetSound(NULL);
}
audioSender = NULL;
}
void UnrealAudioOut::init(int rate, int channels)
{
close();
// this object gets garbage collected at the right time because it is
// assigned to a component below
playbackRate = 1.0f;
audioSender = NewObject<USoundWaveProceduralWithTiming>();
audioSender->SetSampleRate(rate);
audioSender->NumChannels = channels;
audioSender->Duration = INDEFINITELY_LOOPING_DURATION;
audioSender->bLooping = false;
audioSender->bCanProcessAsync = true;
audioSender->paused = false;
if (!audioComponent.IsValid())
{
if (owner)
{
AActor *actor = owner->GetOwner<AActor>();
if (actor)
{
audioComponent = actor->FindComponentByClass<UAudioComponent>();
}
}
}
if (audioComponent.IsValid())
{
audioComponent->SetSound(audioSender.Get());
}
else
{
FAudioDeviceHandle audioDevice = GEngine->GetMainAudioDevice();
audioComponent = audioDevice->CreateComponent(audioSender.Get());
if (audioComponent.IsValid())
{
audioComponent->bIsUISound = true;
audioComponent->bAllowSpatialization = false;
audioComponent->SetVolumeMultiplier(1.0f);
audioComponent->AddToRoot();
audioComponent->Play();
// hold onto strong pointer to audiocomponent so it doesn't get destroyed until
// we do
audioComponentWeCreated = *audioComponent;
}
else
{
UE_LOGFMT(LogDirectVideo, Error, "Unable to create audio component!");
}
}
if (audioComponent.IsValid())
{
audioComponent->Play();
}
numChannels = channels;
sampleRate = rate;
hasTimeOffset = false;
presentationTimeOffset = 0;
isPlaying = true;
UE_LOG(LogDirectVideo, Verbose, TEXT("Audio component: channels %d sampleRate %d"), channels,
sampleRate);
}
void UnrealAudioOut::initSilent()
{
close();
UE_LOGFMT(LogDirectVideo, Verbose, "Audio init silent");
playbackRate = 1.0f;
hasTimeOffset = false;
}
UnrealAudioOut::~UnrealAudioOut()
{
if (audioComponentWeCreated)
{
audioComponent->RemoveFromRoot();
}
UE_LOGFMT(LogDirectVideo, Verbose, "Audio out destroyed");
}
void UnrealAudioOut::sendBuffer(uint8_t *buf, int bufSize, int64_t presentationTimeNS, bool reset)
{
if (buf == NULL || bufSize == 0)
{
return;
}
auto pinnedSender = audioSender.Get();
if (!pinnedSender)
{
UE_LOGFMT(LogDirectVideo, Error, "No audio sender");
return;
}
if (reset)
{
// reset audio time clock
// and clear audio buffers
pinnedSender->ResetAudio();
}
int64_t lastSendTime = pinnedSender->lastBufSendTime;
if (lastSendTime != 0)
{
int64_t samplesInSender = pinnedSender->GetAvailableAudioByteCount() / (numChannels * 2);
if (samplesInSender <= 0)
{
pinnedSender->QueueSilence(4096);
samplesInSender = 4096 / (numChannels * 2L);
}
int64_t timeForBuffer = (1000000000LL * samplesInSender) / static_cast<int64_t>(sampleRate);
int64_t thisOutputTimeNS = lastSendTime + timeForBuffer;
if (afterSeek)
{
// we've seeked and we've seen a video frame already
// so add silence if required so that we can sync on the
// already displayed video frame(s)
afterSeek = false;
int64_t newOffset = presentationTimeNS - thisOutputTimeNS;
if (newOffset > (presentationTimeOffset + 10000000L))
{
UE_LOG(LogDirectVideo, VeryVerbose, TEXT("After seek silence"));
// queue silence to make things line up
int64_t difference = newOffset - presentationTimeOffset;
int64_t silenceSamples =
(difference * static_cast<int64_t>(sampleRate)) / 1000000000LL;
pinnedSender->QueueSilence(silenceSamples * 2L * numChannels);
}
}
else
{
presentationTimeOffset = presentationTimeNS - thisOutputTimeNS;
}
hasTimeOffset = true;
UE_LOG(LogDirectVideo, VeryVerbose,
TEXT("Adjusting offset (%d) - now %ld timeForBuffer %ld lastSendTime %ld "
"thisSendTime %ld presentationTimeNS %ld offset %ld"),
bufSize, _getNSTime(), timeForBuffer, lastSendTime, thisOutputTimeNS,
presentationTimeNS, presentationTimeOffset);
}
pinnedSender->QueueAudio(buf, bufSize);
}
int64_t UnrealAudioOut::getPresentationTimeNS()
{
if (!hasTimeOffset)
{
if (!audioSender)
{
// silent - initialize time to zero on first query
// or else we miss a frame on startup
presentationTimeOffset = -_scaleTime(_getNSTime(), playbackRate);
hasTimeOffset = true;
}
else
{
// with audio - initialize
return -1;
}
}
int64_t retVal = _scaleTime(_getNSTime(), playbackRate) + presentationTimeOffset;
return retVal;
}
IAudioOut::NsTime UnrealAudioOut::getWaitTimeForPresentationTime(int64_t presentationTimeNS,
int64_t maxDurationNS)
{
int64_t thisTime = getPresentationTimeNS();
int64_t duration = presentationTimeNS - thisTime;
// give a little slack for processing time
duration = (duration * 100L) / 98L;
if (maxDurationNS > 0 && duration > maxDurationNS)
{
duration = maxDurationNS;
}
NsTime curTime = std::chrono::steady_clock::now();
if (duration < 0)
{
return curTime;
}
NsTime waitUntilTime = curTime + NsDuration(_scaleTime(duration, playbackRate));
return waitUntilTime;
}
bool UnrealAudioOut::setVolume(float volume)
{
if (!audioComponent.IsValid())
{
return false;
}
if (volume > 1.0)
volume = 1.0;
if (volume < 0.0)
volume = 0.0;
audioComponent->SetVolumeMultiplier(volume);
return true;
}
void UnrealAudioOut::setPlaying(bool playing)
{
if (playing == isPlaying)
{
return;
}
isPlaying = playing;
if (playing)
{
// starting after a pause
// reset presentation time offset
if (pausedPresentationTime != -1 && hasTimeOffset)
{
presentationTimeOffset =
pausedPresentationTime - _scaleTime(_getNSTime(), playbackRate);
}
}
else
{
// pausing, save current prestime for restore
pausedPresentationTime = getPresentationTimeNS();
}
if (audioSender)
{
audioSender->paused = !playing;
}
}
void UnrealAudioOut::onSeek(int64_t newPresentationTimeNs, bool resetAudio)
{
if (!audioSender)
{
presentationTimeOffset = newPresentationTimeNs - _scaleTime(_getNSTime(), playbackRate);
UE_LOG(LogDirectVideo, VeryVerbose, TEXT("Seeking: pt %ld update offset %ld (rate=%f)"),
newPresentationTimeNs, presentationTimeOffset, playbackRate);
}
else
{
// clear audio buffers
if (resetAudio)
{
// after a seek, we may receive either audio or video frames
// first. Here we clear the audio buffers, and reset the time
// offset so that first of either audio or video frame received
// will reset the clock
audioSender->ResetAudio();
hasTimeOffset = false;
}
else
{
// we are looping - don't clear the audio buffer
// and make a rough guess at the clock so that video
// frames will still send out correctly and then resync clock when first audio frames
// received
int64_t lastSendTime = audioSender->lastBufSendTime;
if (lastSendTime == 0)
{
// not actually sent anything yet - assume it will send pretty much now
lastSendTime = _getNSTime();
}
int64_t samplesInSender = audioSender->GetAvailableAudioByteCount() / (numChannels * 2);
int64_t timeForBuffer =
(1000000000LL * samplesInSender) / static_cast<int64_t>(sampleRate);
// when the next buffer (i.e. the one at newPresentationTimeNs)
// will get sent at (in clock time )
int64_t thisOutputTimeNS = lastSendTime + timeForBuffer;
presentationTimeOffset =
newPresentationTimeNs - _scaleTime(thisOutputTimeNS, playbackRate);
}
}
}
bool UnrealAudioOut::setRate(float newRate)
{
if (audioSender)
{
if (newRate != 1.0 && newRate != 0.0)
{
UE_LOGFMT(LogDirectVideo, Error,
"Trying to set playback rate on video with audio, not supported yet");
return false;
}
else
{
return true;
}
}
else
{
if (newRate > 0)
{
if (hasTimeOffset)
{
int64_t presTime = getPresentationTimeNS();
playbackRate = newRate;
onSeek(presTime, false);
}
else
{
playbackRate = newRate;
}
}
}
return false;
}
int64_t UnrealAudioOut::getQueuedTimeNS()
{
if (audioSender)
{
int64_t samplesInSender = audioSender->GetAvailableAudioByteCount() / (numChannels * 2);
int64_t timeTotal = (1000000000L * samplesInSender) / sampleRate;
return timeTotal;
}
else
{
return -1;
}
}
void UnrealAudioOut::onHasVideoTime(int64_t newPresentationTimeNS)
{
if (audioSender && !hasTimeOffset)
{
// if we haven't sent any audio yet, then set current time to this frame time,
// and let first updating of offset add silence
presentationTimeOffset = newPresentationTimeNS - _scaleTime(_getNSTime(), playbackRate);
hasTimeOffset = true;
afterSeek = true;
}
}

View File

@ -0,0 +1,77 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Unreal IAudioOut implementation. This uses a
// USoundWaveProcedural to do the output, and also
// a UAudioComponent object. If given a parent
// actor object which has an existing
// UAudioComponent, it uses that. Otherwise it
// makes a new UAudioComponent and holds a strong
// object pointer to it until this class is
// destroyed.
// ------------------------------------------------
#pragma once
#include "Sound/SoundWaveProcedural.h"
#include <chrono>
#include <cstdint>
#include "IAudioOut.h"
#include "UnrealAudioOut.generated.h"
class USoundWaveProceduralWithTiming;
class UAudioComponent;
class UActorComponent;
UCLASS(MinimalAPI)
class USoundWaveProceduralWithTiming : public USoundWaveProcedural
{
GENERATED_BODY()
public:
virtual int32 GeneratePCMData(uint8 *PCMData, const int32 SamplesNeeded) override;
virtual void ResetAudio();
virtual void QueueSilence(int64_t bytes);
bool paused;
bool hasTiming;
int64_t lastBufSendTime;
};
class UnrealAudioOut : public IAudioOut
{
public:
explicit UnrealAudioOut(UActorComponent *owner);
virtual ~UnrealAudioOut() override;
virtual void init(int sampleRate, int channels) override;
virtual void initSilent() override;
virtual void close() override;
virtual void sendBuffer(uint8_t *buf, int bufSize, int64_t presentationTimeNS,
bool reset = false) override;
virtual int64_t getPresentationTimeNS() override; // current presentation time
virtual IAudioOut::NsTime getWaitTimeForPresentationTime(int64_t presentationTimeNS,
int64_t maxDuration) override;
virtual bool setVolume(float volume) override;
virtual void setPlaying(bool playing) override;
virtual bool setRate(float rate) override;
virtual void onSeek(int64_t newPresentationTimeNS, bool resetAudio) override;
virtual int64_t getQueuedTimeNS() override;
virtual void onHasVideoTime(int64_t newPresentationTimeNS) override;
private:
TObjectPtr<USoundWaveProceduralWithTiming> audioSender;
UActorComponent *owner;
TWeakObjectPtr<UAudioComponent> audioComponent;
TObjectPtr<UAudioComponent> audioComponentWeCreated;
bool hasTimeOffset;
int64_t presentationTimeOffset;
int numChannels;
int sampleRate;
bool isPlaying;
bool afterSeek;
float playbackRate;
int64_t pausedPresentationTime;
};

View File

@ -0,0 +1,10 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Unreal specific log category definition.
// ------------------------------------------------
#include "UnrealLogging.h"
DEFINE_LOG_CATEGORY(LogDirectVideo);

View File

@ -0,0 +1,93 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Unreal specific log category definition.
// ------------------------------------------------
#pragma once
#include "ILogger.h"
#include "Logging/StructuredLog.h"
DECLARE_LOG_CATEGORY_EXTERN(LogDirectVideo, Log, All);
class UnrealLogger : public ILogger
{
public:
UnrealLogger() : very_verbose_bitmask(0), verbose_bitmask(0), bitmask(0), error_bitmask(0)
{
}
void LogWithType(int64_t type, const char *fmt, ...) override
{
if ((type & bitmask) == 0)
{
// don't format the string unless we're going to output it
return;
}
else
{
va_list arg_ptr;
char buffer[1024] = "";
va_start(arg_ptr, fmt);
vsnprintf(buffer, 1023, fmt, arg_ptr);
va_end(arg_ptr);
if ((type & error_bitmask) != 0)
{
// error
UE_LOG(LogDirectVideo, Error, TEXT("%s"), ANSI_TO_TCHAR(buffer));
}
else if ((type & very_verbose_bitmask) != 0)
{
UE_LOG(LogDirectVideo, VeryVerbose, TEXT("%s"), ANSI_TO_TCHAR(buffer));
}
else
{
UE_LOG(LogDirectVideo, Verbose, TEXT("%s"), ANSI_TO_TCHAR(buffer));
}
}
}
void SetLogVisibilityBitmask(int64_t newBitmask)
{
very_verbose_bitmask = 0;
error_bitmask = 1;
verbose_bitmask = 0;
bitmask = newBitmask;
bool veryVerbose = true;
for (int64_t bit = 1; bit < ILogger::LogTypes::LOGTYPE_MAX; bit *= 2)
{
if (veryVerbose)
{
very_verbose_bitmask |= bit;
}
else
{
verbose_bitmask |= bit;
}
veryVerbose = !veryVerbose;
}
if (!UE_LOG_ACTIVE(LogDirectVideo, Verbose))
{
bitmask &= (~verbose_bitmask);
verbose_bitmask = 0;
}
if (!UE_LOG_ACTIVE(LogDirectVideo, VeryVerbose))
{
bitmask &= (~very_verbose_bitmask);
very_verbose_bitmask = 0;
}
if (!UE_LOG_ACTIVE(LogDirectVideo, Error))
{
bitmask = 0;
}
}
private:
int64_t very_verbose_bitmask;
int64_t verbose_bitmask;
int64_t bitmask;
int64_t error_bitmask;
};

View File

@ -0,0 +1,155 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// An implementation of IMediaSamples for the new
// v2 video timing in Unreal media player.
//
// With new timing, whilst in theory IMediaSamples
// is a queue, in use only the most recent sample
// is ever read.
//
// Given we know that, this class dumps any
// previous samples immediately, which allows
// the underlying hardware image to be released as
// soon as possible, which makes video decoding
// work more efficiently.
// ------------------------------------------------
#pragma once
#include "AndroidVulkanTextureSample.h"
#include "IMediaSamples.h"
#include "UnrealLogging.h"
// for v2 video timing, the sample queue just
// holds a single image
class FVideoMediaSampleHolder : public IMediaSamples
{
public:
void AddVideo(TSharedRef<AndroidVulkanTextureSample, ESPMode::ThreadSafe> &InSample)
{
if (VideoSample.IsValid())
{
// force sample texture clear because
// we're not going to display it
VideoSample->Clear();
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Clearing videoframe {0}",
VideoSample.GetSharedReferenceCount());
}
VideoSample = InSample;
UE_LOGFMT(LogDirectVideo, VeryVerbose, "CSet video frame {0}", VideoSample.IsValid());
}
virtual bool FetchVideo(
TRange<FTimespan> TimeRange,
TSharedPtr<IMediaTextureSample, ESPMode::ThreadSafe> &OutSample) override
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "BFetch video frame {0}", VideoSample.IsValid());
if (VideoSample.IsValid())
{
OutSample = VideoSample;
VideoSample.Reset();
return true;
}
return false; // override in child classes, if supported
}
virtual bool FetchVideo(
TRange<FMediaTimeStamp> TimeRange,
TSharedPtr<IMediaTextureSample, ESPMode::ThreadSafe> &OutSample) override
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "AFetch video frame {0}", VideoSample.IsValid());
if (VideoSample.IsValid())
{
OutSample = VideoSample;
VideoSample.Reset();
return true;
}
return false; // override in child classes, if supported
}
virtual EFetchBestSampleResult FetchBestVideoSampleForTimeRange(
const TRange<FMediaTimeStamp> &TimeRange,
TSharedPtr<IMediaTextureSample, ESPMode::ThreadSafe> &OutSample, bool bReverse)
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Best sample {0}", VideoSample.IsValid());
if (VideoSample.IsValid())
{
OutSample = VideoSample;
VideoSample.Reset();
return EFetchBestSampleResult::Ok;
}
else
{
return EFetchBestSampleResult::NoSample;
}
}
virtual void FlushSamples() override
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Flush samples {0}", VideoSample.IsValid());
// override in child classes, if supported
VideoSample.Reset();
FlushCount += 1;
}
virtual uint32 GetFlushCount()
{
return FlushCount;
}
virtual bool PeekVideoSampleTime(FMediaTimeStamp &TimeStamp) override
{
if (VideoSample.IsValid())
{
TimeStamp = VideoSample->GetTime();
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Peek timestamp {0}",
TimeStamp.Time.GetTotalSeconds());
return true;
}
else
{
// UE_LOGFMT(LogDirectVideo, VeryVerbose, "Peek timestamp returning zero");
// TimeStamp =
// FMediaTimeStamp(FTimespan(0LL),FMediaTimeStamp::MakeSequenceIndex(0, 0));
// return true;
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Peek timestamp returning false");
return false;
}
}
virtual int32 NumVideoSamples() const override
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Query num samples {0}", VideoSample.IsValid());
if (VideoSample.IsValid())
{
return 1;
}
else
{
return 0;
}
}
bool DiscardVideoSamples(const TRange<FMediaTimeStamp> &TimeRange, bool bReverse) override
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "Discard video samples {0}", VideoSample.IsValid());
return false;
}
virtual uint32 PurgeOutdatedVideoSamples(const FMediaTimeStamp &ReferenceTime, bool bReversed,
FTimespan MaxAge) override
{
UE_LOGFMT(LogDirectVideo, VeryVerbose, "PurgeOutdatedVideoSamples {0} {1}",
VideoSample.IsValid(), ReferenceTime.Time.GetTicks() * 100);
return 0;
};
TSharedPtr<AndroidVulkanTextureSample, ESPMode::ThreadSafe> VideoSample;
uint32 FlushCount = 0;
};

View File

@ -0,0 +1,835 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Autogenerated shader spv header file.
// ------------------------------------------------
#include <stdint.h>
// Shader generated from shader.2.2.frag.spv
const uint8_t shader_shader_2_2_frag[] = {
3, 2, 35, 7, 0, 3, 1, 0, 10, 0, 13, 0, 41, 0, 0, 0, 0, 0, 0, 0, 17, 0,
2, 0, 1, 0, 0, 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115, 116, 100,
46, 52, 53, 48, 0, 0, 0, 0, 14, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 15, 0,
7, 0, 4, 0, 0, 0, 4, 0, 0, 0, 109, 97, 105, 110, 0, 0, 0, 0, 9, 0, 0, 0,
17, 0, 0, 0, 16, 0, 3, 0, 4, 0, 0, 0, 7, 0, 0, 0, 71, 0, 4, 0, 9, 0,
0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 13, 0, 0, 0, 34, 0, 0, 0,
0, 0, 0, 0, 71, 0, 4, 0, 13, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 71, 0,
4, 0, 17, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 19, 0, 2, 0, 2, 0, 0, 0,
33, 0, 3, 0, 3, 0, 0, 0, 2, 0, 0, 0, 22, 0, 3, 0, 6, 0, 0, 0, 32, 0,
0, 0, 23, 0, 4, 0, 7, 0, 0, 0, 6, 0, 0, 0, 4, 0, 0, 0, 32, 0, 4, 0,
8, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0, 59, 0, 4, 0, 8, 0, 0, 0, 9, 0,
0, 0, 3, 0, 0, 0, 25, 0, 9, 0, 10, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 27, 0,
3, 0, 11, 0, 0, 0, 10, 0, 0, 0, 32, 0, 4, 0, 12, 0, 0, 0, 0, 0, 0, 0,
11, 0, 0, 0, 59, 0, 4, 0, 12, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 23, 0,
4, 0, 15, 0, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0, 32, 0, 4, 0, 16, 0, 0, 0,
1, 0, 0, 0, 15, 0, 0, 0, 59, 0, 4, 0, 16, 0, 0, 0, 17, 0, 0, 0, 1, 0,
0, 0, 43, 0, 4, 0, 6, 0, 0, 0, 22, 0, 0, 0, 205, 204, 12, 64, 23, 0, 4, 0,
23, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 21, 0, 4, 0, 29, 0, 0, 0, 32, 0,
0, 0, 0, 0, 0, 0, 43, 0, 4, 0, 29, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0,
32, 0, 4, 0, 31, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 43, 0, 4, 0, 29, 0,
0, 0, 34, 0, 0, 0, 1, 0, 0, 0, 43, 0, 4, 0, 29, 0, 0, 0, 37, 0, 0, 0,
2, 0, 0, 0, 44, 0, 6, 0, 23, 0, 0, 0, 40, 0, 0, 0, 22, 0, 0, 0, 22, 0,
0, 0, 22, 0, 0, 0, 54, 0, 5, 0, 2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0,
3, 0, 0, 0, 248, 0, 2, 0, 5, 0, 0, 0, 61, 0, 4, 0, 11, 0, 0, 0, 14, 0,
0, 0, 13, 0, 0, 0, 61, 0, 4, 0, 15, 0, 0, 0, 18, 0, 0, 0, 17, 0, 0, 0,
87, 0, 5, 0, 7, 0, 0, 0, 19, 0, 0, 0, 14, 0, 0, 0, 18, 0, 0, 0, 62, 0,
3, 0, 9, 0, 0, 0, 19, 0, 0, 0, 61, 0, 4, 0, 7, 0, 0, 0, 24, 0, 0, 0,
9, 0, 0, 0, 79, 0, 8, 0, 23, 0, 0, 0, 25, 0, 0, 0, 24, 0, 0, 0, 24, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 12, 0, 7, 0, 23, 0, 0, 0,
28, 0, 0, 0, 1, 0, 0, 0, 26, 0, 0, 0, 25, 0, 0, 0, 40, 0, 0, 0, 65, 0,
5, 0, 31, 0, 0, 0, 32, 0, 0, 0, 9, 0, 0, 0, 30, 0, 0, 0, 81, 0, 5, 0,
6, 0, 0, 0, 33, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 62, 0, 3, 0, 32, 0,
0, 0, 33, 0, 0, 0, 65, 0, 5, 0, 31, 0, 0, 0, 35, 0, 0, 0, 9, 0, 0, 0,
34, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 36, 0, 0, 0, 28, 0, 0, 0, 1, 0,
0, 0, 62, 0, 3, 0, 35, 0, 0, 0, 36, 0, 0, 0, 65, 0, 5, 0, 31, 0, 0, 0,
38, 0, 0, 0, 9, 0, 0, 0, 37, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 39, 0,
0, 0, 28, 0, 0, 0, 2, 0, 0, 0, 62, 0, 3, 0, 38, 0, 0, 0, 39, 0, 0, 0,
253, 0, 1, 0, 56, 0, 1, 0};
// Shader generated from shader.2.4.frag.spv
const uint8_t shader_shader_2_4_frag[] = {
3, 2, 35, 7, 0, 3, 1, 0, 10, 0, 13, 0, 55, 0, 0, 0, 0, 0, 0, 0, 17, 0,
2, 0, 1, 0, 0, 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115, 116, 100,
46, 52, 53, 48, 0, 0, 0, 0, 14, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 15, 0,
7, 0, 4, 0, 0, 0, 4, 0, 0, 0, 109, 97, 105, 110, 0, 0, 0, 0, 9, 0, 0, 0,
17, 0, 0, 0, 16, 0, 3, 0, 4, 0, 0, 0, 7, 0, 0, 0, 71, 0, 4, 0, 9, 0,
0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 13, 0, 0, 0, 34, 0, 0, 0,
0, 0, 0, 0, 71, 0, 4, 0, 13, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 71, 0,
4, 0, 17, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 19, 0, 2, 0, 2, 0, 0, 0,
33, 0, 3, 0, 3, 0, 0, 0, 2, 0, 0, 0, 22, 0, 3, 0, 6, 0, 0, 0, 32, 0,
0, 0, 23, 0, 4, 0, 7, 0, 0, 0, 6, 0, 0, 0, 4, 0, 0, 0, 32, 0, 4, 0,
8, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0, 59, 0, 4, 0, 8, 0, 0, 0, 9, 0,
0, 0, 3, 0, 0, 0, 25, 0, 9, 0, 10, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 27, 0,
3, 0, 11, 0, 0, 0, 10, 0, 0, 0, 32, 0, 4, 0, 12, 0, 0, 0, 0, 0, 0, 0,
11, 0, 0, 0, 59, 0, 4, 0, 12, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 23, 0,
4, 0, 15, 0, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0, 32, 0, 4, 0, 16, 0, 0, 0,
1, 0, 0, 0, 15, 0, 0, 0, 59, 0, 4, 0, 16, 0, 0, 0, 17, 0, 0, 0, 1, 0,
0, 0, 43, 0, 4, 0, 6, 0, 0, 0, 22, 0, 0, 0, 154, 153, 25, 64, 23, 0, 4, 0,
23, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 21, 0, 4, 0, 29, 0, 0, 0, 32, 0,
0, 0, 0, 0, 0, 0, 43, 0, 4, 0, 29, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0,
32, 0, 4, 0, 31, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 43, 0, 4, 0, 29, 0,
0, 0, 34, 0, 0, 0, 1, 0, 0, 0, 43, 0, 4, 0, 29, 0, 0, 0, 37, 0, 0, 0,
2, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0, 42, 0, 0, 0, 129, 128, 128, 61, 43, 0,
4, 0, 6, 0, 0, 0, 45, 0, 0, 0, 133, 10, 149, 63, 44, 0, 6, 0, 23, 0, 0, 0,
53, 0, 0, 0, 22, 0, 0, 0, 22, 0, 0, 0, 22, 0, 0, 0, 44, 0, 6, 0, 23, 0,
0, 0, 54, 0, 0, 0, 42, 0, 0, 0, 42, 0, 0, 0, 42, 0, 0, 0, 54, 0, 5, 0,
2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 248, 0, 2, 0, 5, 0,
0, 0, 61, 0, 4, 0, 11, 0, 0, 0, 14, 0, 0, 0, 13, 0, 0, 0, 61, 0, 4, 0,
15, 0, 0, 0, 18, 0, 0, 0, 17, 0, 0, 0, 87, 0, 5, 0, 7, 0, 0, 0, 19, 0,
0, 0, 14, 0, 0, 0, 18, 0, 0, 0, 62, 0, 3, 0, 9, 0, 0, 0, 19, 0, 0, 0,
61, 0, 4, 0, 7, 0, 0, 0, 24, 0, 0, 0, 9, 0, 0, 0, 79, 0, 8, 0, 23, 0,
0, 0, 25, 0, 0, 0, 24, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
2, 0, 0, 0, 12, 0, 7, 0, 23, 0, 0, 0, 28, 0, 0, 0, 1, 0, 0, 0, 26, 0,
0, 0, 25, 0, 0, 0, 53, 0, 0, 0, 65, 0, 5, 0, 31, 0, 0, 0, 32, 0, 0, 0,
9, 0, 0, 0, 30, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 33, 0, 0, 0, 28, 0,
0, 0, 0, 0, 0, 0, 62, 0, 3, 0, 32, 0, 0, 0, 33, 0, 0, 0, 65, 0, 5, 0,
31, 0, 0, 0, 35, 0, 0, 0, 9, 0, 0, 0, 34, 0, 0, 0, 81, 0, 5, 0, 6, 0,
0, 0, 36, 0, 0, 0, 28, 0, 0, 0, 1, 0, 0, 0, 62, 0, 3, 0, 35, 0, 0, 0,
36, 0, 0, 0, 65, 0, 5, 0, 31, 0, 0, 0, 38, 0, 0, 0, 9, 0, 0, 0, 37, 0,
0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 39, 0, 0, 0, 28, 0, 0, 0, 2, 0, 0, 0,
62, 0, 3, 0, 38, 0, 0, 0, 39, 0, 0, 0, 61, 0, 4, 0, 7, 0, 0, 0, 40, 0,
0, 0, 9, 0, 0, 0, 79, 0, 8, 0, 23, 0, 0, 0, 41, 0, 0, 0, 40, 0, 0, 0,
40, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 131, 0, 5, 0, 23, 0,
0, 0, 44, 0, 0, 0, 41, 0, 0, 0, 54, 0, 0, 0, 142, 0, 5, 0, 23, 0, 0, 0,
46, 0, 0, 0, 44, 0, 0, 0, 45, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 48, 0,
0, 0, 46, 0, 0, 0, 0, 0, 0, 0, 62, 0, 3, 0, 32, 0, 0, 0, 48, 0, 0, 0,
81, 0, 5, 0, 6, 0, 0, 0, 50, 0, 0, 0, 46, 0, 0, 0, 1, 0, 0, 0, 62, 0,
3, 0, 35, 0, 0, 0, 50, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 52, 0, 0, 0,
46, 0, 0, 0, 2, 0, 0, 0, 62, 0, 3, 0, 38, 0, 0, 0, 52, 0, 0, 0, 253, 0,
1, 0, 56, 0, 1, 0};
// Shader generated from shader.2.8.frag.spv
const uint8_t shader_shader_2_8_frag[] = {
3, 2, 35, 7, 0, 3, 1, 0, 10, 0, 13, 0, 41, 0, 0, 0, 0, 0, 0, 0, 17, 0,
2, 0, 1, 0, 0, 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115, 116, 100,
46, 52, 53, 48, 0, 0, 0, 0, 14, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 15, 0,
7, 0, 4, 0, 0, 0, 4, 0, 0, 0, 109, 97, 105, 110, 0, 0, 0, 0, 9, 0, 0, 0,
17, 0, 0, 0, 16, 0, 3, 0, 4, 0, 0, 0, 7, 0, 0, 0, 71, 0, 4, 0, 9, 0,
0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 13, 0, 0, 0, 34, 0, 0, 0,
0, 0, 0, 0, 71, 0, 4, 0, 13, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 71, 0,
4, 0, 17, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 19, 0, 2, 0, 2, 0, 0, 0,
33, 0, 3, 0, 3, 0, 0, 0, 2, 0, 0, 0, 22, 0, 3, 0, 6, 0, 0, 0, 32, 0,
0, 0, 23, 0, 4, 0, 7, 0, 0, 0, 6, 0, 0, 0, 4, 0, 0, 0, 32, 0, 4, 0,
8, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0, 59, 0, 4, 0, 8, 0, 0, 0, 9, 0,
0, 0, 3, 0, 0, 0, 25, 0, 9, 0, 10, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 27, 0,
3, 0, 11, 0, 0, 0, 10, 0, 0, 0, 32, 0, 4, 0, 12, 0, 0, 0, 0, 0, 0, 0,
11, 0, 0, 0, 59, 0, 4, 0, 12, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 23, 0,
4, 0, 15, 0, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0, 32, 0, 4, 0, 16, 0, 0, 0,
1, 0, 0, 0, 15, 0, 0, 0, 59, 0, 4, 0, 16, 0, 0, 0, 17, 0, 0, 0, 1, 0,
0, 0, 43, 0, 4, 0, 6, 0, 0, 0, 22, 0, 0, 0, 51, 51, 51, 64, 23, 0, 4, 0,
23, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 21, 0, 4, 0, 29, 0, 0, 0, 32, 0,
0, 0, 0, 0, 0, 0, 43, 0, 4, 0, 29, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0,
32, 0, 4, 0, 31, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 43, 0, 4, 0, 29, 0,
0, 0, 34, 0, 0, 0, 1, 0, 0, 0, 43, 0, 4, 0, 29, 0, 0, 0, 37, 0, 0, 0,
2, 0, 0, 0, 44, 0, 6, 0, 23, 0, 0, 0, 40, 0, 0, 0, 22, 0, 0, 0, 22, 0,
0, 0, 22, 0, 0, 0, 54, 0, 5, 0, 2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0,
3, 0, 0, 0, 248, 0, 2, 0, 5, 0, 0, 0, 61, 0, 4, 0, 11, 0, 0, 0, 14, 0,
0, 0, 13, 0, 0, 0, 61, 0, 4, 0, 15, 0, 0, 0, 18, 0, 0, 0, 17, 0, 0, 0,
87, 0, 5, 0, 7, 0, 0, 0, 19, 0, 0, 0, 14, 0, 0, 0, 18, 0, 0, 0, 62, 0,
3, 0, 9, 0, 0, 0, 19, 0, 0, 0, 61, 0, 4, 0, 7, 0, 0, 0, 24, 0, 0, 0,
9, 0, 0, 0, 79, 0, 8, 0, 23, 0, 0, 0, 25, 0, 0, 0, 24, 0, 0, 0, 24, 0,
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 12, 0, 7, 0, 23, 0, 0, 0,
28, 0, 0, 0, 1, 0, 0, 0, 26, 0, 0, 0, 25, 0, 0, 0, 40, 0, 0, 0, 65, 0,
5, 0, 31, 0, 0, 0, 32, 0, 0, 0, 9, 0, 0, 0, 30, 0, 0, 0, 81, 0, 5, 0,
6, 0, 0, 0, 33, 0, 0, 0, 28, 0, 0, 0, 0, 0, 0, 0, 62, 0, 3, 0, 32, 0,
0, 0, 33, 0, 0, 0, 65, 0, 5, 0, 31, 0, 0, 0, 35, 0, 0, 0, 9, 0, 0, 0,
34, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 36, 0, 0, 0, 28, 0, 0, 0, 1, 0,
0, 0, 62, 0, 3, 0, 35, 0, 0, 0, 36, 0, 0, 0, 65, 0, 5, 0, 31, 0, 0, 0,
38, 0, 0, 0, 9, 0, 0, 0, 37, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 39, 0,
0, 0, 28, 0, 0, 0, 2, 0, 0, 0, 62, 0, 3, 0, 38, 0, 0, 0, 39, 0, 0, 0,
253, 0, 1, 0, 56, 0, 1, 0};
// Shader generated from shader.frag.spv
const uint8_t shader_shader_frag[] = {
3, 2, 35, 7, 0, 0, 1, 0, 11, 0, 8, 0, 44, 0, 0, 0, 0, 0, 0,
0, 17, 0, 2, 0, 1, 0, 0, 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76,
83, 76, 46, 115, 116, 100, 46, 52, 53, 48, 0, 0, 0, 0, 14, 0, 3, 0, 0,
0, 0, 0, 1, 0, 0, 0, 15, 0, 7, 0, 4, 0, 0, 0, 4, 0, 0, 0,
109, 97, 105, 110, 0, 0, 0, 0, 9, 0, 0, 0, 17, 0, 0, 0, 16, 0, 3,
0, 4, 0, 0, 0, 7, 0, 0, 0, 3, 0, 3, 0, 2, 0, 0, 0, 194, 1,
0, 0, 5, 0, 4, 0, 4, 0, 0, 0, 109, 97, 105, 110, 0, 0, 0, 0, 5,
0, 4, 0, 9, 0, 0, 0, 99, 111, 108, 111, 114, 0, 0, 0, 5, 0, 3, 0,
13, 0, 0, 0, 116, 101, 120, 0, 5, 0, 5, 0, 17, 0, 0, 0, 116, 101, 120,
95, 99, 111, 111, 114, 100, 0, 0, 0, 5, 0, 5, 0, 23, 0, 0, 0, 83, 104,
97, 100, 101, 114, 73, 110, 102, 111, 0, 0, 6, 0, 5, 0, 23, 0, 0, 0, 0,
0, 0, 0, 103, 97, 109, 109, 97, 0, 0, 0, 5, 0, 6, 0, 25, 0, 0, 0,
116, 101, 120, 116, 117, 114, 101, 95, 105, 110, 102, 111, 0, 0, 0, 0, 71, 0, 4,
0, 9, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 13, 0,
0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 13, 0, 0, 0, 33,
0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 17, 0, 0, 0, 30, 0, 0, 0,
0, 0, 0, 0, 72, 0, 5, 0, 23, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0,
0, 0, 0, 0, 0, 71, 0, 3, 0, 23, 0, 0, 0, 2, 0, 0, 0, 71, 0,
4, 0, 25, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 25,
0, 0, 0, 33, 0, 0, 0, 1, 0, 0, 0, 19, 0, 2, 0, 2, 0, 0, 0,
33, 0, 3, 0, 3, 0, 0, 0, 2, 0, 0, 0, 22, 0, 3, 0, 6, 0, 0,
0, 32, 0, 0, 0, 23, 0, 4, 0, 7, 0, 0, 0, 6, 0, 0, 0, 4, 0,
0, 0, 32, 0, 4, 0, 8, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0, 59,
0, 4, 0, 8, 0, 0, 0, 9, 0, 0, 0, 3, 0, 0, 0, 25, 0, 9, 0,
10, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 27, 0, 3, 0, 11, 0,
0, 0, 10, 0, 0, 0, 32, 0, 4, 0, 12, 0, 0, 0, 0, 0, 0, 0, 11,
0, 0, 0, 59, 0, 4, 0, 12, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0,
23, 0, 4, 0, 15, 0, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0, 32, 0, 4,
0, 16, 0, 0, 0, 1, 0, 0, 0, 15, 0, 0, 0, 59, 0, 4, 0, 16, 0,
0, 0, 17, 0, 0, 0, 1, 0, 0, 0, 23, 0, 4, 0, 20, 0, 0, 0, 6,
0, 0, 0, 3, 0, 0, 0, 30, 0, 3, 0, 23, 0, 0, 0, 6, 0, 0, 0,
32, 0, 4, 0, 24, 0, 0, 0, 2, 0, 0, 0, 23, 0, 0, 0, 59, 0, 4,
0, 24, 0, 0, 0, 25, 0, 0, 0, 2, 0, 0, 0, 21, 0, 4, 0, 26, 0,
0, 0, 32, 0, 0, 0, 1, 0, 0, 0, 43, 0, 4, 0, 26, 0, 0, 0, 27,
0, 0, 0, 0, 0, 0, 0, 32, 0, 4, 0, 28, 0, 0, 0, 2, 0, 0, 0,
6, 0, 0, 0, 21, 0, 4, 0, 33, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0,
0, 43, 0, 4, 0, 33, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 32, 0,
4, 0, 35, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 43, 0, 4, 0, 33,
0, 0, 0, 38, 0, 0, 0, 1, 0, 0, 0, 43, 0, 4, 0, 33, 0, 0, 0,
41, 0, 0, 0, 2, 0, 0, 0, 54, 0, 5, 0, 2, 0, 0, 0, 4, 0, 0,
0, 0, 0, 0, 0, 3, 0, 0, 0, 248, 0, 2, 0, 5, 0, 0, 0, 61, 0,
4, 0, 11, 0, 0, 0, 14, 0, 0, 0, 13, 0, 0, 0, 61, 0, 4, 0, 15,
0, 0, 0, 18, 0, 0, 0, 17, 0, 0, 0, 87, 0, 5, 0, 7, 0, 0, 0,
19, 0, 0, 0, 14, 0, 0, 0, 18, 0, 0, 0, 62, 0, 3, 0, 9, 0, 0,
0, 19, 0, 0, 0, 61, 0, 4, 0, 7, 0, 0, 0, 21, 0, 0, 0, 9, 0,
0, 0, 79, 0, 8, 0, 20, 0, 0, 0, 22, 0, 0, 0, 21, 0, 0, 0, 21,
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 65, 0, 5, 0,
28, 0, 0, 0, 29, 0, 0, 0, 25, 0, 0, 0, 27, 0, 0, 0, 61, 0, 4,
0, 6, 0, 0, 0, 30, 0, 0, 0, 29, 0, 0, 0, 80, 0, 6, 0, 20, 0,
0, 0, 31, 0, 0, 0, 30, 0, 0, 0, 30, 0, 0, 0, 30, 0, 0, 0, 12,
0, 7, 0, 20, 0, 0, 0, 32, 0, 0, 0, 1, 0, 0, 0, 26, 0, 0, 0,
22, 0, 0, 0, 31, 0, 0, 0, 65, 0, 5, 0, 35, 0, 0, 0, 36, 0, 0,
0, 9, 0, 0, 0, 34, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 37, 0,
0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 62, 0, 3, 0, 36, 0, 0, 0, 37,
0, 0, 0, 65, 0, 5, 0, 35, 0, 0, 0, 39, 0, 0, 0, 9, 0, 0, 0,
38, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 40, 0, 0, 0, 32, 0, 0,
0, 1, 0, 0, 0, 62, 0, 3, 0, 39, 0, 0, 0, 40, 0, 0, 0, 65, 0,
5, 0, 35, 0, 0, 0, 42, 0, 0, 0, 9, 0, 0, 0, 41, 0, 0, 0, 81,
0, 5, 0, 6, 0, 0, 0, 43, 0, 0, 0, 32, 0, 0, 0, 2, 0, 0, 0,
62, 0, 3, 0, 42, 0, 0, 0, 43, 0, 0, 0, 253, 0, 1, 0, 56, 0, 1,
0};
// Shader generated from shader.hlg.frag.spv
const uint8_t shader_shader_hlg_frag[] = {
3, 2, 35, 7, 0, 3, 1, 0, 10, 0, 13, 0, 164, 0, 0, 0, 0, 0, 0, 0,
17, 0, 2, 0, 1, 0, 0, 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76,
46, 115, 116, 100, 46, 52, 53, 48, 0, 0, 0, 0, 14, 0, 3, 0, 0, 0, 0, 0,
1, 0, 0, 0, 15, 0, 7, 0, 4, 0, 0, 0, 4, 0, 0, 0, 109, 97, 105, 110,
0, 0, 0, 0, 40, 0, 0, 0, 48, 0, 0, 0, 16, 0, 3, 0, 4, 0, 0, 0,
7, 0, 0, 0, 71, 0, 4, 0, 40, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0,
71, 0, 4, 0, 44, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0,
44, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 48, 0, 0, 0,
30, 0, 0, 0, 0, 0, 0, 0, 19, 0, 2, 0, 2, 0, 0, 0, 33, 0, 3, 0,
3, 0, 0, 0, 2, 0, 0, 0, 22, 0, 3, 0, 6, 0, 0, 0, 32, 0, 0, 0,
43, 0, 4, 0, 6, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 63, 20, 0, 2, 0,
14, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0, 25, 0, 0, 0, 32, 192, 145, 62,
43, 0, 4, 0, 6, 0, 0, 0, 27, 0, 0, 0, 79, 86, 15, 63, 23, 0, 4, 0,
38, 0, 0, 0, 6, 0, 0, 0, 4, 0, 0, 0, 32, 0, 4, 0, 39, 0, 0, 0,
3, 0, 0, 0, 38, 0, 0, 0, 59, 0, 4, 0, 39, 0, 0, 0, 40, 0, 0, 0,
3, 0, 0, 0, 25, 0, 9, 0, 41, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
27, 0, 3, 0, 42, 0, 0, 0, 41, 0, 0, 0, 32, 0, 4, 0, 43, 0, 0, 0,
0, 0, 0, 0, 42, 0, 0, 0, 59, 0, 4, 0, 43, 0, 0, 0, 44, 0, 0, 0,
0, 0, 0, 0, 23, 0, 4, 0, 46, 0, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0,
32, 0, 4, 0, 47, 0, 0, 0, 1, 0, 0, 0, 46, 0, 0, 0, 59, 0, 4, 0,
47, 0, 0, 0, 48, 0, 0, 0, 1, 0, 0, 0, 21, 0, 4, 0, 52, 0, 0, 0,
32, 0, 0, 0, 0, 0, 0, 0, 43, 0, 4, 0, 52, 0, 0, 0, 53, 0, 0, 0,
0, 0, 0, 0, 32, 0, 4, 0, 54, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0,
43, 0, 4, 0, 52, 0, 0, 0, 59, 0, 0, 0, 1, 0, 0, 0, 43, 0, 4, 0,
52, 0, 0, 0, 64, 0, 0, 0, 2, 0, 0, 0, 23, 0, 4, 0, 68, 0, 0, 0,
6, 0, 0, 0, 3, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0, 78, 0, 0, 0,
205, 204, 76, 63, 44, 0, 6, 0, 68, 0, 0, 0, 79, 0, 0, 0, 78, 0, 0, 0,
78, 0, 0, 0, 78, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0, 89, 0, 0, 0,
129, 128, 128, 61, 43, 0, 4, 0, 6, 0, 0, 0, 92, 0, 0, 0, 133, 10, 149, 63,
44, 0, 6, 0, 68, 0, 0, 0, 160, 0, 0, 0, 89, 0, 0, 0, 89, 0, 0, 0,
89, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0, 161, 0, 0, 0, 41, 240, 178, 64,
43, 0, 4, 0, 6, 0, 0, 0, 162, 0, 0, 0, 171, 170, 170, 61, 43, 0, 4, 0,
6, 0, 0, 0, 163, 0, 0, 0, 171, 170, 170, 62, 54, 0, 5, 0, 2, 0, 0, 0,
4, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 248, 0, 2, 0, 5, 0, 0, 0,
61, 0, 4, 0, 42, 0, 0, 0, 45, 0, 0, 0, 44, 0, 0, 0, 61, 0, 4, 0,
46, 0, 0, 0, 49, 0, 0, 0, 48, 0, 0, 0, 87, 0, 5, 0, 38, 0, 0, 0,
50, 0, 0, 0, 45, 0, 0, 0, 49, 0, 0, 0, 62, 0, 3, 0, 40, 0, 0, 0,
50, 0, 0, 0, 65, 0, 5, 0, 54, 0, 0, 0, 55, 0, 0, 0, 40, 0, 0, 0,
53, 0, 0, 0, 61, 0, 4, 0, 6, 0, 0, 0, 56, 0, 0, 0, 55, 0, 0, 0,
188, 0, 5, 0, 14, 0, 0, 0, 104, 0, 0, 0, 56, 0, 0, 0, 13, 0, 0, 0,
247, 0, 3, 0, 117, 0, 0, 0, 0, 0, 0, 0, 250, 0, 4, 0, 104, 0, 0, 0,
105, 0, 0, 0, 110, 0, 0, 0, 248, 0, 2, 0, 105, 0, 0, 0, 133, 0, 5, 0,
6, 0, 0, 0, 108, 0, 0, 0, 56, 0, 0, 0, 56, 0, 0, 0, 133, 0, 5, 0,
6, 0, 0, 0, 109, 0, 0, 0, 108, 0, 0, 0, 163, 0, 0, 0, 249, 0, 2, 0,
117, 0, 0, 0, 248, 0, 2, 0, 110, 0, 0, 0, 131, 0, 5, 0, 6, 0, 0, 0,
112, 0, 0, 0, 56, 0, 0, 0, 27, 0, 0, 0, 133, 0, 5, 0, 6, 0, 0, 0,
113, 0, 0, 0, 112, 0, 0, 0, 161, 0, 0, 0, 12, 0, 6, 0, 6, 0, 0, 0,
114, 0, 0, 0, 1, 0, 0, 0, 27, 0, 0, 0, 113, 0, 0, 0, 129, 0, 5, 0,
6, 0, 0, 0, 115, 0, 0, 0, 25, 0, 0, 0, 114, 0, 0, 0, 133, 0, 5, 0,
6, 0, 0, 0, 116, 0, 0, 0, 115, 0, 0, 0, 162, 0, 0, 0, 249, 0, 2, 0,
117, 0, 0, 0, 248, 0, 2, 0, 117, 0, 0, 0, 245, 0, 7, 0, 6, 0, 0, 0,
157, 0, 0, 0, 109, 0, 0, 0, 105, 0, 0, 0, 116, 0, 0, 0, 110, 0, 0, 0,
65, 0, 5, 0, 54, 0, 0, 0, 60, 0, 0, 0, 40, 0, 0, 0, 59, 0, 0, 0,
61, 0, 4, 0, 6, 0, 0, 0, 61, 0, 0, 0, 60, 0, 0, 0, 188, 0, 5, 0,
14, 0, 0, 0, 123, 0, 0, 0, 61, 0, 0, 0, 13, 0, 0, 0, 247, 0, 3, 0,
136, 0, 0, 0, 0, 0, 0, 0, 250, 0, 4, 0, 123, 0, 0, 0, 124, 0, 0, 0,
129, 0, 0, 0, 248, 0, 2, 0, 124, 0, 0, 0, 133, 0, 5, 0, 6, 0, 0, 0,
127, 0, 0, 0, 61, 0, 0, 0, 61, 0, 0, 0, 133, 0, 5, 0, 6, 0, 0, 0,
128, 0, 0, 0, 127, 0, 0, 0, 163, 0, 0, 0, 249, 0, 2, 0, 136, 0, 0, 0,
248, 0, 2, 0, 129, 0, 0, 0, 131, 0, 5, 0, 6, 0, 0, 0, 131, 0, 0, 0,
61, 0, 0, 0, 27, 0, 0, 0, 133, 0, 5, 0, 6, 0, 0, 0, 132, 0, 0, 0,
131, 0, 0, 0, 161, 0, 0, 0, 12, 0, 6, 0, 6, 0, 0, 0, 133, 0, 0, 0,
1, 0, 0, 0, 27, 0, 0, 0, 132, 0, 0, 0, 129, 0, 5, 0, 6, 0, 0, 0,
134, 0, 0, 0, 25, 0, 0, 0, 133, 0, 0, 0, 133, 0, 5, 0, 6, 0, 0, 0,
135, 0, 0, 0, 134, 0, 0, 0, 162, 0, 0, 0, 249, 0, 2, 0, 136, 0, 0, 0,
248, 0, 2, 0, 136, 0, 0, 0, 245, 0, 7, 0, 6, 0, 0, 0, 158, 0, 0, 0,
128, 0, 0, 0, 124, 0, 0, 0, 135, 0, 0, 0, 129, 0, 0, 0, 65, 0, 5, 0,
54, 0, 0, 0, 65, 0, 0, 0, 40, 0, 0, 0, 64, 0, 0, 0, 61, 0, 4, 0,
6, 0, 0, 0, 66, 0, 0, 0, 65, 0, 0, 0, 188, 0, 5, 0, 14, 0, 0, 0,
142, 0, 0, 0, 66, 0, 0, 0, 13, 0, 0, 0, 247, 0, 3, 0, 155, 0, 0, 0,
0, 0, 0, 0, 250, 0, 4, 0, 142, 0, 0, 0, 143, 0, 0, 0, 148, 0, 0, 0,
248, 0, 2, 0, 143, 0, 0, 0, 133, 0, 5, 0, 6, 0, 0, 0, 146, 0, 0, 0,
66, 0, 0, 0, 66, 0, 0, 0, 133, 0, 5, 0, 6, 0, 0, 0, 147, 0, 0, 0,
146, 0, 0, 0, 163, 0, 0, 0, 249, 0, 2, 0, 155, 0, 0, 0, 248, 0, 2, 0,
148, 0, 0, 0, 131, 0, 5, 0, 6, 0, 0, 0, 150, 0, 0, 0, 66, 0, 0, 0,
27, 0, 0, 0, 133, 0, 5, 0, 6, 0, 0, 0, 151, 0, 0, 0, 150, 0, 0, 0,
161, 0, 0, 0, 12, 0, 6, 0, 6, 0, 0, 0, 152, 0, 0, 0, 1, 0, 0, 0,
27, 0, 0, 0, 151, 0, 0, 0, 129, 0, 5, 0, 6, 0, 0, 0, 153, 0, 0, 0,
25, 0, 0, 0, 152, 0, 0, 0, 133, 0, 5, 0, 6, 0, 0, 0, 154, 0, 0, 0,
153, 0, 0, 0, 162, 0, 0, 0, 249, 0, 2, 0, 155, 0, 0, 0, 248, 0, 2, 0,
155, 0, 0, 0, 245, 0, 7, 0, 6, 0, 0, 0, 159, 0, 0, 0, 147, 0, 0, 0,
143, 0, 0, 0, 154, 0, 0, 0, 148, 0, 0, 0, 62, 0, 3, 0, 55, 0, 0, 0,
157, 0, 0, 0, 62, 0, 3, 0, 60, 0, 0, 0, 158, 0, 0, 0, 62, 0, 3, 0,
65, 0, 0, 0, 159, 0, 0, 0, 61, 0, 4, 0, 38, 0, 0, 0, 76, 0, 0, 0,
40, 0, 0, 0, 79, 0, 8, 0, 68, 0, 0, 0, 77, 0, 0, 0, 76, 0, 0, 0,
76, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 12, 0, 7, 0,
68, 0, 0, 0, 80, 0, 0, 0, 1, 0, 0, 0, 26, 0, 0, 0, 77, 0, 0, 0,
79, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 82, 0, 0, 0, 80, 0, 0, 0,
0, 0, 0, 0, 62, 0, 3, 0, 55, 0, 0, 0, 82, 0, 0, 0, 81, 0, 5, 0,
6, 0, 0, 0, 84, 0, 0, 0, 80, 0, 0, 0, 1, 0, 0, 0, 62, 0, 3, 0,
60, 0, 0, 0, 84, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 86, 0, 0, 0,
80, 0, 0, 0, 2, 0, 0, 0, 62, 0, 3, 0, 65, 0, 0, 0, 86, 0, 0, 0,
61, 0, 4, 0, 38, 0, 0, 0, 87, 0, 0, 0, 40, 0, 0, 0, 79, 0, 8, 0,
68, 0, 0, 0, 88, 0, 0, 0, 87, 0, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 2, 0, 0, 0, 131, 0, 5, 0, 68, 0, 0, 0, 91, 0, 0, 0,
88, 0, 0, 0, 160, 0, 0, 0, 142, 0, 5, 0, 68, 0, 0, 0, 93, 0, 0, 0,
91, 0, 0, 0, 92, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 95, 0, 0, 0,
93, 0, 0, 0, 0, 0, 0, 0, 62, 0, 3, 0, 55, 0, 0, 0, 95, 0, 0, 0,
81, 0, 5, 0, 6, 0, 0, 0, 97, 0, 0, 0, 93, 0, 0, 0, 1, 0, 0, 0,
62, 0, 3, 0, 60, 0, 0, 0, 97, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0,
99, 0, 0, 0, 93, 0, 0, 0, 2, 0, 0, 0, 62, 0, 3, 0, 65, 0, 0, 0,
99, 0, 0, 0, 253, 0, 1, 0, 56, 0, 1, 0};
// Shader generated from shader.linear.frag.spv
const uint8_t shader_shader_linear_frag[] = {
3, 2, 35, 7, 0, 3, 1, 0, 10, 0, 13, 0, 40, 0, 0, 0, 0, 0, 0, 0, 17,
0, 2, 0, 1, 0, 0, 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115,
116, 100, 46, 52, 53, 48, 0, 0, 0, 0, 14, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0,
0, 15, 0, 7, 0, 4, 0, 0, 0, 4, 0, 0, 0, 109, 97, 105, 110, 0, 0, 0, 0,
9, 0, 0, 0, 17, 0, 0, 0, 16, 0, 3, 0, 4, 0, 0, 0, 7, 0, 0, 0, 71,
0, 4, 0, 9, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 13, 0,
0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 13, 0, 0, 0, 33, 0, 0,
0, 0, 0, 0, 0, 71, 0, 4, 0, 17, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0,
19, 0, 2, 0, 2, 0, 0, 0, 33, 0, 3, 0, 3, 0, 0, 0, 2, 0, 0, 0, 22,
0, 3, 0, 6, 0, 0, 0, 32, 0, 0, 0, 23, 0, 4, 0, 7, 0, 0, 0, 6, 0,
0, 0, 4, 0, 0, 0, 32, 0, 4, 0, 8, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0,
0, 59, 0, 4, 0, 8, 0, 0, 0, 9, 0, 0, 0, 3, 0, 0, 0, 25, 0, 9, 0,
10, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 27, 0, 3, 0, 11, 0, 0, 0, 10, 0,
0, 0, 32, 0, 4, 0, 12, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 59, 0, 4,
0, 12, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 23, 0, 4, 0, 15, 0, 0, 0,
6, 0, 0, 0, 2, 0, 0, 0, 32, 0, 4, 0, 16, 0, 0, 0, 1, 0, 0, 0, 15,
0, 0, 0, 59, 0, 4, 0, 16, 0, 0, 0, 17, 0, 0, 0, 1, 0, 0, 0, 23, 0,
4, 0, 20, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0,
0, 23, 0, 0, 0, 129, 128, 128, 61, 43, 0, 4, 0, 6, 0, 0, 0, 26, 0, 0, 0,
133, 10, 149, 63, 21, 0, 4, 0, 28, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 43,
0, 4, 0, 28, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 32, 0, 4, 0, 30, 0,
0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 43, 0, 4, 0, 28, 0, 0, 0, 33, 0, 0,
0, 1, 0, 0, 0, 43, 0, 4, 0, 28, 0, 0, 0, 36, 0, 0, 0, 2, 0, 0, 0,
44, 0, 6, 0, 20, 0, 0, 0, 39, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23,
0, 0, 0, 54, 0, 5, 0, 2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 3, 0,
0, 0, 248, 0, 2, 0, 5, 0, 0, 0, 61, 0, 4, 0, 11, 0, 0, 0, 14, 0, 0,
0, 13, 0, 0, 0, 61, 0, 4, 0, 15, 0, 0, 0, 18, 0, 0, 0, 17, 0, 0, 0,
87, 0, 5, 0, 7, 0, 0, 0, 19, 0, 0, 0, 14, 0, 0, 0, 18, 0, 0, 0, 62,
0, 3, 0, 9, 0, 0, 0, 19, 0, 0, 0, 61, 0, 4, 0, 7, 0, 0, 0, 21, 0,
0, 0, 9, 0, 0, 0, 79, 0, 8, 0, 20, 0, 0, 0, 22, 0, 0, 0, 21, 0, 0,
0, 21, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 131, 0, 5, 0,
20, 0, 0, 0, 25, 0, 0, 0, 22, 0, 0, 0, 39, 0, 0, 0, 142, 0, 5, 0, 20,
0, 0, 0, 27, 0, 0, 0, 25, 0, 0, 0, 26, 0, 0, 0, 65, 0, 5, 0, 30, 0,
0, 0, 31, 0, 0, 0, 9, 0, 0, 0, 29, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0,
0, 32, 0, 0, 0, 27, 0, 0, 0, 0, 0, 0, 0, 62, 0, 3, 0, 31, 0, 0, 0,
32, 0, 0, 0, 65, 0, 5, 0, 30, 0, 0, 0, 34, 0, 0, 0, 9, 0, 0, 0, 33,
0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 35, 0, 0, 0, 27, 0, 0, 0, 1, 0,
0, 0, 62, 0, 3, 0, 34, 0, 0, 0, 35, 0, 0, 0, 65, 0, 5, 0, 30, 0, 0,
0, 37, 0, 0, 0, 9, 0, 0, 0, 36, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0,
38, 0, 0, 0, 27, 0, 0, 0, 2, 0, 0, 0, 62, 0, 3, 0, 37, 0, 0, 0, 38,
0, 0, 0, 253, 0, 1, 0, 56, 0, 1, 0};
// Shader generated from shader.rec.709.frag.spv
const uint8_t shader_shader_rec_709_frag[] = {
3, 2, 35, 7, 0, 3, 1, 0, 10, 0, 13, 0, 37, 0, 0, 0, 0, 0, 0, 0, 17, 0,
2, 0, 1, 0, 0, 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115, 116, 100,
46, 52, 53, 48, 0, 0, 0, 0, 14, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 15, 0,
7, 0, 4, 0, 0, 0, 4, 0, 0, 0, 109, 97, 105, 110, 0, 0, 0, 0, 9, 0, 0, 0,
17, 0, 0, 0, 16, 0, 3, 0, 4, 0, 0, 0, 7, 0, 0, 0, 71, 0, 3, 0, 9, 0,
0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 9, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0,
71, 0, 4, 0, 13, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 13, 0,
0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 71, 0, 3, 0, 17, 0, 0, 0, 0, 0, 0, 0,
71, 0, 4, 0, 17, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 71, 0, 3, 0, 18, 0,
0, 0, 0, 0, 0, 0, 71, 0, 3, 0, 21, 0, 0, 0, 0, 0, 0, 0, 71, 0, 3, 0,
22, 0, 0, 0, 0, 0, 0, 0, 71, 0, 3, 0, 25, 0, 0, 0, 0, 0, 0, 0, 19, 0,
2, 0, 2, 0, 0, 0, 33, 0, 3, 0, 3, 0, 0, 0, 2, 0, 0, 0, 22, 0, 3, 0,
6, 0, 0, 0, 32, 0, 0, 0, 23, 0, 4, 0, 7, 0, 0, 0, 6, 0, 0, 0, 4, 0,
0, 0, 32, 0, 4, 0, 8, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0, 59, 0, 4, 0,
8, 0, 0, 0, 9, 0, 0, 0, 3, 0, 0, 0, 25, 0, 9, 0, 10, 0, 0, 0, 6, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 27, 0, 3, 0, 11, 0, 0, 0, 10, 0, 0, 0, 32, 0, 4, 0, 12, 0,
0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 59, 0, 4, 0, 12, 0, 0, 0, 13, 0, 0, 0,
0, 0, 0, 0, 23, 0, 4, 0, 15, 0, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0, 32, 0,
4, 0, 16, 0, 0, 0, 1, 0, 0, 0, 15, 0, 0, 0, 59, 0, 4, 0, 16, 0, 0, 0,
17, 0, 0, 0, 1, 0, 0, 0, 23, 0, 4, 0, 20, 0, 0, 0, 6, 0, 0, 0, 3, 0,
0, 0, 43, 0, 4, 0, 6, 0, 0, 0, 23, 0, 0, 0, 145, 237, 4, 64, 44, 0, 6, 0,
20, 0, 0, 0, 24, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 21, 0,
4, 0, 26, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 43, 0, 4, 0, 26, 0, 0, 0,
27, 0, 0, 0, 0, 0, 0, 0, 32, 0, 4, 0, 28, 0, 0, 0, 3, 0, 0, 0, 6, 0,
0, 0, 43, 0, 4, 0, 26, 0, 0, 0, 31, 0, 0, 0, 1, 0, 0, 0, 43, 0, 4, 0,
26, 0, 0, 0, 34, 0, 0, 0, 2, 0, 0, 0, 54, 0, 5, 0, 2, 0, 0, 0, 4, 0,
0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 248, 0, 2, 0, 5, 0, 0, 0, 61, 0, 4, 0,
11, 0, 0, 0, 14, 0, 0, 0, 13, 0, 0, 0, 61, 0, 4, 0, 15, 0, 0, 0, 18, 0,
0, 0, 17, 0, 0, 0, 87, 0, 5, 0, 7, 0, 0, 0, 19, 0, 0, 0, 14, 0, 0, 0,
18, 0, 0, 0, 62, 0, 3, 0, 9, 0, 0, 0, 19, 0, 0, 0, 61, 0, 4, 0, 7, 0,
0, 0, 21, 0, 0, 0, 9, 0, 0, 0, 79, 0, 8, 0, 20, 0, 0, 0, 22, 0, 0, 0,
21, 0, 0, 0, 21, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 12, 0,
7, 0, 20, 0, 0, 0, 25, 0, 0, 0, 1, 0, 0, 0, 26, 0, 0, 0, 22, 0, 0, 0,
24, 0, 0, 0, 65, 0, 5, 0, 28, 0, 0, 0, 29, 0, 0, 0, 9, 0, 0, 0, 27, 0,
0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 30, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0,
62, 0, 3, 0, 29, 0, 0, 0, 30, 0, 0, 0, 65, 0, 5, 0, 28, 0, 0, 0, 32, 0,
0, 0, 9, 0, 0, 0, 31, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 33, 0, 0, 0,
25, 0, 0, 0, 1, 0, 0, 0, 62, 0, 3, 0, 32, 0, 0, 0, 33, 0, 0, 0, 65, 0,
5, 0, 28, 0, 0, 0, 35, 0, 0, 0, 9, 0, 0, 0, 34, 0, 0, 0, 81, 0, 5, 0,
6, 0, 0, 0, 36, 0, 0, 0, 25, 0, 0, 0, 2, 0, 0, 0, 62, 0, 3, 0, 35, 0,
0, 0, 36, 0, 0, 0, 253, 0, 1, 0, 56, 0, 1, 0};
// Shader generated from shader.srgb.frag.spv
const uint8_t shader_shader_srgb_frag[] = {
3, 2, 35, 7, 0, 3, 1, 0, 10, 0, 13, 0, 150, 0, 0, 0, 0, 0, 0, 0,
17, 0, 2, 0, 1, 0, 0, 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76,
46, 115, 116, 100, 46, 52, 53, 48, 0, 0, 0, 0, 14, 0, 3, 0, 0, 0, 0, 0,
1, 0, 0, 0, 15, 0, 7, 0, 4, 0, 0, 0, 4, 0, 0, 0, 109, 97, 105, 110,
0, 0, 0, 0, 35, 0, 0, 0, 43, 0, 0, 0, 16, 0, 3, 0, 4, 0, 0, 0,
7, 0, 0, 0, 71, 0, 4, 0, 35, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0,
71, 0, 4, 0, 39, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0,
39, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 43, 0, 0, 0,
30, 0, 0, 0, 0, 0, 0, 0, 19, 0, 2, 0, 2, 0, 0, 0, 33, 0, 3, 0,
3, 0, 0, 0, 2, 0, 0, 0, 22, 0, 3, 0, 6, 0, 0, 0, 32, 0, 0, 0,
43, 0, 4, 0, 6, 0, 0, 0, 13, 0, 0, 0, 230, 174, 37, 61, 20, 0, 2, 0,
14, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0, 24, 0, 0, 0, 174, 71, 97, 61,
43, 0, 4, 0, 6, 0, 0, 0, 28, 0, 0, 0, 154, 153, 25, 64, 23, 0, 4, 0,
33, 0, 0, 0, 6, 0, 0, 0, 4, 0, 0, 0, 32, 0, 4, 0, 34, 0, 0, 0,
3, 0, 0, 0, 33, 0, 0, 0, 59, 0, 4, 0, 34, 0, 0, 0, 35, 0, 0, 0,
3, 0, 0, 0, 25, 0, 9, 0, 36, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
27, 0, 3, 0, 37, 0, 0, 0, 36, 0, 0, 0, 32, 0, 4, 0, 38, 0, 0, 0,
0, 0, 0, 0, 37, 0, 0, 0, 59, 0, 4, 0, 38, 0, 0, 0, 39, 0, 0, 0,
0, 0, 0, 0, 23, 0, 4, 0, 41, 0, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0,
32, 0, 4, 0, 42, 0, 0, 0, 1, 0, 0, 0, 41, 0, 0, 0, 59, 0, 4, 0,
42, 0, 0, 0, 43, 0, 0, 0, 1, 0, 0, 0, 23, 0, 4, 0, 46, 0, 0, 0,
6, 0, 0, 0, 3, 0, 0, 0, 21, 0, 4, 0, 53, 0, 0, 0, 32, 0, 0, 0,
0, 0, 0, 0, 43, 0, 4, 0, 53, 0, 0, 0, 54, 0, 0, 0, 0, 0, 0, 0,
43, 0, 4, 0, 53, 0, 0, 0, 59, 0, 0, 0, 1, 0, 0, 0, 43, 0, 4, 0,
53, 0, 0, 0, 64, 0, 0, 0, 2, 0, 0, 0, 32, 0, 4, 0, 74, 0, 0, 0,
3, 0, 0, 0, 6, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0, 83, 0, 0, 0,
129, 128, 128, 61, 43, 0, 4, 0, 6, 0, 0, 0, 86, 0, 0, 0, 133, 10, 149, 63,
44, 0, 6, 0, 46, 0, 0, 0, 147, 0, 0, 0, 83, 0, 0, 0, 83, 0, 0, 0,
83, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0, 148, 0, 0, 0, 111, 167, 114, 63,
43, 0, 4, 0, 6, 0, 0, 0, 149, 0, 0, 0, 145, 131, 158, 61, 54, 0, 5, 0,
2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 248, 0, 2, 0,
5, 0, 0, 0, 61, 0, 4, 0, 37, 0, 0, 0, 40, 0, 0, 0, 39, 0, 0, 0,
61, 0, 4, 0, 41, 0, 0, 0, 44, 0, 0, 0, 43, 0, 0, 0, 87, 0, 5, 0,
33, 0, 0, 0, 45, 0, 0, 0, 40, 0, 0, 0, 44, 0, 0, 0, 62, 0, 3, 0,
35, 0, 0, 0, 45, 0, 0, 0, 61, 0, 4, 0, 33, 0, 0, 0, 49, 0, 0, 0,
35, 0, 0, 0, 79, 0, 8, 0, 46, 0, 0, 0, 50, 0, 0, 0, 49, 0, 0, 0,
49, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 12, 0, 6, 0,
46, 0, 0, 0, 51, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 50, 0, 0, 0,
81, 0, 5, 0, 6, 0, 0, 0, 56, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0,
188, 0, 5, 0, 14, 0, 0, 0, 98, 0, 0, 0, 56, 0, 0, 0, 13, 0, 0, 0,
247, 0, 3, 0, 107, 0, 0, 0, 0, 0, 0, 0, 250, 0, 4, 0, 98, 0, 0, 0,
99, 0, 0, 0, 102, 0, 0, 0, 248, 0, 2, 0, 99, 0, 0, 0, 133, 0, 5, 0,
6, 0, 0, 0, 101, 0, 0, 0, 56, 0, 0, 0, 149, 0, 0, 0, 249, 0, 2, 0,
107, 0, 0, 0, 248, 0, 2, 0, 102, 0, 0, 0, 129, 0, 5, 0, 6, 0, 0, 0,
104, 0, 0, 0, 56, 0, 0, 0, 24, 0, 0, 0, 133, 0, 5, 0, 6, 0, 0, 0,
105, 0, 0, 0, 104, 0, 0, 0, 148, 0, 0, 0, 12, 0, 7, 0, 6, 0, 0, 0,
106, 0, 0, 0, 1, 0, 0, 0, 26, 0, 0, 0, 105, 0, 0, 0, 28, 0, 0, 0,
249, 0, 2, 0, 107, 0, 0, 0, 248, 0, 2, 0, 107, 0, 0, 0, 245, 0, 7, 0,
6, 0, 0, 0, 142, 0, 0, 0, 101, 0, 0, 0, 99, 0, 0, 0, 106, 0, 0, 0,
102, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 61, 0, 0, 0, 51, 0, 0, 0,
1, 0, 0, 0, 188, 0, 5, 0, 14, 0, 0, 0, 113, 0, 0, 0, 61, 0, 0, 0,
13, 0, 0, 0, 247, 0, 3, 0, 122, 0, 0, 0, 0, 0, 0, 0, 250, 0, 4, 0,
113, 0, 0, 0, 114, 0, 0, 0, 117, 0, 0, 0, 248, 0, 2, 0, 114, 0, 0, 0,
133, 0, 5, 0, 6, 0, 0, 0, 116, 0, 0, 0, 61, 0, 0, 0, 149, 0, 0, 0,
249, 0, 2, 0, 122, 0, 0, 0, 248, 0, 2, 0, 117, 0, 0, 0, 129, 0, 5, 0,
6, 0, 0, 0, 119, 0, 0, 0, 61, 0, 0, 0, 24, 0, 0, 0, 133, 0, 5, 0,
6, 0, 0, 0, 120, 0, 0, 0, 119, 0, 0, 0, 148, 0, 0, 0, 12, 0, 7, 0,
6, 0, 0, 0, 121, 0, 0, 0, 1, 0, 0, 0, 26, 0, 0, 0, 120, 0, 0, 0,
28, 0, 0, 0, 249, 0, 2, 0, 122, 0, 0, 0, 248, 0, 2, 0, 122, 0, 0, 0,
245, 0, 7, 0, 6, 0, 0, 0, 144, 0, 0, 0, 116, 0, 0, 0, 114, 0, 0, 0,
121, 0, 0, 0, 117, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 66, 0, 0, 0,
51, 0, 0, 0, 2, 0, 0, 0, 188, 0, 5, 0, 14, 0, 0, 0, 128, 0, 0, 0,
66, 0, 0, 0, 13, 0, 0, 0, 247, 0, 3, 0, 137, 0, 0, 0, 0, 0, 0, 0,
250, 0, 4, 0, 128, 0, 0, 0, 129, 0, 0, 0, 132, 0, 0, 0, 248, 0, 2, 0,
129, 0, 0, 0, 133, 0, 5, 0, 6, 0, 0, 0, 131, 0, 0, 0, 66, 0, 0, 0,
149, 0, 0, 0, 249, 0, 2, 0, 137, 0, 0, 0, 248, 0, 2, 0, 132, 0, 0, 0,
129, 0, 5, 0, 6, 0, 0, 0, 134, 0, 0, 0, 66, 0, 0, 0, 24, 0, 0, 0,
133, 0, 5, 0, 6, 0, 0, 0, 135, 0, 0, 0, 134, 0, 0, 0, 148, 0, 0, 0,
12, 0, 7, 0, 6, 0, 0, 0, 136, 0, 0, 0, 1, 0, 0, 0, 26, 0, 0, 0,
135, 0, 0, 0, 28, 0, 0, 0, 249, 0, 2, 0, 137, 0, 0, 0, 248, 0, 2, 0,
137, 0, 0, 0, 245, 0, 7, 0, 6, 0, 0, 0, 146, 0, 0, 0, 131, 0, 0, 0,
129, 0, 0, 0, 136, 0, 0, 0, 132, 0, 0, 0, 80, 0, 6, 0, 46, 0, 0, 0,
68, 0, 0, 0, 142, 0, 0, 0, 144, 0, 0, 0, 146, 0, 0, 0, 61, 0, 4, 0,
33, 0, 0, 0, 69, 0, 0, 0, 35, 0, 0, 0, 79, 0, 8, 0, 46, 0, 0, 0,
70, 0, 0, 0, 69, 0, 0, 0, 69, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
2, 0, 0, 0, 12, 0, 6, 0, 46, 0, 0, 0, 71, 0, 0, 0, 1, 0, 0, 0,
6, 0, 0, 0, 70, 0, 0, 0, 133, 0, 5, 0, 46, 0, 0, 0, 73, 0, 0, 0,
71, 0, 0, 0, 68, 0, 0, 0, 65, 0, 5, 0, 74, 0, 0, 0, 75, 0, 0, 0,
35, 0, 0, 0, 54, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 76, 0, 0, 0,
73, 0, 0, 0, 0, 0, 0, 0, 62, 0, 3, 0, 75, 0, 0, 0, 76, 0, 0, 0,
65, 0, 5, 0, 74, 0, 0, 0, 77, 0, 0, 0, 35, 0, 0, 0, 59, 0, 0, 0,
81, 0, 5, 0, 6, 0, 0, 0, 78, 0, 0, 0, 73, 0, 0, 0, 1, 0, 0, 0,
62, 0, 3, 0, 77, 0, 0, 0, 78, 0, 0, 0, 65, 0, 5, 0, 74, 0, 0, 0,
79, 0, 0, 0, 35, 0, 0, 0, 64, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0,
80, 0, 0, 0, 73, 0, 0, 0, 2, 0, 0, 0, 62, 0, 3, 0, 79, 0, 0, 0,
80, 0, 0, 0, 61, 0, 4, 0, 33, 0, 0, 0, 81, 0, 0, 0, 35, 0, 0, 0,
79, 0, 8, 0, 46, 0, 0, 0, 82, 0, 0, 0, 81, 0, 0, 0, 81, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 131, 0, 5, 0, 46, 0, 0, 0,
85, 0, 0, 0, 82, 0, 0, 0, 147, 0, 0, 0, 142, 0, 5, 0, 46, 0, 0, 0,
87, 0, 0, 0, 85, 0, 0, 0, 86, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0,
89, 0, 0, 0, 87, 0, 0, 0, 0, 0, 0, 0, 62, 0, 3, 0, 75, 0, 0, 0,
89, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 91, 0, 0, 0, 87, 0, 0, 0,
1, 0, 0, 0, 62, 0, 3, 0, 77, 0, 0, 0, 91, 0, 0, 0, 81, 0, 5, 0,
6, 0, 0, 0, 93, 0, 0, 0, 87, 0, 0, 0, 2, 0, 0, 0, 62, 0, 3, 0,
79, 0, 0, 0, 93, 0, 0, 0, 253, 0, 1, 0, 56, 0, 1, 0};
// Shader generated from shader.st2084.frag.spv
const uint8_t shader_shader_st2084_frag[] = {
3, 2, 35, 7, 0, 3, 1, 0, 10, 0, 13, 0, 118, 0, 0, 0, 0, 0, 0, 0,
17, 0, 2, 0, 1, 0, 0, 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76,
46, 115, 116, 100, 46, 52, 53, 48, 0, 0, 0, 0, 14, 0, 3, 0, 0, 0, 0, 0,
1, 0, 0, 0, 15, 0, 7, 0, 4, 0, 0, 0, 4, 0, 0, 0, 109, 97, 105, 110,
0, 0, 0, 0, 9, 0, 0, 0, 17, 0, 0, 0, 16, 0, 3, 0, 4, 0, 0, 0,
7, 0, 0, 0, 71, 0, 4, 0, 9, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0,
71, 0, 4, 0, 13, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0,
13, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 17, 0, 0, 0,
30, 0, 0, 0, 0, 0, 0, 0, 19, 0, 2, 0, 2, 0, 0, 0, 33, 0, 3, 0,
3, 0, 0, 0, 2, 0, 0, 0, 22, 0, 3, 0, 6, 0, 0, 0, 32, 0, 0, 0,
23, 0, 4, 0, 7, 0, 0, 0, 6, 0, 0, 0, 4, 0, 0, 0, 32, 0, 4, 0,
8, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0, 59, 0, 4, 0, 8, 0, 0, 0,
9, 0, 0, 0, 3, 0, 0, 0, 25, 0, 9, 0, 10, 0, 0, 0, 6, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 27, 0, 3, 0, 11, 0, 0, 0, 10, 0, 0, 0, 32, 0, 4, 0,
12, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 59, 0, 4, 0, 12, 0, 0, 0,
13, 0, 0, 0, 0, 0, 0, 0, 23, 0, 4, 0, 15, 0, 0, 0, 6, 0, 0, 0,
2, 0, 0, 0, 32, 0, 4, 0, 16, 0, 0, 0, 1, 0, 0, 0, 15, 0, 0, 0,
59, 0, 4, 0, 16, 0, 0, 0, 17, 0, 0, 0, 1, 0, 0, 0, 23, 0, 4, 0,
20, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0,
23, 0, 0, 0, 172, 205, 79, 60, 44, 0, 6, 0, 20, 0, 0, 0, 24, 0, 0, 0,
23, 0, 0, 0, 23, 0, 0, 0, 23, 0, 0, 0, 21, 0, 4, 0, 26, 0, 0, 0,
32, 0, 0, 0, 0, 0, 0, 0, 43, 0, 4, 0, 26, 0, 0, 0, 27, 0, 0, 0,
0, 0, 0, 0, 32, 0, 4, 0, 28, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0,
43, 0, 4, 0, 26, 0, 0, 0, 31, 0, 0, 0, 1, 0, 0, 0, 43, 0, 4, 0,
26, 0, 0, 0, 34, 0, 0, 0, 2, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0,
39, 0, 0, 0, 0, 0, 86, 63, 44, 0, 6, 0, 20, 0, 0, 0, 40, 0, 0, 0,
39, 0, 0, 0, 39, 0, 0, 0, 39, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0,
42, 0, 0, 0, 0, 0, 0, 0, 44, 0, 6, 0, 20, 0, 0, 0, 43, 0, 0, 0,
42, 0, 0, 0, 42, 0, 0, 0, 42, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0,
45, 0, 0, 0, 0, 208, 150, 65, 44, 0, 6, 0, 20, 0, 0, 0, 46, 0, 0, 0,
45, 0, 0, 0, 45, 0, 0, 0, 45, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0,
47, 0, 0, 0, 0, 128, 149, 65, 44, 0, 6, 0, 20, 0, 0, 0, 48, 0, 0, 0,
47, 0, 0, 0, 47, 0, 0, 0, 47, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0,
62, 0, 0, 0, 107, 224, 200, 64, 44, 0, 6, 0, 20, 0, 0, 0, 63, 0, 0, 0,
62, 0, 0, 0, 62, 0, 0, 0, 62, 0, 0, 0, 20, 0, 2, 0, 82, 0, 0, 0,
43, 0, 4, 0, 6, 0, 0, 0, 89, 0, 0, 0, 166, 155, 68, 59, 43, 0, 4, 0,
6, 0, 0, 0, 106, 0, 0, 0, 129, 128, 128, 61, 43, 0, 4, 0, 6, 0, 0, 0,
109, 0, 0, 0, 133, 10, 149, 63, 44, 0, 6, 0, 20, 0, 0, 0, 117, 0, 0, 0,
106, 0, 0, 0, 106, 0, 0, 0, 106, 0, 0, 0, 54, 0, 5, 0, 2, 0, 0, 0,
4, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 248, 0, 2, 0, 5, 0, 0, 0,
61, 0, 4, 0, 11, 0, 0, 0, 14, 0, 0, 0, 13, 0, 0, 0, 61, 0, 4, 0,
15, 0, 0, 0, 18, 0, 0, 0, 17, 0, 0, 0, 87, 0, 5, 0, 7, 0, 0, 0,
19, 0, 0, 0, 14, 0, 0, 0, 18, 0, 0, 0, 62, 0, 3, 0, 9, 0, 0, 0,
19, 0, 0, 0, 61, 0, 4, 0, 7, 0, 0, 0, 21, 0, 0, 0, 9, 0, 0, 0,
79, 0, 8, 0, 20, 0, 0, 0, 22, 0, 0, 0, 21, 0, 0, 0, 21, 0, 0, 0,
0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 12, 0, 7, 0, 20, 0, 0, 0,
25, 0, 0, 0, 1, 0, 0, 0, 26, 0, 0, 0, 22, 0, 0, 0, 24, 0, 0, 0,
65, 0, 5, 0, 28, 0, 0, 0, 29, 0, 0, 0, 9, 0, 0, 0, 27, 0, 0, 0,
81, 0, 5, 0, 6, 0, 0, 0, 30, 0, 0, 0, 25, 0, 0, 0, 0, 0, 0, 0,
62, 0, 3, 0, 29, 0, 0, 0, 30, 0, 0, 0, 65, 0, 5, 0, 28, 0, 0, 0,
32, 0, 0, 0, 9, 0, 0, 0, 31, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0,
33, 0, 0, 0, 25, 0, 0, 0, 1, 0, 0, 0, 62, 0, 3, 0, 32, 0, 0, 0,
33, 0, 0, 0, 65, 0, 5, 0, 28, 0, 0, 0, 35, 0, 0, 0, 9, 0, 0, 0,
34, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 36, 0, 0, 0, 25, 0, 0, 0,
2, 0, 0, 0, 62, 0, 3, 0, 35, 0, 0, 0, 36, 0, 0, 0, 61, 0, 4, 0,
7, 0, 0, 0, 37, 0, 0, 0, 9, 0, 0, 0, 79, 0, 8, 0, 20, 0, 0, 0,
38, 0, 0, 0, 37, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
2, 0, 0, 0, 131, 0, 5, 0, 20, 0, 0, 0, 41, 0, 0, 0, 38, 0, 0, 0,
40, 0, 0, 0, 12, 0, 7, 0, 20, 0, 0, 0, 44, 0, 0, 0, 1, 0, 0, 0,
40, 0, 0, 0, 41, 0, 0, 0, 43, 0, 0, 0, 61, 0, 4, 0, 7, 0, 0, 0,
49, 0, 0, 0, 9, 0, 0, 0, 79, 0, 8, 0, 20, 0, 0, 0, 50, 0, 0, 0,
49, 0, 0, 0, 49, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0,
133, 0, 5, 0, 20, 0, 0, 0, 51, 0, 0, 0, 48, 0, 0, 0, 50, 0, 0, 0,
131, 0, 5, 0, 20, 0, 0, 0, 52, 0, 0, 0, 46, 0, 0, 0, 51, 0, 0, 0,
136, 0, 5, 0, 20, 0, 0, 0, 53, 0, 0, 0, 44, 0, 0, 0, 52, 0, 0, 0,
81, 0, 5, 0, 6, 0, 0, 0, 55, 0, 0, 0, 53, 0, 0, 0, 0, 0, 0, 0,
62, 0, 3, 0, 29, 0, 0, 0, 55, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0,
57, 0, 0, 0, 53, 0, 0, 0, 1, 0, 0, 0, 62, 0, 3, 0, 32, 0, 0, 0,
57, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 59, 0, 0, 0, 53, 0, 0, 0,
2, 0, 0, 0, 62, 0, 3, 0, 35, 0, 0, 0, 59, 0, 0, 0, 61, 0, 4, 0,
7, 0, 0, 0, 60, 0, 0, 0, 9, 0, 0, 0, 79, 0, 8, 0, 20, 0, 0, 0,
61, 0, 0, 0, 60, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
2, 0, 0, 0, 12, 0, 7, 0, 20, 0, 0, 0, 64, 0, 0, 0, 1, 0, 0, 0,
26, 0, 0, 0, 61, 0, 0, 0, 63, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0,
66, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 62, 0, 3, 0, 29, 0, 0, 0,
66, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 68, 0, 0, 0, 64, 0, 0, 0,
1, 0, 0, 0, 62, 0, 3, 0, 32, 0, 0, 0, 68, 0, 0, 0, 81, 0, 5, 0,
6, 0, 0, 0, 70, 0, 0, 0, 64, 0, 0, 0, 2, 0, 0, 0, 62, 0, 3, 0,
35, 0, 0, 0, 70, 0, 0, 0, 61, 0, 4, 0, 6, 0, 0, 0, 74, 0, 0, 0,
29, 0, 0, 0, 61, 0, 4, 0, 6, 0, 0, 0, 76, 0, 0, 0, 32, 0, 0, 0,
61, 0, 4, 0, 6, 0, 0, 0, 78, 0, 0, 0, 35, 0, 0, 0, 12, 0, 7, 0,
6, 0, 0, 0, 79, 0, 0, 0, 1, 0, 0, 0, 40, 0, 0, 0, 76, 0, 0, 0,
78, 0, 0, 0, 12, 0, 7, 0, 6, 0, 0, 0, 80, 0, 0, 0, 1, 0, 0, 0,
40, 0, 0, 0, 74, 0, 0, 0, 79, 0, 0, 0, 186, 0, 5, 0, 82, 0, 0, 0,
83, 0, 0, 0, 80, 0, 0, 0, 42, 0, 0, 0, 247, 0, 3, 0, 85, 0, 0, 0,
0, 0, 0, 0, 250, 0, 4, 0, 83, 0, 0, 0, 84, 0, 0, 0, 85, 0, 0, 0,
248, 0, 2, 0, 84, 0, 0, 0, 129, 0, 5, 0, 6, 0, 0, 0, 90, 0, 0, 0,
80, 0, 0, 0, 89, 0, 0, 0, 136, 0, 5, 0, 6, 0, 0, 0, 91, 0, 0, 0,
80, 0, 0, 0, 90, 0, 0, 0, 136, 0, 5, 0, 6, 0, 0, 0, 94, 0, 0, 0,
91, 0, 0, 0, 80, 0, 0, 0, 61, 0, 4, 0, 7, 0, 0, 0, 95, 0, 0, 0,
9, 0, 0, 0, 79, 0, 8, 0, 20, 0, 0, 0, 96, 0, 0, 0, 95, 0, 0, 0,
95, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 142, 0, 5, 0,
20, 0, 0, 0, 97, 0, 0, 0, 96, 0, 0, 0, 94, 0, 0, 0, 81, 0, 5, 0,
6, 0, 0, 0, 99, 0, 0, 0, 97, 0, 0, 0, 0, 0, 0, 0, 62, 0, 3, 0,
29, 0, 0, 0, 99, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 101, 0, 0, 0,
97, 0, 0, 0, 1, 0, 0, 0, 62, 0, 3, 0, 32, 0, 0, 0, 101, 0, 0, 0,
81, 0, 5, 0, 6, 0, 0, 0, 103, 0, 0, 0, 97, 0, 0, 0, 2, 0, 0, 0,
62, 0, 3, 0, 35, 0, 0, 0, 103, 0, 0, 0, 249, 0, 2, 0, 85, 0, 0, 0,
248, 0, 2, 0, 85, 0, 0, 0, 61, 0, 4, 0, 7, 0, 0, 0, 104, 0, 0, 0,
9, 0, 0, 0, 79, 0, 8, 0, 20, 0, 0, 0, 105, 0, 0, 0, 104, 0, 0, 0,
104, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 131, 0, 5, 0,
20, 0, 0, 0, 108, 0, 0, 0, 105, 0, 0, 0, 117, 0, 0, 0, 142, 0, 5, 0,
20, 0, 0, 0, 110, 0, 0, 0, 108, 0, 0, 0, 109, 0, 0, 0, 81, 0, 5, 0,
6, 0, 0, 0, 112, 0, 0, 0, 110, 0, 0, 0, 0, 0, 0, 0, 62, 0, 3, 0,
29, 0, 0, 0, 112, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 114, 0, 0, 0,
110, 0, 0, 0, 1, 0, 0, 0, 62, 0, 3, 0, 32, 0, 0, 0, 114, 0, 0, 0,
81, 0, 5, 0, 6, 0, 0, 0, 116, 0, 0, 0, 110, 0, 0, 0, 2, 0, 0, 0,
62, 0, 3, 0, 35, 0, 0, 0, 116, 0, 0, 0, 253, 0, 1, 0, 56, 0, 1, 0};
// Shader generated from shader.test.frag.spv
const uint8_t shader_shader_test_frag[] = {
3, 2, 35, 7, 0, 3, 1, 0, 10, 0, 13, 0, 31, 0, 0, 0, 0, 0, 0, 0, 17, 0,
2, 0, 1, 0, 0, 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115, 116, 100,
46, 52, 53, 48, 0, 0, 0, 0, 14, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 15, 0,
7, 0, 4, 0, 0, 0, 4, 0, 0, 0, 109, 97, 105, 110, 0, 0, 0, 0, 9, 0, 0, 0,
12, 0, 0, 0, 16, 0, 3, 0, 4, 0, 0, 0, 7, 0, 0, 0, 71, 0, 4, 0, 9, 0,
0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 12, 0, 0, 0, 30, 0, 0, 0,
0, 0, 0, 0, 19, 0, 2, 0, 2, 0, 0, 0, 33, 0, 3, 0, 3, 0, 0, 0, 2, 0,
0, 0, 22, 0, 3, 0, 6, 0, 0, 0, 32, 0, 0, 0, 23, 0, 4, 0, 7, 0, 0, 0,
6, 0, 0, 0, 4, 0, 0, 0, 32, 0, 4, 0, 8, 0, 0, 0, 3, 0, 0, 0, 7, 0,
0, 0, 59, 0, 4, 0, 8, 0, 0, 0, 9, 0, 0, 0, 3, 0, 0, 0, 23, 0, 4, 0,
10, 0, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0, 32, 0, 4, 0, 11, 0, 0, 0, 1, 0,
0, 0, 10, 0, 0, 0, 59, 0, 4, 0, 11, 0, 0, 0, 12, 0, 0, 0, 1, 0, 0, 0,
21, 0, 4, 0, 13, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 43, 0, 4, 0, 13, 0,
0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 32, 0, 4, 0, 15, 0, 0, 0, 1, 0, 0, 0,
6, 0, 0, 0, 43, 0, 4, 0, 13, 0, 0, 0, 18, 0, 0, 0, 1, 0, 0, 0, 43, 0,
4, 0, 6, 0, 0, 0, 21, 0, 0, 0, 0, 0, 128, 63, 32, 0, 4, 0, 24, 0, 0, 0,
3, 0, 0, 0, 6, 0, 0, 0, 43, 0, 4, 0, 13, 0, 0, 0, 29, 0, 0, 0, 2, 0,
0, 0, 54, 0, 5, 0, 2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0,
248, 0, 2, 0, 5, 0, 0, 0, 65, 0, 5, 0, 15, 0, 0, 0, 16, 0, 0, 0, 12, 0,
0, 0, 14, 0, 0, 0, 61, 0, 4, 0, 6, 0, 0, 0, 17, 0, 0, 0, 16, 0, 0, 0,
65, 0, 5, 0, 15, 0, 0, 0, 19, 0, 0, 0, 12, 0, 0, 0, 18, 0, 0, 0, 61, 0,
4, 0, 6, 0, 0, 0, 20, 0, 0, 0, 19, 0, 0, 0, 65, 0, 5, 0, 24, 0, 0, 0,
25, 0, 0, 0, 9, 0, 0, 0, 14, 0, 0, 0, 62, 0, 3, 0, 25, 0, 0, 0, 17, 0,
0, 0, 65, 0, 5, 0, 24, 0, 0, 0, 27, 0, 0, 0, 9, 0, 0, 0, 18, 0, 0, 0,
62, 0, 3, 0, 27, 0, 0, 0, 20, 0, 0, 0, 65, 0, 5, 0, 24, 0, 0, 0, 30, 0,
0, 0, 9, 0, 0, 0, 29, 0, 0, 0, 62, 0, 3, 0, 30, 0, 0, 0, 21, 0, 0, 0,
253, 0, 1, 0, 56, 0, 1, 0};
// Shader generated from shader.vert.spv
const uint8_t shader_shader_vert[] = {
3, 2, 35, 7, 0, 3, 1, 0, 10, 0, 13, 0, 73, 0, 0, 0, 0, 0, 0, 0,
17, 0, 2, 0, 1, 0, 0, 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76,
46, 115, 116, 100, 46, 52, 53, 48, 0, 0, 0, 0, 14, 0, 3, 0, 0, 0, 0, 0,
1, 0, 0, 0, 15, 0, 8, 0, 0, 0, 0, 0, 4, 0, 0, 0, 109, 97, 105, 110,
0, 0, 0, 0, 10, 0, 0, 0, 54, 0, 0, 0, 65, 0, 0, 0, 71, 0, 4, 0,
10, 0, 0, 0, 11, 0, 0, 0, 42, 0, 0, 0, 72, 0, 5, 0, 52, 0, 0, 0,
0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 72, 0, 5, 0, 52, 0, 0, 0,
1, 0, 0, 0, 11, 0, 0, 0, 1, 0, 0, 0, 72, 0, 5, 0, 52, 0, 0, 0,
2, 0, 0, 0, 11, 0, 0, 0, 3, 0, 0, 0, 72, 0, 5, 0, 52, 0, 0, 0,
3, 0, 0, 0, 11, 0, 0, 0, 4, 0, 0, 0, 71, 0, 3, 0, 52, 0, 0, 0,
2, 0, 0, 0, 71, 0, 4, 0, 65, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0,
19, 0, 2, 0, 2, 0, 0, 0, 33, 0, 3, 0, 3, 0, 0, 0, 2, 0, 0, 0,
21, 0, 4, 0, 6, 0, 0, 0, 32, 0, 0, 0, 1, 0, 0, 0, 32, 0, 4, 0,
9, 0, 0, 0, 1, 0, 0, 0, 6, 0, 0, 0, 59, 0, 4, 0, 9, 0, 0, 0,
10, 0, 0, 0, 1, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0, 12, 0, 0, 0,
1, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0, 17, 0, 0, 0, 15, 0, 0, 0,
43, 0, 4, 0, 6, 0, 0, 0, 21, 0, 0, 0, 5, 0, 0, 0, 43, 0, 4, 0,
6, 0, 0, 0, 24, 0, 0, 0, 2, 0, 0, 0, 22, 0, 3, 0, 29, 0, 0, 0,
32, 0, 0, 0, 43, 0, 4, 0, 29, 0, 0, 0, 32, 0, 0, 0, 0, 0, 128, 191,
43, 0, 4, 0, 29, 0, 0, 0, 33, 0, 0, 0, 134, 136, 8, 62, 23, 0, 4, 0,
48, 0, 0, 0, 29, 0, 0, 0, 4, 0, 0, 0, 21, 0, 4, 0, 49, 0, 0, 0,
32, 0, 0, 0, 0, 0, 0, 0, 43, 0, 4, 0, 49, 0, 0, 0, 50, 0, 0, 0,
1, 0, 0, 0, 28, 0, 4, 0, 51, 0, 0, 0, 29, 0, 0, 0, 50, 0, 0, 0,
30, 0, 6, 0, 52, 0, 0, 0, 48, 0, 0, 0, 29, 0, 0, 0, 51, 0, 0, 0,
51, 0, 0, 0, 32, 0, 4, 0, 53, 0, 0, 0, 3, 0, 0, 0, 52, 0, 0, 0,
59, 0, 4, 0, 53, 0, 0, 0, 54, 0, 0, 0, 3, 0, 0, 0, 43, 0, 4, 0,
6, 0, 0, 0, 55, 0, 0, 0, 0, 0, 0, 0, 43, 0, 4, 0, 29, 0, 0, 0,
58, 0, 0, 0, 0, 0, 0, 0, 43, 0, 4, 0, 29, 0, 0, 0, 59, 0, 0, 0,
0, 0, 128, 63, 32, 0, 4, 0, 61, 0, 0, 0, 3, 0, 0, 0, 48, 0, 0, 0,
23, 0, 4, 0, 63, 0, 0, 0, 29, 0, 0, 0, 2, 0, 0, 0, 32, 0, 4, 0,
64, 0, 0, 0, 3, 0, 0, 0, 63, 0, 0, 0, 59, 0, 4, 0, 64, 0, 0, 0,
65, 0, 0, 0, 3, 0, 0, 0, 43, 0, 4, 0, 29, 0, 0, 0, 66, 0, 0, 0,
0, 0, 0, 63, 44, 0, 5, 0, 63, 0, 0, 0, 70, 0, 0, 0, 59, 0, 0, 0,
59, 0, 0, 0, 54, 0, 5, 0, 2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0,
3, 0, 0, 0, 248, 0, 2, 0, 5, 0, 0, 0, 61, 0, 4, 0, 6, 0, 0, 0,
11, 0, 0, 0, 10, 0, 0, 0, 199, 0, 5, 0, 6, 0, 0, 0, 13, 0, 0, 0,
11, 0, 0, 0, 12, 0, 0, 0, 195, 0, 5, 0, 6, 0, 0, 0, 16, 0, 0, 0,
11, 0, 0, 0, 12, 0, 0, 0, 199, 0, 5, 0, 6, 0, 0, 0, 18, 0, 0, 0,
16, 0, 0, 0, 17, 0, 0, 0, 195, 0, 5, 0, 6, 0, 0, 0, 22, 0, 0, 0,
11, 0, 0, 0, 21, 0, 0, 0, 199, 0, 5, 0, 6, 0, 0, 0, 26, 0, 0, 0,
22, 0, 0, 0, 12, 0, 0, 0, 132, 0, 5, 0, 6, 0, 0, 0, 27, 0, 0, 0,
24, 0, 0, 0, 26, 0, 0, 0, 130, 0, 5, 0, 6, 0, 0, 0, 28, 0, 0, 0,
12, 0, 0, 0, 27, 0, 0, 0, 111, 0, 4, 0, 29, 0, 0, 0, 35, 0, 0, 0,
18, 0, 0, 0, 133, 0, 5, 0, 29, 0, 0, 0, 36, 0, 0, 0, 33, 0, 0, 0,
35, 0, 0, 0, 129, 0, 5, 0, 29, 0, 0, 0, 37, 0, 0, 0, 32, 0, 0, 0,
36, 0, 0, 0, 111, 0, 4, 0, 29, 0, 0, 0, 39, 0, 0, 0, 28, 0, 0, 0,
133, 0, 5, 0, 29, 0, 0, 0, 40, 0, 0, 0, 37, 0, 0, 0, 39, 0, 0, 0,
128, 0, 5, 0, 6, 0, 0, 0, 44, 0, 0, 0, 22, 0, 0, 0, 13, 0, 0, 0,
111, 0, 4, 0, 29, 0, 0, 0, 45, 0, 0, 0, 44, 0, 0, 0, 133, 0, 5, 0,
29, 0, 0, 0, 46, 0, 0, 0, 33, 0, 0, 0, 45, 0, 0, 0, 129, 0, 5, 0,
29, 0, 0, 0, 47, 0, 0, 0, 32, 0, 0, 0, 46, 0, 0, 0, 80, 0, 7, 0,
48, 0, 0, 0, 60, 0, 0, 0, 40, 0, 0, 0, 47, 0, 0, 0, 58, 0, 0, 0,
59, 0, 0, 0, 65, 0, 5, 0, 61, 0, 0, 0, 62, 0, 0, 0, 54, 0, 0, 0,
55, 0, 0, 0, 62, 0, 3, 0, 62, 0, 0, 0, 60, 0, 0, 0, 61, 0, 4, 0,
48, 0, 0, 0, 68, 0, 0, 0, 62, 0, 0, 0, 79, 0, 7, 0, 63, 0, 0, 0,
69, 0, 0, 0, 68, 0, 0, 0, 68, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
129, 0, 5, 0, 63, 0, 0, 0, 71, 0, 0, 0, 69, 0, 0, 0, 70, 0, 0, 0,
142, 0, 5, 0, 63, 0, 0, 0, 72, 0, 0, 0, 71, 0, 0, 0, 66, 0, 0, 0,
62, 0, 3, 0, 65, 0, 0, 0, 72, 0, 0, 0, 253, 0, 1, 0, 56, 0, 1, 0};
// Shader generated from shader_mesh.vert.spv
const uint8_t shader_shader_mesh_vert[] = {
3, 2, 35, 7, 0, 3, 1, 0, 10, 0, 13, 0, 69, 0, 0, 0, 0, 0, 0, 0, 17, 0,
2, 0, 1, 0, 0, 0, 11, 0, 6, 0, 1, 0, 0, 0, 71, 76, 83, 76, 46, 115, 116, 100,
46, 52, 53, 48, 0, 0, 0, 0, 14, 0, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 15, 0,
9, 0, 0, 0, 0, 0, 4, 0, 0, 0, 109, 97, 105, 110, 0, 0, 0, 0, 38, 0, 0, 0,
57, 0, 0, 0, 65, 0, 0, 0, 67, 0, 0, 0, 72, 0, 4, 0, 11, 0, 0, 0, 0, 0,
0, 0, 5, 0, 0, 0, 72, 0, 5, 0, 11, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0,
0, 0, 0, 0, 72, 0, 5, 0, 11, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 16, 0,
0, 0, 71, 0, 3, 0, 11, 0, 0, 0, 2, 0, 0, 0, 71, 0, 4, 0, 13, 0, 0, 0,
34, 0, 0, 0, 2, 0, 0, 0, 71, 0, 4, 0, 13, 0, 0, 0, 33, 0, 0, 0, 0, 0,
0, 0, 71, 0, 4, 0, 22, 0, 0, 0, 6, 0, 0, 0, 64, 0, 0, 0, 71, 0, 4, 0,
23, 0, 0, 0, 6, 0, 0, 0, 16, 0, 0, 0, 72, 0, 4, 0, 24, 0, 0, 0, 0, 0,
0, 0, 5, 0, 0, 0, 72, 0, 5, 0, 24, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0,
0, 0, 0, 0, 72, 0, 5, 0, 24, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 16, 0,
0, 0, 72, 0, 5, 0, 24, 0, 0, 0, 1, 0, 0, 0, 35, 0, 0, 0, 128, 0, 0, 0,
71, 0, 3, 0, 24, 0, 0, 0, 2, 0, 0, 0, 71, 0, 4, 0, 26, 0, 0, 0, 34, 0,
0, 0, 1, 0, 0, 0, 71, 0, 4, 0, 26, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0,
71, 0, 4, 0, 38, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 72, 0, 5, 0, 55, 0,
0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 72, 0, 5, 0, 55, 0, 0, 0,
1, 0, 0, 0, 11, 0, 0, 0, 1, 0, 0, 0, 72, 0, 5, 0, 55, 0, 0, 0, 2, 0,
0, 0, 11, 0, 0, 0, 3, 0, 0, 0, 72, 0, 5, 0, 55, 0, 0, 0, 3, 0, 0, 0,
11, 0, 0, 0, 4, 0, 0, 0, 71, 0, 3, 0, 55, 0, 0, 0, 2, 0, 0, 0, 71, 0,
4, 0, 65, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 67, 0, 0, 0,
30, 0, 0, 0, 1, 0, 0, 0, 19, 0, 2, 0, 2, 0, 0, 0, 33, 0, 3, 0, 3, 0,
0, 0, 2, 0, 0, 0, 22, 0, 3, 0, 6, 0, 0, 0, 32, 0, 0, 0, 23, 0, 4, 0,
7, 0, 0, 0, 6, 0, 0, 0, 4, 0, 0, 0, 24, 0, 4, 0, 8, 0, 0, 0, 7, 0,
0, 0, 4, 0, 0, 0, 30, 0, 3, 0, 11, 0, 0, 0, 8, 0, 0, 0, 32, 0, 4, 0,
12, 0, 0, 0, 2, 0, 0, 0, 11, 0, 0, 0, 59, 0, 4, 0, 12, 0, 0, 0, 13, 0,
0, 0, 2, 0, 0, 0, 21, 0, 4, 0, 14, 0, 0, 0, 32, 0, 0, 0, 1, 0, 0, 0,
43, 0, 4, 0, 14, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 32, 0, 4, 0, 16, 0,
0, 0, 2, 0, 0, 0, 8, 0, 0, 0, 21, 0, 4, 0, 20, 0, 0, 0, 32, 0, 0, 0,
0, 0, 0, 0, 43, 0, 4, 0, 20, 0, 0, 0, 21, 0, 0, 0, 2, 0, 0, 0, 28, 0,
4, 0, 22, 0, 0, 0, 8, 0, 0, 0, 21, 0, 0, 0, 28, 0, 4, 0, 23, 0, 0, 0,
7, 0, 0, 0, 21, 0, 0, 0, 30, 0, 4, 0, 24, 0, 0, 0, 22, 0, 0, 0, 23, 0,
0, 0, 32, 0, 4, 0, 25, 0, 0, 0, 2, 0, 0, 0, 24, 0, 0, 0, 59, 0, 4, 0,
25, 0, 0, 0, 26, 0, 0, 0, 2, 0, 0, 0, 43, 0, 4, 0, 14, 0, 0, 0, 31, 0,
0, 0, 1, 0, 0, 0, 32, 0, 4, 0, 32, 0, 0, 0, 2, 0, 0, 0, 7, 0, 0, 0,
23, 0, 4, 0, 36, 0, 0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 32, 0, 4, 0, 37, 0,
0, 0, 1, 0, 0, 0, 36, 0, 0, 0, 59, 0, 4, 0, 37, 0, 0, 0, 38, 0, 0, 0,
1, 0, 0, 0, 43, 0, 4, 0, 6, 0, 0, 0, 40, 0, 0, 0, 0, 0, 128, 63, 43, 0,
4, 0, 20, 0, 0, 0, 53, 0, 0, 0, 1, 0, 0, 0, 28, 0, 4, 0, 54, 0, 0, 0,
6, 0, 0, 0, 53, 0, 0, 0, 30, 0, 6, 0, 55, 0, 0, 0, 7, 0, 0, 0, 6, 0,
0, 0, 54, 0, 0, 0, 54, 0, 0, 0, 32, 0, 4, 0, 56, 0, 0, 0, 3, 0, 0, 0,
55, 0, 0, 0, 59, 0, 4, 0, 56, 0, 0, 0, 57, 0, 0, 0, 3, 0, 0, 0, 32, 0,
4, 0, 61, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0, 23, 0, 4, 0, 63, 0, 0, 0,
6, 0, 0, 0, 2, 0, 0, 0, 32, 0, 4, 0, 64, 0, 0, 0, 3, 0, 0, 0, 63, 0,
0, 0, 59, 0, 4, 0, 64, 0, 0, 0, 65, 0, 0, 0, 3, 0, 0, 0, 32, 0, 4, 0,
66, 0, 0, 0, 1, 0, 0, 0, 63, 0, 0, 0, 59, 0, 4, 0, 66, 0, 0, 0, 67, 0,
0, 0, 1, 0, 0, 0, 54, 0, 5, 0, 2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0,
3, 0, 0, 0, 248, 0, 2, 0, 5, 0, 0, 0, 65, 0, 5, 0, 16, 0, 0, 0, 17, 0,
0, 0, 13, 0, 0, 0, 15, 0, 0, 0, 61, 0, 4, 0, 8, 0, 0, 0, 18, 0, 0, 0,
17, 0, 0, 0, 65, 0, 6, 0, 16, 0, 0, 0, 27, 0, 0, 0, 26, 0, 0, 0, 15, 0,
0, 0, 15, 0, 0, 0, 61, 0, 4, 0, 8, 0, 0, 0, 28, 0, 0, 0, 27, 0, 0, 0,
65, 0, 6, 0, 32, 0, 0, 0, 33, 0, 0, 0, 26, 0, 0, 0, 31, 0, 0, 0, 15, 0,
0, 0, 61, 0, 4, 0, 7, 0, 0, 0, 34, 0, 0, 0, 33, 0, 0, 0, 61, 0, 4, 0,
36, 0, 0, 0, 39, 0, 0, 0, 38, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 41, 0,
0, 0, 39, 0, 0, 0, 0, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 42, 0, 0, 0,
39, 0, 0, 0, 1, 0, 0, 0, 81, 0, 5, 0, 6, 0, 0, 0, 43, 0, 0, 0, 39, 0,
0, 0, 2, 0, 0, 0, 80, 0, 7, 0, 7, 0, 0, 0, 44, 0, 0, 0, 41, 0, 0, 0,
42, 0, 0, 0, 43, 0, 0, 0, 40, 0, 0, 0, 145, 0, 5, 0, 7, 0, 0, 0, 48, 0,
0, 0, 18, 0, 0, 0, 44, 0, 0, 0, 129, 0, 5, 0, 7, 0, 0, 0, 52, 0, 0, 0,
48, 0, 0, 0, 34, 0, 0, 0, 145, 0, 5, 0, 7, 0, 0, 0, 60, 0, 0, 0, 28, 0,
0, 0, 52, 0, 0, 0, 65, 0, 5, 0, 61, 0, 0, 0, 62, 0, 0, 0, 57, 0, 0, 0,
15, 0, 0, 0, 62, 0, 3, 0, 62, 0, 0, 0, 60, 0, 0, 0, 61, 0, 4, 0, 63, 0,
0, 0, 68, 0, 0, 0, 67, 0, 0, 0, 62, 0, 3, 0, 65, 0, 0, 0, 68, 0, 0, 0,
253, 0, 1, 0, 56, 0, 1, 0};
// Shader generated from shader_mesh_multiview.vert.spv
const uint8_t shader_shader_mesh_multiview_vert[] = {
3, 2, 35, 7, 0, 3, 1, 0, 10, 0, 13, 0, 73, 0, 0, 0, 0, 0, 0, 0, 17, 0,
2, 0, 1, 0, 0, 0, 17, 0, 2, 0, 87, 17, 0, 0, 11, 0, 6, 0, 1, 0, 0, 0,
71, 76, 83, 76, 46, 115, 116, 100, 46, 52, 53, 48, 0, 0, 0, 0, 14, 0, 3, 0, 0, 0,
0, 0, 1, 0, 0, 0, 15, 0, 10, 0, 0, 0, 0, 0, 4, 0, 0, 0, 109, 97, 105, 110,
0, 0, 0, 0, 28, 0, 0, 0, 42, 0, 0, 0, 61, 0, 0, 0, 69, 0, 0, 0, 71, 0,
0, 0, 72, 0, 4, 0, 11, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 72, 0, 5, 0,
11, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 72, 0, 5, 0, 11, 0,
0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 16, 0, 0, 0, 71, 0, 3, 0, 11, 0, 0, 0,
2, 0, 0, 0, 71, 0, 4, 0, 13, 0, 0, 0, 34, 0, 0, 0, 2, 0, 0, 0, 71, 0,
4, 0, 13, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 22, 0, 0, 0,
6, 0, 0, 0, 64, 0, 0, 0, 71, 0, 4, 0, 23, 0, 0, 0, 6, 0, 0, 0, 16, 0,
0, 0, 72, 0, 4, 0, 24, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 72, 0, 5, 0,
24, 0, 0, 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 72, 0, 5, 0, 24, 0,
0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 16, 0, 0, 0, 72, 0, 5, 0, 24, 0, 0, 0,
1, 0, 0, 0, 35, 0, 0, 0, 128, 0, 0, 0, 71, 0, 3, 0, 24, 0, 0, 0, 2, 0,
0, 0, 71, 0, 4, 0, 26, 0, 0, 0, 34, 0, 0, 0, 1, 0, 0, 0, 71, 0, 4, 0,
26, 0, 0, 0, 33, 0, 0, 0, 0, 0, 0, 0, 71, 0, 4, 0, 28, 0, 0, 0, 11, 0,
0, 0, 88, 17, 0, 0, 71, 0, 4, 0, 42, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0,
72, 0, 5, 0, 59, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 72, 0,
5, 0, 59, 0, 0, 0, 1, 0, 0, 0, 11, 0, 0, 0, 1, 0, 0, 0, 72, 0, 5, 0,
59, 0, 0, 0, 2, 0, 0, 0, 11, 0, 0, 0, 3, 0, 0, 0, 72, 0, 5, 0, 59, 0,
0, 0, 3, 0, 0, 0, 11, 0, 0, 0, 4, 0, 0, 0, 71, 0, 3, 0, 59, 0, 0, 0,
2, 0, 0, 0, 71, 0, 4, 0, 69, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 71, 0,
4, 0, 71, 0, 0, 0, 30, 0, 0, 0, 1, 0, 0, 0, 19, 0, 2, 0, 2, 0, 0, 0,
33, 0, 3, 0, 3, 0, 0, 0, 2, 0, 0, 0, 22, 0, 3, 0, 6, 0, 0, 0, 32, 0,
0, 0, 23, 0, 4, 0, 7, 0, 0, 0, 6, 0, 0, 0, 4, 0, 0, 0, 24, 0, 4, 0,
8, 0, 0, 0, 7, 0, 0, 0, 4, 0, 0, 0, 30, 0, 3, 0, 11, 0, 0, 0, 8, 0,
0, 0, 32, 0, 4, 0, 12, 0, 0, 0, 2, 0, 0, 0, 11, 0, 0, 0, 59, 0, 4, 0,
12, 0, 0, 0, 13, 0, 0, 0, 2, 0, 0, 0, 21, 0, 4, 0, 14, 0, 0, 0, 32, 0,
0, 0, 1, 0, 0, 0, 43, 0, 4, 0, 14, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0,
32, 0, 4, 0, 16, 0, 0, 0, 2, 0, 0, 0, 8, 0, 0, 0, 21, 0, 4, 0, 20, 0,
0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 43, 0, 4, 0, 20, 0, 0, 0, 21, 0, 0, 0,
2, 0, 0, 0, 28, 0, 4, 0, 22, 0, 0, 0, 8, 0, 0, 0, 21, 0, 0, 0, 28, 0,
4, 0, 23, 0, 0, 0, 7, 0, 0, 0, 21, 0, 0, 0, 30, 0, 4, 0, 24, 0, 0, 0,
22, 0, 0, 0, 23, 0, 0, 0, 32, 0, 4, 0, 25, 0, 0, 0, 2, 0, 0, 0, 24, 0,
0, 0, 59, 0, 4, 0, 25, 0, 0, 0, 26, 0, 0, 0, 2, 0, 0, 0, 32, 0, 4, 0,
27, 0, 0, 0, 1, 0, 0, 0, 14, 0, 0, 0, 59, 0, 4, 0, 27, 0, 0, 0, 28, 0,
0, 0, 1, 0, 0, 0, 43, 0, 4, 0, 14, 0, 0, 0, 34, 0, 0, 0, 1, 0, 0, 0,
32, 0, 4, 0, 36, 0, 0, 0, 2, 0, 0, 0, 7, 0, 0, 0, 23, 0, 4, 0, 40, 0,
0, 0, 6, 0, 0, 0, 3, 0, 0, 0, 32, 0, 4, 0, 41, 0, 0, 0, 1, 0, 0, 0,
40, 0, 0, 0, 59, 0, 4, 0, 41, 0, 0, 0, 42, 0, 0, 0, 1, 0, 0, 0, 43, 0,
4, 0, 6, 0, 0, 0, 44, 0, 0, 0, 0, 0, 128, 63, 43, 0, 4, 0, 20, 0, 0, 0,
57, 0, 0, 0, 1, 0, 0, 0, 28, 0, 4, 0, 58, 0, 0, 0, 6, 0, 0, 0, 57, 0,
0, 0, 30, 0, 6, 0, 59, 0, 0, 0, 7, 0, 0, 0, 6, 0, 0, 0, 58, 0, 0, 0,
58, 0, 0, 0, 32, 0, 4, 0, 60, 0, 0, 0, 3, 0, 0, 0, 59, 0, 0, 0, 59, 0,
4, 0, 60, 0, 0, 0, 61, 0, 0, 0, 3, 0, 0, 0, 32, 0, 4, 0, 65, 0, 0, 0,
3, 0, 0, 0, 7, 0, 0, 0, 23, 0, 4, 0, 67, 0, 0, 0, 6, 0, 0, 0, 2, 0,
0, 0, 32, 0, 4, 0, 68, 0, 0, 0, 3, 0, 0, 0, 67, 0, 0, 0, 59, 0, 4, 0,
68, 0, 0, 0, 69, 0, 0, 0, 3, 0, 0, 0, 32, 0, 4, 0, 70, 0, 0, 0, 1, 0,
0, 0, 67, 0, 0, 0, 59, 0, 4, 0, 70, 0, 0, 0, 71, 0, 0, 0, 1, 0, 0, 0,
54, 0, 5, 0, 2, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 248, 0,
2, 0, 5, 0, 0, 0, 65, 0, 5, 0, 16, 0, 0, 0, 17, 0, 0, 0, 13, 0, 0, 0,
15, 0, 0, 0, 61, 0, 4, 0, 8, 0, 0, 0, 18, 0, 0, 0, 17, 0, 0, 0, 61, 0,
4, 0, 14, 0, 0, 0, 29, 0, 0, 0, 28, 0, 0, 0, 65, 0, 6, 0, 16, 0, 0, 0,
30, 0, 0, 0, 26, 0, 0, 0, 15, 0, 0, 0, 29, 0, 0, 0, 61, 0, 4, 0, 8, 0,
0, 0, 31, 0, 0, 0, 30, 0, 0, 0, 65, 0, 6, 0, 36, 0, 0, 0, 37, 0, 0, 0,
26, 0, 0, 0, 34, 0, 0, 0, 29, 0, 0, 0, 61, 0, 4, 0, 7, 0, 0, 0, 38, 0,
0, 0, 37, 0, 0, 0, 61, 0, 4, 0, 40, 0, 0, 0, 43, 0, 0, 0, 42, 0, 0, 0,
81, 0, 5, 0, 6, 0, 0, 0, 45, 0, 0, 0, 43, 0, 0, 0, 0, 0, 0, 0, 81, 0,
5, 0, 6, 0, 0, 0, 46, 0, 0, 0, 43, 0, 0, 0, 1, 0, 0, 0, 81, 0, 5, 0,
6, 0, 0, 0, 47, 0, 0, 0, 43, 0, 0, 0, 2, 0, 0, 0, 80, 0, 7, 0, 7, 0,
0, 0, 48, 0, 0, 0, 45, 0, 0, 0, 46, 0, 0, 0, 47, 0, 0, 0, 44, 0, 0, 0,
145, 0, 5, 0, 7, 0, 0, 0, 52, 0, 0, 0, 18, 0, 0, 0, 48, 0, 0, 0, 129, 0,
5, 0, 7, 0, 0, 0, 56, 0, 0, 0, 52, 0, 0, 0, 38, 0, 0, 0, 145, 0, 5, 0,
7, 0, 0, 0, 64, 0, 0, 0, 31, 0, 0, 0, 56, 0, 0, 0, 65, 0, 5, 0, 65, 0,
0, 0, 66, 0, 0, 0, 61, 0, 0, 0, 15, 0, 0, 0, 62, 0, 3, 0, 66, 0, 0, 0,
64, 0, 0, 0, 61, 0, 4, 0, 67, 0, 0, 0, 72, 0, 0, 0, 71, 0, 0, 0, 62, 0,
3, 0, 69, 0, 0, 0, 72, 0, 0, 0, 253, 0, 1, 0, 56, 0, 1, 0};

View File

@ -0,0 +1,151 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// The main IMediaPlayer implementation. This is a
// shim which provides Unreal interfaces, and then
// defers everything vulkan related to the main
// AndroidVulkanVideoImpl class.
// ------------------------------------------------
#pragma once
#include "IMediaCache.h"
#include "IMediaControls.h"
#include "IMediaEventSink.h"
#include "IMediaPlayer.h"
#include "IMediaTracks.h"
#include "IMediaView.h"
#include "MediaSamples.h"
// for vulkan types in the callback fn
#include "IVulkanDynamicRHI.h"
#include "IAndroidVulkanVideoAVCallback.h"
#include "AndroidVulkanTextureSample.h"
#include "VideoMediaSampleHolder.h"
class IAudioOut;
class IVulkanImpl;
class FAndroidVulkanMediaPlayer : public IMediaPlayer,
protected IMediaCache,
protected IMediaControls,
protected IMediaTracks,
protected IMediaView,
protected IAndroidVulkanVideoAVCallback
{
public:
FAndroidVulkanMediaPlayer(IMediaEventSink &InEventSink);
virtual ~FAndroidVulkanMediaPlayer();
// IMediaPlayer
virtual void Close() override;
virtual IMediaCache &GetCache() override
{
return *this;
}
virtual IMediaControls &GetControls() override
{
return *this;
}
virtual bool FlushOnSeekCompleted() const
{
return false;
}
virtual FString GetInfo() const override;
virtual FGuid GetPlayerPluginGUID() const override;
virtual IMediaSamples &GetSamples() override;
virtual FString GetStats() const override;
virtual IMediaTracks &GetTracks() override
{
return *this;
}
virtual FString GetUrl() const override;
virtual IMediaView &GetView() override
{
return *this;
}
virtual bool Open(const FString &Url, const IMediaOptions *Options) override;
virtual bool Open(const TSharedRef<FArchive, ESPMode::ThreadSafe> &Archive,
const FString &OriginalUrl, const IMediaOptions *Options) override;
virtual void SetGuid(const FGuid &Guid) override;
virtual void TickFetch(FTimespan DeltaTime, FTimespan Timecode) override;
virtual void TickInput(FTimespan DeltaTime, FTimespan Timecode) override;
virtual bool GetPlayerFeatureFlag(EFeatureFlag flag) const override;
// IMediaCache
// don't override these - we don't provide caching (yet?)
// virtual bool QueryCacheState(EMediaCacheState State, TRangeSet<FTimespan>& OutTimeRanges)
// const virtual int32 GetSampleCount(EMediaCacheState State) const
// IMediaControl
virtual bool CanControl(EMediaControl Control) const override;
virtual FTimespan GetDuration() const override;
virtual float GetRate() const override;
virtual EMediaState GetState() const override;
virtual EMediaStatus GetStatus() const override;
virtual TRangeSet<float> GetSupportedRates(EMediaRateThinning Thinning) const override;
virtual FTimespan GetTime() const override;
virtual bool IsLooping() const override;
virtual bool Seek(const FTimespan &Time) override;
virtual bool SetLooping(bool Looping) override;
virtual bool SetRate(float Rate) override;
virtual bool SetNativeVolume(float Volume) override;
// IMediaTracks
virtual bool GetAudioTrackFormat(int32 TrackIndex, int32 FormatIndex,
FMediaAudioTrackFormat &OutFormat) const override;
virtual int32 GetNumTracks(EMediaTrackType TrackType) const override;
virtual int32 GetNumTrackFormats(EMediaTrackType TrackType, int32 TrackIndex) const override;
virtual int32 GetSelectedTrack(EMediaTrackType TrackType) const override;
virtual FText GetTrackDisplayName(EMediaTrackType TrackType, int32 TrackIndex) const override;
virtual int32 GetTrackFormat(EMediaTrackType TrackType, int32 TrackIndex) const override;
virtual FString GetTrackLanguage(EMediaTrackType TrackType, int32 TrackIndex) const override;
virtual FString GetTrackName(EMediaTrackType TrackType, int32 TrackIndex) const override;
virtual bool GetVideoTrackFormat(int32 TrackIndex, int32 FormatIndex,
FMediaVideoTrackFormat &OutFormat) const override;
virtual bool SelectTrack(EMediaTrackType TrackType, int32 TrackIndex) override;
virtual bool SetTrackFormat(EMediaTrackType TrackType, int32 TrackIndex,
int32 FormatIndex) override;
// IMediaView
// don't provide these features, so we don't override any of it
// IAndroidVulkanVideoAVCallback (impl callback)
virtual void onVideoFrame(void *frameHwBuffer, int w, int h, int64_t presTimeNs) override;
virtual void *getVkDeviceProcAddr(const char *name) override;
virtual VkDevice getVkDevice() override;
virtual const VkAllocationCallbacks *getVkAllocationCallbacks() override;
virtual VkPhysicalDevice getNativePhysicalDevice() override;
virtual void onPlaybackEnd(bool looping) override;
virtual void ProcessVideoSamples() override;
private:
// delegates for foreground -> background transitions which should pause
// and resume playback
void OnEnterBackground();
void OnEnterForeground();
FDelegateHandle DelegateEnterBackground;
FDelegateHandle DelegateEnterForeground;
bool Seeking;
int32 SeekIndex;
// our output samples
TSharedPtr<FVideoMediaSampleHolder, ESPMode::ThreadSafe> SampleQueue;
IMediaEventSink &EventSink;
IVulkanImpl *impl;
IAudioOut *AudioOut;
FString CurInfo;
FString VideoURL;
FGuid PlayerGUID;
EMediaState PlayState;
bool Looping;
FAndroidVulkanTextureSamplePool VideoSamplePool;
bool HasVideoThisFrame;
bool SentBlankFrame;
};

View File

@ -0,0 +1,190 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Vulkan texture sample - holds an opaque pointer
// to an Android AHardwareBuffer, and provides the
// IMediaTextureSample and
// IMediaTextureSampleConverter interfaces to allow
// Unreal's media player to show the frame.
// ------------------------------------------------
#pragma once
#include "IMediaTextureSample.h"
#include "IMediaTextureSampleConverter.h"
#include "MediaObjectPool.h"
#include "IVulkanVertexData.h"
#define TMP_REPLACE 5
class IVulkanImpl;
class AndroidVulkanTextureSample : public IMediaTextureSample,
public IMediaTextureSampleConverter,
public IMediaPoolable
{
friend class FAndroidVulkanTextureSamplePool;
public:
AndroidVulkanTextureSample();
void Init(IVulkanImpl *impl, void *hwImage, int w, int h, FMediaTimeStamp sampleTime);
void InitNoVideo();
~AndroidVulkanTextureSample();
virtual const void *GetBuffer() override
{
return NULL;
}
virtual FIntPoint GetDim() const override;
virtual FTimespan GetDuration() const override
{
return FTimespan::Zero();
}
virtual EMediaTextureSampleFormat GetFormat() const override
{
return VideoTextureFormat;
}
virtual FIntPoint GetOutputDim() const override;
virtual uint32 GetStride() const override;
virtual FRHITexture *GetTexture() const override;
virtual IMediaTextureSampleConverter *GetMediaTextureSampleConverter() override;
virtual FMediaTimeStamp GetTime() const;
virtual bool IsCacheable() const
{
return true;
}
virtual bool IsOutputSrgb() const
{
return false;
}
// IMediaTextureSampleConverter interface (changed in 5.5 to add command list)
virtual bool Convert(FTexture2DRHIRef &InDstTexture, const FConversionHints &Hints);
bool ConvertInternal(FRHICommandListImmediate &RHICmdList, FTexture2DRHIRef &InDstTexture,
const FConversionHints &Hints);
// called inside render pass
bool RenderToMesh(FTexture2DRHIRef InDstTexture, FRHICommandList &RHICmdList, void *MeshID);
// called outside a render pass
// n.b. don't make matrices etc. const references
// because then we get dangling references and
// BAD THINGS happen
bool UpdateViewMatrices(FTexture2DRHIRef InDstTexture, FRHICommandList &RHICmdList,
ShaderViewMatrices Matrices, void *MeshID);
bool InitFrameForMeshRendering(FTexture2DRHIRef InDstTexture, FRHICommandList &RHICmdList,
void *MeshID);
bool UpdateMesh(FTexture2DRHIRef InDstTexture, FRHICommandList &RHICmdList,
TArray<VertexData> PositionAndUV, TArray<uint32> Indices,
ShaderModelMatrix Matrix, void *MeshID);
void InitializePoolable() override;
virtual bool IsReadyForReuse() override;
virtual void ShutdownPoolable() override;
void Clear();
void ClearIfShown();
static void SetVideoFormat(EMediaTextureSampleFormat Format)
{
VideoTextureFormat = Format;
}
void ImplDeleted();
private:
void FRenderOnRHIThreadCommand(const FTexture2DRHIRef &DstTexture);
void FRenderMeshOnRHIThreadCommand(const FTexture2DRHIRef &DstTexture, void *MeshID,
int Samples);
void FUpdateMeshOnRHIThreadCommand(const FTexture2DRHIRef &DstTexture,
const TArray<VertexData> &PositionAndUV,
const TArray<uint32> &Indices,
const ShaderModelMatrix &Matrix, void *MeshID);
void FUpdateMatricesOnRHIThreadCommand(const FTexture2DRHIRef &DstTexture,
const ShaderViewMatrices &Matrices, void *MeshID);
void FInitMeshRenderingCommand(const FTexture2DRHIRef &DstTexture, void *MeshID, int Samples);
bool IsEmptyTexture;
FGPUFenceRHIRef Fence;
FIntPoint Dimension;
void *FrameHWImage;
/** Duration for which the sample is valid. */
// FTimespan TimeDuration;
/** Sample time. */
FMediaTimeStamp SampleTime;
int NumSamples; // number of samples used for MSAA on rendering meshes directly
IVulkanImpl *Impl;
bool Shown;
FCriticalSection AccessLock;
static EMediaTextureSampleFormat VideoTextureFormat;
};
class FAndroidVulkanTextureSamplePool : public TMediaObjectPool<AndroidVulkanTextureSample>
{
public:
void Tick()
{
TMediaObjectPool<AndroidVulkanTextureSample>::Tick();
for (auto wp : liveObjects)
{
auto pin = wp.Pin();
if (pin.IsValid())
{
pin->ClearIfShown();
}
}
}
TSharedRef<AndroidVulkanTextureSample, ESPMode::ThreadSafe> AcquireShared()
{
auto ret = TMediaObjectPool<AndroidVulkanTextureSample>::AcquireShared();
TWeakPtr<AndroidVulkanTextureSample, ESPMode::ThreadSafe> wp = ret;
if (!liveObjects.Find(wp))
{
liveObjects.Add(wp);
}
return ret;
}
void ReleaseEverything()
{
for (auto wp : liveObjects)
{
auto pin = wp.Pin();
if (pin.IsValid())
{
pin->ImplDeleted();
}
}
for (auto wp : liveObjects)
{
auto pin = wp.Pin();
if (pin.IsValid())
{
pin->ShutdownPoolable();
}
}
Reset();
}
int bob[TMP_REPLACE];
// for anything that has already been drawn, we want to release textures ASAP,
// rather than whenever Unreal gets round to it.
TArray<TWeakPtr<AndroidVulkanTextureSample, ESPMode::ThreadSafe>> liveObjects;
};

View File

@ -0,0 +1,22 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// AndroidVulkanVideo module implementation
// ------------------------------------------------
#pragma once
#include "Modules/ModuleManager.h"
#include "IMediaEventSink.h"
#include "IMediaPlayer.h"
class IAndroidVulkanVideoModule : public IModuleInterface
{
public:
virtual TSharedPtr<IMediaPlayer, ESPMode::ThreadSafe> CreatePlayer(
IMediaEventSink &EventSink) = 0;
private:
};

View File

@ -0,0 +1,21 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Callback used by implementation class to send
// buffers to media player.
// ------------------------------------------------
#pragma once
class IAndroidVulkanVideoAVCallback
{
public:
// called with video frame hardware buffers
virtual void onVideoFrame(void *frameHwBuffer, int w, int h, int64_t presTimeNs) = 0;
// we need to get vulkan functions and device etc. via Unreal Engine for consistency
virtual void *getVkDeviceProcAddr(const char *name) = 0;
virtual VkDevice getVkDevice() = 0;
virtual const VkAllocationCallbacks *getVkAllocationCallbacks() = 0;
virtual VkPhysicalDevice getNativePhysicalDevice() = 0;
virtual void onPlaybackEnd(bool looping) = 0;
};

View File

@ -0,0 +1,44 @@
#pragma once
struct VertexData
{
float x;
float y;
float z;
float _ignored1;
float u;
float v;
float _ignored2;
float _ignored3;
};
struct ShaderModelMatrix
{
ShaderModelMatrix()
{
memset(this, 0, sizeof(ShaderModelMatrix));
for (int c = 0; c < 4; c++)
{
m44[c + c * 4] = 1.0;
}
}
float m44[16];
};
struct ShaderViewMatrices
{
ShaderViewMatrices()
{
memset(this, 0, sizeof(ShaderViewMatrices));
for (int c = 0; c < 4; c++)
{
vp44[c + c * 4] = 1;
vp44_2[c + c * 4] = 1;
}
}
float vp44[16];
float vp44_2[16];
float pre_view_translation[4];
float pre_view_translation_2[4];
};

View File

@ -0,0 +1,12 @@
<!-- Copyright Joe Marshall 2024 - All Rights Reserved -->
<!-- Copies our custom vulkan layer into the build. -->
<root xmlns:android="http://schemas.android.com/apk/res/android">
<!-- init section is always evaluated once per architecture -->
<trace enable="true"/>
<resourceCopies>
<copyFile src="$S(PluginDir)/$S(Architecture)/libVkLayer_OverrideLib.so" dst="$S(BuildDir)/libs/$S(Architecture)/libVkLayer_OverrideLib.so" />
</resourceCopies>
</root>

View File

@ -0,0 +1,79 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Build AndroidVulkanVideo factory module (to make
// it possible to select this as a video source in
// editor.)
// ------------------------------------------------
using UnrealBuildTool;
using System.IO;
using System;
public class AndroidVulkanVideoFactory : ModuleRules
{
public AndroidVulkanVideoFactory(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
CppStandard = CppStandardVersion.Cpp17;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"Projects"
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject", "Engine","RHI","VulkanRHI","AudioExtensions"
// ... add private dependencies that you statically link with here ...
}
);
PrivateIncludePathModuleNames.AddRange(
new string[] {
"AndroidVulkanVideo",
"Media",
"MediaUtils"
});
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
"Media"
}
);
if (Target.Platform == UnrealTargetPlatform.Android)
{
DynamicallyLoadedModuleNames.Add("AndroidVulkanVideo");
}
// don't precompile video
// factory so any editor can use it
bPrecompile = false;
bUsePrecompiled = false;
}
}

View File

@ -0,0 +1,168 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Factory class to tell Unreal that we can play
// different types of media.
// ------------------------------------------------
#include "AndroidVulkanVideo.h"
#include "IMediaModule.h"
#include "IMediaPlayerFactory.h"
#include "Misc/Guid.h"
#include "Misc/Paths.h"
#include "Modules/ModuleManager.h"
#define LOCTEXT_NAMESPACE "FAndroidVulkanVideoFactoryModule"
class FAndroidVulkanVideoFactoryModule : public IMediaPlayerFactory, public IModuleInterface
{
public:
virtual bool CanPlayUrl(const FString &Url, const IMediaOptions * /*Options*/,
TArray<FText> * /*OutWarnings*/,
TArray<FText> *OutErrors) const override
{
FString Scheme;
FString Location;
// check scheme
if (!Url.Split(TEXT("://"), &Scheme, &Location, ESearchCase::CaseSensitive))
{
if (OutErrors != nullptr)
{
OutErrors->Add(LOCTEXT("NoSchemeFound", "No URI scheme found"));
}
return false;
}
if (!SupportedUriSchemes.Contains(Scheme))
{
if (OutErrors != nullptr)
{
OutErrors->Add(FText::Format(
LOCTEXT("SchemeNotSupported", "The URI scheme '{0}' is not supported"),
FText::FromString(Scheme)));
}
return false;
}
// check file extension
if (Scheme == TEXT("file"))
{
const FString Extension = FPaths::GetExtension(Location, false);
if (!SupportedFileExtensions.Contains(Extension))
{
if (OutErrors != nullptr)
{
OutErrors->Add(
FText::Format(LOCTEXT("ExtensionNotSupported",
"The file extension '{0}' is not supported"),
FText::FromString(Extension)));
}
return false;
}
}
return true;
}
virtual TSharedPtr<IMediaPlayer, ESPMode::ThreadSafe> CreatePlayer(
IMediaEventSink &EventSink) override
{
auto AndroidVulkanVideoModule =
FModuleManager::LoadModulePtr<IAndroidVulkanVideoModule>("AndroidVulkanVideo");
return (AndroidVulkanVideoModule != nullptr)
? AndroidVulkanVideoModule->CreatePlayer(EventSink)
: nullptr;
}
virtual FText GetDisplayName() const override
{
return LOCTEXT("MediaPlayerDisplayName", "Android Vulkan Video");
}
virtual FName GetPlayerName() const override
{
static FName PlayerName(TEXT("DirectVideo Android"));
return PlayerName;
}
virtual FGuid GetPlayerPluginGUID() const override
{
static FGuid OurGUID(0x9bf2d7c6, 0xb2b84d26, 0xb6ae5a3a, 0xc9883569);
return OurGUID;
}
virtual const TArray<FString> &GetSupportedPlatforms() const override
{
return SupportedPlatforms;
}
virtual bool SupportsFeature(EMediaFeature Feature) const override
{
return ((Feature == EMediaFeature::AudioTracks) ||
(Feature == EMediaFeature::VideoSamples) ||
(Feature == EMediaFeature::VideoTracks));
}
virtual void ShutdownModule() override
{
// unregister player factory
auto MediaModule = FModuleManager::GetModulePtr<IMediaModule>("Media");
if (MediaModule != nullptr)
{
MediaModule->UnregisterPlayerFactory(*this);
}
}
virtual void StartupModule() override
{
// supported file extensions
SupportedFileExtensions.Add(TEXT("3gpp"));
SupportedFileExtensions.Add(TEXT("aac"));
SupportedFileExtensions.Add(TEXT("mp4"));
SupportedFileExtensions.Add(TEXT("m3u8"));
SupportedFileExtensions.Add(TEXT("webm"));
// supported platforms
SupportedPlatforms.Add(TEXT("Android"));
// supported schemes
SupportedUriSchemes.Add(TEXT("file"));
/* SupportedUriSchemes.Add(TEXT("http"));
SupportedUriSchemes.Add(TEXT("httpd"));
SupportedUriSchemes.Add(TEXT("https"));
SupportedUriSchemes.Add(TEXT("mms"));
SupportedUriSchemes.Add(TEXT("rtsp"));
SupportedUriSchemes.Add(TEXT("rtspt"));
SupportedUriSchemes.Add(TEXT("rtspu"));*/
// register media player info
auto MediaModule = FModuleManager::LoadModulePtr<IMediaModule>("Media");
if (MediaModule != nullptr)
{
MediaModule->RegisterPlayerFactory(*this);
}
}
private:
/** List of supported media file types. */
TArray<FString> SupportedFileExtensions;
/** List of platforms that the media player support. */
TArray<FString> SupportedPlatforms;
/** List of supported URI schemes. */
TArray<FString> SupportedUriSchemes;
};
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FAndroidVulkanVideoFactoryModule, AndroidVulkanVideoFactory);

View File

@ -0,0 +1,518 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Renders mesh directly to framebuffer from video
// stream (without texture between)
// ------------------------------------------------
#include "DirectVideoMeshRenderer.h"
#include "AndroidVulkanTextureSample.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/StaticMesh.h"
#include "Engine/StaticMeshActor.h"
#include "EngineModule.h"
#include "IMediaPlayer.h"
#include "RHIResources.h"
#include "RawIndexBuffer.h"
#include "RenderGraphBuilder.h"
#include "Rendering/ColorVertexBuffer.h"
#include "Rendering/PositionVertexBuffer.h"
#include "Rendering/StaticMeshVertexBuffer.h"
#include "SceneView.h"
#include "SceneViewExtension.h"
#include "StaticMeshResources.h"
#include "StereoRendering.h"
#include "Math/Matrix.h"
#include "MeshDescription.h"
DEFINE_LOG_CATEGORY(LogDirectVideoMeshRenderer);
class FRendererSceneViewExt : public FSceneViewExtensionBase
{
public:
FRendererSceneViewExt(const FAutoRegister &AutoRegister,
UDirectVideoMeshRendererComponent &InOwner)
: FSceneViewExtensionBase(AutoRegister), Owner(InOwner)
{
}
virtual void PostRenderView_RenderThread(FRDGBuilder &GraphBuilder, FSceneView &InView) override
{
}
virtual void PostRenderBasePassMobile_RenderThread(FRHICommandList &RHICmdList,
FSceneView &InView) override
{
UE_LOGFMT(LogDirectVideoMeshRenderer, VeryVerbose, "PostRenderBasePassMobile");
#if PLATFORM_ANDROID
Owner.DrawToCommandList(RHICmdList, InView);
#endif
}
virtual void SetupViewFamily(FSceneViewFamily &InViewFamily) override
{
}
virtual void SetupView(FSceneViewFamily &InViewFamily, FSceneView &InView) override
{
}
virtual void PreRenderViewFamily_RenderThread(FRDGBuilder &GraphBuilder,
FSceneViewFamily &InViewFamily)
{
#if PLATFORM_ANDROID
// we are outside of a command list, so we can update the mesh if needed
Owner.DoWorkOutsideRenderPass(GraphBuilder.RHICmdList, InViewFamily);
#endif
}
virtual void PreRenderView_RenderThread(FRDGBuilder &GraphBuilder, FSceneView &InView)
{
}
virtual void BeginRenderViewFamily(FSceneViewFamily &InViewFamily) override
{
Owner.UpdateVisibility();
}
UDirectVideoMeshRendererComponent &Owner;
};
UDirectVideoMeshRendererComponent::UDirectVideoMeshRendererComponent()
{
HideMeshWhileWeHaveVideo = true;
PrimaryComponentTick.bCanEverTick = true;
Initialized = false;
}
UDirectVideoMeshRendererComponent::~UDirectVideoMeshRendererComponent()
{
DeInit();
}
void UDirectVideoMeshRendererComponent::DeInit()
{
Initialized = false;
#if PLATFORM_ANDROID
UE_LOGFMT(LogDirectVideoMeshRenderer, VeryVerbose, "Deinit");
// Extensions clean up on destroy
ExtensionHolder.Reset();
if (PreparedSample != NULL)
{
AndroidVulkanTextureSample *VulkanSample =
static_cast<AndroidVulkanTextureSample *>(PreparedSample.Get());
VulkanSample->ImplDeleted();
}
if (CurrentSample != NULL)
{
AndroidVulkanTextureSample *VulkanSample =
static_cast<AndroidVulkanTextureSample *>(CurrentSample.Get());
VulkanSample->ImplDeleted();
}
CurrentSample = NULL;
PreparedSample = NULL;
if (Facade != NULL)
{
auto PFacade = Facade.Pin();
}
#endif
Facade.Reset();
// the media facade sample sink keeps a weak pointer to
// the texture sink and then clears it if destroyed,
// so we don't need to remove the texture sink, we can just
// kill it here.
TextureSink.Reset();
}
void UDirectVideoMeshRendererComponent::BeginDestroy()
{
DeInit();
UActorComponent::BeginDestroy();
}
void UDirectVideoMeshRendererComponent::Initialize()
{
#if PLATFORM_ANDROID
TextureSink = MakeShared<_TextureSink>(this);
if (Facade == NULL)
{
if (LinkedPlayer != NULL)
{
Facade = LinkedPlayer->GetPlayerFacade();
}
}
if (Facade != NULL)
{
if (!HasAnyFlags(RF_ClassDefaultObject))
{
auto PFacade = Facade.Pin();
TSharedRef<FMediaTextureSampleSink> sinkPtr = TextureSink.ToSharedRef();
PFacade.Get()->AddVideoSampleSink(sinkPtr);
UE_LOGFMT(LogDirectVideoMeshRenderer, VeryVerbose, "Registered for frames");
Initialized = true;
}
}
else
{
UE_LOGFMT(LogDirectVideoMeshRenderer, VeryVerbose, "No media player");
}
GetMeshObject();
if (ExtensionHolder == NULL)
{
ExtensionHolder = FSceneViewExtensions::NewExtension<FRendererSceneViewExt>(*this);
}
#endif
}
void UDirectVideoMeshRendererComponent::HandlePlayerMediaEvent(EMediaSampleSinkEvent Event)
{
int IEvent = (int)Event;
UE_LOGFMT(LogDirectVideoMeshRenderer, VeryVerbose, "Handle event {0}", IEvent);
switch (Event)
{
case EMediaSampleSinkEvent::Detached:
case EMediaSampleSinkEvent::MediaClosed:
CurrentSample = NULL;
PreparedSample = NULL;
break;
}
}
void UDirectVideoMeshRendererComponent::TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction *ThisTickFunction)
{
if (!Initialized)
{
Initialize();
}
}
void UDirectVideoMeshRendererComponent::UpdateVisibility()
{
if (HideMeshWhileWeHaveVideo)
{
MeshActor = GetOwner<AStaticMeshActor>();
if (MeshActor == NULL)
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Warning,
"Mesh renderer has to be on StaticMeshActor");
return;
}
// need to unhide or hide the underlying staticmesh here
// depending on whether we have video
UStaticMeshComponent *MeshComponent = MeshActor->GetStaticMeshComponent();
if (MeshComponent == NULL)
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Warning,
"Mesh renderer needs a static mesh component");
return;
}
MeshComponent->bRenderInDepthPass = 0;
MeshComponent->SetRenderInMainPass(CurrentSample == NULL);
}
}
void UDirectVideoMeshRendererComponent::GetMeshObject()
{
MeshActor = GetOwner<AStaticMeshActor>();
if (MeshActor == NULL)
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Warning,
"Mesh renderer has to be on StaticMeshActor");
return;
}
UStaticMeshComponent *MeshComponent = MeshActor->GetStaticMeshComponent();
TObjectPtr<UStaticMesh> Mesh = MeshComponent->GetStaticMesh();
#if WITH_EDITOR
if (Mesh != NULL)
{
FStaticMeshRenderData *RenderData = Mesh->GetRenderData();
if (RenderData != NULL)
{
const FStaticMeshLODResources &LODRenderData = RenderData->LODResources[0];
const auto &VertexBuffers = LODRenderData.VertexBuffers;
const auto &IndexBuffer = LODRenderData.IndexBuffer; // index buffer for rendering this
if (ConstructMeshFromRenderData(VertexBuffers, IndexBuffer))
{
HasMesh = true;
}
else
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Warning, "Mesh making failed");
}
}
else
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Warning, "No render data");
}
}
else
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Warning, "Mesh is null");
}
#endif
}
void DumpMatrix(const char *Name, FMatrix44f Matrix)
{
char MatrixDesc[256];
int ofs = snprintf(MatrixDesc, 250, "%s:\n", Name);
for (int c = 0; c < 4; c++)
{
for (int d = 0; d < 4; d++)
{
int len = snprintf(&MatrixDesc[ofs], 250 - ofs, "%4.4f,", Matrix.M[c][d]);
ofs += len;
}
MatrixDesc[ofs] = '\n';
ofs++;
}
MatrixDesc[ofs] = 0;
UE_LOGFMT(LogDirectVideoMeshRenderer, Verbose, "{0}", MatrixDesc);
}
void UDirectVideoMeshRendererComponent::DrawToCommandList(FRHICommandList &RHICmdList,
FSceneView &InView)
{
bool hasSample = (PreparedSample != NULL);
#if PLATFORM_ANDROID
UE_LOGFMT(LogDirectVideoMeshRenderer, Verbose, "DrawToCOmmandList_Init:{0} HasSample:{1}",
Initialized, hasSample);
if (!Initialized)
{
return;
}
if (PreparedSample != NULL)
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Verbose, "DrawToCOmmandList:{0}",
RHICmdList.IsOutsideRenderPass());
AndroidVulkanTextureSample *VulkanSample =
static_cast<AndroidVulkanTextureSample *>(PreparedSample.Get());
const FTextureRHIRef &ColourTex = InView.Family->RenderTarget->GetRenderTargetTexture();
VulkanSample->RenderToMesh(ColourTex, RHICmdList, (void *)(MeshActor.Get()));
}
#endif
}
void UDirectVideoMeshRendererComponent::GetShaderInfo(const FSceneView *InView, float *m44,
float *vp44, float *pre_view_translation)
{
FTransform Transform = MeshActor->ActorToWorld();
FMatrix44f LocalToRelativeWorld = FMatrix44f(Transform.ToMatrixWithScale());
// DumpMatrix("Model", LocalToRelativeWorld);
auto TranslatedWorldToClip =
FMatrix44f(InView->ViewMatrices.GetTranslatedViewProjectionMatrix());
// DumpMatrix("VP", TranslatedWorldToClip);
auto MVPMatrix = LocalToRelativeWorld * TranslatedWorldToClip;
// DumpMatrix("MVP", MVPMatrix);
auto Translation = InView->ViewMatrices.GetPreViewTranslation();
FVector4f Translation4 = FVector4f(Translation.X, Translation.Y, Translation.Z, 0);
UE_LOGFMT(LogDirectVideoMeshRenderer, VeryVerbose, "Pre view translation 2:{0},{1},{2},{3}",
Translation4.X, Translation4.Y, Translation4.Z, Translation4.W);
memcpy(vp44, TranslatedWorldToClip.M, 16 * sizeof(float));
// auto TransposedVP=TranslatedWorldToClip.GetTransposed();
// memcpy(vp44,TransposedVP.M,16*sizeof(float));
// auto TransposedM = LocalToRelativeWorld.GetTransposed();
// memcpy(m44,TransposedM.M,16*sizeof(float));
memcpy(m44, LocalToRelativeWorld.M, 16 * sizeof(float));
pre_view_translation[0] = Translation4.X;
pre_view_translation[1] = Translation4.Y;
pre_view_translation[2] = Translation4.Z;
pre_view_translation[3] = Translation4.W;
}
void UDirectVideoMeshRendererComponent::DoWorkOutsideRenderPass(FRHICommandList &RHICmdList,
FSceneViewFamily &InViewFamily)
{
// render both views if we are stereo multiview (with single pass rendering)
#if PLATFORM_ANDROID
if (!Initialized)
{
return;
}
if (CurrentSample != NULL)
{
ShaderViewMatrices ViewMatrices;
ShaderModelMatrix ModelMatrix;
PreparedSample = CurrentSample;
AndroidVulkanTextureSample *VulkanSample =
static_cast<AndroidVulkanTextureSample *>(PreparedSample.Get());
UE_LOGFMT(LogDirectVideoMeshRenderer, VeryVerbose, "Work outside render pass:{0}",
RHICmdList.IsOutsideRenderPass());
for (auto &InView : InViewFamily.Views)
{
if (IStereoRendering::IsASecondaryView(*InView))
{
GetShaderInfo(InView, ModelMatrix.m44, ViewMatrices.vp44_2,
ViewMatrices.pre_view_translation_2);
}
else
{
GetShaderInfo(InView, ModelMatrix.m44, ViewMatrices.vp44,
ViewMatrices.pre_view_translation);
}
}
const FTextureRHIRef &ColourTex = InViewFamily.RenderTarget->GetRenderTargetTexture();
VulkanSample->UpdateViewMatrices(ColourTex, RHICmdList, ViewMatrices,
(void *)(MeshActor.Get()));
VulkanSample->UpdateMesh(ColourTex, RHICmdList, Positions, Indices, ModelMatrix,
(void *)(MeshActor.Get()));
VulkanSample->InitFrameForMeshRendering(ColourTex, RHICmdList, (void *)(MeshActor.Get()));
}
else
{
UE_LOGFMT(LogDirectVideoMeshRenderer, VeryVerbose, "Emptying prepared sample");
PreparedSample = NULL;
}
#endif
}
bool UDirectVideoMeshRendererComponent::Enqueue(
const TSharedRef<IMediaTextureSample, ESPMode::ThreadSafe> &Sample)
{
if (!Initialized)
{
return false;
}
static FGuid OurGUID(0x9bf2d7c6, 0xb2b84d26, 0xb6ae5a3a, 0xc9883569);
auto PFacade = Facade.Pin();
if (PFacade->GetPlayer()->GetPlayerPluginGUID() == OurGUID)
{
CurrentSample = Sample;
UE_LOGFMT(LogDirectVideoMeshRenderer, VeryVerbose, "Mesh component has sample");
}
else
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Warning, "Mesh component has wrong player {0}",
PFacade.Get()->GetGuid());
}
return true;
}
void UDirectVideoMeshRendererComponent::Serialize(FArchive &Ar)
{
/* if(!Ar.IsSaving()){
char bob[119];
Ar.Serialize(bob,119);
return;
}*/
#if WITH_EDITOR
if (Ar.IsSaving())
{
if (!HasMesh)
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Warning, "Needs mesh object");
GetMeshObject();
}
if (!HasMesh)
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Warning,
"Couldn't get mesh for DirectVideoMeshRenderComponent");
}
else
{
UE_LOGFMT(LogDirectVideoMeshRenderer, VeryVerbose, "Saving mesh details");
}
}
#endif
UActorComponent::Serialize(Ar);
if (Ar.IsLoading())
{
if (HasMesh)
{
int NumVertices = PositionsAsFloats.Num() / 5;
Positions.Init({}, NumVertices);
for (int c = 0; c < NumVertices; c++)
{
Positions[c].x = PositionsAsFloats[c * 5];
Positions[c].y = PositionsAsFloats[c * 5 + 1];
Positions[c].z = PositionsAsFloats[c * 5 + 2];
Positions[c].u = PositionsAsFloats[c * 5 + 3];
Positions[c].v = PositionsAsFloats[c * 5 + 4];
}
UE_LOGFMT(LogDirectVideoMeshRenderer, VeryVerbose,
"DirectVideoMeshRenderComponent - Vertices {0} Indices {1}", Indices.Num(),
Positions.Num());
}
else
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Warning,
"No saved mesh for DirectVideoMeshRenderComponent");
}
}
}
bool UDirectVideoMeshRendererComponent::ConstructMeshFromRenderData(
const FStaticMeshVertexBuffers &VertexBuffers, const FRawStaticIndexBuffer &IndexBuffer)
{
const FPositionVertexBuffer &Pos =
VertexBuffers.PositionVertexBuffer; // contains vertex positions
const FStaticMeshVertexBuffer &TangentsAndTextureCoords =
VertexBuffers
.StaticMeshVertexBuffer; // contains tangents, texture coords and lightmap coords
const FColorVertexBuffer &Color = VertexBuffers.ColorVertexBuffer; // vertex colours
if (IndexBuffer.GetIndexDataSize() == 0)
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Warning, "No indices for mesh");
return false;
}
// make 1 buffer with:
// position (float * 3, packed as 4 byte)
// texture coordinates (float*2), packed as 4 byte
int NumVertices = Pos.GetNumVertices();
Positions.Init({}, NumVertices);
int NumIndices = IndexBuffer.GetNumIndices();
Indices.Init({}, NumIndices);
for (int c = 0; c < NumVertices; c++)
{
const FVector3f &VPos = Pos.VertexPosition(c);
Positions[c].x = VPos.X;
Positions[c].y = VPos.Y;
Positions[c].z = VPos.Z;
FVector2f TexPos = TangentsAndTextureCoords.GetVertexUV(c, 0);
Positions[c].u = TexPos.X;
Positions[c].v = TexPos.Y;
}
// and 1 index buffer (int32)
IndexBuffer.GetCopy(Indices);
PositionsAsFloats.Init({}, NumVertices * 5);
for (int c = 0; c < NumVertices; c++)
{
PositionsAsFloats[c * 5] = Positions[c].x;
PositionsAsFloats[c * 5 + 1] = Positions[c].y;
PositionsAsFloats[c * 5 + 2] = Positions[c].z;
PositionsAsFloats[c * 5 + 3] = Positions[c].u;
PositionsAsFloats[c * 5 + 4] = Positions[c].v;
}
return true;
}

View File

@ -0,0 +1,26 @@
#include "Modules/ModuleManager.h"
#define LOCTEXT_NAMESPACE "FVulkanVideoMeshComponentModule"
class FVulkanVideoMeshComponentModule : public IModuleInterface
{
void StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is
// specified in the .uplugin file per-module
// Get the base directory of this plugin
// FString BaseDir = IPluginManager::Get().FindPlugin("AndroidVulkanVideo")->GetBaseDir();
//
}
void ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that
// support dynamic reloading, we call this function before unloading the module.
}
};
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FVulkanVideoMeshComponentModule, VulkanVideoMeshComponent)

View File

@ -0,0 +1,188 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Attaches to a static mesh, and allows the vertex data
// to be passed into the vulkanimpl for rendering
// ------------------------------------------------
#pragma once
#include "Engine/StaticMesh.h"
#include "Engine/StaticMeshActor.h"
#include "IMediaEventSink.h"
#include "MediaObjectPool.h"
#include "MediaPlayer.h"
#include "MediaPlayerFacade.h"
#include "MediaSampleSink.h"
#include "Logging/StructuredLog.h"
#include "IVulkanVertexData.h"
#include "RendererInterface.h"
#include "DirectVideoMeshRenderer.generated.h"
class AndroidVulkanTextureSample;
class FRendererSceneViewExt;
class FRawStaticIndexBuffer;
struct FStaticMeshVertexBuffers;
DECLARE_LOG_CATEGORY_EXTERN(LogDirectVideoMeshRenderer, Log, All);
UCLASS(Blueprintable, ClassGroup = (Rendering, Common),
hidecategories = (Object, Activation, "Components|Activation"), ShowCategories = (Mobility),
editinlinenew, meta = (BlueprintSpawnableComponent))
class VULKANVIDEOMESHCOMPONENT_API UDirectVideoMeshRendererComponent : public UActorComponent
{
friend class FRendererSceneViewExt;
GENERATED_BODY()
public:
UDirectVideoMeshRendererComponent();
~UDirectVideoMeshRendererComponent();
void HandlePlayerMediaEvent(EMediaSampleSinkEvent Event);
void DeInit();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Components|DirectVideo")
UMediaPlayer *LinkedPlayer;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Components|DirectVideo")
bool HideMeshWhileWeHaveVideo;
// FMediaTextureSampleSink (called indirectly from _TextureSink)
virtual bool Enqueue(const TSharedRef<IMediaTextureSample, ESPMode::ThreadSafe> &Sample);
virtual int32 Num() const
{
if (CurrentSample.IsValid())
return 1;
return 0;
}
virtual void RequestFlush()
{
CurrentSample.Reset();
PreparedSample.Reset();
}
virtual void BeginDestroy() override;
protected:
// draw to a command list (which is already in a render pass with outputs set up)
void DrawToCommandList(FRHICommandList &RHICmdList, FSceneView &InView);
// load mesh, update matrices, create vulkan images (must be outside render pass)
void DoWorkOutsideRenderPass(FRHICommandList &RHICmdList, FSceneViewFamily &InViewFamily);
void UpdateVisibility();
class _TextureSink : public FMediaTextureSampleSink
{
public:
_TextureSink(UDirectVideoMeshRendererComponent *InOwner)
{
Owner = InOwner;
FlushCount = 0;
EventDelegate =
OnMediaSampleSinkEvent().AddRaw(this, &_TextureSink::ReceiveEventHandler);
}
virtual ~_TextureSink()
{
OnMediaSampleSinkEvent().Remove(EventDelegate);
}
virtual bool Enqueue(
const TSharedRef<IMediaTextureSample, ESPMode::ThreadSafe> &Sample) override
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Verbose, "Enqueue");
auto POwner = Owner.Get();
if (POwner != NULL)
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Verbose, "Enqueue2");
return POwner->Enqueue(Sample);
}
return false;
}
virtual int32 Num() const override
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Verbose, "Num");
auto POwner = Owner.Get();
if (POwner != NULL)
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Verbose, "Num2");
return POwner->Num();
}
return 0;
}
virtual void RequestFlush() override
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Verbose, "Flush");
FlushCount += 1;
auto POwner = Owner.Get();
if (POwner != NULL)
{
UE_LOGFMT(LogDirectVideoMeshRenderer, Verbose, "Flush2");
POwner->RequestFlush();
}
}
void ReceiveEventHandler(EMediaSampleSinkEvent Event, const FMediaSampleSinkEventData &Data)
{
int iEvent = (int)Event;
UE_LOGFMT(LogDirectVideoMeshRenderer, Verbose, "On event:{1}", iEvent);
auto POwner = Owner.Get();
if (POwner != NULL)
{
POwner->HandlePlayerMediaEvent(Event);
}
}
TWeakObjectPtr<UDirectVideoMeshRendererComponent> Owner;
int FlushCount;
FDelegateHandle EventDelegate;
};
void Initialize();
void GetMeshObject();
bool ConstructMeshFromRenderData(const FStaticMeshVertexBuffers &VertexBuffers,
const FRawStaticIndexBuffer &IndexBuffer);
void GetShaderInfo(const FSceneView *InView, float *m44, float *vp44,
float *pre_view_translation);
virtual void TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction *ThisTickFunction) override;
virtual void Serialize(FArchive &Ar) override;
UPROPERTY()
bool HasMesh;
// raw mesh data as passed to RenderToMesh
// we save this in UObject::Serialize so that
// we don't need to load it from the mesh at
// runtime
UPROPERTY()
TArray<float> PositionsAsFloats; // x,y,z,u,v (floats)
// vertexData is not a UStruct because we pass it through to
// native android code
TArray<VertexData> Positions; // x,y,z,u,v (floats)
UPROPERTY()
TArray<uint32> Indices; // index buffer for mesh (uint32)
TSharedPtr<_TextureSink> TextureSink;
TSharedPtr<IMediaTextureSample, ESPMode::ThreadSafe> CurrentSample;
TSharedPtr<IMediaTextureSample, ESPMode::ThreadSafe> PreparedSample;
TWeakPtr<FMediaPlayerFacade, ESPMode::ThreadSafe> Facade;
TObjectPtr<AStaticMeshActor> MeshActor;
TSharedPtr<FRendererSceneViewExt, ESPMode::ThreadSafe> ExtensionHolder;
bool Initialized;
};

View File

@ -0,0 +1,73 @@
// ------------------------------------------------
// Copyright Joe Marshall 2024- All Rights Reserved
// ------------------------------------------------
//
// Build AndroidVulkanVideo factory module (to make
// it possible to select this as a video source in
// editor.)
// ------------------------------------------------
using UnrealBuildTool;
using System.IO;
using System;
public class VulkanVideoMeshComponent : ModuleRules
{
public VulkanVideoMeshComponent(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
CppStandard = CppStandardVersion.Cpp17;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"Projects",
"CoreUObject", "Engine","RHI","VulkanRHI","MediaUtils"
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"Core","CoreUObject", "Engine","RHI","RenderCore","VulkanRHI","MediaUtils","Media","MediaAssets"
// ... add private dependencies that you statically link with here ...
}
);
PrivateIncludePathModuleNames.AddRange(
new string[] {
"AndroidVulkanVideo",
});
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
}
);
if (Target.Platform == UnrealTargetPlatform.Android)
{
DynamicallyLoadedModuleNames.Add("AndroidVulkanVideo");
}
}
}