|
/************************************************************************************
|
|
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
|
|
|
Licensed under the Oculus Utilities SDK License Version 1.31 (the "License"); you may not use
|
|
the Utilities SDK except in compliance with the License, which is provided at the time of installation
|
|
or download, or which otherwise accompanies this software in either electronic or hard copy form.
|
|
|
|
You may obtain a copy of the License at
|
|
https://developer.oculus.com/licenses/utilities-1.31
|
|
|
|
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
|
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
ANY KIND, either express or implied. See the License for the specific language governing
|
|
permissions and limitations under the License.
|
|
************************************************************************************/
|
|
|
|
using UnityEngine;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Runtime.InteropServices;
|
|
|
|
#if UNITY_2017_2_OR_NEWER
|
|
using Settings = UnityEngine.XR.XRSettings;
|
|
#else
|
|
using Settings = UnityEngine.VR.VRSettings;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Add OVROverlay script to an object with an optional mesh primitive
|
|
/// rendered as a TimeWarp overlay instead by drawing it into the eye buffer.
|
|
/// This will take full advantage of the display resolution and avoid double
|
|
/// resampling of the texture.
|
|
///
|
|
/// We support 3 types of Overlay shapes right now
|
|
/// 1. Quad : This is most common overlay type , you render a quad in Timewarp space.
|
|
/// 2. Cylinder: [Mobile Only][Experimental], Display overlay as partial surface of a cylinder
|
|
/// * The cylinder's center will be your game object's center
|
|
/// * We encoded the cylinder's parameters in transform.scale,
|
|
/// **[scale.z] is the radius of the cylinder
|
|
/// **[scale.y] is the height of the cylinder
|
|
/// **[scale.x] is the length of the arc of cylinder
|
|
/// * Limitations
|
|
/// **Only the half of the cylinder can be displayed, which means the arc angle has to be smaller than 180 degree, [scale.x] / [scale.z] <= PI
|
|
/// **Your camera has to be inside of the inscribed sphere of the cylinder, the overlay will be faded out automatically when the camera is close to the inscribed sphere's surface.
|
|
/// **Translation only works correctly with vrDriver 1.04 or above
|
|
/// 3. Cubemap: Display overlay as a cube map
|
|
/// 4. OffcenterCubemap: [Mobile Only] Display overlay as a cube map with a texture coordinate offset
|
|
/// * The actually sampling will looks like [color = texture(cubeLayerSampler, normalize(direction) + offset)] instead of [color = texture( cubeLayerSampler, direction )]
|
|
/// * The extra center offset can be feed from transform.position
|
|
/// * Note: if transform.position's magnitude is greater than 1, which will cause some cube map pixel always invisible
|
|
/// Which is usually not what people wanted, we don't kill the ability for developer to do so here, but will warn out.
|
|
/// 5. Equirect: Display overlay as a 360-degree equirectangular skybox.
|
|
/// </summary>
|
|
public class OVROverlay : MonoBehaviour
|
|
{
|
|
#region Interface
|
|
|
|
/// <summary>
|
|
/// Determines the on-screen appearance of a layer.
|
|
/// </summary>
|
|
public enum OverlayShape
|
|
{
|
|
Quad = OVRPlugin.OverlayShape.Quad,
|
|
Cylinder = OVRPlugin.OverlayShape.Cylinder,
|
|
Cubemap = OVRPlugin.OverlayShape.Cubemap,
|
|
OffcenterCubemap = OVRPlugin.OverlayShape.OffcenterCubemap,
|
|
Equirect = OVRPlugin.OverlayShape.Equirect,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the layer appears behind or infront of other content in the scene.
|
|
/// </summary>
|
|
public enum OverlayType
|
|
{
|
|
None,
|
|
Underlay,
|
|
Overlay,
|
|
};
|
|
|
|
/// <summary>
|
|
/// Specify overlay's type
|
|
/// </summary>
|
|
[Tooltip("Specify overlay's type")]
|
|
public OverlayType currentOverlayType = OverlayType.Overlay;
|
|
|
|
/// <summary>
|
|
/// If true, the texture's content is copied to the compositor each frame.
|
|
/// </summary>
|
|
[Tooltip("If true, the texture's content is copied to the compositor each frame.")]
|
|
public bool isDynamic = false;
|
|
|
|
/// <summary>
|
|
/// If true, the layer would be used to present protected content (e.g. HDCP). The flag is effective only on PC.
|
|
/// </summary>
|
|
[Tooltip("If true, the layer would be used to present protected content (e.g. HDCP). The flag is effective only on PC.")]
|
|
public bool isProtectedContent = false;
|
|
|
|
//Source and dest rects
|
|
public Rect srcRectLeft = new Rect();
|
|
public Rect srcRectRight = new Rect();
|
|
public Rect destRectLeft = new Rect();
|
|
public Rect destRectRight = new Rect();
|
|
|
|
private OVRPlugin.TextureRectMatrixf textureRectMatrix = OVRPlugin.TextureRectMatrixf.zero;
|
|
|
|
public bool overrideTextureRectMatrix = false;
|
|
|
|
public bool overridePerLayerColorScaleAndOffset = false;
|
|
|
|
public Vector4 colorScale = Vector4.one;
|
|
|
|
public Vector4 colorOffset = Vector4.zero;
|
|
|
|
//Warning: Developers should only use this supersample setting if they absolutely have the budget and need for it. It is extremely expensive, and will not be relevant for most developers.
|
|
public bool useExpensiveSuperSample = false;
|
|
|
|
//Property that can hide overlays when required. Should be false when present, true when hidden.
|
|
public bool hidden = false;
|
|
|
|
/// <summary>
|
|
/// If true, the layer will be created as an external surface. externalSurfaceObject contains the Surface object. It's effective only on Android.
|
|
/// </summary>
|
|
[Tooltip("If true, the layer will be created as an external surface. externalSurfaceObject contains the Surface object. It's effective only on Android.")]
|
|
public bool isExternalSurface = false;
|
|
|
|
/// <summary>
|
|
/// The width which will be used to create the external surface. It's effective only on Android.
|
|
/// </summary>
|
|
[Tooltip("The width which will be used to create the external surface. It's effective only on Android.")]
|
|
public int externalSurfaceWidth = 0;
|
|
|
|
/// <summary>
|
|
/// The height which will be used to create the external surface. It's effective only on Android.
|
|
/// </summary>
|
|
[Tooltip("The height which will be used to create the external surface. It's effective only on Android.")]
|
|
public int externalSurfaceHeight = 0;
|
|
|
|
/// <summary>
|
|
/// The compositionDepth defines the order of the OVROverlays in composition. The overlay/underlay with smaller compositionDepth would be composited in the front of the overlay/underlay with larger compositionDepth.
|
|
/// </summary>
|
|
[Tooltip("The compositionDepth defines the order of the OVROverlays in composition. The overlay/underlay with smaller compositionDepth would be composited in the front of the overlay/underlay with larger compositionDepth.")]
|
|
public int compositionDepth = 0;
|
|
|
|
/// <summary>
|
|
/// The noDepthBufferTesting will stop layer's depth buffer compositing even if the engine has "Depth buffer sharing" enabled on Rift.
|
|
/// </summary>
|
|
[Tooltip("The noDepthBufferTesting will stop layer's depth buffer compositing even if the engine has \"Shared Depth Buffer\" enabled")]
|
|
public bool noDepthBufferTesting = false;
|
|
|
|
//Format corresponding to the source texture for this layer. sRGB by default, but can be modified if necessary
|
|
public OVRPlugin.EyeTextureFormat layerTextureFormat = OVRPlugin.EyeTextureFormat.R8G8B8A8_sRGB;
|
|
|
|
/// <summary>
|
|
/// Specify overlay's shape
|
|
/// </summary>
|
|
[Tooltip("Specify overlay's shape")]
|
|
public OverlayShape currentOverlayShape = OverlayShape.Quad;
|
|
private OverlayShape prevOverlayShape = OverlayShape.Quad;
|
|
|
|
/// <summary>
|
|
/// The left- and right-eye Textures to show in the layer.
|
|
/// \note If you need to change the texture on a per-frame basis, please use OverrideOverlayTextureInfo(..) to avoid caching issues.
|
|
/// </summary>
|
|
[Tooltip("The left- and right-eye Textures to show in the layer.")]
|
|
public Texture[] textures = new Texture[] { null, null };
|
|
|
|
protected IntPtr[] texturePtrs = new IntPtr[] { IntPtr.Zero, IntPtr.Zero };
|
|
|
|
/// <summary>
|
|
/// The Surface object (Android only).
|
|
/// </summary>
|
|
public System.IntPtr externalSurfaceObject;
|
|
|
|
public delegate void ExternalSurfaceObjectCreated();
|
|
/// <summary>
|
|
/// Will be triggered after externalSurfaceTextueObject get created.
|
|
/// </summary>
|
|
public ExternalSurfaceObjectCreated externalSurfaceObjectCreated;
|
|
|
|
/// <summary>
|
|
/// Use this function to set texture and texNativePtr when app is running
|
|
/// GetNativeTexturePtr is a slow behavior, the value should be pre-cached
|
|
/// </summary>
|
|
#if UNITY_2017_2_OR_NEWER
|
|
public void OverrideOverlayTextureInfo(Texture srcTexture, IntPtr nativePtr, UnityEngine.XR.XRNode node)
|
|
#else
|
|
public void OverrideOverlayTextureInfo(Texture srcTexture, IntPtr nativePtr, UnityEngine.VR.VRNode node)
|
|
#endif
|
|
{
|
|
#if UNITY_2017_2_OR_NEWER
|
|
int index = (node == UnityEngine.XR.XRNode.RightEye) ? 1 : 0;
|
|
#else
|
|
int index = (node == UnityEngine.VR.VRNode.RightEye) ? 1 : 0;
|
|
#endif
|
|
|
|
if (textures.Length <= index)
|
|
return;
|
|
|
|
textures[index] = srcTexture;
|
|
texturePtrs[index] = nativePtr;
|
|
|
|
isOverridePending = true;
|
|
}
|
|
|
|
protected bool isOverridePending;
|
|
|
|
internal const int maxInstances = 15;
|
|
public static OVROverlay[] instances = new OVROverlay[maxInstances];
|
|
|
|
#endregion
|
|
|
|
private static Material tex2DMaterial;
|
|
private static Material cubeMaterial;
|
|
|
|
private OVRPlugin.LayerLayout layout {
|
|
get {
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|
if (textures.Length == 2 && textures[1] != null)
|
|
return OVRPlugin.LayerLayout.Stereo;
|
|
#endif
|
|
return OVRPlugin.LayerLayout.Mono;
|
|
}
|
|
}
|
|
|
|
private struct LayerTexture {
|
|
public Texture appTexture;
|
|
public IntPtr appTexturePtr;
|
|
public Texture[] swapChain;
|
|
public IntPtr[] swapChainPtr;
|
|
};
|
|
private LayerTexture[] layerTextures;
|
|
|
|
private OVRPlugin.LayerDesc layerDesc;
|
|
private int stageCount = -1;
|
|
|
|
private int layerIndex = -1; // Controls the composition order based on wake-up time.
|
|
|
|
private int layerId = 0; // The layer's internal handle in the compositor.
|
|
private GCHandle layerIdHandle;
|
|
private IntPtr layerIdPtr = IntPtr.Zero;
|
|
|
|
private int frameIndex = 0;
|
|
private int prevFrameIndex = -1;
|
|
|
|
private Renderer rend;
|
|
|
|
private int texturesPerStage { get { return (layout == OVRPlugin.LayerLayout.Stereo) ? 2 : 1; } }
|
|
|
|
private bool CreateLayer(int mipLevels, int sampleCount, OVRPlugin.EyeTextureFormat etFormat, int flags, OVRPlugin.Sizei size, OVRPlugin.OverlayShape shape)
|
|
{
|
|
if (!layerIdHandle.IsAllocated || layerIdPtr == IntPtr.Zero)
|
|
{
|
|
layerIdHandle = GCHandle.Alloc(layerId, GCHandleType.Pinned);
|
|
layerIdPtr = layerIdHandle.AddrOfPinnedObject();
|
|
}
|
|
|
|
if (layerIndex == -1)
|
|
{
|
|
for (int i = 0; i < maxInstances; ++i)
|
|
{
|
|
if (instances[i] == null || instances[i] == this)
|
|
{
|
|
layerIndex = i;
|
|
instances[i] = this;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool needsSetup = (
|
|
isOverridePending ||
|
|
layerDesc.MipLevels != mipLevels ||
|
|
layerDesc.SampleCount != sampleCount ||
|
|
layerDesc.Format != etFormat ||
|
|
layerDesc.Layout != layout ||
|
|
layerDesc.LayerFlags != flags ||
|
|
!layerDesc.TextureSize.Equals(size) ||
|
|
layerDesc.Shape != shape);
|
|
|
|
if (!needsSetup)
|
|
return false;
|
|
|
|
OVRPlugin.LayerDesc desc = OVRPlugin.CalculateLayerDesc(shape, layout, size, mipLevels, sampleCount, etFormat, flags);
|
|
OVRPlugin.EnqueueSetupLayer(desc, compositionDepth, layerIdPtr);
|
|
layerId = (int)layerIdHandle.Target;
|
|
|
|
if (layerId > 0)
|
|
{
|
|
layerDesc = desc;
|
|
if (isExternalSurface)
|
|
{
|
|
stageCount = 1;
|
|
}
|
|
else
|
|
{
|
|
stageCount = OVRPlugin.GetLayerTextureStageCount(layerId);
|
|
}
|
|
}
|
|
|
|
isOverridePending = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool CreateLayerTextures(bool useMipmaps, OVRPlugin.Sizei size, bool isHdr)
|
|
{
|
|
if (isExternalSurface)
|
|
{
|
|
if (externalSurfaceObject == System.IntPtr.Zero)
|
|
{
|
|
externalSurfaceObject = OVRPlugin.GetLayerAndroidSurfaceObject(layerId);
|
|
if (externalSurfaceObject != System.IntPtr.Zero)
|
|
{
|
|
Debug.LogFormat("GetLayerAndroidSurfaceObject returns {0}", externalSurfaceObject);
|
|
if (externalSurfaceObjectCreated != null)
|
|
{
|
|
externalSurfaceObjectCreated();
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool needsCopy = false;
|
|
|
|
if (stageCount <= 0)
|
|
return false;
|
|
|
|
// For newer SDKs, blit directly to the surface that will be used in compositing.
|
|
|
|
if (layerTextures == null)
|
|
layerTextures = new LayerTexture[texturesPerStage];
|
|
|
|
for (int eyeId = 0; eyeId < texturesPerStage; ++eyeId)
|
|
{
|
|
if (layerTextures[eyeId].swapChain == null)
|
|
layerTextures[eyeId].swapChain = new Texture[stageCount];
|
|
|
|
if (layerTextures[eyeId].swapChainPtr == null)
|
|
layerTextures[eyeId].swapChainPtr = new IntPtr[stageCount];
|
|
|
|
for (int stage = 0; stage < stageCount; ++stage)
|
|
{
|
|
Texture sc = layerTextures[eyeId].swapChain[stage];
|
|
IntPtr scPtr = layerTextures[eyeId].swapChainPtr[stage];
|
|
|
|
if (sc != null && scPtr != IntPtr.Zero && size.w == sc.width && size.h == sc.height)
|
|
continue;
|
|
|
|
if (scPtr == IntPtr.Zero)
|
|
scPtr = OVRPlugin.GetLayerTexture(layerId, stage, (OVRPlugin.Eye)eyeId);
|
|
|
|
if (scPtr == IntPtr.Zero)
|
|
continue;
|
|
|
|
var txFormat = (isHdr) ? TextureFormat.RGBAHalf : TextureFormat.RGBA32;
|
|
|
|
if (currentOverlayShape != OverlayShape.Cubemap && currentOverlayShape != OverlayShape.OffcenterCubemap)
|
|
sc = Texture2D.CreateExternalTexture(size.w, size.h, txFormat, useMipmaps, true, scPtr);
|
|
#if UNITY_2017_1_OR_NEWER
|
|
else
|
|
sc = Cubemap.CreateExternalTexture(size.w, txFormat, useMipmaps, scPtr);
|
|
#endif
|
|
|
|
layerTextures[eyeId].swapChain[stage] = sc;
|
|
layerTextures[eyeId].swapChainPtr[stage] = scPtr;
|
|
|
|
needsCopy = true;
|
|
}
|
|
}
|
|
|
|
return needsCopy;
|
|
}
|
|
|
|
private void DestroyLayerTextures()
|
|
{
|
|
if (isExternalSurface)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int eyeId = 0; layerTextures != null && eyeId < texturesPerStage; ++eyeId)
|
|
{
|
|
if (layerTextures[eyeId].swapChain != null)
|
|
{
|
|
for (int stage = 0; stage < stageCount; ++stage)
|
|
DestroyImmediate(layerTextures[eyeId].swapChain[stage]);
|
|
}
|
|
}
|
|
|
|
layerTextures = null;
|
|
}
|
|
|
|
private void DestroyLayer()
|
|
{
|
|
if (layerIndex != -1)
|
|
{
|
|
// Turn off the overlay if it was on.
|
|
OVRPlugin.EnqueueSubmitLayer(true, false, false, IntPtr.Zero, IntPtr.Zero, -1, 0, OVRPose.identity.ToPosef_Legacy(), Vector3.one.ToVector3f(), layerIndex, (OVRPlugin.OverlayShape)prevOverlayShape);
|
|
instances[layerIndex] = null;
|
|
layerIndex = -1;
|
|
}
|
|
|
|
if (layerIdPtr != IntPtr.Zero)
|
|
{
|
|
OVRPlugin.EnqueueDestroyLayer(layerIdPtr);
|
|
layerIdPtr = IntPtr.Zero;
|
|
layerIdHandle.Free();
|
|
layerId = 0;
|
|
}
|
|
|
|
layerDesc = new OVRPlugin.LayerDesc();
|
|
|
|
frameIndex = 0;
|
|
prevFrameIndex = -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the source and dest rects for both eyes. Source explains what portion of the source texture is used, and
|
|
/// dest is what portion of the destination texture is rendered into.
|
|
/// </summary>
|
|
public void SetSrcDestRects(Rect srcLeft, Rect srcRight, Rect destLeft, Rect destRight)
|
|
{
|
|
srcRectLeft = srcLeft;
|
|
srcRectRight = srcRight;
|
|
destRectLeft = destLeft;
|
|
destRectRight = destRight;
|
|
}
|
|
|
|
public void UpdateTextureRectMatrix()
|
|
{
|
|
Rect srcRectLeftConverted = new Rect(srcRectLeft.x, 1 - srcRectLeft.y - srcRectLeft.height, srcRectLeft.width, srcRectLeft.height);
|
|
Rect srcRectRightConverted = new Rect(srcRectRight.x, 1 - srcRectRight.y - srcRectRight.height, srcRectRight.width, srcRectRight.height);
|
|
textureRectMatrix.leftRect = srcRectLeftConverted;
|
|
textureRectMatrix.rightRect = srcRectRightConverted;
|
|
float leftWidthFactor = srcRectLeftConverted.width / destRectLeft.width;
|
|
float leftHeightFactor = srcRectLeftConverted.height / destRectLeft.height;
|
|
textureRectMatrix.leftScaleBias = new Vector4(leftWidthFactor, leftHeightFactor, srcRectLeftConverted.x - destRectLeft.x * leftWidthFactor, srcRectLeftConverted.y - destRectLeft.y * leftHeightFactor);
|
|
float rightWidthFactor = srcRectRightConverted.width / destRectRight.width;
|
|
float rightHeightFactor = srcRectRightConverted.height / destRectRight.height;
|
|
textureRectMatrix.rightScaleBias = new Vector4(rightWidthFactor, rightHeightFactor, srcRectRightConverted.x - destRectRight.x * rightWidthFactor, srcRectRightConverted.y - destRectRight.y * rightHeightFactor);
|
|
}
|
|
|
|
public void SetPerLayerColorScaleAndOffset(Vector4 scale, Vector4 offset)
|
|
{
|
|
colorScale = scale;
|
|
colorOffset = offset;
|
|
}
|
|
|
|
private bool LatchLayerTextures()
|
|
{
|
|
if (isExternalSurface)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
for (int i = 0; i < texturesPerStage; ++i)
|
|
{
|
|
if (textures[i] != layerTextures[i].appTexture || layerTextures[i].appTexturePtr == IntPtr.Zero)
|
|
{
|
|
if (textures[i] != null)
|
|
{
|
|
#if UNITY_EDITOR
|
|
var assetPath = UnityEditor.AssetDatabase.GetAssetPath(textures[i]);
|
|
var importer = (UnityEditor.TextureImporter)UnityEditor.TextureImporter.GetAtPath(assetPath);
|
|
if (importer && importer.textureType != UnityEditor.TextureImporterType.Default)
|
|
{
|
|
Debug.LogError("Need Default Texture Type for overlay");
|
|
return false;
|
|
}
|
|
#endif
|
|
var rt = textures[i] as RenderTexture;
|
|
if (rt && !rt.IsCreated())
|
|
rt.Create();
|
|
|
|
layerTextures[i].appTexturePtr = (texturePtrs[i] != IntPtr.Zero) ? texturePtrs[i] : textures[i].GetNativeTexturePtr();
|
|
|
|
if (layerTextures[i].appTexturePtr != IntPtr.Zero)
|
|
layerTextures[i].appTexture = textures[i];
|
|
}
|
|
}
|
|
|
|
if (currentOverlayShape == OverlayShape.Cubemap)
|
|
{
|
|
if (textures[i] as Cubemap == null)
|
|
{
|
|
Debug.LogError("Need Cubemap texture for cube map overlay");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if !UNITY_ANDROID || UNITY_EDITOR
|
|
if (currentOverlayShape == OverlayShape.OffcenterCubemap)
|
|
{
|
|
Debug.LogWarning("Overlay shape " + currentOverlayShape + " is not supported on current platform");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
if (layerTextures[0].appTexture == null || layerTextures[0].appTexturePtr == IntPtr.Zero)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
private OVRPlugin.LayerDesc GetCurrentLayerDesc()
|
|
{
|
|
OVRPlugin.Sizei textureSize = new OVRPlugin.Sizei() { w = 0, h = 0 };
|
|
|
|
if (isExternalSurface)
|
|
{
|
|
textureSize.w = externalSurfaceWidth;
|
|
textureSize.h = externalSurfaceHeight;
|
|
}
|
|
else
|
|
{
|
|
if (textures[0] == null)
|
|
{
|
|
Debug.LogWarning("textures[0] hasn't been set");
|
|
}
|
|
textureSize.w = textures[0] ? textures[0].width : 0;
|
|
textureSize.h = textures[0] ? textures[0].height : 0;
|
|
}
|
|
|
|
OVRPlugin.LayerDesc newDesc = new OVRPlugin.LayerDesc() {
|
|
Format = layerTextureFormat,
|
|
LayerFlags = isExternalSurface ? 0 : (int)OVRPlugin.LayerFlags.TextureOriginAtBottomLeft,
|
|
Layout = layout,
|
|
MipLevels = 1,
|
|
SampleCount = 1,
|
|
Shape = (OVRPlugin.OverlayShape)currentOverlayShape,
|
|
TextureSize = textureSize
|
|
};
|
|
|
|
var tex2D = textures[0] as Texture2D;
|
|
if (tex2D != null)
|
|
{
|
|
if (tex2D.format == TextureFormat.RGBAHalf || tex2D.format == TextureFormat.RGBAFloat)
|
|
newDesc.Format = OVRPlugin.EyeTextureFormat.R16G16B16A16_FP;
|
|
|
|
newDesc.MipLevels = tex2D.mipmapCount;
|
|
}
|
|
|
|
var texCube = textures[0] as Cubemap;
|
|
if (texCube != null)
|
|
{
|
|
if (texCube.format == TextureFormat.RGBAHalf || texCube.format == TextureFormat.RGBAFloat)
|
|
newDesc.Format = OVRPlugin.EyeTextureFormat.R16G16B16A16_FP;
|
|
|
|
newDesc.MipLevels = texCube.mipmapCount;
|
|
}
|
|
|
|
var rt = textures[0] as RenderTexture;
|
|
if (rt != null)
|
|
{
|
|
newDesc.SampleCount = rt.antiAliasing;
|
|
|
|
if (rt.format == RenderTextureFormat.ARGBHalf || rt.format == RenderTextureFormat.ARGBFloat || rt.format == RenderTextureFormat.RGB111110Float)
|
|
newDesc.Format = OVRPlugin.EyeTextureFormat.R16G16B16A16_FP;
|
|
}
|
|
|
|
if (isProtectedContent)
|
|
{
|
|
newDesc.LayerFlags |= (int)OVRPlugin.LayerFlags.ProtectedContent;
|
|
}
|
|
|
|
if (isExternalSurface)
|
|
{
|
|
newDesc.LayerFlags |= (int)OVRPlugin.LayerFlags.AndroidSurfaceSwapChain;
|
|
}
|
|
|
|
return newDesc;
|
|
}
|
|
|
|
private bool PopulateLayer(int mipLevels, bool isHdr, OVRPlugin.Sizei size, int sampleCount, int stage)
|
|
{
|
|
if (isExternalSurface)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool ret = false;
|
|
|
|
RenderTextureFormat rtFormat = (isHdr) ? RenderTextureFormat.ARGBHalf : RenderTextureFormat.ARGB32;
|
|
|
|
for (int eyeId = 0; eyeId < texturesPerStage; ++eyeId)
|
|
{
|
|
Texture et = layerTextures[eyeId].swapChain[stage];
|
|
if (et == null)
|
|
continue;
|
|
|
|
for (int mip = 0; mip < mipLevels; ++mip)
|
|
{
|
|
int width = size.w >> mip;
|
|
if (width < 1) width = 1;
|
|
int height = size.h >> mip;
|
|
if (height < 1) height = 1;
|
|
#if UNITY_2017_1_1 || UNITY_2017_2_OR_NEWER
|
|
RenderTextureDescriptor descriptor = new RenderTextureDescriptor(width, height, rtFormat, 0);
|
|
descriptor.msaaSamples = sampleCount;
|
|
descriptor.useMipMap = true;
|
|
descriptor.autoGenerateMips = false;
|
|
descriptor.sRGB = false;
|
|
|
|
var tempRTDst = RenderTexture.GetTemporary(descriptor);
|
|
#else
|
|
var tempRTDst = RenderTexture.GetTemporary(width, height, 0, rtFormat, RenderTextureReadWrite.Linear, sampleCount);
|
|
#endif
|
|
|
|
if (!tempRTDst.IsCreated())
|
|
tempRTDst.Create();
|
|
|
|
tempRTDst.DiscardContents();
|
|
|
|
bool dataIsLinear = isHdr || (QualitySettings.activeColorSpace == ColorSpace.Linear);
|
|
|
|
#if !UNITY_2017_1_OR_NEWER
|
|
var rt = textures[eyeId] as RenderTexture;
|
|
dataIsLinear |= rt != null && rt.sRGB; //HACK: Unity 5.6 and earlier convert to linear on read from sRGB RenderTexture.
|
|
#endif
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|
dataIsLinear = true; //HACK: Graphics.CopyTexture causes linear->srgb conversion on target write with D3D but not GLES.
|
|
#endif
|
|
|
|
if (currentOverlayShape != OverlayShape.Cubemap && currentOverlayShape != OverlayShape.OffcenterCubemap)
|
|
{
|
|
tex2DMaterial.SetInt("_linearToSrgb", (!isHdr && dataIsLinear) ? 1 : 0);
|
|
|
|
//Resolve, decompress, swizzle, etc not handled by simple CopyTexture.
|
|
#if !UNITY_ANDROID || UNITY_EDITOR
|
|
// The PC compositor uses premultiplied alpha, so multiply it here.
|
|
tex2DMaterial.SetInt("_premultiply", 1);
|
|
#endif
|
|
Graphics.Blit(textures[eyeId], tempRTDst, tex2DMaterial);
|
|
Graphics.CopyTexture(tempRTDst, 0, 0, et, 0, mip);
|
|
}
|
|
#if UNITY_2017_1_OR_NEWER
|
|
else // Cubemap
|
|
{
|
|
for (int face = 0; face < 6; ++face)
|
|
{
|
|
cubeMaterial.SetInt("_linearToSrgb", (!isHdr && dataIsLinear) ? 1 : 0);
|
|
|
|
#if !UNITY_ANDROID || UNITY_EDITOR
|
|
// The PC compositor uses premultiplied alpha, so multiply it here.
|
|
cubeMaterial.SetInt("_premultiply", 1);
|
|
#endif
|
|
cubeMaterial.SetInt("_face", face);
|
|
//Resolve, decompress, swizzle, etc not handled by simple CopyTexture.
|
|
Graphics.Blit(textures[eyeId], tempRTDst, cubeMaterial);
|
|
Graphics.CopyTexture(tempRTDst, 0, 0, et, face, mip);
|
|
}
|
|
}
|
|
#endif
|
|
RenderTexture.ReleaseTemporary(tempRTDst);
|
|
|
|
ret = true;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
private bool SubmitLayer(bool overlay, bool headLocked, bool noDepthBufferTesting, OVRPose pose, Vector3 scale, int frameIndex)
|
|
{
|
|
int rightEyeIndex = (texturesPerStage >= 2) ? 1 : 0;
|
|
if (overrideTextureRectMatrix)
|
|
{
|
|
UpdateTextureRectMatrix();
|
|
}
|
|
bool isOverlayVisible = OVRPlugin.EnqueueSubmitLayer(overlay, headLocked, noDepthBufferTesting,
|
|
isExternalSurface ? System.IntPtr.Zero : layerTextures[0].appTexturePtr,
|
|
isExternalSurface ? System.IntPtr.Zero : layerTextures[rightEyeIndex].appTexturePtr,
|
|
layerId, frameIndex, pose.flipZ().ToPosef_Legacy(), scale.ToVector3f(), layerIndex, (OVRPlugin.OverlayShape)currentOverlayShape,
|
|
overrideTextureRectMatrix, textureRectMatrix, overridePerLayerColorScaleAndOffset, colorScale, colorOffset, useExpensiveSuperSample,
|
|
hidden);
|
|
|
|
prevOverlayShape = currentOverlayShape;
|
|
|
|
return isOverlayVisible;
|
|
}
|
|
|
|
#region Unity Messages
|
|
|
|
void Awake()
|
|
{
|
|
Debug.Log("Overlay Awake");
|
|
|
|
if (tex2DMaterial == null)
|
|
tex2DMaterial = new Material(Shader.Find("Oculus/Texture2D Blit"));
|
|
|
|
if (cubeMaterial == null)
|
|
cubeMaterial = new Material(Shader.Find("Oculus/Cubemap Blit"));
|
|
|
|
rend = GetComponent<Renderer>();
|
|
|
|
if (textures.Length == 0)
|
|
textures = new Texture[] { null };
|
|
|
|
// Backward compatibility
|
|
if (rend != null && textures[0] == null)
|
|
textures[0] = rend.material.mainTexture;
|
|
}
|
|
|
|
static public string OpenVROverlayKey { get { return "unity:" + Application.companyName + "." + Application.productName; } }
|
|
private ulong OpenVROverlayHandle = OVR.OpenVR.OpenVR.k_ulOverlayHandleInvalid;
|
|
|
|
void OnEnable()
|
|
{
|
|
if (OVRManager.OVRManagerinitialized)
|
|
InitOVROverlay();
|
|
}
|
|
|
|
void InitOVROverlay()
|
|
{
|
|
if (!OVRManager.isHmdPresent)
|
|
{
|
|
enabled = false;
|
|
return;
|
|
}
|
|
|
|
constructedOverlayXRDevice = OVRManager.XRDevice.Unknown;
|
|
if (OVRManager.loadedXRDevice == OVRManager.XRDevice.OpenVR)
|
|
{
|
|
OVR.OpenVR.CVROverlay overlay = OVR.OpenVR.OpenVR.Overlay;
|
|
if (overlay != null)
|
|
{
|
|
OVR.OpenVR.EVROverlayError error = overlay.CreateOverlay(OpenVROverlayKey + transform.name, gameObject.name, ref OpenVROverlayHandle);
|
|
if (error != OVR.OpenVR.EVROverlayError.None)
|
|
{
|
|
enabled = false;
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
enabled = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
constructedOverlayXRDevice = OVRManager.loadedXRDevice;
|
|
xrDeviceConstructed = true;
|
|
}
|
|
|
|
void OnDisable()
|
|
{
|
|
if ((gameObject.hideFlags & HideFlags.DontSaveInBuild) != 0)
|
|
return;
|
|
|
|
if (!OVRManager.OVRManagerinitialized)
|
|
return;
|
|
|
|
if (OVRManager.loadedXRDevice != constructedOverlayXRDevice)
|
|
return;
|
|
|
|
if (OVRManager.loadedXRDevice == OVRManager.XRDevice.Oculus)
|
|
{
|
|
DestroyLayerTextures();
|
|
DestroyLayer();
|
|
}
|
|
else if (OVRManager.loadedXRDevice == OVRManager.XRDevice.OpenVR)
|
|
{
|
|
if (OpenVROverlayHandle != OVR.OpenVR.OpenVR.k_ulOverlayHandleInvalid)
|
|
{
|
|
OVR.OpenVR.CVROverlay overlay = OVR.OpenVR.OpenVR.Overlay;
|
|
if (overlay != null)
|
|
{
|
|
overlay.DestroyOverlay(OpenVROverlayHandle);
|
|
}
|
|
OpenVROverlayHandle = OVR.OpenVR.OpenVR.k_ulOverlayHandleInvalid;
|
|
}
|
|
}
|
|
constructedOverlayXRDevice = OVRManager.XRDevice.Unknown;
|
|
xrDeviceConstructed = false;
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
DestroyLayerTextures();
|
|
DestroyLayer();
|
|
}
|
|
|
|
bool ComputeSubmit(ref OVRPose pose, ref Vector3 scale, ref bool overlay, ref bool headLocked)
|
|
{
|
|
Camera headCamera = Camera.main;
|
|
|
|
overlay = (currentOverlayType == OverlayType.Overlay);
|
|
headLocked = false;
|
|
for (var t = transform; t != null && !headLocked; t = t.parent)
|
|
headLocked |= (t == headCamera.transform);
|
|
|
|
pose = (headLocked) ? transform.ToHeadSpacePose(headCamera) : transform.ToTrackingSpacePose(headCamera);
|
|
scale = transform.lossyScale;
|
|
for (int i = 0; i < 3; ++i)
|
|
scale[i] /= headCamera.transform.lossyScale[i];
|
|
|
|
if (currentOverlayShape == OverlayShape.Cubemap)
|
|
{
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|
//HACK: VRAPI cubemaps assume are yawed 180 degrees relative to LibOVR.
|
|
pose.orientation = pose.orientation * Quaternion.AngleAxis(180, Vector3.up);
|
|
#endif
|
|
pose.position = headCamera.transform.position;
|
|
}
|
|
|
|
// Pack the offsetCenter directly into pose.position for offcenterCubemap
|
|
if (currentOverlayShape == OverlayShape.OffcenterCubemap)
|
|
{
|
|
pose.position = transform.position;
|
|
if (pose.position.magnitude > 1.0f)
|
|
{
|
|
Debug.LogWarning("Your cube map center offset's magnitude is greater than 1, which will cause some cube map pixel always invisible .");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Cylinder overlay sanity checking
|
|
if (currentOverlayShape == OverlayShape.Cylinder)
|
|
{
|
|
float arcAngle = scale.x / scale.z / (float)Math.PI * 180.0f;
|
|
if (arcAngle > 180.0f)
|
|
{
|
|
Debug.LogWarning("Cylinder overlay's arc angle has to be below 180 degree, current arc angle is " + arcAngle + " degree." );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void OpenVROverlayUpdate(Vector3 scale, OVRPose pose)
|
|
{
|
|
OVR.OpenVR.CVROverlay overlayRef = OVR.OpenVR.OpenVR.Overlay;
|
|
if (overlayRef == null)
|
|
return;
|
|
|
|
Texture overlayTex = textures[0];
|
|
|
|
if (overlayTex != null)
|
|
{
|
|
OVR.OpenVR.EVROverlayError error = overlayRef.ShowOverlay(OpenVROverlayHandle);
|
|
if (error == OVR.OpenVR.EVROverlayError.InvalidHandle || error == OVR.OpenVR.EVROverlayError.UnknownOverlay)
|
|
{
|
|
if (overlayRef.FindOverlay(OpenVROverlayKey + transform.name, ref OpenVROverlayHandle) != OVR.OpenVR.EVROverlayError.None)
|
|
return;
|
|
}
|
|
|
|
OVR.OpenVR.Texture_t tex = new OVR.OpenVR.Texture_t();
|
|
tex.handle = overlayTex.GetNativeTexturePtr();
|
|
tex.eType = SystemInfo.graphicsDeviceVersion.StartsWith("OpenGL") ? OVR.OpenVR.ETextureType.OpenGL : OVR.OpenVR.ETextureType.DirectX;
|
|
tex.eColorSpace = OVR.OpenVR.EColorSpace.Auto;
|
|
overlayRef.SetOverlayTexture(OpenVROverlayHandle, ref tex);
|
|
|
|
OVR.OpenVR.VRTextureBounds_t textureBounds = new OVR.OpenVR.VRTextureBounds_t();
|
|
textureBounds.uMin = (0 + OpenVRUVOffsetAndScale.x) * OpenVRUVOffsetAndScale.z;
|
|
textureBounds.vMin = (1 + OpenVRUVOffsetAndScale.y) * OpenVRUVOffsetAndScale.w;
|
|
textureBounds.uMax = (1 + OpenVRUVOffsetAndScale.x) * OpenVRUVOffsetAndScale.z;
|
|
textureBounds.vMax = (0 + OpenVRUVOffsetAndScale.y) * OpenVRUVOffsetAndScale.w;
|
|
|
|
overlayRef.SetOverlayTextureBounds(OpenVROverlayHandle, ref textureBounds);
|
|
|
|
OVR.OpenVR.HmdVector2_t vecMouseScale = new OVR.OpenVR.HmdVector2_t();
|
|
vecMouseScale.v0 = OpenVRMouseScale.x;
|
|
vecMouseScale.v1 = OpenVRMouseScale.y;
|
|
overlayRef.SetOverlayMouseScale(OpenVROverlayHandle, ref vecMouseScale);
|
|
|
|
overlayRef.SetOverlayWidthInMeters(OpenVROverlayHandle, scale.x);
|
|
|
|
Matrix4x4 mat44 = Matrix4x4.TRS(pose.position, pose.orientation, Vector3.one);
|
|
|
|
OVR.OpenVR.HmdMatrix34_t pose34 = mat44.ConvertToHMDMatrix34();
|
|
|
|
overlayRef.SetOverlayTransformAbsolute(OpenVROverlayHandle, OVR.OpenVR.ETrackingUniverseOrigin.TrackingUniverseStanding, ref pose34);
|
|
|
|
}
|
|
}
|
|
|
|
private Vector4 OpenVRUVOffsetAndScale = new Vector4(0, 0, 1.0f, 1.0f);
|
|
private Vector2 OpenVRMouseScale = new Vector2(1, 1);
|
|
private OVRManager.XRDevice constructedOverlayXRDevice;
|
|
private bool xrDeviceConstructed = false;
|
|
|
|
void LateUpdate()
|
|
{
|
|
if (!OVRManager.OVRManagerinitialized)
|
|
return;
|
|
if (!xrDeviceConstructed)
|
|
{
|
|
InitOVROverlay();
|
|
}
|
|
|
|
if (OVRManager.loadedXRDevice != constructedOverlayXRDevice)
|
|
{
|
|
Debug.LogError("Warning-XR Device was switched during runtime with overlays still enabled. When doing so, all overlays constructed with the previous XR device must first be disabled.");
|
|
return;
|
|
}
|
|
// The overlay must be specified every eye frame, because it is positioned relative to the
|
|
// current head location. If frames are dropped, it will be time warped appropriately,
|
|
// just like the eye buffers.
|
|
if (currentOverlayType == OverlayType.None || ((textures.Length < texturesPerStage || textures[0] == null) && !isExternalSurface))
|
|
return;
|
|
|
|
OVRPose pose = OVRPose.identity;
|
|
Vector3 scale = Vector3.one;
|
|
bool overlay = false;
|
|
bool headLocked = false;
|
|
if (!ComputeSubmit(ref pose, ref scale, ref overlay, ref headLocked))
|
|
return;
|
|
|
|
if (OVRManager.loadedXRDevice == OVRManager.XRDevice.OpenVR)
|
|
{
|
|
if (currentOverlayShape == OverlayShape.Quad)
|
|
OpenVROverlayUpdate(scale, pose);
|
|
//No more Overlay processing is required if we're on OpenVR
|
|
return;
|
|
}
|
|
|
|
OVRPlugin.LayerDesc newDesc = GetCurrentLayerDesc();
|
|
bool isHdr = (newDesc.Format == OVRPlugin.EyeTextureFormat.R16G16B16A16_FP);
|
|
|
|
// If the layer and textures are created but sizes differ, force re-creating them
|
|
if (!layerDesc.TextureSize.Equals(newDesc.TextureSize) && layerId > 0)
|
|
{
|
|
DestroyLayerTextures();
|
|
DestroyLayer();
|
|
}
|
|
|
|
bool createdLayer = CreateLayer(newDesc.MipLevels, newDesc.SampleCount, newDesc.Format, newDesc.LayerFlags, newDesc.TextureSize, newDesc.Shape);
|
|
|
|
if (layerIndex == -1 || layerId <= 0)
|
|
return;
|
|
|
|
bool useMipmaps = (newDesc.MipLevels > 1);
|
|
|
|
createdLayer |= CreateLayerTextures(useMipmaps, newDesc.TextureSize, isHdr);
|
|
|
|
if (!isExternalSurface && (layerTextures[0].appTexture as RenderTexture != null))
|
|
isDynamic = true;
|
|
|
|
if (!LatchLayerTextures())
|
|
return;
|
|
|
|
// Don't populate the same frame image twice.
|
|
if (frameIndex > prevFrameIndex)
|
|
{
|
|
int stage = frameIndex % stageCount;
|
|
if (!PopulateLayer (newDesc.MipLevels, isHdr, newDesc.TextureSize, newDesc.SampleCount, stage))
|
|
return;
|
|
}
|
|
|
|
bool isOverlayVisible = SubmitLayer(overlay, headLocked, noDepthBufferTesting, pose, scale, frameIndex);
|
|
|
|
prevFrameIndex = frameIndex;
|
|
if (isDynamic)
|
|
++frameIndex;
|
|
|
|
// Backward compatibility: show regular renderer if overlay isn't visible.
|
|
if (rend)
|
|
rend.enabled = !isOverlayVisible;
|
|
}
|
|
|
|
#endregion
|
|
}
|