Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

264 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "OpenXRHMD_Layer.h"
#include "IStereoLayers.h"
#include "StereoLayerManager.h"
#include "OpenXRHMD_Swapchain.h"
#include "OpenXRCore.h"
#include "OpenXRPlatformRHI.h"
FIntRect FOpenXRLayer::GetRightViewportSize() const
{
FBox2D Viewport(RightEye.SwapchainSize * Desc.UVRect.Min, RightEye.SwapchainSize * Desc.UVRect.Max);
return FIntRect(Viewport.Min.IntPoint(), Viewport.Max.IntPoint());
}
FIntRect FOpenXRLayer::GetLeftViewportSize() const
{
FBox2D Viewport(LeftEye.SwapchainSize * Desc.UVRect.Min, LeftEye.SwapchainSize * Desc.UVRect.Max);
return FIntRect(Viewport.Min.IntPoint(), Viewport.Max.IntPoint());
}
FVector2D FOpenXRLayer::GetRightQuadSize() const
{
if (Desc.Flags & IStereoLayers::LAYER_FLAG_QUAD_PRESERVE_TEX_RATIO)
{
float AspectRatio = RightEye.SwapchainSize.Y / RightEye.SwapchainSize.X;
return FVector2D(Desc.QuadSize.X, Desc.QuadSize.X * AspectRatio);
}
return Desc.QuadSize;
}
FVector2D FOpenXRLayer::GetLeftQuadSize() const
{
if (Desc.Flags & IStereoLayers::LAYER_FLAG_QUAD_PRESERVE_TEX_RATIO)
{
float AspectRatio = LeftEye.SwapchainSize.Y / LeftEye.SwapchainSize.X;
return FVector2D(Desc.QuadSize.X, Desc.QuadSize.X * AspectRatio);
}
return Desc.QuadSize;
}
TArray<FXrCompositionLayerUnion> FOpenXRLayer::CreateOpenXRLayer(FTransform InvTrackingToWorld, float WorldToMeters, XrSpace Space, EOpenXRLayerCreationFlags CreationFlags) const
{
TArray<FXrCompositionLayerUnion> Headers;
const bool bNoAlpha = Desc.Flags & IStereoLayers::LAYER_FLAG_TEX_NO_ALPHA_CHANNEL;
const bool bIsStereo = LeftEye.Texture.IsValid();
FTransform PositionTransform = Desc.PositionType == IStereoLayers::ELayerType::WorldLocked ?
InvTrackingToWorld : FTransform::Identity;
if (Desc.HasShape<FQuadLayer>())
{
CreateOpenXRQuadLayer(bIsStereo, bNoAlpha, PositionTransform, WorldToMeters, Space, Headers);
}
else if (Desc.HasShape<FCylinderLayer>())
{
CreateOpenXRCylinderLayer(bIsStereo, bNoAlpha, PositionTransform, WorldToMeters, Space, Headers);
}
else if (Desc.HasShape<FEquirectLayer>())
{
if(EnumHasAllFlags(CreationFlags, EOpenXRLayerCreationFlags::EquirectLayer2Supported))
{
CreateOpenXREquirect2Layer(bIsStereo, bNoAlpha, PositionTransform, WorldToMeters, Space, Headers);
}
else
{
CreateOpenXREquirectLayer(bIsStereo, bNoAlpha, PositionTransform, WorldToMeters, Space, Headers);
}
}
return Headers;
}
void FOpenXRLayer::ApplyCompositionDepthTestLayer(TArray<FXrCompositionLayerUnion>& Headers, EOpenXRLayerCreationFlags LayerCreationFlags, TArray<XrCompositionLayerDepthTestFB>& InCompositionDepthTestLayers) const
{
const bool bUseDepthTest = EnumHasAllFlags((IStereoLayers::ELayerFlags)Desc.Flags, IStereoLayers::LAYER_FLAG_SUPPORT_DEPTH) &&
EnumHasAllFlags(LayerCreationFlags, EOpenXRLayerCreationFlags::DepthTestSupported);
if (bUseDepthTest)
{
for (FXrCompositionLayerUnion& Header : Headers)
{
InCompositionDepthTestLayers.AddUninitialized();
XrCompositionLayerDepthTestFB& LayerDepthTest = InCompositionDepthTestLayers.Last();
LayerDepthTest.type = XR_TYPE_COMPOSITION_LAYER_DEPTH_TEST_FB;
LayerDepthTest.next = Header.Header.next;
LayerDepthTest.depthMask = true;
LayerDepthTest.compareOp = XR_COMPARE_OP_LESS_FB;
Header.Header.next = &LayerDepthTest;
}
}
}
void FOpenXRLayer::CreateOpenXRCylinderLayer(bool bIsStereo, bool bNoAlpha, FTransform PositionTransform, float WorldToMeters, XrSpace Space, TArray<FXrCompositionLayerUnion>& Headers) const
{
XrCompositionLayerCylinderKHR Cylinder = { XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR, /*next*/ nullptr };
Cylinder.layerFlags = bNoAlpha ? 0 : XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT |
XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
Cylinder.space = Space;
Cylinder.subImage.imageArrayIndex = 0;
Cylinder.pose = ToXrPose(Desc.Transform * PositionTransform, WorldToMeters);
const FCylinderLayer& CylinderProps = Desc.GetShape<FCylinderLayer>();
Cylinder.radius = FMath::Abs(CylinderProps.Radius / WorldToMeters);
Cylinder.centralAngle = FMath::Min((float)(2.0f * PI), FMath::Abs(CylinderProps.OverlayArc / CylinderProps.Radius));
Cylinder.aspectRatio = FMath::Abs(CylinderProps.OverlayArc / CylinderProps.Height);
FXrCompositionLayerUnion LayerUnion;
LayerUnion.Cylinder = Cylinder;
if (RightEye.Swapchain.IsValid())
{
LayerUnion.Cylinder.eyeVisibility = bIsStereo ? XR_EYE_VISIBILITY_RIGHT : XR_EYE_VISIBILITY_BOTH;
LayerUnion.Cylinder.subImage.imageRect = ToXrRect(GetRightViewportSize());
LayerUnion.Cylinder.subImage.swapchain = static_cast<FOpenXRSwapchain*>(RightEye.Swapchain.Get())->GetHandle();
Headers.Add(LayerUnion);
}
if (LeftEye.Swapchain.IsValid())
{
LayerUnion.Cylinder.eyeVisibility = XR_EYE_VISIBILITY_LEFT;
LayerUnion.Cylinder.subImage.imageRect = ToXrRect(GetLeftViewportSize());
LayerUnion.Cylinder.subImage.swapchain = static_cast<FOpenXRSwapchain*>(LeftEye.Swapchain.Get())->GetHandle();
Headers.Add(LayerUnion);
}
}
void FOpenXRLayer::CreateOpenXRQuadLayer(bool bIsStereo, bool bNoAlpha, FTransform PositionTransform, float WorldToMeters, XrSpace Space, TArray<FXrCompositionLayerUnion>& Headers) const
{
XrCompositionLayerQuad Quad = { XR_TYPE_COMPOSITION_LAYER_QUAD, nullptr };
Quad.layerFlags = bNoAlpha ? 0 : XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT |
XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
Quad.space = Space;
Quad.subImage.imageArrayIndex = 0;
Quad.pose = ToXrPose(Desc.Transform * PositionTransform, WorldToMeters);
// The layer pose doesn't take the transform scale into consideration, so we need to manually apply it the quad size.
const FVector2D LayerComponentScaler(Desc.Transform.GetScale3D().Y, Desc.Transform.GetScale3D().Z);
FXrCompositionLayerUnion LayerUnion;
LayerUnion.Quad = Quad;
// We need to copy each layer into an OpenXR swapchain so they can be displayed by the compositor
if (RightEye.Swapchain.IsValid())
{
LayerUnion.Quad.eyeVisibility = bIsStereo ? XR_EYE_VISIBILITY_RIGHT : XR_EYE_VISIBILITY_BOTH;
LayerUnion.Quad.subImage.imageRect = ToXrRect(GetRightViewportSize());
LayerUnion.Quad.subImage.swapchain = static_cast<FOpenXRSwapchain*>(RightEye.Swapchain.Get())->GetHandle();
LayerUnion.Quad.size = ToXrExtent2D(GetRightQuadSize() * LayerComponentScaler, WorldToMeters);
Headers.Add(LayerUnion);
}
if (LeftEye.Swapchain.IsValid())
{
LayerUnion.Quad.eyeVisibility = XR_EYE_VISIBILITY_LEFT;
LayerUnion.Quad.subImage.imageRect = ToXrRect(GetLeftViewportSize());
LayerUnion.Quad.subImage.swapchain = static_cast<FOpenXRSwapchain*>(LeftEye.Swapchain.Get())->GetHandle();
LayerUnion.Quad.size = ToXrExtent2D(GetLeftQuadSize() * LayerComponentScaler, WorldToMeters);
Headers.Add(LayerUnion);
}
}
void FOpenXRLayer::SetupEquirect2(FVector2D UVScale, FVector2D UVBias, FVector2D UVPosition, FVector2D UVSize, FTransform PositionTransform, float WorldToMeters, XrCompositionLayerEquirect2KHR& Equirect2) const
{
const FVector2D AdjustedPosition = FVector2D(
(-UVBias.X + UVPosition.X) / UVScale.X,
(-UVBias.Y + UVPosition.Y) / UVScale.Y);
const FVector2D AdjustedSize = FVector2D(UVSize.X / UVScale.X, UVSize.Y / UVScale.Y);
const float CentralHorizontalAngle = (UE_PI * 2.0f) * AdjustedSize.X;
const float UpperVerticalAngle = (UE_PI / 2.0f) - ((1.0f - AdjustedPosition.Y - AdjustedSize.Y) * UE_PI);
const float LowerVerticalAngle = (-UE_PI / 2.0f) + (AdjustedPosition.Y * UE_PI);
Equirect2.centralHorizontalAngle = CentralHorizontalAngle;
Equirect2.upperVerticalAngle = UpperVerticalAngle;
Equirect2.lowerVerticalAngle = LowerVerticalAngle;
Equirect2.pose = ToXrPose(Desc.Transform * PositionTransform, WorldToMeters);
}
void FOpenXRLayer::CreateOpenXREquirect2Layer(bool bIsStereo, bool bNoAlpha, FTransform PositionTransform, float WorldToMeters, XrSpace Space, TArray<FXrCompositionLayerUnion>& Headers) const
{
const FEquirectLayer& EquirectProps = Desc.GetShape<FEquirectLayer>();
XrCompositionLayerEquirect2KHR Equirect2 = { XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR, nullptr };
Equirect2.layerFlags = bNoAlpha ? 0 : XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT |
XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
Equirect2.space = Space;
Equirect2.subImage.imageArrayIndex = 0;
FXrCompositionLayerUnion LayerUnion;
if (RightEye.Swapchain.IsValid())
{
const FVector2D Size = EquirectProps.RightUVRect.Max - EquirectProps.RightUVRect.Min;
SetupEquirect2(EquirectProps.RightScale,
EquirectProps.RightBias,
EquirectProps.RightUVRect.Min,
Size,
PositionTransform,
WorldToMeters,
Equirect2);
LayerUnion.Equirect2 = Equirect2;
LayerUnion.Equirect2.eyeVisibility = bIsStereo ? XR_EYE_VISIBILITY_RIGHT : XR_EYE_VISIBILITY_BOTH;
LayerUnion.Equirect2.subImage.imageRect = ToXrRect(GetRightViewportSize());
LayerUnion.Equirect2.subImage.swapchain = static_cast<FOpenXRSwapchain*>(RightEye.Swapchain.Get())->GetHandle();
Headers.Add(LayerUnion);
}
if (LeftEye.Swapchain.IsValid())
{
const FVector2D Size = EquirectProps.LeftUVRect.Max - EquirectProps.LeftUVRect.Min;
SetupEquirect2(EquirectProps.LeftScale,
EquirectProps.LeftBias,
EquirectProps.LeftUVRect.Min,
Size,
PositionTransform,
WorldToMeters,
Equirect2);
LayerUnion.Equirect2 = Equirect2;
LayerUnion.Equirect2.eyeVisibility = XR_EYE_VISIBILITY_LEFT;
LayerUnion.Equirect2.subImage.imageRect = ToXrRect(GetLeftViewportSize());
LayerUnion.Equirect2.subImage.swapchain = static_cast<FOpenXRSwapchain*>(LeftEye.Swapchain.Get())->GetHandle();
Headers.Add(LayerUnion);
}
}
void FOpenXRLayer::CreateOpenXREquirectLayer(bool bIsStereo, bool bNoAlpha, FTransform PositionTransform, float WorldToMeters, XrSpace Space, TArray<FXrCompositionLayerUnion>& Headers) const
{
XrCompositionLayerEquirectKHR Equirect = { XR_TYPE_COMPOSITION_LAYER_EQUIRECT_KHR, nullptr };
Equirect.layerFlags = bNoAlpha ? 0 : XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT |
XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
Equirect.space = Space;
Equirect.subImage.imageArrayIndex = 0;
Equirect.pose = ToXrPose(Desc.Transform * PositionTransform, WorldToMeters);
const FEquirectLayer& EquirectProps = Desc.GetShape<FEquirectLayer>();
// An equirect layer with a radius of 0 is an infinite sphere.
// As of UE 5.3, equirect layers are supported only by the Oculus OpenXR runtime and
// only with a radius of 0. Other radius values will be ignored.
Equirect.radius = FMath::Abs(EquirectProps.Radius / WorldToMeters);
FXrCompositionLayerUnion LayerUnion;
LayerUnion.Equirect = Equirect;
// We need to copy each layer into an OpenXR swapchain so they can be displayed by the compositor
if (RightEye.Swapchain.IsValid())
{
LayerUnion.Equirect.eyeVisibility = bIsStereo ? XR_EYE_VISIBILITY_RIGHT : XR_EYE_VISIBILITY_BOTH;
LayerUnion.Equirect.subImage.imageRect = ToXrRect(GetRightViewportSize());
LayerUnion.Equirect.subImage.swapchain = static_cast<FOpenXRSwapchain*>(RightEye.Swapchain.Get())->GetHandle();
LayerUnion.Equirect.scale = ToXrVector2f(EquirectProps.RightScale);
LayerUnion.Equirect.bias = ToXrVector2f(EquirectProps.RightBias);
Headers.Add(LayerUnion);
}
if (LeftEye.Swapchain.IsValid())
{
LayerUnion.Equirect.eyeVisibility = XR_EYE_VISIBILITY_LEFT;
LayerUnion.Equirect.subImage.imageRect = ToXrRect(GetLeftViewportSize());
LayerUnion.Equirect.subImage.swapchain = static_cast<FOpenXRSwapchain*>(LeftEye.Swapchain.Get())->GetHandle();
LayerUnion.Equirect.scale = ToXrVector2f(EquirectProps.LeftScale);
LayerUnion.Equirect.bias = ToXrVector2f(EquirectProps.LeftBias);
Headers.Add(LayerUnion);
}
}