Android build settings + metaxr
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
// @lint-ignore-every LICENSELINT
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace UnrealBuildTool.Rules
|
||||
{
|
||||
public class OculusXRPassthrough : ModuleRules
|
||||
{
|
||||
public OculusXRPassthrough(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
bUseUnity = true;
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"ProceduralMeshComponent",
|
||||
"OculusXRHMD",
|
||||
"KhronosOpenXRHeaders",
|
||||
"OVRPluginXR",
|
||||
"RHI",
|
||||
"HeadMountedDisplay",
|
||||
"XRBase",
|
||||
"OpenXR",
|
||||
"OpenXRHMD",
|
||||
"RenderCore",
|
||||
});
|
||||
|
||||
PublicIncludePaths.AddRange(new string[] {
|
||||
"Runtime/Engine/Classes/Components",
|
||||
"Runtime/Engine/Classes/Kismet",
|
||||
});
|
||||
|
||||
PrivateIncludePaths.AddRange(new string[] {
|
||||
// Relative to Engine\Plugins\Runtime\Oculus\OculusVR\Source
|
||||
"OculusXRHMD/Private",
|
||||
});
|
||||
|
||||
PrivateIncludePathModuleNames.Add("OpenXRHMD");
|
||||
|
||||
AddEngineThirdPartyPrivateStaticDependencies(Target, "OpenXR");
|
||||
|
||||
//Needed for OpenXRHMD_Swapchain include
|
||||
{
|
||||
if (Target.Platform == UnrealTargetPlatform.Win64)
|
||||
{
|
||||
PublicDependencyModuleNames.AddRange(new string[]
|
||||
{
|
||||
"D3D11RHI",
|
||||
"D3D12RHI"
|
||||
});
|
||||
|
||||
if (!bUsePrecompiled || Target.LinkType == TargetLinkType.Monolithic)
|
||||
{
|
||||
PublicDependencyModuleNames.AddRange(new string[]
|
||||
{
|
||||
"DX11",
|
||||
"DX12"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (Target.Platform == UnrealTargetPlatform.Win64 || Target.Platform == UnrealTargetPlatform.Android)
|
||||
{
|
||||
PublicDependencyModuleNames.Add("OpenGLDrv");
|
||||
|
||||
if (!bUsePrecompiled || Target.LinkType == TargetLinkType.Monolithic)
|
||||
{
|
||||
PublicDependencyModuleNames.Add("OpenGL");
|
||||
}
|
||||
}
|
||||
|
||||
if (Target.Platform == UnrealTargetPlatform.Win64 || Target.Platform == UnrealTargetPlatform.Android
|
||||
|| Target.IsInPlatformGroup(UnrealPlatformGroup.Linux))
|
||||
{
|
||||
PublicDependencyModuleNames.Add("VulkanRHI");
|
||||
|
||||
if (!bUsePrecompiled || Target.LinkType == TargetLinkType.Monolithic)
|
||||
{
|
||||
PublicDependencyModuleNames.Add("Vulkan");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRPassthroughColorLut.h"
|
||||
#include "OculusXRPassthroughXR.h"
|
||||
#include "OculusXRPassthroughLayerComponent.h"
|
||||
#include "OculusXRPassthroughXRFunctions.h"
|
||||
#include "OculusXRHMDPrivate.h"
|
||||
#include "OculusXRHMDModule.h"
|
||||
#include "OpenXR/OculusXROpenXRUtilities.h"
|
||||
#include "Math/UnrealMathUtility.h"
|
||||
#include "GenericPlatform/GenericPlatformMath.h"
|
||||
#include "UObject/ObjectSaveContext.h"
|
||||
#include "OculusXRHMD.h"
|
||||
#include "TextureResource.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
ovrpPassthroughColorLutChannels ToOVRPColorLutChannels(EColorLutChannels InColorLutChannels)
|
||||
{
|
||||
switch (InColorLutChannels)
|
||||
{
|
||||
case ColorLutChannels_RGB:
|
||||
return ovrpPassthroughColorLutChannels_Rgb;
|
||||
case ColorLutChannels_RGBA:
|
||||
return ovrpPassthroughColorLutChannels_Rgba;
|
||||
default:
|
||||
return ovrpPassthroughColorLutChannels_Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
TArray<uint8> ColorArrayToColorData(const TArray<FColor>& InColorArray, bool IgnoreAlphaChannel)
|
||||
{
|
||||
TArray<uint8> Data;
|
||||
const size_t ElementSize = IgnoreAlphaChannel ? 3 : 4;
|
||||
Data.SetNum(InColorArray.Num() * ElementSize);
|
||||
uint8* Dest = Data.GetData();
|
||||
for (size_t i = 0; i < InColorArray.Num(); i++)
|
||||
{
|
||||
Data[i * ElementSize + 0] = InColorArray[i].R;
|
||||
Data[i * ElementSize + 1] = InColorArray[i].G;
|
||||
Data[i * ElementSize + 2] = InColorArray[i].B;
|
||||
|
||||
if (!IgnoreAlphaChannel)
|
||||
{
|
||||
Data[i * ElementSize + 3] = InColorArray[i].A;
|
||||
}
|
||||
}
|
||||
|
||||
return Data;
|
||||
}
|
||||
|
||||
bool IsTextureDataValid(const FLutTextureData& Data)
|
||||
{
|
||||
return Data.Data.Num() > 0 && Data.Resolution > 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void UOculusXRPassthroughColorLut::SetLutFromArray(const TArray<FColor>& InColorArray, bool InIgnoreAlphaChannel)
|
||||
{
|
||||
const int32 Size = InColorArray.Num();
|
||||
const int32 Resolution = FPlatformMath::RoundToInt(FPlatformMath::Pow(Size, 1.0 / 3));
|
||||
if (Resolution > GetMaxResolution())
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("Setting array ignored: Resoluton is exceeding maximum resoluton of %d."), GetMaxResolution());
|
||||
return;
|
||||
}
|
||||
if (Resolution * Resolution * Resolution != Size)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("Setting array ignored: Provided array size is not cube."));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check if size if power of 2 */
|
||||
if ((Size & (Size - 1)) != 0)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("Setting array ignored: Provided array does not result in a resolution that is a power of two."));
|
||||
return;
|
||||
}
|
||||
|
||||
ColorLutType = EColorLutType::Array;
|
||||
|
||||
const TArray<uint8>& Data = ColorArrayToColorData(InColorArray, InIgnoreAlphaChannel);
|
||||
|
||||
if (LutHandle == 0)
|
||||
{
|
||||
LutHandle = CreateLutObject(Data, Resolution);
|
||||
return;
|
||||
}
|
||||
|
||||
if (InIgnoreAlphaChannel == IgnoreAlphaChannel && Resolution == ColorArrayResolution)
|
||||
{
|
||||
UpdateLutObject(LutHandle, Data);
|
||||
return;
|
||||
}
|
||||
|
||||
DestroyLutObject(LutHandle);
|
||||
LutHandle = CreateLutObject(Data, Resolution);
|
||||
|
||||
IgnoreAlphaChannel = InIgnoreAlphaChannel;
|
||||
ColorArrayResolution = Resolution;
|
||||
}
|
||||
|
||||
uint64 UOculusXRPassthroughColorLut::GetHandle(UOculusXRPassthroughLayerBase* LayerRef)
|
||||
{
|
||||
if (LutHandle == 0 && ColorLutType == EColorLutType::TextureLUT && IsTextureDataValid(StoredTextureData))
|
||||
{
|
||||
LutHandle = CreateLutObject(StoredTextureData.Data, StoredTextureData.Resolution);
|
||||
}
|
||||
|
||||
// Add layer to reference list
|
||||
LayerRefs.AddUnique(LayerRef->GetUniqueID());
|
||||
|
||||
return LutHandle;
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughColorLut::PreSave(FObjectPreSaveContext ObjectSaveContext)
|
||||
{
|
||||
Super::PreSave(ObjectSaveContext);
|
||||
#if WITH_EDITOR
|
||||
StoredTextureData = TextureToColorData(LutTexture);
|
||||
#endif
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughColorLut::RemoveReference(UOculusXRPassthroughLayerBase* LayerRef)
|
||||
{
|
||||
LayerRefs.Remove(LayerRef->GetUniqueID());
|
||||
|
||||
if (LayerRefs.Num() == 0)
|
||||
{
|
||||
DestroyLutObject(LutHandle);
|
||||
LutHandle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
FLutTextureData UOculusXRPassthroughColorLut::TextureToColorData(class UTexture2D* InLutTexture) const
|
||||
{
|
||||
|
||||
if (ColorLutType != EColorLutType::TextureLUT)
|
||||
{
|
||||
return FLutTextureData();
|
||||
}
|
||||
|
||||
if (InLutTexture == nullptr)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("Ignoring provided LUT texture. Provided texture is NULL."));
|
||||
return FLutTextureData();
|
||||
}
|
||||
|
||||
if (InLutTexture->LODGroup != TextureGroup::TEXTUREGROUP_ColorLookupTable)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("Ignoring provided LUT texture. Provided texture is not LUT texture."));
|
||||
return FLutTextureData();
|
||||
}
|
||||
|
||||
if (InLutTexture->GetPlatformData()->Mips.Num() <= 0)
|
||||
{
|
||||
if (IsTextureDataValid(StoredTextureData))
|
||||
{
|
||||
// We do not need to save it again. Use previously saved data.
|
||||
return StoredTextureData;
|
||||
}
|
||||
return FLutTextureData();
|
||||
}
|
||||
|
||||
const uint32 TextureWidth = InLutTexture->GetImportedSize().X;
|
||||
const uint32 TextureHeight = InLutTexture->GetImportedSize().Y;
|
||||
|
||||
uint32 ColorMapSize;
|
||||
uint32 SlicesPerRow;
|
||||
|
||||
if (TextureWidth == TextureHeight)
|
||||
{
|
||||
float EdgeLength = FPlatformMath::Pow(TextureWidth, 2.0f / 3.0f);
|
||||
ColorMapSize = FPlatformMath::RoundToInt(EdgeLength);
|
||||
if (FPlatformMath::Abs(EdgeLength - ColorMapSize) > ZERO_ANIMWEIGHT_THRESH)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("LUT width and height are equal but don't correspond to an 'exploded cube'"));
|
||||
return FLutTextureData();
|
||||
}
|
||||
|
||||
SlicesPerRow = FPlatformMath::Sqrt(ColorMapSize * 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TextureWidth != TextureHeight * TextureHeight)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("For rectangular LUTs, the width is expected to be equal to edgeLength^2"));
|
||||
return FLutTextureData();
|
||||
}
|
||||
ColorMapSize = TextureHeight;
|
||||
SlicesPerRow = TextureHeight;
|
||||
}
|
||||
|
||||
FTexture2DMipMap& MipMap = InLutTexture->GetPlatformData()->Mips[0];
|
||||
FByteBulkData* BulkData = &MipMap.BulkData;
|
||||
const FColor* FormatedImageData = reinterpret_cast<const FColor*>(BulkData->Lock(LOCK_READ_ONLY));
|
||||
|
||||
TArray<FColor> Colors;
|
||||
Colors.SetNum(ColorMapSize * ColorMapSize * ColorMapSize);
|
||||
|
||||
for (uint32 bi = 0; bi < ColorMapSize; bi++)
|
||||
{
|
||||
uint32 bi_row = bi % SlicesPerRow;
|
||||
uint32 bi_col = bi / SlicesPerRow;
|
||||
for (uint32 gi = 0; gi < ColorMapSize; gi++)
|
||||
{
|
||||
for (uint32 ri = 0; ri < ColorMapSize; ri++)
|
||||
{
|
||||
uint32 sX = ri + bi_row * ColorMapSize;
|
||||
uint32 sY = gi + bi_col * ColorMapSize;
|
||||
Colors[bi * ColorMapSize * ColorMapSize + gi * ColorMapSize + ri] = FormatedImageData[sX + sY * TextureWidth];
|
||||
}
|
||||
}
|
||||
}
|
||||
BulkData->Unlock();
|
||||
return FLutTextureData(ColorArrayToColorData(Colors, IgnoreAlphaChannel), ColorMapSize);
|
||||
}
|
||||
|
||||
uint64 UOculusXRPassthroughColorLut::CreateLutObject(const TArray<uint8>& InData, uint32 Resolution) const
|
||||
{
|
||||
if (OculusXR::IsOpenXRSystem())
|
||||
{
|
||||
TWeakPtr<XRPassthrough::FPassthroughXR> Passthrough = XRPassthrough::FPassthroughXR::GetInstance();
|
||||
if (!Passthrough.IsValid())
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("Couldn't retrieve passthrough plugin extension."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!Passthrough.Pin()->GetSettings()->bExtColorLutAvailable)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("XR_META_passthrough_color_lut extension is not available."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
XrPassthroughFB PassthroughHandle = Passthrough.Pin()->GetPassthroughInstance();
|
||||
if (PassthroughHandle == XR_NULL_HANDLE)
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("Passthrough handle is null."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
XrPassthroughColorLutCreateInfoMETA createInfo = { XR_TYPE_PASSTHROUGH_COLOR_LUT_CREATE_INFO_META };
|
||||
createInfo.channels = IgnoreAlphaChannel ? XR_PASSTHROUGH_COLOR_LUT_CHANNELS_RGB_META : XR_PASSTHROUGH_COLOR_LUT_CHANNELS_RGBA_META;
|
||||
createInfo.resolution = Resolution;
|
||||
|
||||
XrPassthroughColorLutDataMETA lutData;
|
||||
lutData.bufferSize = InData.Num();
|
||||
lutData.buffer = InData.GetData();
|
||||
createInfo.data = lutData;
|
||||
|
||||
XrPassthroughColorLutMETA outLut = XR_NULL_HANDLE;
|
||||
if (XR_FAILED(XRPassthrough::xrCreatePassthroughColorLutMETA(PassthroughHandle, &createInfo, &outLut)))
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("Failed creating passthrough color lut."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return reinterpret_cast<uint64_t>(outLut);
|
||||
}
|
||||
else
|
||||
{
|
||||
ovrpPassthroughColorLutData OVRPData;
|
||||
OVRPData.Buffer = InData.GetData();
|
||||
OVRPData.BufferSize = InData.Num();
|
||||
const EColorLutChannels Channels = IgnoreAlphaChannel ? EColorLutChannels::ColorLutChannels_RGB : EColorLutChannels::ColorLutChannels_RGBA;
|
||||
ovrpPassthroughColorLut Handle;
|
||||
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().CreatePassthroughColorLut(
|
||||
ToOVRPColorLutChannels(Channels),
|
||||
Resolution,
|
||||
OVRPData,
|
||||
&Handle)))
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("Failed creating passthrough color lut."));
|
||||
return 0;
|
||||
}
|
||||
return Handle;
|
||||
}
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughColorLut::UpdateLutObject(uint64 Handle, const TArray<uint8>& InData) const
|
||||
{
|
||||
if (Handle == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OculusXR::IsOpenXRSystem())
|
||||
{
|
||||
XrPassthroughColorLutUpdateInfoMETA UpdateInfo = { XR_TYPE_PASSTHROUGH_COLOR_LUT_UPDATE_INFO_META };
|
||||
|
||||
XrPassthroughColorLutDataMETA LutData;
|
||||
LutData.bufferSize = InData.Num();
|
||||
LutData.buffer = reinterpret_cast<const uint8_t*>(InData.GetData());
|
||||
|
||||
UpdateInfo.data = LutData;
|
||||
|
||||
if (XR_FAILED(XRPassthrough::xrUpdatePassthroughColorLutMETA(reinterpret_cast<XrPassthroughColorLutMETA>(Handle), &UpdateInfo)))
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("Failed updating passthrough color lut data."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ovrpPassthroughColorLutData OVRPData;
|
||||
OVRPData.Buffer = InData.GetData();
|
||||
OVRPData.BufferSize = InData.Num();
|
||||
|
||||
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().UpdatePassthroughColorLut(
|
||||
Handle,
|
||||
OVRPData)))
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("Failed updating passthrough color lut data."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughColorLut::DestroyLutObject(uint64 Handle) const
|
||||
{
|
||||
if (Handle == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (OculusXR::IsOpenXRSystem())
|
||||
{
|
||||
if (XR_FAILED(XRPassthrough::xrDestroyPassthroughColorLutMETA(reinterpret_cast<XrPassthroughColorLutMETA>(Handle))))
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("Failed to destroy passthrough color lut."));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().DestroyPassthroughColorLut(Handle)))
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("Failed to destroy passthrough color lut."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughColorLut::BeginDestroy()
|
||||
{
|
||||
Super::BeginDestroy();
|
||||
DestroyLutObject(LutHandle);
|
||||
LutHandle = 0;
|
||||
}
|
||||
|
||||
int UOculusXRPassthroughColorLut::GetMaxResolution()
|
||||
{
|
||||
if (MaxResolution > -1)
|
||||
{
|
||||
return MaxResolution;
|
||||
}
|
||||
|
||||
ovrpInsightPassthroughCapabilities PassthroughCapabilites;
|
||||
PassthroughCapabilites.Fields =
|
||||
static_cast<ovrpInsightPassthroughCapabilityFields>(
|
||||
ovrpInsightPassthroughCapabilityFields::ovrpInsightPassthroughCapabilityFields_Flags | ovrpInsightPassthroughCapabilityFields::ovrpInsightPassthroughCapabilityFields_MaxColorLutResolution);
|
||||
|
||||
if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetPassthroughCapabilities(&PassthroughCapabilites)))
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("Failed to fetch passthrough capabilities."));
|
||||
// Default MAX resoulution is 64.
|
||||
return 64;
|
||||
}
|
||||
MaxResolution = PassthroughCapabilites.MaxColorLutResolution;
|
||||
return MaxResolution;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRPassthroughEventHandling.h"
|
||||
|
||||
#include "OculusXRHMD.h"
|
||||
#include "OculusXRPassthroughModule.h"
|
||||
#include "OculusXRPassthroughSubsystem.h"
|
||||
|
||||
namespace OculusXRPassthrough
|
||||
{
|
||||
FOculusXRPassthroughEventDelegates::FOculusXRPassthroughLayerResumedDelegate FOculusXRPassthroughEventDelegates::OculusPassthroughLayerResumed;
|
||||
|
||||
template <typename T>
|
||||
void GetEventData(ovrpEventDataBuffer& Buffer, T& OutEventData)
|
||||
{
|
||||
unsigned char* BufData = Buffer.EventData;
|
||||
BufData -= sizeof(Buffer.EventType); // Offset buffer data to get to the actual event payload
|
||||
|
||||
memcpy(&OutEventData, BufData, sizeof(T));
|
||||
}
|
||||
|
||||
void FOculusXRPassthroughEventHandling::OnPollEvent(ovrpEventDataBuffer* EventDataBuffer, bool& EventPollResult)
|
||||
{
|
||||
ovrpEventDataBuffer& buf = *EventDataBuffer;
|
||||
EventPollResult = true;
|
||||
|
||||
switch (buf.EventType)
|
||||
{
|
||||
case ovrpEventType_PassthroughLayerResumed:
|
||||
{
|
||||
OculusXRHMD::FOculusXRHMD* HMD = OculusXRHMD::FOculusXRHMD::GetOculusXRHMD();
|
||||
|
||||
check(HMD);
|
||||
|
||||
ovrpEventDataPassthroughLayerResumed passthroughLayerResumedEvent;
|
||||
GetEventData(buf, passthroughLayerResumedEvent);
|
||||
|
||||
// Convert OVR plugin layerID to UE layerID
|
||||
int ovrpID = passthroughLayerResumedEvent.LayerId;
|
||||
uint32 LayerID = HMD->GetLayerIdFromOvrpId(ovrpID);
|
||||
|
||||
UE_LOG(LogOculusXRPassthrough, Log, TEXT("FOculusXRPassthroughEventHandling - Passthrough Layer #%d resumed"), LayerID);
|
||||
|
||||
// Send event
|
||||
FOculusXRPassthroughEventDelegates::OculusPassthroughLayerResumed.Broadcast(LayerID);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case ovrpEventType_None:
|
||||
default:
|
||||
{
|
||||
EventPollResult = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace OculusXRPassthrough
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OculusXRPluginWrapper.h"
|
||||
#include "CoreMinimal.h"
|
||||
#include "OculusXRHMDPrivate.h"
|
||||
|
||||
namespace OculusXRPassthrough
|
||||
{
|
||||
class FOculusXRPassthroughEventDelegates
|
||||
{
|
||||
public:
|
||||
/* ovrpEventType_PassthroughLayerResumed
|
||||
*
|
||||
* PassthroughLayerResumed
|
||||
* Prefix:
|
||||
* FOculusXRPassthroughLayerResumed
|
||||
* Suffix:
|
||||
* FOculusXRPassthroughLayerResumedDelegate
|
||||
*/
|
||||
DECLARE_MULTICAST_DELEGATE_OneParam(FOculusXRPassthroughLayerResumedDelegate, int /*layerId*/);
|
||||
static OCULUSXRPASSTHROUGH_API FOculusXRPassthroughLayerResumedDelegate OculusPassthroughLayerResumed;
|
||||
|
||||
};
|
||||
|
||||
struct FOculusXRPassthroughEventHandling
|
||||
{
|
||||
public:
|
||||
static void OnPollEvent(ovrpEventDataBuffer* EventDataBuffer, bool& EventPollResult);
|
||||
};
|
||||
|
||||
} // namespace OculusXRPassthrough
|
||||
@@ -0,0 +1,657 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRPassthroughLayer.h"
|
||||
|
||||
#include "Components/MeshComponent.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Materials/Material.h"
|
||||
#include "Materials/MaterialInstanceDynamic.h"
|
||||
#include "Materials/MaterialInterface.h"
|
||||
#include "OculusXRHMDPrivate.h"
|
||||
#include "OculusXRPassthroughXRFunctions.h"
|
||||
#include "OculusXRPassthroughXR.h"
|
||||
#include "OculusXRPassthroughModule.h"
|
||||
#include "ProceduralMeshComponent.h"
|
||||
#include "XRThreadUtils.h"
|
||||
|
||||
namespace XRPassthrough
|
||||
{
|
||||
static UWorld* GetWorld()
|
||||
{
|
||||
UWorld* World = nullptr;
|
||||
for (const FWorldContext& Context : GEngine->GetWorldContexts())
|
||||
{
|
||||
if (Context.WorldType == EWorldType::Game || Context.WorldType == EWorldType::PIE)
|
||||
{
|
||||
World = Context.World();
|
||||
}
|
||||
}
|
||||
return World;
|
||||
}
|
||||
|
||||
FPassthroughLayer::FPassthroughLayer(XrPassthroughFB PassthroughInstance, TWeakPtr<FPassthroughXR> Extension)
|
||||
: PassthroughExtension(Extension)
|
||||
, UserDefinedGeometryMap(nullptr)
|
||||
, PassthroughPokeActorMap(nullptr)
|
||||
, XrPassthroughLayer{ XR_NULL_HANDLE }
|
||||
, XrCompositionLayerHeader{}
|
||||
, XrPassthroughInstance(PassthroughInstance)
|
||||
{
|
||||
}
|
||||
|
||||
FPassthroughLayer::FPassthroughLayer(const FPassthroughLayer& Layer)
|
||||
: PassthroughExtension(Layer.PassthroughExtension)
|
||||
, UserDefinedGeometryMap(Layer.UserDefinedGeometryMap)
|
||||
, PassthroughPokeActorMap(Layer.PassthroughPokeActorMap)
|
||||
, Session(Layer.Session)
|
||||
, LayerDesc(Layer.LayerDesc)
|
||||
, XrPassthroughLayer(Layer.XrPassthroughLayer)
|
||||
, XrCompositionLayerHeader(Layer.XrCompositionLayerHeader)
|
||||
, XrPassthroughInstance(Layer.XrPassthroughInstance)
|
||||
{
|
||||
}
|
||||
|
||||
TSharedPtr<FPassthroughLayer, ESPMode::ThreadSafe> FPassthroughLayer::Clone() const
|
||||
{
|
||||
return MakeShareable(new FPassthroughLayer(*this));
|
||||
}
|
||||
|
||||
FPassthroughLayer::~FPassthroughLayer()
|
||||
{
|
||||
}
|
||||
|
||||
void FPassthroughLayer::SetDesc(const IStereoLayers::FLayerDesc& InLayerDesc)
|
||||
{
|
||||
LayerDesc = InLayerDesc;
|
||||
|
||||
if (!PassthroughPokeActorMap)
|
||||
{
|
||||
PassthroughPokeActorMap = MakeShared<TMap<FString, FPassthroughPokeActor>, ESPMode::ThreadSafe>();
|
||||
}
|
||||
|
||||
UpdatePassthroughPokeActors_GameThread();
|
||||
}
|
||||
|
||||
void FPassthroughLayer::DestroyLayer()
|
||||
{
|
||||
OculusXRHMD::CheckInGameThread();
|
||||
|
||||
ClearPassthroughPokeActors();
|
||||
}
|
||||
|
||||
void FPassthroughLayer::DestroyLayer_RenderThread()
|
||||
{
|
||||
OculusXRHMD::CheckInRenderThread();
|
||||
|
||||
// Clear user defined meshes
|
||||
for (auto& Entry : *UserDefinedGeometryMap)
|
||||
{
|
||||
const XrTriangleMeshFB MeshHandle = Entry.Value.MeshHandle;
|
||||
const XrGeometryInstanceFB InstanceHandle = Entry.Value.InstanceHandle;
|
||||
RemovePassthroughMesh_RenderThread(MeshHandle, InstanceHandle);
|
||||
}
|
||||
UserDefinedGeometryMap->Empty();
|
||||
|
||||
// Destroy passthrough layer
|
||||
if (XrPassthroughLayer != XR_NULL_HANDLE)
|
||||
{
|
||||
xrDestroyPassthroughLayerFB(XrPassthroughLayer);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Warning, TEXT("Failed to destroy layer as handle was null"));
|
||||
}
|
||||
}
|
||||
|
||||
bool FPassthroughLayer::IsPassthoughLayerDesc(const IStereoLayers::FLayerDesc& LayerDesc)
|
||||
{
|
||||
return LayerDesc.HasShape<FReconstructedLayer>() || LayerDesc.HasShape<FUserDefinedLayer>();
|
||||
}
|
||||
|
||||
bool FPassthroughLayer::CanReuseResources(const FPassthroughLayer* InLayer) const
|
||||
{
|
||||
if (!InLayer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsPassthoughLayerDesc(InLayer->LayerDesc) || InLayer->LayerDesc.HasShape<FReconstructedLayer>() != LayerDesc.HasShape<FReconstructedLayer>() || InLayer->LayerDesc.HasShape<FUserDefinedLayer>() != LayerDesc.HasShape<FUserDefinedLayer>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FPassthroughLayer::Initialize_RenderThread(XrSession InSession, const FPassthroughLayer* InLayer)
|
||||
{
|
||||
OculusXRHMD::CheckInRenderThread();
|
||||
|
||||
if (!CanReuseResources(InLayer))
|
||||
{
|
||||
Session = InSession;
|
||||
|
||||
if (XrPassthroughLayer != XR_NULL_HANDLE)
|
||||
{
|
||||
xrDestroyPassthroughLayerFB(XrPassthroughLayer);
|
||||
XrPassthroughLayer = XR_NULL_HANDLE;
|
||||
}
|
||||
|
||||
if (LayerDesc.HasShape<FReconstructedLayer>() || LayerDesc.HasShape<FUserDefinedLayer>())
|
||||
{
|
||||
XrPassthroughLayerCreateInfoFB PassthroughLayerCreateInfo = { XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB };
|
||||
PassthroughLayerCreateInfo.passthrough = XrPassthroughInstance;
|
||||
PassthroughLayerCreateInfo.purpose = LayerDesc.HasShape<FReconstructedLayer>() ? XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB : XR_PASSTHROUGH_LAYER_PURPOSE_PROJECTED_FB;
|
||||
|
||||
XrResult CreateLayerResult = xrCreatePassthroughLayerFB(Session, &PassthroughLayerCreateInfo, &XrPassthroughLayer);
|
||||
if (!XR_SUCCEEDED(CreateLayerResult))
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Warning, TEXT("Failed to create passthrough layer, error : %i"), CreateLayerResult);
|
||||
return false;
|
||||
}
|
||||
|
||||
XrResult ResumeLayerResult = xrPassthroughLayerResumeFB(XrPassthroughLayer);
|
||||
if (!XR_SUCCEEDED(ResumeLayerResult))
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Warning, TEXT("Failed to resume passthrough layer, error : %i"), ResumeLayerResult);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PassthroughExtension = InLayer->PassthroughExtension;
|
||||
UserDefinedGeometryMap = InLayer->UserDefinedGeometryMap;
|
||||
PassthroughPokeActorMap = InLayer->PassthroughPokeActorMap;
|
||||
Session = InLayer->Session;
|
||||
XrPassthroughLayer = InLayer->XrPassthroughLayer;
|
||||
XrCompositionLayerHeader = InLayer->XrCompositionLayerHeader;
|
||||
XrPassthroughInstance = InLayer->XrPassthroughInstance;
|
||||
}
|
||||
|
||||
if (!UserDefinedGeometryMap)
|
||||
{
|
||||
UserDefinedGeometryMap = MakeShared<TMap<FString, FPassthroughMesh>, ESPMode::ThreadSafe>();
|
||||
}
|
||||
|
||||
check(IsPassthoughLayerDesc(LayerDesc));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FPassthroughLayer::BuildPassthroughPokeActor(OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh, FPassthroughPokeActor& OutPassthroughPokeActor)
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
|
||||
if (!World || !PassthroughExtension.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const FString BaseComponentName = FString::Printf(TEXT("OculusPassthroughPoke_%d"), LayerDesc.Id);
|
||||
const FName ComponentName(*BaseComponentName);
|
||||
AActor* PassthoughPokeActor = World->SpawnActor<AActor>();
|
||||
UProceduralMeshComponent* PassthoughPokeComponentPtr = NewObject<UProceduralMeshComponent>(PassthoughPokeActor, ComponentName);
|
||||
PassthoughPokeComponentPtr->RegisterComponent();
|
||||
|
||||
const TArray<int32>& Triangles = PassthroughMesh->GetTriangles();
|
||||
const TArray<FVector>& Vertices = PassthroughMesh->GetVertices();
|
||||
TArray<FVector> Normals;
|
||||
TArray<FVector2D> UV0;
|
||||
TArray<FLinearColor> VertexColors;
|
||||
TArray<FProcMeshTangent> Tangents;
|
||||
|
||||
PassthoughPokeComponentPtr->CreateMeshSection_LinearColor(0, Vertices, Triangles, Normals, UV0, VertexColors, Tangents, false);
|
||||
|
||||
UMaterial* PokeAHoleMaterial = PassthroughExtension.Pin()->GetSettings()->PokeAHoleMaterial;
|
||||
|
||||
if (PokeAHoleMaterial)
|
||||
{
|
||||
UMaterialInstanceDynamic* DynamicMaterial = UMaterialInstanceDynamic::Create(PokeAHoleMaterial, nullptr);
|
||||
PassthoughPokeComponentPtr->SetMaterial(0, DynamicMaterial);
|
||||
}
|
||||
|
||||
OutPassthroughPokeActor.PokeAHoleActor = PassthoughPokeActor;
|
||||
OutPassthroughPokeActor.PokeAHoleComponentPtr = PassthoughPokeComponentPtr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FPassthroughLayer::UpdatePassthroughPokeActors_GameThread()
|
||||
{
|
||||
if (LayerDesc.HasShape<FUserDefinedLayer>())
|
||||
{
|
||||
const FUserDefinedLayer& UserDefinedLayerProps = LayerDesc.GetShape<FUserDefinedLayer>();
|
||||
const TArray<FUserDefinedGeometryDesc>& UserGeometryList = UserDefinedLayerProps.UserGeometryList;
|
||||
TSet<FString> UsedSet = {};
|
||||
|
||||
if (PassthroughSupportsDepth())
|
||||
{
|
||||
for (const FUserDefinedGeometryDesc& GeometryDesc : UserGeometryList)
|
||||
{
|
||||
const FString MeshName = GeometryDesc.MeshName;
|
||||
UsedSet.Add(MeshName);
|
||||
|
||||
FPassthroughPokeActor* FoundPassthroughPokeActor = PassthroughPokeActorMap->Find(MeshName);
|
||||
if (!FoundPassthroughPokeActor)
|
||||
{
|
||||
OculusXRHMD::FOculusPassthroughMeshRef GeomPassthroughMesh = GeometryDesc.PassthroughMesh;
|
||||
if (GeomPassthroughMesh)
|
||||
{
|
||||
FPassthroughPokeActor PassthroughPokeActor;
|
||||
if (BuildPassthroughPokeActor(GeomPassthroughMesh, PassthroughPokeActor))
|
||||
{
|
||||
PassthroughPokeActor.PokeAHoleComponentPtr->SetWorldTransform(GeometryDesc.Transform);
|
||||
PassthroughPokeActorMap->Add(MeshName, PassthroughPokeActor);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (GeometryDesc.bUpdateTransform && FoundPassthroughPokeActor->PokeAHoleComponentPtr.IsValid())
|
||||
{
|
||||
FoundPassthroughPokeActor->PokeAHoleComponentPtr->SetWorldTransform(GeometryDesc.Transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find actors that no longer exist
|
||||
TArray<FString> ItemsToRemove;
|
||||
for (auto& Entry : *PassthroughPokeActorMap)
|
||||
{
|
||||
if (!UsedSet.Contains(Entry.Key))
|
||||
{
|
||||
ItemsToRemove.Add(Entry.Key);
|
||||
}
|
||||
}
|
||||
|
||||
for (FString Entry : ItemsToRemove)
|
||||
{
|
||||
FPassthroughPokeActor* PassthroughPokeActor = PassthroughPokeActorMap->Find(Entry);
|
||||
if (PassthroughPokeActor)
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
if (World && PassthroughPokeActor->PokeAHoleActor.IsValid())
|
||||
{
|
||||
World->DestroyActor(PassthroughPokeActor->PokeAHoleActor.Get());
|
||||
}
|
||||
}
|
||||
PassthroughPokeActorMap->Remove(Entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FPassthroughLayer::UpdatePassthroughStyle_RenderThread(const FEdgeStyleParameters& EdgeStyleParameters)
|
||||
{
|
||||
if (!PassthroughExtension.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
XrPassthroughStyleFB Style = { XR_TYPE_PASSTHROUGH_STYLE_FB };
|
||||
|
||||
Style.textureOpacityFactor = EdgeStyleParameters.TextureOpacityFactor;
|
||||
|
||||
Style.edgeColor = { 0, 0, 0, 0 };
|
||||
if (EdgeStyleParameters.bEnableEdgeColor)
|
||||
{
|
||||
Style.edgeColor = {
|
||||
EdgeStyleParameters.EdgeColor.R,
|
||||
EdgeStyleParameters.EdgeColor.G,
|
||||
EdgeStyleParameters.EdgeColor.B,
|
||||
EdgeStyleParameters.EdgeColor.A
|
||||
};
|
||||
}
|
||||
|
||||
/// Color map
|
||||
union AllColorMapDescriptors
|
||||
{
|
||||
XrPassthroughColorMapMonoToRgbaFB rgba;
|
||||
XrPassthroughColorMapMonoToMonoFB mono;
|
||||
XrPassthroughBrightnessContrastSaturationFB bcs;
|
||||
XrPassthroughColorMapLutMETA lut;
|
||||
XrPassthroughColorMapInterpolatedLutMETA interpLut;
|
||||
};
|
||||
AllColorMapDescriptors colorMap;
|
||||
if (PassthroughExtension.Pin()->GetSettings()->bExtColorLutAvailable && EdgeStyleParameters.bEnableColorMap)
|
||||
{
|
||||
void* colorMapDataDestination = nullptr;
|
||||
unsigned int expectedColorMapDataSize = 0;
|
||||
switch (EdgeStyleParameters.ColorMapType)
|
||||
{
|
||||
case ColorMapType_None:
|
||||
break;
|
||||
case ColorMapType_GrayscaleToColor:
|
||||
colorMap.rgba = { XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_RGBA_FB };
|
||||
expectedColorMapDataSize = sizeof(colorMap.rgba.textureColorMap);
|
||||
colorMapDataDestination = colorMap.rgba.textureColorMap;
|
||||
Style.next = &colorMap.rgba;
|
||||
break;
|
||||
case ColorMapType_Grayscale:
|
||||
colorMap.mono = { XR_TYPE_PASSTHROUGH_COLOR_MAP_MONO_TO_MONO_FB };
|
||||
expectedColorMapDataSize = sizeof(colorMap.mono.textureColorMap);
|
||||
colorMapDataDestination = colorMap.mono.textureColorMap;
|
||||
Style.next = &colorMap.mono;
|
||||
break;
|
||||
case ColorMapType_ColorAdjustment:
|
||||
colorMap.bcs = { XR_TYPE_PASSTHROUGH_BRIGHTNESS_CONTRAST_SATURATION_FB };
|
||||
expectedColorMapDataSize = 3 * sizeof(float);
|
||||
colorMapDataDestination = &colorMap.bcs.brightness;
|
||||
Style.next = &colorMap.bcs;
|
||||
break;
|
||||
case ColorMapType_ColorLut:
|
||||
colorMap.lut = { XR_TYPE_PASSTHROUGH_COLOR_MAP_LUT_META };
|
||||
colorMap.lut.colorLut = reinterpret_cast<const XrPassthroughColorLutMETA&>(EdgeStyleParameters.ColorLutDesc.ColorLuts[0]);
|
||||
colorMap.lut.weight = EdgeStyleParameters.ColorLutDesc.Weight;
|
||||
Style.next = &colorMap.lut;
|
||||
break;
|
||||
case ColorMapType_ColorLut_Interpolated:
|
||||
colorMap.interpLut = { XR_TYPE_PASSTHROUGH_COLOR_MAP_INTERPOLATED_LUT_META };
|
||||
colorMap.interpLut.sourceColorLut = reinterpret_cast<const XrPassthroughColorLutMETA&>(EdgeStyleParameters.ColorLutDesc.ColorLuts[0]);
|
||||
colorMap.interpLut.targetColorLut = reinterpret_cast<const XrPassthroughColorLutMETA&>(EdgeStyleParameters.ColorLutDesc.ColorLuts[1]);
|
||||
colorMap.interpLut.weight = EdgeStyleParameters.ColorLutDesc.Weight;
|
||||
Style.next = &colorMap.lut;
|
||||
break;
|
||||
default:
|
||||
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Passthrough style has unexpected color map type: %i"), EdgeStyleParameters.ColorMapType);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate color map data size and copy it over
|
||||
if (colorMapDataDestination != nullptr)
|
||||
{
|
||||
if (EdgeStyleParameters.ColorMapData.Num() != expectedColorMapDataSize)
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Error,
|
||||
TEXT("Passthrough color map size for type %i is expected to be %i instead of %i"),
|
||||
EdgeStyleParameters.ColorMapType,
|
||||
expectedColorMapDataSize,
|
||||
EdgeStyleParameters.ColorMapData.Num());
|
||||
return;
|
||||
}
|
||||
|
||||
uint8* ColorMapData = (uint8*)EdgeStyleParameters.ColorMapData.GetData();
|
||||
memcpy(colorMapDataDestination, ColorMapData, expectedColorMapDataSize);
|
||||
}
|
||||
}
|
||||
|
||||
XrResult Result = xrPassthroughLayerSetStyleFB(XrPassthroughLayer, &Style);
|
||||
if (!XR_SUCCEEDED(Result))
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed setting passthrough style, error : %i"), Result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static FMatrix TransformToPassthroughSpace(FTransform Transform, float WorldToMetersScale, FTransform TrackingToWorld)
|
||||
{
|
||||
const FVector WorldToMetersScaleInv = FVector(WorldToMetersScale).Reciprocal();
|
||||
FTransform TransformWorld = Transform * TrackingToWorld.Inverse();
|
||||
TransformWorld.MultiplyScale3D(WorldToMetersScaleInv);
|
||||
TransformWorld.ScaleTranslation(WorldToMetersScaleInv);
|
||||
const FMatrix TransformWorldScaled = TransformWorld.ToMatrixWithScale();
|
||||
|
||||
const FMatrix SwapAxisMatrix(
|
||||
FPlane(0.0f, 0.0f, -1.0f, 0.0f),
|
||||
FPlane(1.0f, 0.0f, 0.0f, 0.0f),
|
||||
FPlane(0.0f, 1.0f, 0.0f, 0.0f),
|
||||
FPlane(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
|
||||
return TransformWorldScaled * SwapAxisMatrix;
|
||||
}
|
||||
|
||||
void FPassthroughLayer::UpdatePassthrough_RenderThread(FRHICommandListImmediate& RHICmdList, XrSpace Space, XrTime Time, float WorldToMetersScale, FTransform TrackingToWorld)
|
||||
{
|
||||
check(IsInRenderingThread());
|
||||
|
||||
if (LayerDesc.HasShape<FReconstructedLayer>())
|
||||
{
|
||||
const FReconstructedLayer& ReconstructedLayerProps = LayerDesc.GetShape<FReconstructedLayer>();
|
||||
UpdatePassthroughStyle_RenderThread(ReconstructedLayerProps.EdgeStyleParameters);
|
||||
}
|
||||
else if (LayerDesc.HasShape<FUserDefinedLayer>())
|
||||
{
|
||||
const FUserDefinedLayer& UserDefinedLayerProps = LayerDesc.GetShape<FUserDefinedLayer>();
|
||||
UpdatePassthroughStyle_RenderThread(UserDefinedLayerProps.EdgeStyleParameters);
|
||||
}
|
||||
|
||||
if (LayerDesc.HasShape<FUserDefinedLayer>())
|
||||
{
|
||||
const FUserDefinedLayer& UserDefinedLayerProps = LayerDesc.GetShape<FUserDefinedLayer>();
|
||||
const TArray<FUserDefinedGeometryDesc>& UserGeometryList = UserDefinedLayerProps.UserGeometryList;
|
||||
TSet<FString> UsedSet;
|
||||
|
||||
for (const FUserDefinedGeometryDesc& GeometryDesc : UserGeometryList)
|
||||
{
|
||||
const FString MeshName = GeometryDesc.MeshName;
|
||||
UsedSet.Add(MeshName);
|
||||
|
||||
FPassthroughMesh* LayerPassthroughMesh = UserDefinedGeometryMap->Find(MeshName);
|
||||
if (!LayerPassthroughMesh)
|
||||
{
|
||||
OculusXRHMD::FOculusPassthroughMeshRef GeomPassthroughMesh = GeometryDesc.PassthroughMesh;
|
||||
if (GeomPassthroughMesh)
|
||||
{
|
||||
const FMatrix Transform = TransformToPassthroughSpace(GeometryDesc.Transform, WorldToMetersScale, TrackingToWorld);
|
||||
XrTriangleMeshFB MeshHandle = 0;
|
||||
XrGeometryInstanceFB InstanceHandle = 0;
|
||||
AddPassthroughMesh_RenderThread(GeomPassthroughMesh->GetVertices(), GeomPassthroughMesh->GetTriangles(), Transform, Space, MeshHandle, InstanceHandle);
|
||||
UserDefinedGeometryMap->Add(MeshName, FPassthroughMesh(MeshHandle, InstanceHandle, GeometryDesc.Transform));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const FMatrix Transform = TransformToPassthroughSpace(GeometryDesc.Transform, WorldToMetersScale, TrackingToWorld);
|
||||
UpdatePassthroughMeshTransform_RenderThread(LayerPassthroughMesh->InstanceHandle, Transform, Space, Time);
|
||||
LayerPassthroughMesh->LastTransform = GeometryDesc.Transform;
|
||||
}
|
||||
}
|
||||
|
||||
// find meshes that no longer exist
|
||||
TArray<FString> ItemsToRemove;
|
||||
for (auto& Entry : *UserDefinedGeometryMap)
|
||||
{
|
||||
if (!UsedSet.Contains(Entry.Key))
|
||||
{
|
||||
ItemsToRemove.Add(Entry.Key);
|
||||
}
|
||||
}
|
||||
|
||||
for (FString Entry : ItemsToRemove)
|
||||
{
|
||||
FPassthroughMesh* PassthroughMesh = UserDefinedGeometryMap->Find(Entry);
|
||||
if (PassthroughMesh)
|
||||
{
|
||||
const XrTriangleMeshFB MeshHandle = PassthroughMesh->MeshHandle;
|
||||
const XrGeometryInstanceFB InstanceHandle = PassthroughMesh->InstanceHandle;
|
||||
RemovePassthroughMesh_RenderThread(MeshHandle, InstanceHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Error, TEXT("PassthroughMesh: %s doesn't exist."), *Entry);
|
||||
return;
|
||||
}
|
||||
|
||||
UserDefinedGeometryMap->Remove(Entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XrCompositionLayerBaseHeaderType* FPassthroughLayer::GetXrCompositionLayerHeader()
|
||||
{
|
||||
OculusXRHMD::CheckInRHIThread();
|
||||
if (XrPassthroughLayer != nullptr)
|
||||
{
|
||||
XrCompositionLayerPassthroughFB& CompositionLayer = XrCompositionLayerHeader;
|
||||
memset(&CompositionLayer, 0, sizeof(CompositionLayer));
|
||||
CompositionLayer.type = XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB;
|
||||
CompositionLayer.layerHandle = XrPassthroughLayer;
|
||||
CompositionLayer.flags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
|
||||
CompositionLayer.space = XR_NULL_HANDLE;
|
||||
return reinterpret_cast<XrCompositionLayerBaseHeaderType*>(&CompositionLayer);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool FPassthroughLayer::IsBackgroundLayer() const
|
||||
{
|
||||
return (LayerDesc.HasShape<FReconstructedLayer>() && (LayerDesc.GetShape<FReconstructedLayer>().PassthroughLayerOrder == PassthroughLayerOrder_Underlay))
|
||||
|| (LayerDesc.HasShape<FUserDefinedLayer>() && (LayerDesc.GetShape<FUserDefinedLayer>().PassthroughLayerOrder == PassthroughLayerOrder_Underlay));
|
||||
}
|
||||
|
||||
bool FPassthroughLayer::IsOverlayLayer() const
|
||||
{
|
||||
return (LayerDesc.HasShape<FReconstructedLayer>() && (LayerDesc.GetShape<FReconstructedLayer>().PassthroughLayerOrder == PassthroughLayerOrder_Overlay))
|
||||
|| (LayerDesc.HasShape<FUserDefinedLayer>() && (LayerDesc.GetShape<FUserDefinedLayer>().PassthroughLayerOrder == PassthroughLayerOrder_Overlay));
|
||||
}
|
||||
|
||||
bool FPassthroughLayer::PassthroughSupportsDepth() const
|
||||
{
|
||||
return ((LayerDesc.Flags & IStereoLayers::LAYER_FLAG_SUPPORT_DEPTH) != 0) && LayerDesc.HasShape<FUserDefinedLayer>();
|
||||
}
|
||||
|
||||
// Code taken from OVRPlugin (InsightMrManager.cpp)
|
||||
static bool DecomposeTransformMatrix(FMatrix Transform, XrPosef& OutPose, XrVector3f& OutScale)
|
||||
{
|
||||
FTransform outTransform = FTransform(Transform);
|
||||
FVector3f scale = FVector3f(outTransform.GetScale3D());
|
||||
FQuat4f rotation = FQuat4f(outTransform.GetRotation());
|
||||
FVector3f position = FVector3f(outTransform.GetLocation());
|
||||
|
||||
if (scale.X == 0 || scale.Y == 0 || scale.Z == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutScale = XrVector3f{ scale.X, scale.Y, scale.Z };
|
||||
OutPose = XrPosef{ XrQuaternionf{ rotation.X, rotation.Y, rotation.Z, rotation.W }, XrVector3f{ position.X, position.Y, position.Z } };
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FPassthroughLayer::AddPassthroughMesh_RenderThread(const TArray<FVector>& Vertices, const TArray<int32>& Triangles, FMatrix Transformation, XrSpace Space, XrTriangleMeshFB& OutMeshHandle, XrGeometryInstanceFB& OutInstanceHandle)
|
||||
{
|
||||
OculusXRHMD::CheckInRenderThread();
|
||||
|
||||
XrTriangleMeshFB MeshHandle = 0;
|
||||
XrGeometryInstanceFB InstanceHandle = 0;
|
||||
|
||||
// Explicit conversion is needed since FVector contains double elements.
|
||||
// Converting Vertices.Data() to float* causes issues when memory is parsed.
|
||||
TArray<XrVector3f> VertexData;
|
||||
VertexData.SetNumUninitialized(Vertices.Num());
|
||||
|
||||
size_t i = 0;
|
||||
for (const FVector& vertex : Vertices)
|
||||
{
|
||||
VertexData[i++] = { (float)vertex.X, (float)vertex.Y, (float)vertex.Z };
|
||||
}
|
||||
|
||||
TArray<uint32_t> TriangleData;
|
||||
TriangleData.SetNumUninitialized(Triangles.Num());
|
||||
|
||||
i = 0;
|
||||
for (const int32& tri : Triangles)
|
||||
{
|
||||
TriangleData[i++] = (uint32_t)tri;
|
||||
}
|
||||
|
||||
XrTriangleMeshCreateInfoFB TriangleMeshInfo = { XR_TYPE_TRIANGLE_MESH_CREATE_INFO_FB };
|
||||
TriangleMeshInfo.flags = 0; // not mutable
|
||||
TriangleMeshInfo.triangleCount = Triangles.Num() / 3;
|
||||
TriangleMeshInfo.indexBuffer = TriangleData.GetData();
|
||||
TriangleMeshInfo.vertexCount = Vertices.Num();
|
||||
TriangleMeshInfo.vertexBuffer = VertexData.GetData();
|
||||
TriangleMeshInfo.windingOrder = XR_WINDING_ORDER_UNKNOWN_FB;
|
||||
|
||||
if (XR_FAILED(xrCreateTriangleMeshFB.GetValue()(Session, &TriangleMeshInfo, &MeshHandle)))
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed creating passthrough mesh surface."));
|
||||
return;
|
||||
}
|
||||
|
||||
XrGeometryInstanceCreateInfoFB createInfo = { XR_TYPE_GEOMETRY_INSTANCE_CREATE_INFO_FB };
|
||||
|
||||
bool result = DecomposeTransformMatrix(Transformation, createInfo.pose, createInfo.scale);
|
||||
if (!result)
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed decomposing the transform matrix."));
|
||||
return;
|
||||
}
|
||||
|
||||
createInfo.layer = XrPassthroughLayer;
|
||||
createInfo.mesh = MeshHandle;
|
||||
createInfo.baseSpace = Space;
|
||||
|
||||
if (XR_FAILED(xrCreateGeometryInstanceFB(Session, &createInfo, &InstanceHandle)))
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed adding passthrough mesh surface to scene."));
|
||||
return;
|
||||
}
|
||||
|
||||
OutMeshHandle = MeshHandle;
|
||||
OutInstanceHandle = InstanceHandle;
|
||||
}
|
||||
|
||||
void FPassthroughLayer::UpdatePassthroughMeshTransform_RenderThread(XrGeometryInstanceFB InstanceHandle, FMatrix Transformation, XrSpace Space, XrTime Time)
|
||||
{
|
||||
OculusXRHMD::CheckInRenderThread();
|
||||
|
||||
XrGeometryInstanceTransformFB UpdateInfo = { XR_TYPE_GEOMETRY_INSTANCE_TRANSFORM_FB };
|
||||
bool result = DecomposeTransformMatrix(Transformation, UpdateInfo.pose, UpdateInfo.scale);
|
||||
if (!result)
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed decomposing the transform matrix."));
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateInfo.baseSpace = Space;
|
||||
UpdateInfo.time = Time;
|
||||
|
||||
if (XR_FAILED(xrGeometryInstanceSetTransformFB(InstanceHandle, &UpdateInfo)))
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed updating passthrough mesh surface transform."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void FPassthroughLayer::RemovePassthroughMesh_RenderThread(XrTriangleMeshFB MeshHandle, XrGeometryInstanceFB InstanceHandle)
|
||||
{
|
||||
OculusXRHMD::CheckInRenderThread();
|
||||
|
||||
if (XR_FAILED(xrDestroyGeometryInstanceFB(InstanceHandle)))
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed removing passthrough surface from scene."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (XR_FAILED(xrDestroyTriangleMeshFB.GetValue()(MeshHandle)))
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Error, TEXT("Failed destroying passthrough surface mesh."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void FPassthroughLayer::ClearPassthroughPokeActors()
|
||||
{
|
||||
if (PassthroughPokeActorMap)
|
||||
{
|
||||
UWorld* World = GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Warning, TEXT("Couldn't retrieve World. Passthrough Pokeahole actors will not be destroyed."));
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& Entry : *PassthroughPokeActorMap)
|
||||
{
|
||||
// Check if actor is still valid. In some specific cases the actor might be destroyed before we clean the PokeActorMap
|
||||
// (e.g. when loading to a new level)
|
||||
if (Entry.Value.PokeAHoleActor.IsValid())
|
||||
{
|
||||
World->DestroyActor(Entry.Value.PokeAHoleActor.Get());
|
||||
}
|
||||
}
|
||||
PassthroughPokeActorMap.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace XRPassthrough
|
||||
@@ -0,0 +1,155 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "khronos/openxr/openxr.h"
|
||||
#include "IStereoLayers.h"
|
||||
#include "OculusXRPassthroughLayerShapes.h"
|
||||
#include "OculusXRPassthroughMesh.h"
|
||||
|
||||
class UProceduralMeshComponent;
|
||||
|
||||
namespace XRPassthrough
|
||||
{
|
||||
|
||||
#ifdef WITH_OCULUS_BRANCH
|
||||
using XrCompositionLayerBaseHeaderType = XrCompositionLayerBaseHeader;
|
||||
#else
|
||||
// epic branch has member as const
|
||||
using XrCompositionLayerBaseHeaderType = const XrCompositionLayerBaseHeader;
|
||||
#endif
|
||||
|
||||
class FPassthroughXR;
|
||||
|
||||
class FPassthroughLayer
|
||||
{
|
||||
private:
|
||||
struct FPassthroughMesh
|
||||
{
|
||||
FPassthroughMesh(XrTriangleMeshFB MeshHandle, XrGeometryInstanceFB InstanceHandle, FTransform Transform)
|
||||
: MeshHandle(MeshHandle)
|
||||
, InstanceHandle(InstanceHandle)
|
||||
, LastTransform(Transform)
|
||||
{
|
||||
}
|
||||
XrTriangleMeshFB MeshHandle;
|
||||
XrGeometryInstanceFB InstanceHandle;
|
||||
FTransform LastTransform;
|
||||
};
|
||||
typedef TSharedPtr<TMap<FString, FPassthroughMesh>, ESPMode::ThreadSafe> FUserDefinedGeometryMapPtr;
|
||||
|
||||
struct FPassthroughPokeActor
|
||||
{
|
||||
FPassthroughPokeActor(){};
|
||||
FPassthroughPokeActor(TWeakObjectPtr<UProceduralMeshComponent> PokeAHoleComponentPtr, TWeakObjectPtr<AActor> PokeAHoleActor)
|
||||
: PokeAHoleComponentPtr(PokeAHoleComponentPtr)
|
||||
, PokeAHoleActor(PokeAHoleActor){};
|
||||
TWeakObjectPtr<UProceduralMeshComponent> PokeAHoleComponentPtr;
|
||||
TWeakObjectPtr<AActor> PokeAHoleActor;
|
||||
};
|
||||
|
||||
typedef TSharedPtr<TMap<FString, FPassthroughPokeActor>, ESPMode::ThreadSafe> FPassthroughPokeActorMapPtr;
|
||||
|
||||
public:
|
||||
static bool IsPassthoughLayerDesc(const IStereoLayers::FLayerDesc& LayerDesc);
|
||||
FPassthroughLayer(XrPassthroughFB PassthroughInstance, TWeakPtr<FPassthroughXR> Extension);
|
||||
FPassthroughLayer(const FPassthroughLayer& Layer);
|
||||
TSharedPtr<FPassthroughLayer, ESPMode::ThreadSafe> Clone() const;
|
||||
virtual ~FPassthroughLayer();
|
||||
void SetDesc(const IStereoLayers::FLayerDesc& InLayerDesc);
|
||||
void DestroyLayer();
|
||||
void DestroyLayer_RenderThread();
|
||||
bool CanReuseResources(const FPassthroughLayer* InLayer) const;
|
||||
|
||||
bool Initialize_RenderThread(XrSession InSession, const FPassthroughLayer* InLayer = nullptr);
|
||||
bool BuildPassthroughPokeActor(OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh, FPassthroughPokeActor& OutPassthroughPokeActor);
|
||||
void UpdatePassthroughPokeActors_GameThread();
|
||||
void UpdatePassthroughStyle_RenderThread(const FEdgeStyleParameters& EdgeStyleParameters);
|
||||
void UpdatePassthrough_RenderThread(FRHICommandListImmediate& RHICmdList, XrSpace Space, XrTime Time, float WorldToMetersScale, FTransform TrackingToWorld);
|
||||
|
||||
XrCompositionLayerBaseHeaderType* GetXrCompositionLayerHeader();
|
||||
bool IsBackgroundLayer() const;
|
||||
bool IsOverlayLayer() const;
|
||||
bool PassthroughSupportsDepth() const;
|
||||
const IStereoLayers::FLayerDesc& GetDesc() const { return LayerDesc; };
|
||||
const XrPassthroughLayerFB GetLayerHandle() const { return XrPassthroughLayer; }
|
||||
|
||||
void AddPassthroughMesh_RenderThread(const TArray<FVector>& Vertices, const TArray<int32>& Triangles, FMatrix Transformation, XrSpace Space, XrTriangleMeshFB& OutMeshHandle, XrGeometryInstanceFB& OutInstanceHandle);
|
||||
void UpdatePassthroughMeshTransform_RenderThread(XrGeometryInstanceFB InstanceHandle, FMatrix Transformation, XrSpace Space, XrTime Time);
|
||||
void RemovePassthroughMesh_RenderThread(XrTriangleMeshFB MeshHandle, XrGeometryInstanceFB InstanceHandle);
|
||||
void ClearPassthroughPokeActors();
|
||||
|
||||
private:
|
||||
TWeakPtr<FPassthroughXR> PassthroughExtension;
|
||||
|
||||
FUserDefinedGeometryMapPtr UserDefinedGeometryMap;
|
||||
FPassthroughPokeActorMapPtr PassthroughPokeActorMap;
|
||||
|
||||
XrSession Session;
|
||||
IStereoLayers::FLayerDesc LayerDesc;
|
||||
XrPassthroughLayerFB XrPassthroughLayer;
|
||||
XrCompositionLayerPassthroughFB XrCompositionLayerHeader;
|
||||
XrPassthroughFB XrPassthroughInstance;
|
||||
};
|
||||
|
||||
typedef TSharedPtr<FPassthroughLayer, ESPMode::ThreadSafe> FPassthroughLayerPtr;
|
||||
|
||||
struct FPassthroughLayerPtr_CompareId
|
||||
{
|
||||
FORCEINLINE bool operator()(const FPassthroughLayerPtr& A, const FPassthroughLayerPtr& B) const
|
||||
{
|
||||
return A->GetDesc().GetLayerId() < B->GetDesc().GetLayerId();
|
||||
}
|
||||
};
|
||||
|
||||
struct FLayerDesc_ComparePriority
|
||||
{
|
||||
FORCEINLINE int32 GetLayerTypePriority(const IStereoLayers::FLayerDesc& LayerDesc) const
|
||||
{
|
||||
const bool IsPokeAHole = ((LayerDesc.Flags & IStereoLayers::LAYER_FLAG_SUPPORT_DEPTH) != 0) && LayerDesc.HasShape<FUserDefinedLayer>();
|
||||
bool IsUnderlay = false;
|
||||
|
||||
if (LayerDesc.HasShape<FReconstructedLayer>())
|
||||
{
|
||||
const FReconstructedLayer& ReconstructedLayerProps = LayerDesc.GetShape<FReconstructedLayer>();
|
||||
IsUnderlay = (ReconstructedLayerProps.PassthroughLayerOrder == PassthroughLayerOrder_Underlay);
|
||||
}
|
||||
else if (LayerDesc.HasShape<FUserDefinedLayer>())
|
||||
{
|
||||
const FUserDefinedLayer& UserDefinedLayerProps = LayerDesc.GetShape<FUserDefinedLayer>();
|
||||
IsUnderlay = (UserDefinedLayerProps.PassthroughLayerOrder == PassthroughLayerOrder_Underlay);
|
||||
}
|
||||
|
||||
const int32 Priority = IsUnderlay ? -2 : IsPokeAHole ? -1
|
||||
: 1;
|
||||
return Priority;
|
||||
}
|
||||
|
||||
FORCEINLINE bool operator()(const IStereoLayers::FLayerDesc& A, const IStereoLayers::FLayerDesc& B) const
|
||||
{
|
||||
// First order layers by type
|
||||
const int32 PassA = GetLayerTypePriority(A);
|
||||
const int32 PassB = GetLayerTypePriority(B);
|
||||
|
||||
if (PassA != PassB)
|
||||
{
|
||||
return PassA < PassB;
|
||||
}
|
||||
|
||||
// Draw layers by ascending priority
|
||||
if (A.Priority != B.Priority)
|
||||
{
|
||||
return A.Priority < B.Priority;
|
||||
}
|
||||
|
||||
// Draw layers by ascending id
|
||||
return A.Id < B.Id;
|
||||
}
|
||||
|
||||
FORCEINLINE bool operator()(const FPassthroughLayerPtr& A, const FPassthroughLayerPtr& B) const
|
||||
{
|
||||
return (*this)(A->GetDesc(), B->GetDesc());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace XRPassthrough
|
||||
@@ -0,0 +1,728 @@
|
||||
// @lint-ignore-every LICENSELINT
|
||||
// Copyright 1998-2020 Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "OculusXRPassthroughLayerComponent.h"
|
||||
|
||||
#include "Engine/StaticMesh.h"
|
||||
#include "Engine/GameEngine.h"
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
#include "ProceduralMeshComponent.h"
|
||||
#include "OculusXRHMD.h"
|
||||
#include "OculusXRPassthroughLayerShapes.h"
|
||||
#include "OculusXRPersistentPassthroughInstance.h"
|
||||
#include "OculusXRPassthroughSubsystem.h"
|
||||
#include "Curves/CurveLinearColor.h"
|
||||
#include "StaticMeshResources.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOculusPassthrough);
|
||||
|
||||
void UOculusXRStereoLayerShapeReconstructed::ApplyShape(IStereoLayers::FLayerDesc& LayerDesc)
|
||||
{
|
||||
const FEdgeStyleParameters EdgeStyleParameters(
|
||||
bEnableEdgeColor,
|
||||
bEnableColorMap,
|
||||
TextureOpacityFactor,
|
||||
Brightness,
|
||||
Contrast,
|
||||
Posterize,
|
||||
Saturation,
|
||||
EdgeColor,
|
||||
ColorScale,
|
||||
ColorOffset,
|
||||
ColorMapType,
|
||||
GetColorArray(bUseColorMapCurve, ColorMapCurve),
|
||||
GenerateColorLutDescription(LutWeight, ColorLUTSource, ColorLUTTarget));
|
||||
LayerDesc.SetShape<FReconstructedLayer>(EdgeStyleParameters, LayerOrder);
|
||||
}
|
||||
|
||||
void UOculusXRStereoLayerShapeUserDefined::ApplyShape(IStereoLayers::FLayerDesc& LayerDesc)
|
||||
{
|
||||
// If there is no user geometry, set the layer hidden to avoid unnecessary cost
|
||||
if (UserGeometryList.IsEmpty())
|
||||
LayerDesc.Flags |= IStereoLayers::LAYER_FLAG_HIDDEN;
|
||||
|
||||
const FEdgeStyleParameters EdgeStyleParameters(
|
||||
bEnableEdgeColor,
|
||||
bEnableColorMap,
|
||||
TextureOpacityFactor,
|
||||
Brightness,
|
||||
Contrast,
|
||||
Posterize,
|
||||
Saturation,
|
||||
EdgeColor,
|
||||
ColorScale,
|
||||
ColorOffset,
|
||||
ColorMapType,
|
||||
GetColorArray(bUseColorMapCurve, ColorMapCurve),
|
||||
GenerateColorLutDescription(LutWeight, ColorLUTSource, ColorLUTTarget));
|
||||
LayerDesc.SetShape<FUserDefinedLayer>(UserGeometryList, EdgeStyleParameters, LayerOrder);
|
||||
}
|
||||
|
||||
void UOculusXRStereoLayerShapeUserDefined::AddGeometry(const FString& MeshName, OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh, FTransform Transform, bool bUpdateTransform)
|
||||
{
|
||||
FUserDefinedGeometryDesc UserDefinedGeometryDesc(
|
||||
MeshName,
|
||||
PassthroughMesh,
|
||||
Transform,
|
||||
bUpdateTransform);
|
||||
|
||||
UserGeometryList.Add(UserDefinedGeometryDesc);
|
||||
}
|
||||
|
||||
void UOculusXRStereoLayerShapeUserDefined::RemoveGeometry(const FString& MeshName)
|
||||
{
|
||||
UserGeometryList.RemoveAll([MeshName](const FUserDefinedGeometryDesc& Desc) {
|
||||
return Desc.MeshName == MeshName;
|
||||
});
|
||||
}
|
||||
|
||||
UOculusXRPassthroughLayerComponent::UOculusXRPassthroughLayerComponent(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerComponent::DestroyComponent(bool bPromoteChildren)
|
||||
{
|
||||
Super::DestroyComponent(bPromoteChildren);
|
||||
|
||||
UOculusXRPassthroughSubsystem* ptSubsystem = UOculusXRPassthroughSubsystem::GetPassthroughSubsystem(GetWorld());
|
||||
if (ptSubsystem != nullptr)
|
||||
{
|
||||
ptSubsystem->OnAnyLayerResumed.RemoveDynamic(this, &UOculusXRPassthroughLayerComponent::OnAnyLayerResumedEvent);
|
||||
}
|
||||
|
||||
#ifdef WITH_OCULUS_BRANCH
|
||||
IStereoLayers* StereoLayers;
|
||||
if (LayerId && GEngine->StereoRenderingDevice.IsValid() && (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) != nullptr)
|
||||
{
|
||||
StereoLayers->DestroyLayer(LayerId);
|
||||
LayerId = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerComponent::OnRegister()
|
||||
{
|
||||
Super::OnRegister();
|
||||
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerComponent::BeginPlay()
|
||||
{
|
||||
UOculusXRPassthroughSubsystem* ptSubsystem = UOculusXRPassthroughSubsystem::GetPassthroughSubsystem(GetWorld());
|
||||
if (ptSubsystem != nullptr)
|
||||
{
|
||||
ptSubsystem->OnAnyLayerResumed.AddDynamic(this, &UOculusXRPassthroughLayerComponent::OnAnyLayerResumedEvent);
|
||||
}
|
||||
|
||||
Super::BeginPlay();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
|
||||
{
|
||||
if (Texture == nullptr && !LayerRequiresTexture())
|
||||
{
|
||||
// UStereoLayerComponent hides components without textures
|
||||
Texture = GEngine->DefaultTexture;
|
||||
}
|
||||
|
||||
UpdatePassthroughObjects();
|
||||
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerComponent::UpdatePassthroughObjects()
|
||||
{
|
||||
UOculusXRStereoLayerShapeUserDefined* UserShape = Cast<UOculusXRStereoLayerShapeUserDefined>(Shape);
|
||||
if (UserShape)
|
||||
{
|
||||
bool bDirty = false;
|
||||
for (FUserDefinedGeometryDesc& Entry : UserShape->GetUserGeometryList())
|
||||
{
|
||||
if (Entry.bUpdateTransform)
|
||||
{
|
||||
const UMeshComponent** MeshComponent = PassthroughComponentMap.Find(Entry.MeshName);
|
||||
if (MeshComponent)
|
||||
{
|
||||
Entry.Transform = (*MeshComponent)->GetComponentTransform();
|
||||
bDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bDirty)
|
||||
{
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OculusXRHMD::FOculusPassthroughMeshRef UOculusXRPassthroughLayerComponent::CreatePassthroughMesh(UProceduralMeshComponent* ProceduralMeshComponent)
|
||||
{
|
||||
if (!ProceduralMeshComponent)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Procedural Mesh is nullptr"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TArray<int32> Triangles;
|
||||
TArray<FVector> Vertices;
|
||||
int32 NumSections = ProceduralMeshComponent->GetNumSections();
|
||||
int VertexOffset = 0; // Each section start with vertex IDs of 0, in order to create a single mesh from all sections we need to offset those IDs by the amount of previous vertices
|
||||
for (int32 s = 0; s < NumSections; ++s)
|
||||
{
|
||||
FProcMeshSection* ProcMeshSection = ProceduralMeshComponent->GetProcMeshSection(s);
|
||||
for (int32 i = 0; i < ProcMeshSection->ProcIndexBuffer.Num(); ++i)
|
||||
{
|
||||
Triangles.Add(VertexOffset + ProcMeshSection->ProcIndexBuffer[i]);
|
||||
}
|
||||
|
||||
for (int32 i = 0; i < ProcMeshSection->ProcVertexBuffer.Num(); ++i)
|
||||
{
|
||||
Vertices.Add(ProcMeshSection->ProcVertexBuffer[i].Position);
|
||||
}
|
||||
|
||||
VertexOffset += ProcMeshSection->ProcVertexBuffer.Num();
|
||||
}
|
||||
|
||||
OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh = new OculusXRHMD::FOculusPassthroughMesh(Vertices, Triangles);
|
||||
return PassthroughMesh;
|
||||
}
|
||||
|
||||
OculusXRHMD::FOculusPassthroughMeshRef UOculusXRPassthroughLayerComponent::CreatePassthroughMesh(UStaticMeshComponent* StaticMeshComponent)
|
||||
{
|
||||
if (!StaticMeshComponent)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Static Mesh is nullptr"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
|
||||
|
||||
if (!Mesh || !Mesh->GetRenderData())
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Static Mesh has no Renderdata"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (Mesh->GetNumLODs() == 0)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Static Mesh has no LODs"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!Mesh->bAllowCPUAccess)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Static Mesh Requires CPU Access"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const int32 LODIndex = 0;
|
||||
FStaticMeshLODResources& LOD = Mesh->GetRenderData()->LODResources[LODIndex];
|
||||
|
||||
TArray<int32> Triangles;
|
||||
const int32 NumIndices = LOD.IndexBuffer.GetNumIndices();
|
||||
for (int32 i = 0; i < NumIndices; ++i)
|
||||
{
|
||||
Triangles.Add(LOD.IndexBuffer.GetIndex(i));
|
||||
}
|
||||
|
||||
TArray<FVector> Vertices;
|
||||
const int32 NumVertices = LOD.VertexBuffers.PositionVertexBuffer.GetNumVertices();
|
||||
for (int32 i = 0; i < NumVertices; ++i)
|
||||
{
|
||||
Vertices.Add((FVector)LOD.VertexBuffers.PositionVertexBuffer.VertexPosition(i));
|
||||
}
|
||||
|
||||
OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh = new OculusXRHMD::FOculusPassthroughMesh(Vertices, Triangles);
|
||||
return PassthroughMesh;
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerComponent::AddSurfaceGeometry(AStaticMeshActor* StaticMeshActor, bool updateTransform)
|
||||
{
|
||||
if (StaticMeshActor)
|
||||
{
|
||||
UStaticMeshComponent* StaticMeshComponent = StaticMeshActor->GetStaticMeshComponent();
|
||||
if (StaticMeshComponent)
|
||||
AddStaticSurfaceGeometry(StaticMeshComponent, updateTransform);
|
||||
}
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerComponent::AddStaticSurfaceGeometry(UStaticMeshComponent* StaticMeshComponent, bool updateTransform)
|
||||
{
|
||||
if (!StaticMeshComponent)
|
||||
return;
|
||||
|
||||
UOculusXRStereoLayerShapeUserDefined* UserShape = Cast<UOculusXRStereoLayerShapeUserDefined>(Shape);
|
||||
if (!UserShape)
|
||||
return;
|
||||
|
||||
OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh = CreatePassthroughMesh(StaticMeshComponent);
|
||||
if (!PassthroughMesh)
|
||||
return;
|
||||
|
||||
const FString MeshName = StaticMeshComponent->GetFullName();
|
||||
const FTransform Transform = StaticMeshComponent->GetComponentTransform();
|
||||
UserShape->AddGeometry(MeshName, PassthroughMesh, Transform, updateTransform);
|
||||
|
||||
PassthroughComponentMap.Add(MeshName, StaticMeshComponent);
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerComponent::AddProceduralSurfaceGeometry(UProceduralMeshComponent* ProceduralMeshComponent, bool updateTransform)
|
||||
{
|
||||
if (!ProceduralMeshComponent)
|
||||
return;
|
||||
|
||||
UOculusXRStereoLayerShapeUserDefined* UserShape = Cast<UOculusXRStereoLayerShapeUserDefined>(Shape);
|
||||
if (!UserShape)
|
||||
return;
|
||||
|
||||
OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh = CreatePassthroughMesh(ProceduralMeshComponent);
|
||||
if (!PassthroughMesh)
|
||||
return;
|
||||
|
||||
const FString MeshName = ProceduralMeshComponent->GetFullName();
|
||||
const FTransform Transform = ProceduralMeshComponent->GetComponentTransform();
|
||||
UserShape->AddGeometry(MeshName, PassthroughMesh, Transform, updateTransform);
|
||||
|
||||
PassthroughComponentMap.Add(MeshName, ProceduralMeshComponent);
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerComponent::RemoveSurfaceGeometry(AStaticMeshActor* StaticMeshActor)
|
||||
{
|
||||
if (StaticMeshActor)
|
||||
RemoveSurfaceGeometryComponent(StaticMeshActor->GetStaticMeshComponent());
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerComponent::RemoveStaticSurfaceGeometry(UStaticMeshComponent* StaticMeshComponent)
|
||||
{
|
||||
RemoveSurfaceGeometryComponent(StaticMeshComponent);
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerComponent::RemoveProceduralSurfaceGeometry(UProceduralMeshComponent* ProceduralMeshComponent)
|
||||
{
|
||||
RemoveSurfaceGeometryComponent(ProceduralMeshComponent);
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerComponent::RemoveSurfaceGeometryComponent(UMeshComponent* MeshComponent)
|
||||
{
|
||||
if (!MeshComponent)
|
||||
return;
|
||||
|
||||
UOculusXRStereoLayerShapeUserDefined* UserShape = Cast<UOculusXRStereoLayerShapeUserDefined>(Shape);
|
||||
if (!UserShape)
|
||||
return;
|
||||
|
||||
const FString MeshName = MeshComponent->GetFullName();
|
||||
|
||||
UserShape->RemoveGeometry(MeshName);
|
||||
PassthroughComponentMap.Remove(MeshName);
|
||||
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
bool UOculusXRPassthroughLayerComponent::IsSurfaceGeometry(AStaticMeshActor* StaticMeshActor) const
|
||||
{
|
||||
return StaticMeshActor ? IsSurfaceGeometryComponent(StaticMeshActor->GetStaticMeshComponent()) : false;
|
||||
}
|
||||
|
||||
bool UOculusXRPassthroughLayerComponent::IsSurfaceGeometryComponent(const UMeshComponent* MeshComponent) const
|
||||
{
|
||||
return MeshComponent ? PassthroughComponentMap.Contains(MeshComponent->GetFullName()) : false;
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerComponent::MarkPassthroughStyleForUpdate()
|
||||
{
|
||||
bPassthroughStyleNeedsUpdate = true;
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
bool UOculusXRPassthroughLayerComponent::CanEditChange(const FProperty* InProperty) const
|
||||
{
|
||||
if (!Super::CanEditChange(InProperty))
|
||||
return false;
|
||||
|
||||
const FName PropertyName = InProperty->GetFName();
|
||||
if (PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, Texture)
|
||||
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, LeftTexture)
|
||||
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, bLiveTexture)
|
||||
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, bNoAlphaChannel)
|
||||
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, bQuadPreserveTextureRatio)
|
||||
#if defined(WITH_OCULUS_BRANCH) && UE_VERSION_OLDER_THAN(5, 5, 0)
|
||||
// clang-format off
|
||||
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
||||
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, bBicubicFiltering)
|
||||
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
||||
// clang-format on
|
||||
#endif
|
||||
#if !UE_VERSION_OLDER_THAN(5, 4, 0)
|
||||
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, AdditionalFlags)
|
||||
#endif
|
||||
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, QuadSize)
|
||||
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, UVRect)
|
||||
|| PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRPassthroughLayerComponent, StereoLayerType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif // WITH_EDITOR
|
||||
|
||||
void UOculusXRPassthroughLayerComponent::OnAnyLayerResumedEvent(int InLayerId)
|
||||
{
|
||||
if (LayerId == InLayerId)
|
||||
{
|
||||
OnLayerResumed.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
bool UOculusXRPassthroughLayerComponent::LayerRequiresTexture()
|
||||
{
|
||||
const bool bIsPassthroughShape = Shape && (Shape->IsA<UOculusXRStereoLayerShapeReconstructed>() || Shape->IsA<UOculusXRStereoLayerShapeUserDefined>());
|
||||
return !bIsPassthroughShape;
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::SetTextureOpacity(float InOpacity)
|
||||
{
|
||||
if (TextureOpacityFactor == InOpacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TextureOpacityFactor = InOpacity;
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::EnableEdgeColor(bool bInEnableEdgeColor)
|
||||
{
|
||||
if (bEnableEdgeColor == bInEnableEdgeColor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bEnableEdgeColor = bInEnableEdgeColor;
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::EnableColorMap(bool bInEnableColorMap)
|
||||
{
|
||||
if (bEnableColorMap == bInEnableColorMap)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bEnableColorMap = bInEnableColorMap;
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::SetEdgeRenderingColor(FLinearColor InEdgeColor)
|
||||
{
|
||||
if (EdgeColor == InEdgeColor)
|
||||
{
|
||||
return;
|
||||
}
|
||||
EdgeColor = InEdgeColor;
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::EnableColorMapCurve(bool bInEnableColorMapCurve)
|
||||
{
|
||||
if (bUseColorMapCurve == bInEnableColorMapCurve)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bUseColorMapCurve = bInEnableColorMapCurve;
|
||||
ColorArray = GenerateColorArray(bUseColorMapCurve, ColorMapCurve);
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::SetColorMapCurve(UCurveLinearColor* InColorMapCurve)
|
||||
{
|
||||
if (ColorMapCurve == InColorMapCurve)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ColorMapCurve = InColorMapCurve;
|
||||
ColorArray = GenerateColorArray(bUseColorMapCurve, ColorMapCurve);
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::SetColorMapType(EOculusXRColorMapType InColorMapType)
|
||||
{
|
||||
if (ColorMapType == InColorMapType)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ColorMapType = InColorMapType;
|
||||
ColorArray = GenerateColorArray(bUseColorMapCurve, ColorMapCurve);
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::SetColorArray(const TArray<FLinearColor>& InColorArray)
|
||||
{
|
||||
if (InColorArray.Num() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ColorMapType != ColorMapType_GrayscaleToColor)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("SetColorArray is ignored for color map types other than Grayscale to Color."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (bUseColorMapCurve)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("UseColorMapCurve is enabled on the layer. Automatic disable and use the Array for color lookup"));
|
||||
}
|
||||
bUseColorMapCurve = false;
|
||||
|
||||
ColorArray = InColorArray;
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::ClearColorMap()
|
||||
{
|
||||
ColorArray.Empty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::SetColorMapControls(float InContrast, float InBrightness, float InPosterize)
|
||||
{
|
||||
if (ColorMapType != ColorMapType_Grayscale && ColorMapType != ColorMapType_GrayscaleToColor)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("SetColorMapControls is ignored for color map types other than Grayscale and Grayscale to color."));
|
||||
return;
|
||||
}
|
||||
Contrast = FMath::Clamp(InContrast, -1.0f, 1.0f);
|
||||
Brightness = FMath::Clamp(InBrightness, -1.0f, 1.0f);
|
||||
Posterize = FMath::Clamp(InPosterize, 0.0f, 1.0f);
|
||||
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::SetBrightnessContrastSaturation(float InContrast, float InBrightness, float InSaturation)
|
||||
{
|
||||
if (ColorMapType != ColorMapType_ColorAdjustment)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("SetBrightnessContrastSaturation is ignored for color map types other than Color Adjustment."));
|
||||
return;
|
||||
}
|
||||
Contrast = FMath::Clamp(InContrast, -1.0f, 1.0f);
|
||||
Brightness = FMath::Clamp(InBrightness, -1.0f, 1.0f);
|
||||
Saturation = FMath::Clamp(InSaturation, -1.0f, 1.0f);
|
||||
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::SetColorScaleAndOffset(FLinearColor InColorScale, FLinearColor InColorOffset)
|
||||
{
|
||||
if (ColorScale == InColorScale && ColorOffset == InColorOffset)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ColorScale = InColorScale;
|
||||
ColorOffset = InColorOffset;
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::SetLayerPlacement(EOculusXRPassthroughLayerOrder InLayerOrder)
|
||||
{
|
||||
if (LayerOrder == InLayerOrder)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("Same layer order as before, no change needed"));
|
||||
return;
|
||||
}
|
||||
|
||||
LayerOrder = InLayerOrder;
|
||||
this->MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::SetColorLUTSource(class UOculusXRPassthroughColorLut* InColorLUTSource)
|
||||
{
|
||||
if (ColorMapType != ColorMapType_ColorLut && ColorMapType != ColorMapType_ColorLut_Interpolated)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("SetColorLUT is ignored for color map types other than Color LUT."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (InColorLUTSource == ColorLUTSource)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("Same color LUT source as before, no change needed"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove reference from the old LUT
|
||||
if (ColorLUTSource)
|
||||
ColorLUTSource->RemoveReference(this);
|
||||
|
||||
ColorLUTSource = InColorLUTSource;
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::SetColorLUTTarget(class UOculusXRPassthroughColorLut* InColorLUTTarget)
|
||||
{
|
||||
if (ColorMapType != ColorMapType_ColorLut_Interpolated)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("SetColorLUTTarget is ignored for color map types other than Interpolated Color LUT."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (InColorLUTTarget == ColorLUTTarget)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("Same color LUT source as before, no change needed"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove reference from the old LUT
|
||||
if (ColorLUTTarget)
|
||||
ColorLUTTarget->RemoveReference(this);
|
||||
|
||||
ColorLUTTarget = InColorLUTTarget;
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::SetColorLUTWeight(float InWeight)
|
||||
{
|
||||
if (ColorMapType != ColorMapType_ColorLut && ColorMapType != ColorMapType_ColorLut_Interpolated)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("SetWeight is ignored for color map types other than Color LUT."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (LutWeight == InWeight)
|
||||
{
|
||||
UE_LOG(LogOculusPassthrough, Warning, TEXT("Same lut weight as before, no change needed"));
|
||||
return;
|
||||
}
|
||||
|
||||
LutWeight = InWeight;
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::RemoveColorLut()
|
||||
{
|
||||
ClearLUTsReferences();
|
||||
|
||||
ColorLUTSource = nullptr;
|
||||
ColorLUTTarget = nullptr;
|
||||
MarkStereoLayerDirty();
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::ClearLUTsReferences()
|
||||
{
|
||||
// Clear lut references
|
||||
if (ColorLUTSource)
|
||||
ColorLUTSource->RemoveReference(this);
|
||||
if (ColorLUTTarget)
|
||||
ColorLUTTarget->RemoveReference(this);
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::BeginDestroy()
|
||||
{
|
||||
ClearLUTsReferences();
|
||||
|
||||
Super::BeginDestroy();
|
||||
}
|
||||
|
||||
TArray<FLinearColor> UOculusXRPassthroughLayerBase::GenerateColorArrayFromColorCurve(const UCurveLinearColor* InColorMapCurve) const
|
||||
{
|
||||
if (InColorMapCurve == nullptr)
|
||||
{
|
||||
return TArray<FLinearColor>();
|
||||
}
|
||||
|
||||
TArray<FLinearColor> NewColorArray;
|
||||
constexpr uint32 TotalEntries = 256;
|
||||
NewColorArray.Empty();
|
||||
NewColorArray.SetNum(TotalEntries);
|
||||
|
||||
for (int32 Index = 0; Index < TotalEntries; ++Index)
|
||||
{
|
||||
const float Alpha = ((float)Index / TotalEntries);
|
||||
NewColorArray[Index] = InColorMapCurve->GetLinearColorValue(Alpha);
|
||||
}
|
||||
return NewColorArray;
|
||||
}
|
||||
|
||||
TArray<FLinearColor> UOculusXRPassthroughLayerBase::GetOrGenerateNeutralColorArray()
|
||||
{
|
||||
if (NeutralColorArray.Num() == 0)
|
||||
{
|
||||
const uint32 TotalEntries = 256;
|
||||
NeutralColorArray.SetNum(TotalEntries);
|
||||
|
||||
for (int32 Index = 0; Index < TotalEntries; ++Index)
|
||||
{
|
||||
NeutralColorArray[Index] = FLinearColor((float)Index / TotalEntries, (float)Index / TotalEntries, (float)Index / TotalEntries);
|
||||
}
|
||||
}
|
||||
|
||||
return NeutralColorArray;
|
||||
}
|
||||
|
||||
TArray<FLinearColor> UOculusXRPassthroughLayerBase::GenerateColorArray(bool bInUseColorMapCurve, const UCurveLinearColor* InColorMapCurve)
|
||||
{
|
||||
TArray<FLinearColor> NewColorArray;
|
||||
if (bInUseColorMapCurve)
|
||||
{
|
||||
NewColorArray = GenerateColorArrayFromColorCurve(InColorMapCurve);
|
||||
}
|
||||
|
||||
// Check for existing Array, otherwise generate a neutral one
|
||||
if (NewColorArray.Num() == 0)
|
||||
{
|
||||
NewColorArray = GetOrGenerateNeutralColorArray();
|
||||
}
|
||||
|
||||
return NewColorArray;
|
||||
}
|
||||
|
||||
TArray<FLinearColor> UOculusXRPassthroughLayerBase::GetColorArray(bool bInUseColorMapCurve, const UCurveLinearColor* InColorMapCurve)
|
||||
{
|
||||
if (ColorArray.Num() == 0)
|
||||
{
|
||||
if (bInUseColorMapCurve)
|
||||
{
|
||||
return GenerateColorArray(bInUseColorMapCurve, InColorMapCurve);
|
||||
}
|
||||
return GetOrGenerateNeutralColorArray();
|
||||
}
|
||||
|
||||
return ColorArray;
|
||||
}
|
||||
|
||||
FColorLutDesc UOculusXRPassthroughLayerBase::GenerateColorLutDescription(float InLutWeight, UOculusXRPassthroughColorLut* InLutSource, UOculusXRPassthroughColorLut* InLutTarget)
|
||||
{
|
||||
TArray<uint64> ColorLuts;
|
||||
if (InLutSource != nullptr && InLutSource->ColorLutType != EColorLutType::None)
|
||||
{
|
||||
uint64 ColorLutHandle = InLutSource->GetHandle(this);
|
||||
if (ColorLutHandle != 0)
|
||||
{
|
||||
ColorLuts.Add(ColorLutHandle);
|
||||
}
|
||||
}
|
||||
|
||||
if (InLutTarget != nullptr && ColorMapType == EOculusXRColorMapType::ColorMapType_ColorLut_Interpolated && ColorLuts.Num() > 0 && InLutSource->ColorLutType != EColorLutType::None)
|
||||
{
|
||||
uint64 ColorLutHandle = InLutTarget->GetHandle(this);
|
||||
if (ColorLutHandle != 0)
|
||||
{
|
||||
ColorLuts.Add(ColorLutHandle);
|
||||
}
|
||||
}
|
||||
|
||||
return FColorLutDesc(ColorLuts, InLutWeight);
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughLayerBase::MarkStereoLayerDirty()
|
||||
{
|
||||
if (UStereoLayerComponent* stereoLayerComponent = Cast<UStereoLayerComponent>(GetOuter()))
|
||||
{
|
||||
stereoLayerComponent->MarkStereoLayerDirty();
|
||||
}
|
||||
else if (UOculusXRPersistentPassthroughInstance* pptInstance = Cast<UOculusXRPersistentPassthroughInstance>(GetOuter()))
|
||||
{
|
||||
pptInstance->UpdateLayer();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRPassthroughModule.h"
|
||||
|
||||
#include "OculusXRHMD.h"
|
||||
#include "OculusXRPassthroughEventHandling.h"
|
||||
#include "Misc/CoreDelegates.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogOculusXRPassthrough);
|
||||
|
||||
#define LOCTEXT_NAMESPACE "OculusXRPassthrough"
|
||||
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
// FOculusXRPassthroughModule
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
|
||||
FOculusXRPassthroughModule::FOculusXRPassthroughModule()
|
||||
{
|
||||
}
|
||||
|
||||
void FOculusXRPassthroughModule::StartupModule()
|
||||
{
|
||||
PassthroughXR = MakeShareable(new XRPassthrough::FPassthroughXR());
|
||||
PassthroughXR->RegisterAsOpenXRExtension();
|
||||
|
||||
FCoreDelegates::OnPostEngineInit.AddRaw(this, &FOculusXRPassthroughModule::OnPostEngineInit);
|
||||
}
|
||||
|
||||
void FOculusXRPassthroughModule::ShutdownModule()
|
||||
{
|
||||
}
|
||||
|
||||
void FOculusXRPassthroughModule::OnPostEngineInit()
|
||||
{
|
||||
if (IsRunningCommandlet())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GEngine)
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Warning, TEXT("No GEngine, cannot add event polling delegate."));
|
||||
return;
|
||||
}
|
||||
|
||||
OculusXRHMD::FOculusXRHMD* HMD = OculusXRHMD::FOculusXRHMD::GetOculusXRHMD();
|
||||
if (!HMD)
|
||||
{
|
||||
FName XRSystemName = GEngine->XRSystem ? GEngine->XRSystem->GetSystemName() : "None";
|
||||
UE_LOG(LogOculusXRPassthrough, Warning, TEXT("Unable to retrieve OculusXRHMD, cannot add event polling delegate. (current xrsystem : %s)"), *XRSystemName.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
HMD->AddEventPollingDelegate(OculusXRHMD::FOculusXRHMDEventPollingDelegate::CreateStatic(&OculusXRPassthrough::FOculusXRPassthroughEventHandling::OnPollEvent));
|
||||
}
|
||||
|
||||
IMPLEMENT_MODULE(FOculusXRPassthroughModule, OculusXRPassthrough)
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
#include "IOculusXRPassthroughModule.h"
|
||||
|
||||
#include "OculusXRPassthroughXR.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "OculusXRPassthrough"
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogOculusXRPassthrough, Log, All);
|
||||
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
// FOculusXRPassthroughModule
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
|
||||
class FOculusXRPassthroughModule : public IOculusXRPassthroughModule
|
||||
{
|
||||
public:
|
||||
FOculusXRPassthroughModule();
|
||||
|
||||
static inline FOculusXRPassthroughModule& Get()
|
||||
{
|
||||
return FModuleManager::LoadModuleChecked<FOculusXRPassthroughModule>("OculusXRPassthrough");
|
||||
}
|
||||
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
void OnPostEngineInit();
|
||||
|
||||
TWeakPtr<XRPassthrough::FPassthroughXR, ESPMode::ThreadSafe> GetPassthroughExtensionPlugin()
|
||||
{
|
||||
return PassthroughXR;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef TSharedPtr<XRPassthrough::FPassthroughXR, ESPMode::ThreadSafe> FPassthroughXRPtr;
|
||||
FPassthroughXRPtr PassthroughXR;
|
||||
};
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRPassthroughSubsystem.h"
|
||||
|
||||
#include "Engine/World.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "OculusXRPersistentPassthroughInstance.h"
|
||||
#include "OculusXRHMDRuntimeSettings.h"
|
||||
#include "OculusXRHMD.h"
|
||||
#include "OculusXRPassthroughEventHandling.h"
|
||||
#include "OculusXRPassthroughModule.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Engine/Texture2D.h"
|
||||
#include "Rendering/Texture2DResource.h"
|
||||
|
||||
UOculusXRPassthroughSubsystem* UOculusXRPassthroughSubsystem::GetPassthroughSubsystem(const UWorld* InWorld)
|
||||
{
|
||||
if (InWorld)
|
||||
{
|
||||
return UGameInstance::GetSubsystem<UOculusXRPassthroughSubsystem>(InWorld->GetGameInstance());
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UOculusXRPassthroughSubsystem::UOculusXRPassthroughSubsystem()
|
||||
{
|
||||
}
|
||||
|
||||
bool UOculusXRPassthroughSubsystem::ShouldCreateSubsystem(UObject* Outer) const
|
||||
{
|
||||
// return true if "Passthrough Enabled" is checked in MetaXR plugin's settings.
|
||||
|
||||
return GetDefault<UOculusXRHMDRuntimeSettings>()->bInsightPassthroughEnabled;
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
DelegateHandleLayerResumed = OculusXRPassthrough::FOculusXRPassthroughEventDelegates::OculusPassthroughLayerResumed.AddLambda([this](int layerID) {
|
||||
UE_LOG(LogOculusXRPassthrough, Log, TEXT("UOculusXRPassthroughSubsystem - Received layer #%d resumed event"), layerID);
|
||||
OnAnyLayerResumed.Broadcast(layerID);
|
||||
});
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughSubsystem::Deinitialize()
|
||||
{
|
||||
OculusXRPassthrough::FOculusXRPassthroughEventDelegates::OculusPassthroughLayerResumed.Remove(DelegateHandleLayerResumed);
|
||||
}
|
||||
|
||||
UOculusXRPersistentPassthroughInstance* UOculusXRPassthroughSubsystem::InitializePersistentPassthrough(FOculusXRPersistentPassthroughParameters Parameters, const FOculusXRPassthrough_LayerResumed_Single& LayerResumed)
|
||||
{
|
||||
if (IsValid(PPTInstance))
|
||||
{
|
||||
PPTInstance->AddLayerResumedSingleDelegate(LayerResumed);
|
||||
PPTInstance->UpdateParameters(Parameters);
|
||||
|
||||
return PPTInstance;
|
||||
}
|
||||
|
||||
PPTInstance = NewObject<UOculusXRPersistentPassthroughInstance>(this, "PersistentPassthroughInstance", RF_NoFlags);
|
||||
PPTInstance->AddLayerResumedSingleDelegate(LayerResumed);
|
||||
PPTInstance->InitLayer(Parameters);
|
||||
OnAnyLayerResumed.AddDynamic(PPTInstance, &UOculusXRPersistentPassthroughInstance::OnAnyLayerResumedEvent);
|
||||
|
||||
return PPTInstance;
|
||||
}
|
||||
|
||||
void UOculusXRPassthroughSubsystem::DestroyPersistentPassthrough()
|
||||
{
|
||||
if (!IsValid(PPTInstance))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OnAnyLayerResumed.RemoveDynamic(PPTInstance, &UOculusXRPersistentPassthroughInstance::OnAnyLayerResumedEvent);
|
||||
PPTInstance->ConditionalBeginDestroy();
|
||||
PPTInstance = nullptr;
|
||||
}
|
||||
|
||||
UOculusXRPersistentPassthroughInstance* UOculusXRPassthroughSubsystem::GetPersistentPassthrough() const
|
||||
{
|
||||
return PPTInstance;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,581 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRPassthroughXR.h"
|
||||
|
||||
#include "Engine/GameEngine.h"
|
||||
#include "Engine/RendererSettings.h"
|
||||
#include "IOpenXRHMDModule.h"
|
||||
#include "Materials/Material.h"
|
||||
#include "OculusXRHMD/Private/OculusXRResourceHolder.h"
|
||||
#include "OculusXRHMD_CustomPresent.h"
|
||||
#include "OculusXRHMDRuntimeSettings.h"
|
||||
#include "OculusXRPassthroughXRFunctions.h"
|
||||
#include "OculusXRPassthroughModule.h"
|
||||
#include "OculusXRPassthroughEventHandling.h"
|
||||
#include "OpenXRHMD.h"
|
||||
#include "OpenXRHMD_Swapchain.h"
|
||||
#include "StereoRendering.h"
|
||||
#include "RenderGraphBuilder.h"
|
||||
|
||||
#include "XRThreadUtils.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "OculusXRPassthrough"
|
||||
|
||||
namespace XRPassthrough
|
||||
{
|
||||
TWeakPtr<FPassthroughXR> FPassthroughXR::GetInstance()
|
||||
{
|
||||
return FOculusXRPassthroughModule::Get().GetPassthroughExtensionPlugin();
|
||||
}
|
||||
|
||||
FPassthroughXR::FPassthroughXR()
|
||||
: InvAlphaTexture(nullptr)
|
||||
, ColorSwapchain(nullptr)
|
||||
, ColorSwapChainTexture(nullptr)
|
||||
, ProjectionLayerAlphaBlend{}
|
||||
, Layers_RenderThread{}
|
||||
, bPassthroughInitialized(false)
|
||||
, PassthroughInstance{ XR_NULL_HANDLE }
|
||||
, Settings(nullptr)
|
||||
, OpenXRHMD(nullptr)
|
||||
, WorldToMetersScale(100.0f)
|
||||
, WorldToMetersScale_RenderThread(100.0f)
|
||||
{
|
||||
static const FName RendererModuleName("Renderer");
|
||||
RendererModule = FModuleManager::GetModulePtr<IRendererModule>(RendererModuleName);
|
||||
Settings = MakeShareable(new FSettings());
|
||||
}
|
||||
|
||||
FPassthroughXR::~FPassthroughXR()
|
||||
{
|
||||
}
|
||||
|
||||
void FPassthroughXR::RegisterAsOpenXRExtension()
|
||||
{
|
||||
#if defined(WITH_OCULUS_BRANCH)
|
||||
// Feature not enabled on Marketplace build. Currently only for the meta fork
|
||||
RegisterOpenXRExtensionModularFeature();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool FPassthroughXR::GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions)
|
||||
{
|
||||
OutExtensions.Add(XR_FB_PASSTHROUGH_EXTENSION_NAME);
|
||||
OutExtensions.Add(XR_META_PASSTHROUGH_LAYER_RESUMED_EVENT_EXTENSION_NAME);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FPassthroughXR::GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions)
|
||||
{
|
||||
OutExtensions.Add(XR_FB_COMPOSITION_LAYER_ALPHA_BLEND_EXTENSION_NAME);
|
||||
OutExtensions.Add(XR_FB_TRIANGLE_MESH_EXTENSION_NAME);
|
||||
OutExtensions.Add(XR_META_PASSTHROUGH_COLOR_LUT_EXTENSION_NAME);
|
||||
return true;
|
||||
}
|
||||
|
||||
const void* FPassthroughXR::OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext)
|
||||
{
|
||||
if (InModule != nullptr)
|
||||
{
|
||||
Settings->bExtLayerAlphaBlendAvailable = InModule->IsExtensionEnabled(XR_FB_COMPOSITION_LAYER_ALPHA_BLEND_EXTENSION_NAME);
|
||||
Settings->bExtPassthroughAvailable = InModule->IsExtensionEnabled(XR_FB_PASSTHROUGH_EXTENSION_NAME);
|
||||
Settings->bExtTriangleMeshAvailable = InModule->IsExtensionEnabled(XR_FB_TRIANGLE_MESH_EXTENSION_NAME);
|
||||
Settings->bExtColorLutAvailable = InModule->IsExtensionEnabled(XR_META_PASSTHROUGH_COLOR_LUT_EXTENSION_NAME);
|
||||
Settings->bExtLayerResumedEventAvailable = InModule->IsExtensionEnabled(XR_META_PASSTHROUGH_LAYER_RESUMED_EVENT_EXTENSION_NAME);
|
||||
}
|
||||
return InNext;
|
||||
}
|
||||
|
||||
const void* FPassthroughXR::OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext)
|
||||
{
|
||||
InitOpenXRFunctions(InInstance);
|
||||
|
||||
Settings->PokeAHoleMaterial = Cast<UMaterial>(FSoftObjectPath(TEXT("/OculusXR/Materials/PokeAHoleMaterial")).TryLoad());
|
||||
|
||||
OpenXRHMD = (FOpenXRHMD*)GEngine->XRSystem.Get();
|
||||
|
||||
const UOculusXRHMDRuntimeSettings* HMDSettings = GetDefault<UOculusXRHMDRuntimeSettings>();
|
||||
Settings->bPassthroughEnabled = HMDSettings->bInsightPassthroughEnabled;
|
||||
|
||||
return InNext;
|
||||
}
|
||||
|
||||
void FPassthroughXR::PostCreateSession(XrSession InSession)
|
||||
{
|
||||
if (Settings->bPassthroughEnabled)
|
||||
{
|
||||
ExecuteOnRenderThread([this, InSession](FRHICommandListImmediate& RHICmdList) {
|
||||
InitializePassthrough(InSession);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void FPassthroughXR::OnDestroySession(XrSession InSession)
|
||||
{
|
||||
// Release resources
|
||||
ExecuteOnRenderThread([this, InSession]() {
|
||||
Layers_RenderThread.Reset();
|
||||
InvAlphaTexture.SafeRelease();
|
||||
|
||||
DeferredDeletion.HandleLayerDeferredDeletionQueue_RenderThread(true);
|
||||
|
||||
ShutdownPassthrough(InSession);
|
||||
});
|
||||
|
||||
OpenXRHMD = nullptr;
|
||||
}
|
||||
|
||||
void* FPassthroughXR::OnWaitFrame(XrSession InSession, void* InNext)
|
||||
{
|
||||
Update_GameThread(InSession);
|
||||
return InNext;
|
||||
}
|
||||
|
||||
bool FPassthroughXR::IsPassthroughEnabled(void) const
|
||||
{
|
||||
return bPassthroughInitialized;
|
||||
}
|
||||
|
||||
void FPassthroughXR::InvertTextureAlpha_RenderThread(FRHICommandList& RHICmdList, FRHITexture* Texture, FRHITexture* TempTexture, const FIntRect& ViewportRect)
|
||||
{
|
||||
{
|
||||
FRHITexture* SrcTexture = Texture;
|
||||
FRHITexture* DstTexture = TempTexture;
|
||||
const FIntRect SrcRect(ViewportRect);
|
||||
const FIntRect DstRect(0, 0, ViewportRect.Size().X, ViewportRect.Size().Y);
|
||||
|
||||
const bool bAlphaPremultiply = false;
|
||||
const bool bNoAlphaWrite = false;
|
||||
const bool bInvertSrcY = false;
|
||||
const bool sRGBSource = false;
|
||||
const bool bInvertAlpha = true;
|
||||
const auto FeatureLevel = GEngine ? GEngine->GetDefaultWorldFeatureLevel() : GMaxRHIFeatureLevel;
|
||||
const bool bUsingVulkan = RHIGetInterfaceType() == ERHIInterfaceType::Vulkan;
|
||||
OculusXRHMD::FCustomPresent::CopyTexture_RenderThread(RHICmdList.GetAsImmediate(), RendererModule, DstTexture, SrcTexture, FeatureLevel, bUsingVulkan,
|
||||
DstRect, SrcRect, bAlphaPremultiply, bNoAlphaWrite, bInvertSrcY, sRGBSource, bInvertAlpha);
|
||||
}
|
||||
|
||||
{
|
||||
FRHICopyTextureInfo CopyInfo;
|
||||
CopyInfo.Size = FIntVector(ViewportRect.Size().X, ViewportRect.Size().Y, 1);
|
||||
CopyInfo.SourcePosition = FIntVector::ZeroValue;
|
||||
CopyInfo.DestPosition = FIntVector(ViewportRect.Min.X, ViewportRect.Min.Y, 0);
|
||||
CopyInfo.SourceSliceIndex = 0;
|
||||
CopyInfo.DestSliceIndex = 0;
|
||||
|
||||
if (Texture->GetDesc().IsTextureArray() && TempTexture->GetDesc().IsTextureArray())
|
||||
{
|
||||
CopyInfo.NumSlices = FMath::Min(Texture->GetDesc().ArraySize, TempTexture->GetDesc().ArraySize);
|
||||
}
|
||||
|
||||
FRHITexture* SrcTexture = TempTexture;
|
||||
FRHITexture* DstTexture = Texture;
|
||||
RHICmdList.Transition(FRHITransitionInfo(SrcTexture, ERHIAccess::Unknown, ERHIAccess::CopySrc));
|
||||
RHICmdList.Transition(FRHITransitionInfo(DstTexture, ERHIAccess::Unknown, ERHIAccess::CopyDest));
|
||||
RHICmdList.CopyTexture(SrcTexture, DstTexture, CopyInfo);
|
||||
RHICmdList.Transition(FRHITransitionInfo(DstTexture, ERHIAccess::CopyDest, ERHIAccess::SRVMask));
|
||||
RHICmdList.Transition(FRHITransitionInfo(SrcTexture, ERHIAccess::CopySrc, ERHIAccess::SRVMask));
|
||||
}
|
||||
}
|
||||
|
||||
FPassthroughLayerPtr FPassthroughXR::CreateStereoLayerFromDesc(const IStereoLayers::FLayerDesc& LayerDesc) const
|
||||
{
|
||||
FPassthroughLayerPtr Layer = nullptr;
|
||||
|
||||
if (FPassthroughLayer::IsPassthoughLayerDesc(LayerDesc))
|
||||
{
|
||||
check(PassthroughInstance != XR_NULL_HANDLE);
|
||||
Layer = MakeShareable(new FPassthroughLayer(PassthroughInstance, GetInstance()));
|
||||
}
|
||||
return Layer;
|
||||
}
|
||||
|
||||
void FPassthroughXR::OnSetupLayers_RenderThread(XrSession InSession, const TArray<uint32_t>& LayerIds)
|
||||
{
|
||||
check(IsInRenderingThread());
|
||||
|
||||
#ifdef WITH_OCULUS_BRANCH
|
||||
ColorSwapchain = OpenXRHMD->GetColorSwapchain_RenderThread();
|
||||
#endif
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
if (ColorSwapchain && InvAlphaTexture == nullptr)
|
||||
{
|
||||
#if UE_VERSION_OLDER_THAN(5, 5, 0)
|
||||
const auto CVarPropagateAlpha = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.PostProcessing.PropagateAlpha"));
|
||||
const bool bPropagateAlpha = EAlphaChannelMode::FromInt(CVarPropagateAlpha->GetValueOnRenderThread()) == EAlphaChannelMode::AllowThroughTonemapper;
|
||||
#else
|
||||
const auto CVarPropagateAlpha = IConsoleManager::Get().FindConsoleVariable(TEXT("r.PostProcessing.PropagateAlpha"));
|
||||
const bool bPropagateAlpha = CVarPropagateAlpha->GetBool();
|
||||
#endif
|
||||
if (bPropagateAlpha)
|
||||
{
|
||||
const FRHITextureDesc TextureDesc = ColorSwapchain->GetTexture()->GetDesc();
|
||||
uint32 SizeX = TextureDesc.GetSize().X;
|
||||
uint32 SizeY = TextureDesc.GetSize().Y;
|
||||
EPixelFormat ColorFormat = TextureDesc.Format;
|
||||
uint32 NumMips = TextureDesc.NumMips;
|
||||
uint32 NumSamples = TextureDesc.NumSamples;
|
||||
|
||||
FClearValueBinding ColorTextureBinding = FClearValueBinding::Black;
|
||||
|
||||
const ETextureCreateFlags InvTextureCreateFlags = TexCreate_ShaderResource | TexCreate_RenderTargetable;
|
||||
FRHITextureCreateDesc InvTextureDesc{};
|
||||
if (ColorSwapchain->GetTexture2DArray() != nullptr)
|
||||
{
|
||||
InvTextureDesc = FRHITextureCreateDesc::Create2DArray(TEXT("InvAlphaTexture"))
|
||||
.SetArraySize(2)
|
||||
.SetExtent(SizeX, SizeY)
|
||||
.SetFormat(ColorFormat)
|
||||
.SetNumMips(NumMips)
|
||||
.SetNumSamples(NumSamples)
|
||||
.SetFlags(InvTextureCreateFlags | TexCreate_TargetArraySlicesIndependently)
|
||||
.SetClearValue(ColorTextureBinding);
|
||||
}
|
||||
else
|
||||
{
|
||||
InvTextureDesc = FRHITextureCreateDesc::Create2D(TEXT("InvAlphaTexture"))
|
||||
.SetExtent(SizeX, SizeY)
|
||||
.SetFormat(ColorFormat)
|
||||
.SetNumMips(NumMips)
|
||||
.SetNumSamples(NumSamples)
|
||||
.SetFlags(InvTextureCreateFlags)
|
||||
.SetClearValue(ColorTextureBinding);
|
||||
}
|
||||
InvAlphaTexture = RHICreateTexture(InvTextureDesc);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void FPassthroughXR::UpdateCompositionLayers(XrSession InSession, TArray<XrCompositionLayerBaseHeaderType*>& Headers)
|
||||
{
|
||||
check(IsInRenderingThread() || IsInRHIThread());
|
||||
|
||||
TArray<FPassthroughLayerPtr> SortedLayers = Layers_RenderThread;
|
||||
SortedLayers.Sort(FLayerDesc_ComparePriority());
|
||||
|
||||
// Headers array already contains (at least) one layer which is the eye's layer.
|
||||
// Underlay/SupportDetph layers need to be inserted before that layer, ordered by priority.
|
||||
int EyeLayerId = 0;
|
||||
for (const FPassthroughLayerPtr& Layer : SortedLayers)
|
||||
{
|
||||
if (Layer->IsBackgroundLayer() || Layer->PassthroughSupportsDepth())
|
||||
{
|
||||
XrCompositionLayerBaseHeaderType* CompositionLayerHeader = Layer->GetXrCompositionLayerHeader();
|
||||
if (CompositionLayerHeader != nullptr)
|
||||
{
|
||||
Headers.Insert(CompositionLayerHeader, EyeLayerId++);
|
||||
}
|
||||
}
|
||||
else if (Layer->IsOverlayLayer())
|
||||
{
|
||||
XrCompositionLayerBaseHeaderType* CompositionLayerHeader = Layer->GetXrCompositionLayerHeader();
|
||||
if (CompositionLayerHeader != nullptr)
|
||||
{
|
||||
Headers.Add(CompositionLayerHeader);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_OCULUS_BRANCH
|
||||
bool FPassthroughXR::OnEndGameFrame(FWorldContext& WorldContext)
|
||||
{
|
||||
FXRTrackingSystemBase* TS = static_cast<FXRTrackingSystemBase*>(GEngine->XRSystem.Get());
|
||||
TrackingToWorld = TS->GetTrackingToWorldTransform();
|
||||
WorldToMetersScale = TS->GetWorldToMetersScale();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void FPassthroughXR::OnBeginRendering_GameThread(XrSession InSession)
|
||||
{
|
||||
// Send game thread layers to render thread ones
|
||||
TArray<FPassthroughLayerPtr> XLayers;
|
||||
XLayers.Empty(LayerMap.Num());
|
||||
|
||||
for (auto& Pair : LayerMap)
|
||||
{
|
||||
XLayers.Emplace(Pair.Value->Clone());
|
||||
}
|
||||
|
||||
XLayers.Sort(FPassthroughLayerPtr_CompareId());
|
||||
|
||||
ENQUEUE_RENDER_COMMAND(TransferFrameStateToRenderingThread)
|
||||
([this, TrackingToWorld = TrackingToWorld, WorldToMetersScale = WorldToMetersScale, XLayers, InSession](FRHICommandListImmediate& RHICmdList) mutable {
|
||||
TrackingToWorld_RenderThread = TrackingToWorld;
|
||||
WorldToMetersScale_RenderThread = WorldToMetersScale;
|
||||
|
||||
int32 XLayerIndex = 0;
|
||||
int32 LayerIndex_RenderThread = 0;
|
||||
TArray<FPassthroughLayerPtr> ValidLayers;
|
||||
|
||||
// Scan for changes
|
||||
while (XLayerIndex < XLayers.Num() && LayerIndex_RenderThread < Layers_RenderThread.Num())
|
||||
{
|
||||
uint32 LayerIdA = XLayers[XLayerIndex]->GetDesc().GetLayerId();
|
||||
uint32 LayerIdB = Layers_RenderThread[LayerIndex_RenderThread]->GetDesc().GetLayerId();
|
||||
|
||||
if (LayerIdA < LayerIdB) // If a layer was inserted in the middle of existing ones
|
||||
{
|
||||
if (XLayers[XLayerIndex]->Initialize_RenderThread(InSession))
|
||||
{
|
||||
ValidLayers.Add(XLayers[XLayerIndex]);
|
||||
}
|
||||
XLayerIndex++;
|
||||
}
|
||||
else if (LayerIdA > LayerIdB) // If a layer was removed in the middle of existing ones
|
||||
{
|
||||
DeferredDeletion.AddOpenXRLayerToDeferredDeletionQueue(Layers_RenderThread[LayerIndex_RenderThread++]);
|
||||
}
|
||||
else // This layer is not new nor removed
|
||||
{
|
||||
if (XLayers[XLayerIndex]->Initialize_RenderThread(InSession, Layers_RenderThread[LayerIndex_RenderThread].Get()))
|
||||
{
|
||||
LayerIndex_RenderThread++;
|
||||
ValidLayers.Add(XLayers[XLayerIndex]);
|
||||
}
|
||||
XLayerIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// Create missing layers
|
||||
while (XLayerIndex < XLayers.Num())
|
||||
{
|
||||
if (XLayers[XLayerIndex]->Initialize_RenderThread(InSession))
|
||||
{
|
||||
ValidLayers.Add(XLayers[XLayerIndex]);
|
||||
}
|
||||
XLayerIndex++;
|
||||
}
|
||||
|
||||
// Delete remaining layers
|
||||
while (LayerIndex_RenderThread < Layers_RenderThread.Num())
|
||||
{
|
||||
DeferredDeletion.AddOpenXRLayerToDeferredDeletionQueue(Layers_RenderThread[LayerIndex_RenderThread++]);
|
||||
}
|
||||
|
||||
Layers_RenderThread = ValidLayers;
|
||||
|
||||
DeferredDeletion.HandleLayerDeferredDeletionQueue_RenderThread();
|
||||
});
|
||||
}
|
||||
|
||||
#ifdef WITH_OCULUS_BRANCH
|
||||
void FPassthroughXR::OnBeginRenderingLate_RenderThread(XrSession InSession, FRHICommandListImmediate& RHICmdList)
|
||||
{
|
||||
ColorSwapchain = OpenXRHMD->GetColorSwapchain_RenderThread();
|
||||
|
||||
if (ColorSwapchain && InvAlphaTexture)
|
||||
{
|
||||
ColorSwapChainTexture = ColorSwapchain->GetTexture();
|
||||
}
|
||||
}
|
||||
|
||||
void FPassthroughXR::FinishRenderFrame_RenderThread(FRDGBuilder& GraphBuilder)
|
||||
{
|
||||
check(IsInRenderingThread());
|
||||
|
||||
if (ColorSwapChainTexture && InvAlphaTexture)
|
||||
{
|
||||
FRDGEventName PassName = RDG_EVENT_NAME("FPassthroughXR_InvertTextureAlpha");
|
||||
GraphBuilder.AddPass(MoveTemp(PassName), ERDGPassFlags::None,
|
||||
[this, SwapchainTexture = ColorSwapChainTexture](FRHICommandListImmediate& RHICmdList) {
|
||||
const FRHITextureDesc TextureDesc = SwapchainTexture->GetDesc();
|
||||
FIntRect TextureRect = FIntRect(0, 0, TextureDesc.GetSize().X, TextureDesc.GetSize().Y);
|
||||
|
||||
InvertTextureAlpha_RenderThread(RHICmdList, SwapchainTexture, InvAlphaTexture, TextureRect);
|
||||
});
|
||||
}
|
||||
|
||||
XrSpace Space = OpenXRHMD->GetTrackingSpace();
|
||||
XrTime DisplayTime = OpenXRHMD->GetDisplayTime();
|
||||
|
||||
FRDGEventName PassName = RDG_EVENT_NAME("FPassthroughXR_UpdatePassthroughLayers");
|
||||
GraphBuilder.AddPass(MoveTemp(PassName), ERDGPassFlags::None,
|
||||
[this, Space, DisplayTime](FRHICommandListImmediate& RHICmdList) {
|
||||
for (const FPassthroughLayerPtr& Layer : Layers_RenderThread)
|
||||
{
|
||||
Layer->UpdatePassthrough_RenderThread(RHICmdList,
|
||||
Space,
|
||||
DisplayTime,
|
||||
WorldToMetersScale_RenderThread,
|
||||
TrackingToWorld_RenderThread);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
void FPassthroughXR::OnEvent(XrSession InSession, const XrEventDataBaseHeader* InHeader)
|
||||
{
|
||||
switch (InHeader->type)
|
||||
{
|
||||
case XR_TYPE_EVENT_DATA_PASSTHROUGH_LAYER_RESUMED_META:
|
||||
if (Settings->bExtLayerResumedEventAvailable)
|
||||
{
|
||||
const XrEventDataPassthroughLayerResumedMETA* LayerResumedEvent =
|
||||
reinterpret_cast<const XrEventDataPassthroughLayerResumedMETA*>(InHeader);
|
||||
|
||||
for (auto& Pair : LayerMap)
|
||||
{
|
||||
if (Pair.Value->GetLayerHandle() == LayerResumedEvent->layer)
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Log, TEXT("FOculusXRPassthroughEventHandling - Passthrough Layer #%d resumed"), Pair.Value->GetDesc().GetLayerId());
|
||||
|
||||
// Send event
|
||||
OculusXRPassthrough::FOculusXRPassthroughEventDelegates::OculusPassthroughLayerResumed.Broadcast(Pair.Value->GetDesc().GetLayerId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const void* FPassthroughXR::OnEndProjectionLayer(XrSession InSession, int32 InLayerIndex, const void* InNext, XrCompositionLayerFlags& OutFlags)
|
||||
{
|
||||
check(IsInRenderingThread() || IsInRHIThread());
|
||||
|
||||
bool bHasBackgroundLayer = false;
|
||||
for (const FPassthroughLayerPtr& Layer : Layers_RenderThread)
|
||||
{
|
||||
if (Layer->IsBackgroundLayer() || Layer->PassthroughSupportsDepth())
|
||||
{
|
||||
bHasBackgroundLayer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bHasBackgroundLayer)
|
||||
{
|
||||
OutFlags |= XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
|
||||
|
||||
if (Settings->bExtLayerAlphaBlendAvailable)
|
||||
{
|
||||
InNext = &ProjectionLayerAlphaBlend;
|
||||
ProjectionLayerAlphaBlend.type = XR_TYPE_COMPOSITION_LAYER_ALPHA_BLEND_FB;
|
||||
ProjectionLayerAlphaBlend.srcFactorColor = XR_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA_FB;
|
||||
ProjectionLayerAlphaBlend.srcFactorAlpha = XR_BLEND_FACTOR_ONE_FB;
|
||||
ProjectionLayerAlphaBlend.dstFactorColor = XR_BLEND_FACTOR_SRC_ALPHA_FB;
|
||||
ProjectionLayerAlphaBlend.dstFactorAlpha = XR_BLEND_FACTOR_ZERO_FB;
|
||||
ProjectionLayerAlphaBlend.next = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
// XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT is required for the eye layer to be correctly blended
|
||||
// when XR_FB_composition_layer_alpha_blend extension is not available (e.g. Link)
|
||||
OutFlags |= XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT;
|
||||
}
|
||||
}
|
||||
|
||||
return InNext;
|
||||
}
|
||||
|
||||
#ifdef WITH_OCULUS_BRANCH
|
||||
void FPassthroughXR::OnCreateLayer(const IStereoLayers::FLayerDesc& InLayerDesc, uint32 LayerId)
|
||||
{
|
||||
OculusXRHMD::CheckInGameThread();
|
||||
|
||||
if (FPassthroughLayerPtr Layer = CreateStereoLayerFromDesc(InLayerDesc))
|
||||
{
|
||||
Layer->SetDesc(InLayerDesc);
|
||||
|
||||
LayerMap.Add(LayerId, Layer);
|
||||
}
|
||||
}
|
||||
|
||||
void FPassthroughXR::OnDestroyLayer(uint32 LayerId)
|
||||
{
|
||||
OculusXRHMD::CheckInGameThread();
|
||||
|
||||
FPassthroughLayerPtr* LayerFound = LayerMap.Find(LayerId);
|
||||
if (LayerFound)
|
||||
{
|
||||
(*LayerFound)->DestroyLayer();
|
||||
}
|
||||
LayerMap.Remove(LayerId);
|
||||
}
|
||||
|
||||
void FPassthroughXR::OnSetLayerDesc(uint32 LayerId, const IStereoLayers::FLayerDesc& InLayerDesc)
|
||||
{
|
||||
OculusXRHMD::CheckInGameThread();
|
||||
|
||||
FPassthroughLayerPtr* LayerFound = LayerMap.Find(LayerId);
|
||||
|
||||
if (LayerFound)
|
||||
{
|
||||
(*LayerFound)->SetDesc(InLayerDesc);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void FPassthroughXR::InitializePassthrough(XrSession InSession)
|
||||
{
|
||||
if (bPassthroughInitialized)
|
||||
return;
|
||||
|
||||
bPassthroughInitialized = true;
|
||||
|
||||
check(IsInRenderingThread());
|
||||
|
||||
const XrPassthroughCreateInfoFB PassthroughCreateInfo = { XR_TYPE_PASSTHROUGH_CREATE_INFO_FB };
|
||||
|
||||
XrResult CreatePassthroughResult = xrCreatePassthroughFB(InSession, &PassthroughCreateInfo, &PassthroughInstance);
|
||||
if (!XR_SUCCEEDED(CreatePassthroughResult))
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Error, TEXT("xrCreatePassthroughFB failed, error : %i"), CreatePassthroughResult);
|
||||
return;
|
||||
}
|
||||
|
||||
XrResult PassthroughStartResult = xrPassthroughStartFB(PassthroughInstance);
|
||||
if (!XR_SUCCEEDED(PassthroughStartResult))
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Error, TEXT("xrPassthroughStartFB failed, error : %i"), PassthroughStartResult);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void FPassthroughXR::ShutdownPassthrough(XrSession InSession)
|
||||
{
|
||||
if (!bPassthroughInitialized)
|
||||
return;
|
||||
|
||||
bPassthroughInitialized = false;
|
||||
|
||||
check(IsInRenderingThread());
|
||||
|
||||
if (PassthroughInstance != XR_NULL_HANDLE)
|
||||
{
|
||||
XrResult Result = xrDestroyPassthroughFB(PassthroughInstance);
|
||||
if (!XR_SUCCEEDED(Result))
|
||||
{
|
||||
UE_LOG(LogOculusXRPassthrough, Error, TEXT("xrDestroyPassthroughFB failed, error : %i"), Result);
|
||||
}
|
||||
PassthroughInstance = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void FPassthroughXR::Update_GameThread(XrSession InSession)
|
||||
{
|
||||
check(IsInGameThread());
|
||||
|
||||
check(Settings != nullptr);
|
||||
const bool bPassthroughEnabled = Settings->bPassthroughEnabled;
|
||||
|
||||
ExecuteOnRenderThread_DoNotWait([this, InSession, bPassthroughEnabled](FRHICommandListImmediate& RHICmdList) {
|
||||
if (bPassthroughEnabled && !bPassthroughInitialized)
|
||||
{
|
||||
InitializePassthrough(InSession);
|
||||
}
|
||||
|
||||
if (!bPassthroughEnabled && bPassthroughInitialized)
|
||||
{
|
||||
ShutdownPassthrough(InSession);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace XRPassthrough
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Including the khronos openxr header here to override the one included from IOpenXREntensionPlugin, making sure we use the latest one.
|
||||
#include "khronos/openxr/openxr.h"
|
||||
#include "khronos/openxr/meta_openxr_preview/meta_passthrough_layer_resumed_event.h"
|
||||
#include "IOpenXRExtensionPlugin.h"
|
||||
#include "IStereoLayers.h"
|
||||
#include "OculusXRPassthroughLayer.h"
|
||||
#include "OculusXRPassthroughXR_DeletionQueue.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "OculusXRPassthrough"
|
||||
|
||||
class FOpenXRSwapchain;
|
||||
class FOpenXRHMD;
|
||||
class UMaterial;
|
||||
|
||||
namespace XRPassthrough
|
||||
{
|
||||
struct FSettings
|
||||
{
|
||||
UMaterial* PokeAHoleMaterial;
|
||||
|
||||
bool bPassthroughEnabled;
|
||||
|
||||
bool bExtLayerAlphaBlendAvailable;
|
||||
bool bExtPassthroughAvailable;
|
||||
bool bExtTriangleMeshAvailable;
|
||||
bool bExtColorLutAvailable;
|
||||
bool bExtLayerResumedEventAvailable;
|
||||
};
|
||||
typedef TSharedPtr<FSettings, ESPMode::ThreadSafe> FSettingsPtr;
|
||||
|
||||
class FPassthroughXR : public IOpenXRExtensionPlugin
|
||||
{
|
||||
public:
|
||||
// IOculusXROpenXRHMDPlugin
|
||||
virtual void* OnWaitFrame(XrSession InSession, void* InNext) override;
|
||||
virtual bool GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
|
||||
virtual bool GetOptionalExtensions(TArray<const ANSICHAR*>& OutExtensions) override;
|
||||
virtual const void* OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext) override;
|
||||
virtual const void* OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext) override;
|
||||
virtual void PostCreateSession(XrSession InSession) override;
|
||||
virtual void OnDestroySession(XrSession InSession) override;
|
||||
virtual const void* OnEndProjectionLayer(XrSession InSession, int32 InLayerIndex, const void* InNext, XrCompositionLayerFlags& OutFlags) override;
|
||||
|
||||
#ifdef WITH_OCULUS_BRANCH
|
||||
virtual void OnCreateLayer(const IStereoLayers::FLayerDesc& InLayerDesc, uint32 LayerId) override;
|
||||
virtual void OnDestroyLayer(uint32 LayerId) override;
|
||||
virtual void OnSetLayerDesc(uint32 LayerId, const IStereoLayers::FLayerDesc& InLayerDesc) override;
|
||||
#endif
|
||||
|
||||
virtual void OnSetupLayers_RenderThread(XrSession InSession, const TArray<uint32_t>& LayerIds) override;
|
||||
virtual void UpdateCompositionLayers(XrSession InSession, TArray<XrCompositionLayerBaseHeaderType*>& Headers) override;
|
||||
|
||||
#ifdef WITH_OCULUS_BRANCH
|
||||
virtual bool OnEndGameFrame(FWorldContext& WorldContext) override;
|
||||
#endif
|
||||
|
||||
virtual void OnBeginRendering_GameThread(XrSession InSession) override;
|
||||
#ifdef WITH_OCULUS_BRANCH
|
||||
virtual void OnBeginRenderingLate_RenderThread(XrSession InSession, FRHICommandListImmediate& RHICmdList) override;
|
||||
virtual void FinishRenderFrame_RenderThread(FRDGBuilder& GraphBuilder) override;
|
||||
#endif
|
||||
|
||||
virtual void OnEvent(XrSession InSession, const XrEventDataBaseHeader* InHeader) override;
|
||||
|
||||
public:
|
||||
static TWeakPtr<FPassthroughXR> GetInstance();
|
||||
static bool IsPassthoughLayerDesc(const IStereoLayers::FLayerDesc& LayerDesc);
|
||||
FPassthroughXR();
|
||||
virtual ~FPassthroughXR();
|
||||
void RegisterAsOpenXRExtension();
|
||||
|
||||
XrPassthroughFB GetPassthroughInstance() const
|
||||
{
|
||||
return PassthroughInstance;
|
||||
}
|
||||
|
||||
FSettingsPtr GetSettings() const
|
||||
{
|
||||
return Settings;
|
||||
}
|
||||
|
||||
OCULUSXRPASSTHROUGH_API bool IsPassthroughEnabled(void) const;
|
||||
|
||||
private:
|
||||
void InvertTextureAlpha_RenderThread(FRHICommandList& RHICmdList, FRHITexture* Texture, FRHITexture* TempTexture, const FIntRect& ViewportRect);
|
||||
|
||||
FPassthroughLayerPtr CreateStereoLayerFromDesc(const IStereoLayers::FLayerDesc& LayerDesc) const;
|
||||
void ShutdownPassthrough(XrSession InSession);
|
||||
void InitializePassthrough(XrSession InSession);
|
||||
void Update_GameThread(XrSession InSession);
|
||||
|
||||
IRendererModule* RendererModule;
|
||||
FTextureRHIRef InvAlphaTexture;
|
||||
FOpenXRSwapchain* ColorSwapchain;
|
||||
FRHITexture* ColorSwapChainTexture;
|
||||
|
||||
XrCompositionLayerAlphaBlendFB ProjectionLayerAlphaBlend;
|
||||
TMap<uint32, FPassthroughLayerPtr> LayerMap;
|
||||
TArray<FPassthroughLayerPtr> Layers_RenderThread;
|
||||
|
||||
XRPassthrough::FDeferredDeletionQueue DeferredDeletion;
|
||||
|
||||
bool bPassthroughInitialized;
|
||||
XrPassthroughFB PassthroughInstance;
|
||||
|
||||
FSettingsPtr Settings;
|
||||
|
||||
FOpenXRHMD* OpenXRHMD;
|
||||
|
||||
float WorldToMetersScale;
|
||||
float WorldToMetersScale_RenderThread;
|
||||
FTransform TrackingToWorld;
|
||||
FTransform TrackingToWorld_RenderThread;
|
||||
};
|
||||
|
||||
} // namespace XRPassthrough
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRPassthroughXRFunctions.h"
|
||||
#include "OpenXRCore.h"
|
||||
#include "OpenXR/OculusXROpenXRUtilities.h"
|
||||
|
||||
namespace XRPassthrough
|
||||
{
|
||||
PFN_xrCreatePassthroughFB xrCreatePassthroughFB = nullptr;
|
||||
PFN_xrDestroyPassthroughFB xrDestroyPassthroughFB = nullptr;
|
||||
PFN_xrPassthroughStartFB xrPassthroughStartFB = nullptr;
|
||||
PFN_xrPassthroughPauseFB xrPassthroughPauseFB = nullptr;
|
||||
PFN_xrCreatePassthroughLayerFB xrCreatePassthroughLayerFB = nullptr;
|
||||
PFN_xrDestroyPassthroughLayerFB xrDestroyPassthroughLayerFB = nullptr;
|
||||
PFN_xrPassthroughLayerPauseFB xrPassthroughLayerPauseFB = nullptr;
|
||||
PFN_xrPassthroughLayerResumeFB xrPassthroughLayerResumeFB = nullptr;
|
||||
PFN_xrPassthroughLayerSetStyleFB xrPassthroughLayerSetStyleFB = nullptr;
|
||||
TOptional<PFN_xrCreateTriangleMeshFB> xrCreateTriangleMeshFB = nullptr;
|
||||
TOptional<PFN_xrDestroyTriangleMeshFB> xrDestroyTriangleMeshFB = nullptr;
|
||||
PFN_xrCreateGeometryInstanceFB xrCreateGeometryInstanceFB = nullptr;
|
||||
PFN_xrDestroyGeometryInstanceFB xrDestroyGeometryInstanceFB = nullptr;
|
||||
PFN_xrGeometryInstanceSetTransformFB xrGeometryInstanceSetTransformFB = nullptr;
|
||||
PFN_xrCreatePassthroughColorLutMETA xrCreatePassthroughColorLutMETA = nullptr;
|
||||
PFN_xrDestroyPassthroughColorLutMETA xrDestroyPassthroughColorLutMETA = nullptr;
|
||||
PFN_xrUpdatePassthroughColorLutMETA xrUpdatePassthroughColorLutMETA = nullptr;
|
||||
|
||||
void InitOpenXRFunctions(XrInstance InInstance)
|
||||
{
|
||||
OculusXR::XRGetInstanceProcAddr(InInstance, "xrCreatePassthroughFB", &xrCreatePassthroughFB);
|
||||
OculusXR::XRGetInstanceProcAddr(InInstance, "xrDestroyPassthroughFB", &xrDestroyPassthroughFB);
|
||||
OculusXR::XRGetInstanceProcAddr(InInstance, "xrPassthroughStartFB", &xrPassthroughStartFB);
|
||||
OculusXR::XRGetInstanceProcAddr(InInstance, "xrPassthroughPauseFB", &xrPassthroughPauseFB);
|
||||
OculusXR::XRGetInstanceProcAddr(InInstance, "xrCreatePassthroughLayerFB", &xrCreatePassthroughLayerFB);
|
||||
OculusXR::XRGetInstanceProcAddr(InInstance, "xrDestroyPassthroughLayerFB", &xrDestroyPassthroughLayerFB);
|
||||
OculusXR::XRGetInstanceProcAddr(InInstance, "xrPassthroughLayerPauseFB", &xrPassthroughLayerPauseFB);
|
||||
OculusXR::XRGetInstanceProcAddr(InInstance, "xrPassthroughLayerResumeFB", &xrPassthroughLayerResumeFB);
|
||||
OculusXR::XRGetInstanceProcAddr(InInstance, "xrPassthroughLayerSetStyleFB", &xrPassthroughLayerSetStyleFB);
|
||||
OculusXR::XRGetInstanceProcAddr(InInstance, "xrCreateTriangleMeshFB", &xrCreateTriangleMeshFB);
|
||||
OculusXR::XRGetInstanceProcAddr(InInstance, "xrDestroyTriangleMeshFB", &xrDestroyTriangleMeshFB);
|
||||
OculusXR::XRGetInstanceProcAddr(InInstance, "xrCreateGeometryInstanceFB", &xrCreateGeometryInstanceFB);
|
||||
OculusXR::XRGetInstanceProcAddr(InInstance, "xrDestroyGeometryInstanceFB", &xrDestroyGeometryInstanceFB);
|
||||
OculusXR::XRGetInstanceProcAddr(InInstance, "xrGeometryInstanceSetTransformFB", &xrGeometryInstanceSetTransformFB);
|
||||
OculusXR::XRGetInstanceProcAddr(InInstance, "xrCreatePassthroughColorLutMETA", &xrCreatePassthroughColorLutMETA);
|
||||
OculusXR::XRGetInstanceProcAddr(InInstance, "xrDestroyPassthroughColorLutMETA", &xrDestroyPassthroughColorLutMETA);
|
||||
OculusXR::XRGetInstanceProcAddr(InInstance, "xrUpdatePassthroughColorLutMETA", &xrUpdatePassthroughColorLutMETA);
|
||||
}
|
||||
|
||||
} // namespace XRPassthrough
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "khronos/openxr/openxr.h"
|
||||
#include "Misc/Optional.h"
|
||||
|
||||
namespace XRPassthrough
|
||||
{
|
||||
extern PFN_xrCreatePassthroughFB xrCreatePassthroughFB;
|
||||
extern PFN_xrDestroyPassthroughFB xrDestroyPassthroughFB;
|
||||
extern PFN_xrPassthroughStartFB xrPassthroughStartFB;
|
||||
extern PFN_xrPassthroughPauseFB xrPassthroughPauseFB;
|
||||
extern PFN_xrCreatePassthroughLayerFB xrCreatePassthroughLayerFB;
|
||||
extern PFN_xrDestroyPassthroughLayerFB xrDestroyPassthroughLayerFB;
|
||||
extern PFN_xrPassthroughLayerPauseFB xrPassthroughLayerPauseFB;
|
||||
extern PFN_xrPassthroughLayerResumeFB xrPassthroughLayerResumeFB;
|
||||
extern PFN_xrPassthroughLayerSetStyleFB xrPassthroughLayerSetStyleFB;
|
||||
extern TOptional<PFN_xrCreateTriangleMeshFB> xrCreateTriangleMeshFB;
|
||||
extern TOptional<PFN_xrDestroyTriangleMeshFB> xrDestroyTriangleMeshFB;
|
||||
extern PFN_xrCreateGeometryInstanceFB xrCreateGeometryInstanceFB;
|
||||
extern PFN_xrDestroyGeometryInstanceFB xrDestroyGeometryInstanceFB;
|
||||
extern PFN_xrGeometryInstanceSetTransformFB xrGeometryInstanceSetTransformFB;
|
||||
extern PFN_xrCreatePassthroughColorLutMETA xrCreatePassthroughColorLutMETA;
|
||||
extern PFN_xrDestroyPassthroughColorLutMETA xrDestroyPassthroughColorLutMETA;
|
||||
extern PFN_xrUpdatePassthroughColorLutMETA xrUpdatePassthroughColorLutMETA;
|
||||
|
||||
void InitOpenXRFunctions(XrInstance InInstance);
|
||||
} // namespace XRPassthrough
|
||||
@@ -0,0 +1,50 @@
|
||||
// @lint-ignore-every LICENSELINT
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#include "OculusXRPassthroughXR_DeletionQueue.h"
|
||||
|
||||
#include "XRThreadUtils.h"
|
||||
#include "OculusXRHMDModule.h"
|
||||
#include "OculusXRPassthroughXRFunctions.h"
|
||||
#include "OculusXRPassthroughLayer.h"
|
||||
|
||||
namespace XRPassthrough
|
||||
{
|
||||
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
// FDeferredDeletionQueue
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
const uint32 NUM_FRAMES_TO_WAIT_FOR_OPENXR_LAYER_DELETE = 7;
|
||||
|
||||
void FDeferredDeletionQueue::AddOpenXRLayerToDeferredDeletionQueue(const XRPassthrough::FPassthroughLayerPtr& ptr)
|
||||
{
|
||||
DeferredDeletionEntry Entry;
|
||||
Entry.OpenXRLayer = ptr;
|
||||
Entry.FrameEnqueued = GFrameCounter;
|
||||
Entry.EntryType = DeferredDeletionEntry::DeferredDeletionEntryType::OpenXRLayer;
|
||||
DeferredDeletionArray.Add(Entry);
|
||||
}
|
||||
|
||||
void FDeferredDeletionQueue::HandleLayerDeferredDeletionQueue_RenderThread(bool bDeleteImmediately)
|
||||
{
|
||||
// Traverse list backwards so the swap switches to elements already tested
|
||||
for (int32 Index = DeferredDeletionArray.Num() - 1; Index >= 0; --Index)
|
||||
{
|
||||
DeferredDeletionEntry* Entry = &DeferredDeletionArray[Index];
|
||||
if (Entry->EntryType == DeferredDeletionEntry::DeferredDeletionEntryType::OpenXRLayer)
|
||||
{
|
||||
if (bDeleteImmediately || GFrameCounter > Entry->FrameEnqueued + NUM_FRAMES_TO_WAIT_FOR_OPENXR_LAYER_DELETE)
|
||||
{
|
||||
Entry->OpenXRLayer->DestroyLayer_RenderThread();
|
||||
|
||||
#if UE_VERSION_OLDER_THAN(5, 5, 0)
|
||||
DeferredDeletionArray.RemoveAtSwap(Index, 1, false);
|
||||
#else
|
||||
DeferredDeletionArray.RemoveAtSwap(Index, 1, EAllowShrinking::No);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace XRPassthrough
|
||||
@@ -0,0 +1,42 @@
|
||||
// @lint-ignore-every LICENSELINT
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
#include "OculusXRHMDPrivate.h"
|
||||
|
||||
namespace XRPassthrough
|
||||
{
|
||||
class FPassthroughLayer;
|
||||
typedef TSharedPtr<XRPassthrough::FPassthroughLayer, ESPMode::ThreadSafe> FPassthroughLayerPtr;
|
||||
} // namespace XRPassthrough
|
||||
|
||||
namespace XRPassthrough
|
||||
{
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
// FDeferredDeletionQueue
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
|
||||
class FDeferredDeletionQueue
|
||||
{
|
||||
public:
|
||||
void AddOpenXRLayerToDeferredDeletionQueue(const FPassthroughLayerPtr& ptr);
|
||||
void HandleLayerDeferredDeletionQueue_RenderThread(bool bDeleteImmediately = false);
|
||||
|
||||
private:
|
||||
struct DeferredDeletionEntry
|
||||
{
|
||||
enum class DeferredDeletionEntryType
|
||||
{
|
||||
OpenXRLayer
|
||||
};
|
||||
|
||||
XRPassthrough::FPassthroughLayerPtr OpenXRLayer;
|
||||
|
||||
uint32 FrameEnqueued;
|
||||
DeferredDeletionEntryType EntryType;
|
||||
};
|
||||
|
||||
TArray<DeferredDeletionEntry> DeferredDeletionArray;
|
||||
};
|
||||
|
||||
} // namespace XRPassthrough
|
||||
@@ -0,0 +1,179 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "OculusXRPersistentPassthroughInstance.h"
|
||||
#include "Engine/GameEngine.h"
|
||||
#include "IStereoLayers.h"
|
||||
#include "OculusXRPassthroughLayerComponent.h"
|
||||
#include "TextureResource.h"
|
||||
|
||||
void UOculusXRPersistentPassthroughInstance::SetVisible(bool InVisible)
|
||||
{
|
||||
Parameters.bVisible = InVisible;
|
||||
|
||||
UpdateLayer();
|
||||
}
|
||||
|
||||
void UOculusXRPersistentPassthroughInstance::SetPriority(int32 InPriority)
|
||||
{
|
||||
Parameters.Priority = InPriority;
|
||||
|
||||
UpdateLayer();
|
||||
}
|
||||
|
||||
UOculusXRPersistentPassthroughInstance::UOculusXRPersistentPassthroughInstance(const FObjectInitializer& ObjectInitializer)
|
||||
{
|
||||
Parameters.Shape = ObjectInitializer.CreateDefaultSubobject<UOculusXRStereoLayerShapeReconstructed>(this, TEXT("PPT_StereoLayerShapeReconstructed"));
|
||||
}
|
||||
|
||||
void UOculusXRPersistentPassthroughInstance::InitLayer(FOculusXRPersistentPassthroughParameters InParameters)
|
||||
{
|
||||
UpdateParameters(InParameters);
|
||||
}
|
||||
|
||||
void UOculusXRPersistentPassthroughInstance::UpdateParameters(FOculusXRPersistentPassthroughParameters InParameters)
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
// We need the instance to be the owner of the shape, but in editor the shape still exist and its owner is the BP calling the method.
|
||||
// So we clear it to force a new one to be created with LoadShape(this)
|
||||
InParameters.Shape = nullptr;
|
||||
#endif
|
||||
|
||||
if (InParameters.LoadShape(this) == nullptr)
|
||||
{
|
||||
Parameters.Shape = NewObject<UOculusXRStereoLayerShapeReconstructed>(this, NAME_None, RF_Public);
|
||||
}
|
||||
|
||||
Parameters = InParameters;
|
||||
|
||||
UpdateLayer();
|
||||
}
|
||||
|
||||
void UOculusXRPersistentPassthroughInstance::UpdateLayer()
|
||||
{
|
||||
IStereoLayers* StereoLayers;
|
||||
if (!GEngine->StereoRenderingDevice.IsValid() || (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IStereoLayers::FLayerDesc LayerDesc;
|
||||
LayerDesc.Priority = Parameters.Priority;
|
||||
LayerDesc.QuadSize = FVector2D(100.f, 100.f);
|
||||
|
||||
LayerDesc.Transform = FTransform::Identity;
|
||||
|
||||
TObjectPtr<class UTexture2D> Texture = GEngine->DefaultTexture;
|
||||
if (Texture)
|
||||
{
|
||||
Texture->SetForceMipLevelsToBeResident(30.0f);
|
||||
LayerDesc.Texture = Texture->GetResource()->TextureRHI;
|
||||
LayerDesc.Flags |= (Texture->GetMaterialType() == MCT_TextureExternal) ? IStereoLayers::LAYER_FLAG_TEX_EXTERNAL : 0;
|
||||
}
|
||||
|
||||
LayerDesc.Flags |= (!Parameters.bVisible) ? IStereoLayers::LAYER_FLAG_HIDDEN : 0;
|
||||
|
||||
LayerDesc.PositionType = IStereoLayers::FaceLocked;
|
||||
|
||||
// Set the correct layer shape and apply any shape-specific properties
|
||||
Parameters.Shape->ApplyShape(LayerDesc);
|
||||
|
||||
if (LayerId != IStereoLayers::FLayerDesc::INVALID_LAYER_ID)
|
||||
{
|
||||
StereoLayers->SetLayerDesc(LayerId, LayerDesc);
|
||||
}
|
||||
else
|
||||
{
|
||||
LayerId = StereoLayers->CreateLayer(LayerDesc);
|
||||
}
|
||||
|
||||
StereoLayers->MarkTextureForUpdate(LayerId);
|
||||
}
|
||||
|
||||
void UOculusXRPersistentPassthroughInstance::BeginDestroy()
|
||||
{
|
||||
IStereoLayers* StereoLayers;
|
||||
if (LayerId != IStereoLayers::FLayerDesc::INVALID_LAYER_ID && GEngine->StereoRenderingDevice.IsValid() && (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) != nullptr)
|
||||
{
|
||||
StereoLayers->DestroyLayer(LayerId);
|
||||
LayerId = IStereoLayers::FLayerDesc::INVALID_LAYER_ID;
|
||||
}
|
||||
|
||||
Super::BeginDestroy();
|
||||
}
|
||||
|
||||
void UOculusXRPersistentPassthroughInstance::OnAnyLayerResumedEvent(int InLayerId)
|
||||
{
|
||||
if (LayerId == InLayerId)
|
||||
{
|
||||
// Execute all single delegates (added from UOculusXRPassthroughSubsystem::InitializePersistentPassthrough)
|
||||
for (const FOculusXRPassthrough_LayerResumed_Single& Delegate : LayerResumedSingleDelegates)
|
||||
{
|
||||
Delegate.ExecuteIfBound();
|
||||
}
|
||||
|
||||
OnLayerResumed.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
void UOculusXRPersistentPassthroughInstance::AddLayerResumedSingleDelegate(const FOculusXRPassthrough_LayerResumed_Single& Delegate)
|
||||
{
|
||||
if (!Delegate.IsBound())
|
||||
return;
|
||||
|
||||
LayerResumedSingleDelegates.Add(Delegate);
|
||||
}
|
||||
|
||||
// Save all of the Shape's properties in temporary properties which can always be serialized
|
||||
void FOculusXRPersistentPassthroughParameters::ApplyShape()
|
||||
{
|
||||
if (!Shape)
|
||||
return;
|
||||
|
||||
TempShape_LayerOrder = Shape->LayerOrder;
|
||||
TempShape_TextureOpacityFactor = Shape->TextureOpacityFactor;
|
||||
TempShape_bEnableEdgeColor = Shape->bEnableEdgeColor;
|
||||
TempShape_EdgeColor = Shape->EdgeColor;
|
||||
TempShape_bEnableColorMap = Shape->bEnableColorMap;
|
||||
TempShape_ColorMapType = Shape->ColorMapType;
|
||||
TempShape_bUseColorMapCurve = Shape->bUseColorMapCurve;
|
||||
TempShape_ColorMapCurve = Shape->ColorMapCurve;
|
||||
TempShape_Contrast = Shape->Contrast;
|
||||
TempShape_Brightness = Shape->Brightness;
|
||||
TempShape_Posterize = Shape->Posterize;
|
||||
TempShape_Saturation = Shape->Saturation;
|
||||
TempShape_LutWeight = Shape->LutWeight;
|
||||
TempShape_ColorLUTSource = Shape->ColorLUTSource;
|
||||
TempShape_ColorLUTTarget = Shape->ColorLUTTarget;
|
||||
TempShape_ColorScale = Shape->ColorScale;
|
||||
TempShape_ColorOffset = Shape->ColorOffset;
|
||||
}
|
||||
|
||||
// If Shape doesn't already exists, create a new one and set it up using the temporary properties
|
||||
UOculusXRStereoLayerShapeReconstructed* FOculusXRPersistentPassthroughParameters::LoadShape(UObject* owner)
|
||||
{
|
||||
if (Shape)
|
||||
return Shape;
|
||||
|
||||
UOculusXRStereoLayerShapeReconstructed* NewShape = NewObject<UOculusXRStereoLayerShapeReconstructed>(owner);
|
||||
NewShape->LayerOrder = TempShape_LayerOrder;
|
||||
NewShape->TextureOpacityFactor = TempShape_TextureOpacityFactor;
|
||||
NewShape->bEnableEdgeColor = TempShape_bEnableEdgeColor;
|
||||
NewShape->EdgeColor = TempShape_EdgeColor;
|
||||
NewShape->bEnableColorMap = TempShape_bEnableColorMap;
|
||||
NewShape->ColorMapType = TempShape_ColorMapType;
|
||||
NewShape->bUseColorMapCurve = TempShape_bUseColorMapCurve;
|
||||
NewShape->ColorMapCurve = TempShape_ColorMapCurve;
|
||||
NewShape->Contrast = TempShape_Contrast;
|
||||
NewShape->Brightness = TempShape_Brightness;
|
||||
NewShape->Posterize = TempShape_Posterize;
|
||||
NewShape->Saturation = TempShape_Saturation;
|
||||
NewShape->LutWeight = TempShape_LutWeight;
|
||||
NewShape->ColorLUTSource = TempShape_ColorLUTSource;
|
||||
NewShape->ColorLUTTarget = TempShape_ColorLUTTarget;
|
||||
NewShape->ColorScale = TempShape_ColorScale;
|
||||
NewShape->ColorOffset = TempShape_ColorOffset;
|
||||
|
||||
Shape = NewShape;
|
||||
|
||||
return Shape;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
#include "Modules/ModuleInterface.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
/**
|
||||
* The public interface to this module. In most cases, this interface is only public to sibling modules
|
||||
* within this plugin.
|
||||
*/
|
||||
class IOculusXRPassthroughModule : public IModuleInterface
|
||||
{
|
||||
|
||||
public:
|
||||
/**
|
||||
* Singleton-like access to this module's interface. This is just for convenience!
|
||||
* Beware of calling this during the shutdown phase, though. Your module might have been unloaded already.
|
||||
*
|
||||
* @return Returns singleton instance, loading the module on demand if needed
|
||||
*/
|
||||
static inline IOculusXRPassthroughModule& Get()
|
||||
{
|
||||
return FModuleManager::GetModuleChecked<IOculusXRPassthroughModule>("OculusXRPassthrough");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true.
|
||||
*
|
||||
* @return True if the module is loaded and ready to use
|
||||
*/
|
||||
static inline bool IsAvailable()
|
||||
{
|
||||
return FModuleManager::Get().IsModuleLoaded("OculusXRPassthrough");
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/ObjectMacros.h"
|
||||
#include "Engine/Texture2D.h"
|
||||
|
||||
#include "OculusXRPassthroughColorLut.generated.h"
|
||||
|
||||
class UOculusXRPassthroughLayerBase;
|
||||
|
||||
enum EColorLutChannels
|
||||
{
|
||||
ColorLutChannels_RGB,
|
||||
ColorLutChannels_RGBA
|
||||
};
|
||||
|
||||
USTRUCT()
|
||||
struct FLutTextureData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY()
|
||||
TArray<uint8> Data;
|
||||
|
||||
UPROPERTY()
|
||||
uint32 Resolution;
|
||||
|
||||
FLutTextureData()
|
||||
: Data{}, Resolution(0) {}
|
||||
|
||||
FLutTextureData(const TArray<uint8>& InData, uint32 InResolution)
|
||||
: Data(InData), Resolution(InResolution) {}
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EColorLutType : uint8
|
||||
{
|
||||
None = 0 UMETA(DisplayName = "None"),
|
||||
TextureLUT = 1 UMETA(DisplayName = "Texture"),
|
||||
Array = 2 UMETA(Hidden)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This class represents a color look-up table (LUT) that can be applied to an \ref UOculusXRPassthroughLayerBase
|
||||
* in order to change the color reproduction of that passthrough layer.
|
||||
*
|
||||
* Color LUTs map each RGB input color to a new RGB(A) color. It unlocks a number of effects:
|
||||
* - Color grading and color correction
|
||||
* - Stylizations, such as posterization or hue rotation
|
||||
* - Color filtering and chroma keying.
|
||||
*
|
||||
* To apply a color LUT to a Passthrough layer, create a new instance of `UOculusXRPassthroughLayerComponent`
|
||||
* and call \ref UOculusXRPassthroughLayerBase::SetColorLUTSource()"/>.
|
||||
*
|
||||
* @see https://developers.meta.com/horizon/documentation/unreal/unreal-customize-passthrough-color-mapping/ to learn more about passthrough and color LUTs.
|
||||
*/
|
||||
UCLASS(BlueprintType, CollapseCategories, meta = (DisplayName = "Passthrough Color LUT"))
|
||||
class OCULUSXRPASSTHROUGH_API UOculusXRPassthroughColorLut : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
/** Keeps the information of the source of the current LUT instance. The source can be an array, another LUT texture, or None. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Color LUT")
|
||||
EColorLutType ColorLutType = EColorLutType::None;
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
/** Color LUT texture. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Color LUT", meta = (EditCondition = "ColorLutType == EColorLutType::TextureLUT", EditConditionHides))
|
||||
UTexture2D* LutTexture;
|
||||
#endif
|
||||
/** Informs if the alpha channel of LUT should be ignored. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Color LUT", meta = (EditCondition = "ColorLutType == EColorLutType::TextureLUT", EditConditionHides))
|
||||
bool IgnoreAlphaChannel = false;
|
||||
|
||||
/** Generate color LUT from array. Array should have format of exploded cube. Its size should be power of 2. */
|
||||
UFUNCTION(BlueprintCallable, Category = "Passthrough Color LUT")
|
||||
void SetLutFromArray(const TArray<FColor>& InColorArray, bool InIgnoreAlphaChannel);
|
||||
|
||||
/**
|
||||
* Gets the handle of the lut object. It asks for a layer reference to track the list of objects who currently need the handle.
|
||||
* Call \ref RemoveReference() when you don't need the lut anymore.
|
||||
*/
|
||||
uint64 GetHandle(UOculusXRPassthroughLayerBase* LayerRef);
|
||||
/** Remove a layer reference. When there's no reference to any layer we destroy the lut object and clear the handle. */
|
||||
void RemoveReference(UOculusXRPassthroughLayerBase* LayerRef);
|
||||
virtual void PreSave(FObjectPreSaveContext ObjectSaveContext) override;
|
||||
|
||||
void BeginDestroy() override;
|
||||
|
||||
private:
|
||||
UPROPERTY()
|
||||
FLutTextureData StoredTextureData;
|
||||
uint64 LutHandle = 0;
|
||||
int32 ColorArrayResolution = 0;
|
||||
int MaxResolution = -1;
|
||||
TArray<uint32> LayerRefs;
|
||||
FLutTextureData TextureToColorData(class UTexture2D* InLutTexture) const;
|
||||
uint64 CreateLutObject(const TArray<uint8>& InData, uint32 Resolution) const;
|
||||
void UpdateLutObject(uint64 Handle, const TArray<uint8>& InData) const;
|
||||
void DestroyLutObject(uint64 Handle) const;
|
||||
int GetMaxResolution();
|
||||
};
|
||||
@@ -0,0 +1,447 @@
|
||||
// @lint-ignore-every LICENSELINT
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
// OculusEventComponent.h: Component to handle receiving events from Oculus HMDs
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/StaticMeshActor.h"
|
||||
#include "UObject/ObjectMacros.h"
|
||||
#include "Components/StereoLayerComponent.h"
|
||||
#include "OculusXRPassthroughLayerShapes.h"
|
||||
#include "OculusXRPassthroughColorLut.h"
|
||||
#include "OculusXRHMDRuntimeSettings.h"
|
||||
#include "OculusXRPassthroughLayerComponent.generated.h"
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogOculusPassthrough, Log, All);
|
||||
|
||||
/**
|
||||
* @brief Represents a layer used for passthrough.
|
||||
*
|
||||
* The Passthrough API enables you to show the user's real environment in your mixed reality experiences.
|
||||
* It offers several options to customize the appearance of passthrough, such as adjusting opacity,
|
||||
* highlight salient edges in the image, or control the color reproduction.
|
||||
* For passthrough to be visible, it must be enabled in the Meta XR Plugin
|
||||
* via the \ref UOculusXRHMDRuntimeSettings::bInsightPassthroughEnabled property.
|
||||
*
|
||||
* @see https://developers.meta.com/horizon/documentation/unreal/unreal-passthrough-overview/ to learn more about passthrough and its features
|
||||
* @see https://developers.meta.com/horizon/documentation/unreal/unreal-passthrough-tutorial/ to create a simple app which uses passthrough
|
||||
*/
|
||||
UCLASS(Abstract, meta = (DisplayName = "Passthrough Layer Base"))
|
||||
class OCULUSXRPASSTHROUGH_API UOculusXRPassthroughLayerBase : public UStereoLayerShape
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
/**
|
||||
* Specifies whether passthrough should appear on top of (when \ref LayerOrder is `PassthroughLayerOrder_Overlay`)
|
||||
* or beneath (when \ref LayerOrder is `PassthroughLayerOrder_Underlay`) the virtual content. The default is `Overlay`.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", DisplayName = "Layer Placement")
|
||||
TEnumAsByte<enum EOculusXRPassthroughLayerOrder> LayerOrder;
|
||||
|
||||
/**
|
||||
* Defines the passthrough opacity. It can be used to blend between passthrough and VR when \ref LayerOrder is set to `Overlay`,
|
||||
* or to dim passthrough when \ref LayerOrder is set to `Underlay`.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (UIMin = 0.0, UIMax = 1.0, ClampMin = 0.0, ClampMax = 1.0))
|
||||
float TextureOpacityFactor = 1.0f;
|
||||
|
||||
/**
|
||||
* Enables or disables edge rendering.
|
||||
* Use this flag to toggle the edge rendering but retain the previously selected color (including alpha) in the UI when it is disabled.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (DisplayName = "Enable Edge Rendering"))
|
||||
bool bEnableEdgeColor = false;
|
||||
|
||||
/** Color for the edge rendering. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (EditCondition = "bEnableEdgeColor", EditConditionHides))
|
||||
FLinearColor EdgeColor;
|
||||
|
||||
/** Enables or disables the color mapping for this layer */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties")
|
||||
bool bEnableColorMap = false;
|
||||
|
||||
/** Represents the color mapping technique applied to this layer. Prefer setting it with \ref SetColorMapType() method */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (EditCondition = "bEnableColorMap", EditConditionHides))
|
||||
TEnumAsByte<enum EOculusXRColorMapType> ColorMapType;
|
||||
|
||||
/** Controls whether to use a color map curve or a gradient. Use it together with \ref ColorMapCurve property, or set it with \ref EnableColorMapCurve(). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (EditCondition = "bEnableColorMap && ColorMapType == 1", EditConditionHides))
|
||||
bool bUseColorMapCurve = false;
|
||||
|
||||
/** Contains color mapping gradient which is used to convert grayscale passthrough to color. This property only has an effect if \ref ColorMapType is set to \ref ColorMapType_GrayscaleToColor. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (EditCondition = "bEnableColorMap && bUseColorMapCurve && ColorMapType == 1", EditConditionHides))
|
||||
UCurveLinearColor* ColorMapCurve;
|
||||
|
||||
/** Contains contrast setting for color mapping. Ranges from -1 (minimum) to 1 (maximum). This property only has an effect if \ref ColorMapType is set to \ref ColorMapType_ColorAdjustment, \ref ColorMapType_Grayscale or \ref ColorMapType_GrayscaleToColor. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (ClampMin = "-1", ClampMax = "1", EditCondition = "bEnableColorMap && ColorMapType > 0 && ColorMapType < 4", EditConditionHides))
|
||||
float Contrast = 0.0f;
|
||||
|
||||
/** Contains brightness setting for color mapping. Ranges from -1 (minimum) to 1 (maximum). This property only has an effect if \ref ColorMapType is set to \ref ColorMapType_ColorAdjustment, \ref ColorMapType_Grayscale or \ref ColorMapType_GrayscaleToColor. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (ClampMin = "-1", ClampMax = "1", EditCondition = "bEnableColorMap && ColorMapType > 0 && ColorMapType < 4", EditConditionHides))
|
||||
float Brightness = 0.0f;
|
||||
|
||||
/** Contains posterize setting for grayscale and grayscale to color mappings. Ranges from 0 to 1, where 0 = no posterization (no effect), 1 = reduce to two colors. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (ClampMin = "0", ClampMax = "1", EditCondition = "bEnableColorMap && ColorMapType > 0 && ColorMapType < 3", EditConditionHides))
|
||||
float Posterize = 0.0f;
|
||||
|
||||
/** Contains saturation for color adjustment mapping. Ranges from -1 (minimum) to 1 (maximum). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (ClampMin = "-1", ClampMax = "1", EditCondition = "bEnableColorMap && ColorMapType == 3", EditConditionHides))
|
||||
float Saturation = 0.0f;
|
||||
|
||||
/**
|
||||
* Controls how \ref ColorMapType_ColorLut color mapping technique is applied to passthrough.
|
||||
* If the value is 0, then the appearance of Passthrough is unchanged. If it is 1, the colors are fully taken from the LUT. Values between 0 and 1 lead to a linear interpolation
|
||||
* between the original color and the LUT color. If two LUTs are provided LutWeight is used to blend them.
|
||||
* This value can be animated to create smooth transitions.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (ClampMin = "0", ClampMax = "1", EditCondition = "bEnableColorMap && ColorMapType > 3", EditConditionHides))
|
||||
float LutWeight = 1.0f;
|
||||
|
||||
/**
|
||||
* Color LUT properties. If only \ref ColorLUTSource is provided it will be blended with passthrough layer using following formula:
|
||||
* Result = ColorLUTSource * LutWeight + Passthrough * ( 1 - LutWeight )
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "Passthrough Properties", meta = (EditCondition = "bEnableColorMap && ColorMapType > 3", EditConditionHides))
|
||||
UOculusXRPassthroughColorLut* ColorLUTSource;
|
||||
|
||||
/**
|
||||
* Color LUT properties. If both \ref ColorLUTSource and \ref ColorLUTTarget are provided they will be blended using following formula:
|
||||
* Result = ColorLUTsSource * ( 1 - LutWeight ) + ColorLUTsTarget * LutWeight
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category = "Passthrough Properties", meta = (EditCondition = "bEnableColorMap && ColorMapType > 4", EditConditionHides))
|
||||
UOculusXRPassthroughColorLut* ColorLUTTarget;
|
||||
|
||||
/**
|
||||
* Contains the color value that will be multiplied to the pixel color values during compositing. Default is white = `(1,1,1,1)`.
|
||||
* This property only has an effect if \ref ColorMapType is set to \ref ColorMapType_GrayscaleToColor.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (EditCondition = "bEnableColorMap && ColorMapType == 1", EditConditionHides))
|
||||
FLinearColor ColorScale = FLinearColor::White;
|
||||
|
||||
/**
|
||||
* Contains the color value that will be added to the pixel color values during compositing. Default is black = `(0,0,0,0)`.
|
||||
* This property only has an effect if \ref ColorMapType is set to \ref ColorMapType_GrayscaleToColor.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (EditCondition = "bEnableColorMap && ColorMapType == 1", EditConditionHides))
|
||||
FLinearColor ColorOffset = FLinearColor::Black;
|
||||
|
||||
/**
|
||||
* This method changes the passthrough texture opacity. See \ref TextureOpacityFactor for more details.
|
||||
* @param InOpacity New value of the passthrough texture opacity.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
|
||||
void SetTextureOpacity(float InOpacity);
|
||||
|
||||
/**
|
||||
* This method allows to enable or disable the edge rendering in this passthrough layer. See \ref bEnableEdgeColor for more details.
|
||||
* @param bInEnableEdgeColor Specify `true` to enable edge rendering.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
|
||||
void EnableEdgeColor(bool bInEnableEdgeColor);
|
||||
|
||||
/**
|
||||
* This method allows to enable or disable the color mapping technique which is configured for this passthrough layer.
|
||||
* @param bInEnableColorMap Specify `true` to enable the color mapping, and `false` to disable it.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
|
||||
void EnableColorMap(bool bInEnableColorMap);
|
||||
|
||||
/**
|
||||
* Enable or disables the color map curve used to convert grayscale passthrough to color. This mehtod only has an effect when the color mapping type is set to \ref ColorMapType_GrayscaleToColor.
|
||||
* See \ref bUseColorMapCurve for more details.
|
||||
* @param bInEnableColorMapCurve Specify `true` to apply the color map curve.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
|
||||
void EnableColorMapCurve(bool bInEnableColorMapCurve);
|
||||
|
||||
/**
|
||||
* This method controls the color of the edges when edge rendering is enabled. See \ref EdgeColor for more details.
|
||||
* @param InEdgeColor Edge rendering color.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
|
||||
void SetEdgeRenderingColor(FLinearColor InEdgeColor);
|
||||
|
||||
/**
|
||||
* Sets the color map controls for grayscale and grayscale to rgb color mappings. The method fails if ColorMapType is not set to ColorMapType_Grayscale or ColorMapType_GrayscaleToColor.
|
||||
* @param InContrast Contast of passthrough. Valid range: [-1, 1]. A value of 0 means that contrast is left unchanged.
|
||||
* @param InBrightness Brightness of passthrough. Valid range: [-1, 1]. A value of 0 means that brightness is left unchanged.
|
||||
* @param InPosterize Posterize of passthrough. Ranges from 0 to 1, where 0 = no posterization (no effect), 1 = reduce to two colors.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
|
||||
void SetColorMapControls(float InContrast = 0, float InBrightness = 0, float InPosterize = 0);
|
||||
|
||||
/**
|
||||
* This method allows to configure brightness and contrast adjustment for Passthrough images. It fails if ColorMapType is not set to ColorMapType_ColorAdjustment.
|
||||
* @param InContrast Contast of passthrough. Valid range: [-1, 1]. A value of 0 means that contrast is left unchanged.
|
||||
* @param InBrightness Brightness of passthrough. Valid range: [-1, 1]. A value of 0 means that brightness is left unchanged.
|
||||
* @param InSaturation Saturation of passthrough. Valid range: [-1, 1]. A value of 0 means that saturation is left unchanged.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
|
||||
void SetBrightnessContrastSaturation(float InContrast = 0, float InBrightness = 0, float InSaturation = 0);
|
||||
|
||||
/**
|
||||
* This method allows to configure the color scale and offset values applied to passthrough pixels.
|
||||
* The method only has an effect if \ref ColorMapType is set to \ref ColorMapType_GrayscaleToColor.
|
||||
* @param InColorScale Color value that will be multiplied to the pixel color values during compositing. Default is `{1,1,1,1}`
|
||||
* @param InColorOffset Color value that will be added to the pixel color values during compositing. Default is `{0,0,0,0}`.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
|
||||
void SetColorScaleAndOffset(FLinearColor InColorScale = FLinearColor::White, FLinearColor InColorOffset = FLinearColor::Black);
|
||||
|
||||
/** Sets the color curve that will be added to the color map in grayscale modes --> will be converted into a gradient */
|
||||
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
|
||||
void SetColorMapCurve(UCurveLinearColor* InColorMapCurve);
|
||||
|
||||
/** Sets the color mapping technique applied to the passthrough texture if \ref bEnableColorMap is set to `true` */
|
||||
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
|
||||
void SetColorMapType(EOculusXRColorMapType InColorMapType);
|
||||
|
||||
/** Set color map array directly instead through a color curve. This method only has an affect when \ref ColorMapType is set to \ref ColorMapType_GrayscaleToColor */
|
||||
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
|
||||
void SetColorArray(const TArray<FLinearColor>& InColorArray);
|
||||
|
||||
/** Clears any color maps previously applied to this layer and sets ColorMapType to \ref ColorMapType_None value */
|
||||
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
|
||||
void ClearColorMap();
|
||||
|
||||
/**
|
||||
* Specifies whether passthrough should appear on top of (\ref PassthroughLayerOrder_Overlay)
|
||||
* or beneath (\ref PassthroughLayerOrder_Underlay) the virtual content. The default is `Overlay`.
|
||||
* See \ref LayerOrder property for more details */
|
||||
UFUNCTION(BlueprintCallable, Category = "Passthrough Properties")
|
||||
void SetLayerPlacement(EOculusXRPassthroughLayerOrder InLayerOrder);
|
||||
|
||||
/**
|
||||
* Sets Color LUT source.
|
||||
* If ColorMapType is `Color LUT`, then source will be blended with passthrough
|
||||
* using folowing formula:
|
||||
* Result = ColorLUTSource * LutWeight + Passthrough * (1 - LutWeight )
|
||||
* If ColorMapType is `Interpolated Color LUT`, then source will be blended with color LUT target
|
||||
* using folowing formula:
|
||||
* Result = ColorLUTSource * ( 1 - LutWeight ) + ColorLUTTarget * LutWeight
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
|
||||
void SetColorLUTSource(class UOculusXRPassthroughColorLut* InColorLUTSource);
|
||||
|
||||
/**
|
||||
* Sets Color LUT target.
|
||||
* If ColorMapType is `Interpolated Color LUT`, then target will be blended with passthrough
|
||||
* using folowing formula:
|
||||
* Result = ColorLUTSource * ( 1 - LutWeight ) + ColorLUTTarget * LutWeight
|
||||
* Note: If ColorLUTSource is not specified, Color LUT will be not be applied to the Passthrough layer.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
|
||||
void SetColorLUTTarget(class UOculusXRPassthroughColorLut* InColorLUTTarget);
|
||||
|
||||
/** Sets the color LUT weight. See \ref LutWeight for more details */
|
||||
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
|
||||
void SetColorLUTWeight(float InWeight = 1.0f);
|
||||
|
||||
/** Removes color grading if any is active */
|
||||
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
|
||||
void RemoveColorLut();
|
||||
|
||||
void ClearLUTsReferences();
|
||||
|
||||
virtual void BeginDestroy();
|
||||
|
||||
protected:
|
||||
TArray<FLinearColor> ColorArray;
|
||||
TArray<FLinearColor> NeutralColorArray;
|
||||
TArray<FLinearColor> GenerateColorArrayFromColorCurve(const UCurveLinearColor* InColorMapCurve) const;
|
||||
TArray<FLinearColor> GetOrGenerateNeutralColorArray();
|
||||
TArray<FLinearColor> GenerateColorArray(bool bInUseColorMapCurve, const UCurveLinearColor* InColorMapCurve);
|
||||
TArray<FLinearColor> GetColorArray(bool bInUseColorMapCurve, const UCurveLinearColor* InColorMapCurve);
|
||||
FColorLutDesc GenerateColorLutDescription(float InLutWeight, UOculusXRPassthroughColorLut* InLutSource, UOculusXRPassthroughColorLut* InLutTarget);
|
||||
|
||||
void MarkStereoLayerDirty();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Represents a passthrough layer which uses automatic environment depth reconstruction to render itself.
|
||||
*
|
||||
* The Passthrough API enables you to show the user's real environment in your mixed reality experiences.
|
||||
* It offers several options to customize the appearance of passthrough, such as adjusting opacity,
|
||||
* highlight salient edges in the image, or control the color reproduction.
|
||||
* For passthrough to be visible, it must be enabled in the Meta XR Plugin
|
||||
* via the \ref UOculusXRHMDRuntimeSettings::bInsightPassthroughEnabled property.
|
||||
*
|
||||
* Reconstructed passthrough used via this class vanishes when the current component
|
||||
* is destroyed. Consider using Persistent Passthrough Layer instead, which remains active throughout the application's lifetime,
|
||||
* including across level transitions. See https://developers.meta.com/horizon/documentation/unreal/unreal-persistent-passthrough/ for more details.
|
||||
*/
|
||||
UCLASS(meta = (DisplayName = "Reconstructed Passthrough Layer"))
|
||||
class OCULUSXRPASSTHROUGH_API UOculusXRStereoLayerShapeReconstructed : public UOculusXRPassthroughLayerBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
virtual void ApplyShape(IStereoLayers::FLayerDesc& LayerDesc) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Represents a passthrough layer which relies on the geometry and the depth provided by the client to render itself.
|
||||
*
|
||||
* The Passthrough API enables you to show the user's real environment in your mixed reality experiences.
|
||||
* It offers several options to customize the appearance of passthrough, such as adjusting opacity,
|
||||
* highlight salient edges in the image, or control the color reproduction.
|
||||
* For passthrough to be visible, it must be enabled in the Meta XR Plugin
|
||||
* via the \ref UOculusXRHMDRuntimeSettings::bInsightPassthroughEnabled property.
|
||||
*
|
||||
* @see https://developers.meta.com/horizon/documentation/unreal/unreal-customize-passthrough-surface-projected-passthrough/ to learn more about surface projected passthrough.
|
||||
*/
|
||||
UCLASS(meta = (DisplayName = "User Defined Passthrough Layer"))
|
||||
class OCULUSXRPASSTHROUGH_API UOculusXRStereoLayerShapeUserDefined : public UOculusXRPassthroughLayerBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
/**
|
||||
* Adds a geometry onto which the passthrough images will be projected.
|
||||
* @param MeshName Name for the geometry to be added. It is used if later you decide to remove the geometry from the layer.
|
||||
* @param PassthroughMesh Reference to the mesh to be added.
|
||||
* @param Transform Transform of the mesh to be added.
|
||||
* @param bUpdateTransform When the value is `true`, current layer will update the transform of the surface mesh every frame.
|
||||
* Otherwise only the initial transform is recorded.
|
||||
*/
|
||||
void AddGeometry(const FString& MeshName, OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh, FTransform Transform, bool bUpdateTransform);
|
||||
/**
|
||||
* Removes the geometry that has previously been added using \ref AddGeometry() from the projection surface
|
||||
* @param MeshName Name for the geometry to be removed.
|
||||
*/
|
||||
void RemoveGeometry(const FString& MeshName);
|
||||
|
||||
virtual void ApplyShape(IStereoLayers::FLayerDesc& LayerDesc) override;
|
||||
/**
|
||||
* Returns a list of geometries previously added to this surface projected passthrough layer.
|
||||
*/
|
||||
TArray<FUserDefinedGeometryDesc>& GetUserGeometryList() { return UserGeometryList; };
|
||||
|
||||
private:
|
||||
TArray<FUserDefinedGeometryDesc> UserGeometryList;
|
||||
};
|
||||
|
||||
class UProceduralMeshComponent;
|
||||
|
||||
/**
|
||||
* @brief A component which defines reusable passthrough behavior that can be added to different types of Actors.
|
||||
*
|
||||
* The Passthrough API enables you to show the user's real environment in your mixed reality experiences.
|
||||
* It offers several options to customize the appearance of passthrough, such as adjusting opacity,
|
||||
* highlight salient edges in the image, or control the color reproduction.
|
||||
*
|
||||
* @see https://developers.meta.com/horizon/documentation/unreal/unreal-passthrough-overview/ to learn more about passthrough and its features
|
||||
* @see https://developers.meta.com/horizon/documentation/unreal/unreal-passthrough-tutorial/ to create a simple app which uses passthrough
|
||||
*/
|
||||
UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = OculusXRHMD)
|
||||
class OCULUSXRPASSTHROUGH_API UOculusXRPassthroughLayerComponent : public UStereoLayerComponent
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
public:
|
||||
void DestroyComponent(bool bPromoteChildren) override;
|
||||
void OnRegister() override;
|
||||
|
||||
void BeginPlay() override;
|
||||
void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||
|
||||
void UpdatePassthroughObjects();
|
||||
|
||||
/**
|
||||
* \deprecated Adds a static geometry onto which the passthrough images will be projected. This method is deprecated in favour of \ref AddStaticSurfaceGeometry().
|
||||
*
|
||||
* @param StaticMeshActor The actor containing the static geometry.
|
||||
* @param bUpdateTransform When the value is `true`, current layer will update the transform of the mesh every frame.
|
||||
* Otherwise only the initial transform is recorded.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Passthrough", meta = (DeprecatedFunction, DeprecationMessage = "Please use AddStaticSurfaceGeometry instead"))
|
||||
void AddSurfaceGeometry(AStaticMeshActor* StaticMeshActor, bool updateTransform);
|
||||
/**
|
||||
* Adds a static geometry onto which the passthrough images will be projected. This only has an effect with the surface projected passthrough layers.
|
||||
* @see https://developers.meta.com/horizon/documentation/unreal/unreal-customize-passthrough-surface-projected-passthrough/ to learn more about it.
|
||||
*
|
||||
* @param StaticMeshComponent Reference to the component that contains the static mesh to be added.
|
||||
* @param updateTransform When the value is `true`, corresponding passthrough layer will update the transform of the surface mesh every frame.
|
||||
* Otherwise only the initial transform is recorded.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Passthrough")
|
||||
void AddStaticSurfaceGeometry(UStaticMeshComponent* StaticMeshComponent, bool updateTransform);
|
||||
/**
|
||||
* Adds a procedural geometry onto which the passthrough images will be projected. This only has an effect with the surface projected passthrough layers.
|
||||
* @see https://developers.meta.com/horizon/documentation/unreal/unreal-customize-passthrough-surface-projected-passthrough/ to learn more about it.
|
||||
*
|
||||
* @param ProceduralMeshComponent Reference to the component that contains the procedural mesh to be added.
|
||||
* @param updateTransform When the value is `true`, corresponding passthrough will update the transform of the surface mesh every frame.
|
||||
* Otherwise only the initial transform is recorded.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Passthrough")
|
||||
void AddProceduralSurfaceGeometry(UProceduralMeshComponent* ProceduralMeshComponent, bool updateTransform);
|
||||
|
||||
/**
|
||||
* \deprecated Removes previously added static geometry from the projection surface. This method is deprecated in favour of \ref RemoveStaticSurfaceGeometry().
|
||||
* @param StaticMeshActor The actor with the static geometry to be removed.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Passthrough", meta = (DeprecatedFunction, DeprecationMessage = "Please use RemoveStaticSurfaceGeometry instead"))
|
||||
void RemoveSurfaceGeometry(AStaticMeshActor* StaticMeshActor);
|
||||
|
||||
/**
|
||||
* Removes previously added static geometry from the projection surface.
|
||||
* @param StaticMeshComponent Reference to the component that contains the static mesh to be removed.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Passthrough")
|
||||
void RemoveStaticSurfaceGeometry(UStaticMeshComponent* StaticMeshComponent);
|
||||
/**
|
||||
* Removes previously added procedural geometry from the projection surface.
|
||||
* @param ProceduralMeshComponent Reference to the component that contains the procedural mesh to be removed.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Passthrough")
|
||||
void RemoveProceduralSurfaceGeometry(UProceduralMeshComponent* ProceduralMeshComponent);
|
||||
|
||||
/**
|
||||
* \deprecated Checks if the actor contains a static geomenty that has been added to the current component. This method is deprecated in favour of \ref IsSurfaceGeometryComponent().
|
||||
* @param StaticMeshActor The actor for which the check is to be made.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Passthrough", meta = (DeprecatedFunction, DeprecationMessage = "Please use IsSurfaceGeometryComponent instead"))
|
||||
bool IsSurfaceGeometry(AStaticMeshActor* StaticMeshActor) const;
|
||||
/**
|
||||
* Checks if the current component contains the mesh passed as an argument.
|
||||
* @param MeshComponent The component with the mesh for which the check is to be made.
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category = "Passthrough")
|
||||
bool IsSurfaceGeometryComponent(const UMeshComponent* MeshComponent) const;
|
||||
|
||||
/**
|
||||
* Manually mark the stereo layer passthrough effect for updating.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer")
|
||||
void MarkPassthroughStyleForUpdate();
|
||||
|
||||
#if WITH_EDITOR
|
||||
virtual bool CanEditChange(const FProperty* InProperty) const override;
|
||||
#endif // WITH_EDITOR
|
||||
|
||||
/**
|
||||
* Calling this method results in broadcasting \ref OnLayerResumed event if `InLayerId` matches the Id of the current passthrough layer.
|
||||
*/
|
||||
UFUNCTION()
|
||||
void OnAnyLayerResumedEvent(int InLayerId);
|
||||
|
||||
/**
|
||||
* Occurs when current passthrough layer has been rendered and presented on the HMD screen for the first time after being restarted.
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXRPassthrough_LayerResumed OnLayerResumed;
|
||||
|
||||
protected:
|
||||
virtual bool LayerRequiresTexture();
|
||||
virtual void RemoveSurfaceGeometryComponent(UMeshComponent* MeshComponent);
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TMap<FString, const UMeshComponent*> PassthroughComponentMap;
|
||||
|
||||
private:
|
||||
OculusXRHMD::FOculusPassthroughMeshRef CreatePassthroughMesh(UProceduralMeshComponent* ProceduralMeshComponent);
|
||||
OculusXRHMD::FOculusPassthroughMeshRef CreatePassthroughMesh(UStaticMeshComponent* StaticMeshComponent);
|
||||
|
||||
/** Passthrough style needs to be marked for update **/
|
||||
bool bPassthroughStyleNeedsUpdate;
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "Tickable.h"
|
||||
#include "OculusXRHMDTypes.h"
|
||||
#include "OculusXRPersistentPassthroughInstance.h"
|
||||
#include "OculusXRPassthroughLayerShapes.h"
|
||||
#include "OculusXRPassthroughSubsystem.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXRPassthrough_AnyLayerResumed, int, LayerID);
|
||||
|
||||
UCLASS(DisplayName = "Passthrough Subsystem")
|
||||
class OCULUSXRPASSTHROUGH_API UOculusXRPassthroughSubsystem : public UGameInstanceSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
static UOculusXRPassthroughSubsystem* GetPassthroughSubsystem(const UWorld* InWorld);
|
||||
|
||||
UOculusXRPassthroughSubsystem();
|
||||
|
||||
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
virtual void Deinitialize() override;
|
||||
|
||||
/**
|
||||
* Creates a Reconstructed Passthrough layer that stays alive until "Destroy Persistent Passthrough" is called.
|
||||
* If used in blueprints, layer's parameters are available in the Details window when selecting the node.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintInternalUseOnly, Category = "Passthrough|Persistent", meta = (AutoCreateRefTerm = "LayerResumed"))
|
||||
UOculusXRPersistentPassthroughInstance* InitializePersistentPassthrough(FOculusXRPersistentPassthroughParameters Parameters, const FOculusXRPassthrough_LayerResumed_Single& LayerResumed);
|
||||
UFUNCTION(BlueprintCallable, Category = "Passthrough|Persistent")
|
||||
void DestroyPersistentPassthrough();
|
||||
UFUNCTION(BlueprintPure, Category = "Passthrough|Persistent")
|
||||
UOculusXRPersistentPassthroughInstance* GetPersistentPassthrough() const;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXRPassthrough_AnyLayerResumed OnAnyLayerResumed;
|
||||
|
||||
|
||||
private:
|
||||
|
||||
UPROPERTY()
|
||||
UOculusXRPersistentPassthroughInstance* PPTInstance;
|
||||
|
||||
|
||||
FDelegateHandle DelegateHandleLayerResumed;
|
||||
};
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
#include "Curves/CurveLinearColor.h"
|
||||
#include "OculusXRPassthroughLayerShapes.h"
|
||||
#include "StereoLayerComponent.h"
|
||||
#include "StereoRendering.h"
|
||||
#include "UObject/NoExportTypes.h"
|
||||
|
||||
#include "OculusXRPersistentPassthroughInstance.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_DELEGATE(FOculusXRPassthrough_LayerResumed_Single);
|
||||
|
||||
class UOculusXRStereoLayerShapeReconstructed;
|
||||
class UOculusXRPassthroughColorLut;
|
||||
|
||||
USTRUCT(BlueprintType, meta = (DisableSplitPin, HasNativeMake = "", HasNativeBreak = ""))
|
||||
struct OCULUSXRPASSTHROUGH_API FOculusXRPersistentPassthroughParameters
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(Category = "Passthrough|Persistent", EditAnywhere, BlueprintReadWrite)
|
||||
bool bVisible;
|
||||
|
||||
UPROPERTY(Category = "Passthrough|Persistent", EditAnywhere, BlueprintReadWrite)
|
||||
int32 Priority;
|
||||
|
||||
UPROPERTY(Category = "Passthrough|Persistent", EditAnywhere, BlueprintReadOnly, NoClear, Instanced)
|
||||
TObjectPtr<UOculusXRStereoLayerShapeReconstructed> Shape;
|
||||
|
||||
FOculusXRPersistentPassthroughParameters()
|
||||
{
|
||||
bVisible = true;
|
||||
Priority = 0;
|
||||
|
||||
TempShape_LayerOrder = EOculusXRPassthroughLayerOrder::PassthroughLayerOrder_Overlay;
|
||||
TempShape_TextureOpacityFactor = 0.f;
|
||||
TempShape_bEnableEdgeColor = false;
|
||||
TempShape_EdgeColor = FLinearColor::Black;
|
||||
TempShape_bEnableColorMap = false;
|
||||
TempShape_ColorMapType = EOculusXRColorMapType::ColorMapType_None;
|
||||
TempShape_bUseColorMapCurve = false;
|
||||
TempShape_ColorMapCurve = nullptr;
|
||||
TempShape_Contrast = 0.f;
|
||||
TempShape_Brightness = 0.f;
|
||||
TempShape_Posterize = 0.f;
|
||||
TempShape_Saturation = 0.f;
|
||||
TempShape_LutWeight = 0.f;
|
||||
TempShape_ColorLUTSource = nullptr;
|
||||
TempShape_ColorLUTTarget = nullptr;
|
||||
TempShape_ColorScale = FLinearColor::Black;
|
||||
TempShape_ColorOffset = FLinearColor::Black;
|
||||
}
|
||||
|
||||
void ApplyShape();
|
||||
UOculusXRStereoLayerShapeReconstructed* LoadShape(UObject* Owner);
|
||||
|
||||
private:
|
||||
// These properties are all the ones from UOculusXRStereoLayerShapeReconstructed.
|
||||
UPROPERTY()
|
||||
TEnumAsByte<EOculusXRPassthroughLayerOrder> TempShape_LayerOrder;
|
||||
UPROPERTY()
|
||||
float TempShape_TextureOpacityFactor;
|
||||
UPROPERTY()
|
||||
bool TempShape_bEnableEdgeColor;
|
||||
UPROPERTY()
|
||||
FLinearColor TempShape_EdgeColor;
|
||||
UPROPERTY()
|
||||
bool TempShape_bEnableColorMap;
|
||||
UPROPERTY()
|
||||
TEnumAsByte<EOculusXRColorMapType> TempShape_ColorMapType;
|
||||
UPROPERTY()
|
||||
bool TempShape_bUseColorMapCurve;
|
||||
UPROPERTY()
|
||||
UCurveLinearColor* TempShape_ColorMapCurve;
|
||||
UPROPERTY()
|
||||
float TempShape_Contrast;
|
||||
UPROPERTY()
|
||||
float TempShape_Brightness;
|
||||
UPROPERTY()
|
||||
float TempShape_Posterize;
|
||||
UPROPERTY()
|
||||
float TempShape_Saturation;
|
||||
UPROPERTY()
|
||||
float TempShape_LutWeight;
|
||||
UPROPERTY()
|
||||
UOculusXRPassthroughColorLut* TempShape_ColorLUTSource;
|
||||
UPROPERTY()
|
||||
UOculusXRPassthroughColorLut* TempShape_ColorLUTTarget;
|
||||
UPROPERTY()
|
||||
FLinearColor TempShape_ColorScale;
|
||||
UPROPERTY()
|
||||
FLinearColor TempShape_ColorOffset;
|
||||
};
|
||||
|
||||
UCLASS(EditInlineNew, DefaultToInstanced, BlueprintType)
|
||||
class OCULUSXRPASSTHROUGH_API UOculusXRPersistentPassthroughInstance : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
UPROPERTY()
|
||||
FOculusXRPersistentPassthroughParameters Parameters;
|
||||
|
||||
public:
|
||||
UFUNCTION(Category = "Passthrough|Persistent", BlueprintCallable)
|
||||
void SetVisible(bool InVisible);
|
||||
UFUNCTION(Category = "Passthrough|Persistent", BlueprintPure)
|
||||
bool IsVisible() const { return Parameters.bVisible; }
|
||||
|
||||
UFUNCTION(Category = "Passthrough|Persistent", BlueprintCallable)
|
||||
void SetPriority(int32 InPriority);
|
||||
UFUNCTION(Category = "Passthrough|Persistent", BlueprintPure)
|
||||
int32 GetPriority() const { return Parameters.Priority; }
|
||||
|
||||
UFUNCTION(Category = "Passthrough|Persistent", BlueprintPure)
|
||||
UOculusXRStereoLayerShapeReconstructed* GetShape() const { return Parameters.Shape; }
|
||||
|
||||
UOculusXRPersistentPassthroughInstance(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
virtual void InitLayer(FOculusXRPersistentPassthroughParameters InParameters);
|
||||
virtual void UpdateParameters(FOculusXRPersistentPassthroughParameters InParameters);
|
||||
virtual void UpdateLayer();
|
||||
virtual void BeginDestroy() override;
|
||||
|
||||
void AddLayerResumedSingleDelegate(const FOculusXRPassthrough_LayerResumed_Single& Delegate);
|
||||
|
||||
UFUNCTION()
|
||||
void OnAnyLayerResumedEvent(int InLayerId);
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FOculusXRPassthrough_LayerResumed OnLayerResumed;
|
||||
|
||||
protected:
|
||||
TArray<FOculusXRPassthrough_LayerResumed_Single> LayerResumedSingleDelegates;
|
||||
uint32 LayerId = IStereoLayers::FLayerDesc::INVALID_LAYER_ID;
|
||||
};
|
||||
Reference in New Issue
Block a user