Initialer Upload neues Unity-Projekt

This commit is contained in:
Daniel Ocks
2025-07-21 09:11:14 +02:00
commit eeca72985b
14558 changed files with 1508140 additions and 0 deletions

View File

@ -0,0 +1,668 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus 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/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus 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 System.Runtime.InteropServices;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
/// <summary>
/// Passthrough Color Look-Up Tables (LUTs).
/// </summary>
[HelpURL("https://developer.oculus.com/reference/unity/latest/class_o_v_r_passthrough_color_lut")]
public class OVRPassthroughColorLut : System.IDisposable
{
private const int RecomendedBatchSize = 128;
public uint Resolution { get; private set; }
public ColorChannels Channels { get; private set; }
[System.Obsolete("IsInitialized is obsoleted. Instead use IsValid.", false)]
public bool IsInitialized => IsValid;
/// <summary>
/// Checks if the LUT is usable. Can become invalid at runtime.
/// </summary>
public bool IsValid => _createState != CreateState.Invalid;
internal ulong _colorLutHandle;
private GCHandle _allocHandle;
private OVRPlugin.PassthroughColorLutData _lutData;
private int _channelCount;
private byte[] _colorBytes;
private object _locker = new object();
private CreateState _createState = CreateState.Invalid;
/// <summary>
/// Initialize the color LUT data from a texture. Color channels are inferred from texture format.
/// Use `UpdateFrom()` to update LUT data after construction.
/// </summary>
/// <param name="initialLutTexture">Texture to initialize the LUT from</param>
/// <param name="flipY">Flag to inform whether the LUT texture should be flipped vertically. This is needed for LUT images which have color (0, 0, 0)
/// in the top-left corner. Some color grading systems, e.g. Unity post-processing, have color (0, 0, 0) in the bottom-left corner,
/// in which case flipping is not needed.</param>
public OVRPassthroughColorLut(Texture2D initialLutTexture, bool flipY = true)
: this(GetTextureSize(initialLutTexture), GetChannelsForTextureFormat(initialLutTexture.format))
{
Create(CreateLutDataFromTexture(initialLutTexture, flipY));
}
/// <summary>
/// Set the color LUT data from an array of `Color`. The resolution is
/// inferred from the array size, thus the size needs to be a result of
/// `resolution = size ^ 3 * numColorChannels`, where `numColorChannels` depends
/// on `channels`.
/// Use `UpdateFrom()` to update color LUT data after construction.
/// </summary>
/// <param name="initialColorLut">Color array to initialize the LUT from</param>
/// <param name="channels">Color channels for one color LUT entry</param>
public OVRPassthroughColorLut(Color[] initialColorLut, ColorChannels channels)
: this(GetArraySize(initialColorLut), channels)
{
Create(CreateLutDataFromArray(initialColorLut));
}
/// <summary>
/// Set the color LUT data from an array of `Color`. The resolution is
/// inferred from the array size, thus the size needs to be a result of
/// `resolution = size ^ 3 * numColorChannels`, where `numColorChannels` depends
/// on `channels`.
/// Use `UpdateFrom()` to update color LUT data after construction.
/// </summary>
/// <param name="initialColorLut">Color32 array to initialize the LUT from</param>
/// <param name="channels">Color channels for one color LUT entry</param>
public OVRPassthroughColorLut(Color32[] initialColorLut, ColorChannels channels)
: this(GetArraySize(initialColorLut), channels)
{
Create(CreateLutDataFromArray(initialColorLut));
}
/// <summary>
/// Set the color LUT data from an array of `Color`. The resolution is
/// inferred from the array size, thus the size needs to be a result of
/// `resolution = size ^ 3 * numColorChannels`, where `numColorChannels` depends
/// on `channels`.
/// Use `UpdateFrom()` to update color LUT data after construction.
/// </summary>
/// <param name="initialColorLut">Color byte array to initialize the LUT from</param>
/// <param name="channels">Color channels for one color LUT entry</param>
public OVRPassthroughColorLut(byte[] initialColorLut, ColorChannels channels)
: this(GetTextureSizeFromByteArray(initialColorLut, channels), channels)
{
Create(CreateLutDataFromArray(initialColorLut));
}
/// <summary>
/// Update color LUT data from an array of Colors.
/// Color channels and resolution must match the original.
/// </summary>
/// <param name="colors">Color array</param>
public void UpdateFrom(Color[] colors)
{
if (IsValidLutUpdate(colors, _channelCount))
{
WriteColorsAsBytes(colors, _colorBytes);
OVRPlugin.UpdatePassthroughColorLut(_colorLutHandle, _lutData);
}
}
/// <summary>
/// Update color LUT data from an array of Colors.
/// Color channels and resolution must match the original.
/// </summary>
/// <param name="colors">Color array</param>
public void UpdateFrom(Color32[] colors)
{
if (IsValidLutUpdate(colors, _channelCount))
{
WriteColorsAsBytes(colors, _colorBytes);
OVRPlugin.UpdatePassthroughColorLut(_colorLutHandle, _lutData);
}
}
/// <summary>
/// Update color LUT data from an array of Colors.
/// Color channels and resolution must match the original.
/// </summary>
/// <param name="colors">Color array</param>
public void UpdateFrom(byte[] colors)
{
if (IsValidLutUpdate(colors, 1))
{
colors.CopyTo(_colorBytes, 0);
OVRPlugin.UpdatePassthroughColorLut(_colorLutHandle, _lutData);
}
}
/// <summary>
/// Update color LUT data from a texture.
/// Color channels and resolution must match the original.
/// </summary>
/// <param name="lutTexture">Color LUT texture</param>
/// <param name="flipY">Flag to inform whether the LUT texture should be flipped vertically. This is needed for LUT images which have color (0, 0, 0)
/// in the top-left corner. Some color grading systems, e.g. Unity post-processing, have color (0, 0, 0) in the bottom-left corner,
/// in which case flipping is not needed.</param>
public void UpdateFrom(Texture2D lutTexture, bool flipY = true)
{
if (IsValidUpdateResolution(GetTextureSize(lutTexture), _channelCount))
{
ColorLutTextureConverter.TextureToColorByteMap(lutTexture, _channelCount, _colorBytes, flipY);
OVRPlugin.UpdatePassthroughColorLut(_colorLutHandle, _lutData);
}
}
public void Dispose()
{
if (IsValid)
{
OVRManager.OnPassthroughInitializedStateChange -= RefreshIfInitialized;
}
Destroy();
FreeAllocHandle();
}
private void FreeAllocHandle()
{
if (_allocHandle != null && _allocHandle.IsAllocated)
{
_allocHandle.Free();
}
}
/// <summary>
/// Check if texture is in acceptable LUT format
/// </summary>
/// <param name="texture">Texture to check</param>
/// <param name="errorMessage">Error message describing acceptance fail reason</param>
/// <returns></returns>
public static bool IsTextureSupported(Texture2D texture, out string errorMessage)
{
try
{
GetChannelsForTextureFormat(texture.format);
}
catch (System.ArgumentException e)
{
errorMessage = e.Message;
return false;
}
if (!ColorLutTextureConverter.TryGetTextureLayout(texture.width, texture.height, out _, out _,
out var layoutMessage))
{
errorMessage = layoutMessage;
return false;
}
var size = texture.width * texture.height;
if (!IsResolutionAccepted(GetResolutionFromSize(size), size, out var resolutionMessage))
{
errorMessage = resolutionMessage;
return false;
}
errorMessage = string.Empty;
return true;
}
private OVRPassthroughColorLut(int size, ColorChannels channels)
{
Channels = channels;
Resolution = GetResolutionFromSize(size);
_channelCount = ChannelsToCount(channels);
if (!IsResolutionAccepted(Resolution, size, out var message))
{
throw new System.ArgumentException(message);
}
var passthroughCapabilities = OVRManager.GetPassthroughCapabilities();
if (passthroughCapabilities != null)
{
if (passthroughCapabilities.MaxColorLutResolution == 0)
{
throw new System.Exception($"Passthrough Color LUTs are not supported.");
}
if (Resolution > passthroughCapabilities.MaxColorLutResolution)
{
throw new System.Exception(
$"Color LUT resolution {Resolution} exceeds the maximum of {passthroughCapabilities.MaxColorLutResolution}.");
}
}
else
{
Debug.LogWarning(
"Unable to validate the maximum LUT resolution. Please instantiate OVRPassthroughColorLut after initializing the Oculus XR Plugin.");
}
}
private bool IsValidUpdateResolution(int lutSize, int elementByteSize)
{
if (!IsValid)
{
Debug.LogError("Can not update an uninitialized lut object.");
return false;
}
var resolution = GetResolutionFromSize(lutSize * elementByteSize / _channelCount);
if (resolution != Resolution)
{
Debug.LogError($"Can only update with the same resolution of {Resolution}.");
return false;
}
return true;
}
private bool IsValidLutUpdate<T>(T[] colorArray, int elementByteSize)
{
var arraySize = GetArraySize(colorArray);
if (!IsValidUpdateResolution(arraySize, elementByteSize))
{
return false;
}
if (arraySize * elementByteSize != _colorBytes.Length)
{
Debug.LogError("New color byte array doesn't match LUT size.");
return false;
}
return true;
}
private static ColorChannels GetChannelsForTextureFormat(TextureFormat format)
{
switch (format)
{
case TextureFormat.RGB24:
return ColorChannels.Rgb;
case TextureFormat.RGBA32:
return ColorChannels.Rgba;
default:
throw new System.ArgumentException(
$"Texture format {format} not supported for Color LUTs. Supported formats are RGB24 and RGBA32.");
}
}
private static int GetTextureSizeFromByteArray(byte[] initialColorLut, ColorChannels channels)
{
var arraySize = GetArraySize(initialColorLut);
var channelCount = ChannelsToCount(channels);
if (arraySize % channelCount != 0)
{
throw new System.ArgumentException(
$"Invalid byte array given, {channelCount} bytes required for each color for {channels} color channels.");
}
return initialColorLut.Length / channelCount;
}
private static int GetTextureSize(Texture2D texture)
{
if (texture == null)
{
throw new System.ArgumentNullException("Lut texture is undefined.");
}
return texture.width * texture.height;
}
private static int GetArraySize<T>(T[] array)
{
if (array == null)
{
throw new System.ArgumentNullException($"Lut {typeof(T).Name} array is undefined.");
}
return array.Length;
}
private static int ChannelsToCount(ColorChannels channels)
{
return channels == ColorChannels.Rgb ? 3 : 4;
}
private static bool IsResolutionAccepted(uint resolution, int size, out string errorMessage)
{
if (!IsPowerOfTwo(resolution))
{
errorMessage = "Color LUT texture resolution should be a power of 2.";
return false;
}
if (resolution * resolution * resolution != size)
{
errorMessage = "Unexpected LUT resolution, LUT size should be resolution in a power of 3.";
return false;
}
errorMessage = string.Empty;
return true;
}
private static bool IsPowerOfTwo(uint x)
{
return (x != 0) && ((x & (x - 1)) == 0);
}
private void Create(OVRPlugin.PassthroughColorLutData lutData)
{
_lutData = lutData;
if (OVRManager.IsInsightPassthroughInitialized())
{
InternalCreate();
}
else
{
_createState = CreateState.Pending;
}
if (IsValid)
{
OVRManager.OnPassthroughInitializedStateChange += RefreshIfInitialized;
}
}
private void RefreshIfInitialized(bool isInitialized)
{
if (isInitialized)
{
Recreate();
}
}
private void Recreate()
{
Destroy();
InternalCreate();
}
private void InternalCreate()
{
var result = OVRPlugin.CreatePassthroughColorLut((OVRPlugin.PassthroughColorLutChannels)Channels,
Resolution, _lutData, out _colorLutHandle);
_createState = result ? CreateState.Created : CreateState.Invalid;
if (!IsValid)
{
Debug.LogError("Failed to create Passthrough Color LUT.");
}
}
private static uint GetResolutionFromSize(int size)
{
return (uint)Mathf.Round(Mathf.Pow(size, 1f / 3f));
}
private OVRPlugin.PassthroughColorLutData CreateLutData(out byte[] colorBytes)
{
OVRPlugin.PassthroughColorLutData lutData = default;
lutData.BufferSize = (uint)(Resolution * Resolution * Resolution * _channelCount);
colorBytes = new byte[lutData.BufferSize];
_allocHandle = GCHandle.Alloc(colorBytes, GCHandleType.Pinned);
lutData.Buffer = _allocHandle.AddrOfPinnedObject();
return lutData;
}
private OVRPlugin.PassthroughColorLutData CreateLutDataFromTexture(Texture2D lut, bool flipY)
{
var lutData = CreateLutData(out _colorBytes);
ColorLutTextureConverter.TextureToColorByteMap(lut, _channelCount, _colorBytes, flipY);
return lutData;
}
private OVRPlugin.PassthroughColorLutData CreateLutDataFromArray(Color[] colors)
{
var lutData = CreateLutData(out _colorBytes);
WriteColorsAsBytes(colors, _colorBytes);
return lutData;
}
private OVRPlugin.PassthroughColorLutData CreateLutDataFromArray(Color32[] colors)
{
var lutData = CreateLutData(out _colorBytes);
WriteColorsAsBytes(colors, _colorBytes);
return lutData;
}
private OVRPlugin.PassthroughColorLutData CreateLutDataFromArray(byte[] colors)
{
var lutData = CreateLutData(out _colorBytes);
colors.CopyTo(_colorBytes, 0);
return lutData;
}
private void WriteColorsAsBytes(Color[] colors, byte[] target)
{
using var source = new NativeArray<Color>(colors, Allocator.TempJob);
using var targetNative = new NativeArray<byte>(target, Allocator.TempJob);
var job = new WriteColorsAsBytesJob
{
source = source,
target = targetNative,
channelCount = _channelCount
}.Schedule(source.Length, RecomendedBatchSize);
job.Complete();
targetNative.CopyTo(target);
}
private void WriteColorsAsBytes(Color32[] colors, byte[] target)
{
for (int i = 0; i < colors.Length; i++)
{
for (int c = 0; c < _channelCount; c++)
{
target[i * _channelCount + c] = colors[i][c];
}
}
}
~OVRPassthroughColorLut()
{
Dispose();
}
private void Destroy()
{
if (_createState == CreateState.Created)
{
lock (_locker)
{
OVRPlugin.DestroyPassthroughColorLut(_colorLutHandle);
}
}
_createState = CreateState.Invalid;
}
public enum ColorChannels
{
Rgb = OVRPlugin.PassthroughColorLutChannels.Rgb,
Rgba = OVRPlugin.PassthroughColorLutChannels.Rgba
}
private struct WriteColorsAsBytesJob : IJobParallelFor
{
[NativeDisableParallelForRestriction]
[WriteOnly]
public NativeArray<byte> target;
[NativeDisableParallelForRestriction]
[ReadOnly]
public NativeArray<Color> source;
public int channelCount;
public void Execute(int index)
{
for (int c = 0; c < channelCount; c++)
{
target[index * channelCount + c] = (byte)Mathf.Min(source[index][c] * 255.0f, 255.0f);
}
}
}
private static class ColorLutTextureConverter
{
/// <summary>
/// Read colors from LUT texture and write them in the pre-allocated target byte array
/// </summary>
/// <param name="lut">Color LUT texture</param>
/// <param name="channelCount">{RGB = 3, RGBA = 4}</param>
/// <param name="target">Pre-allocated byte array that should fit all colors of the texture</param>
/// <param name="flipY">Flag to inform whether the LUT texture should be flipped vertically</param>
public static void TextureToColorByteMap(Texture2D lut, int channelCount, byte[] target, bool flipY)
{
MapColorValues(GetTextureSettings(lut, channelCount, flipY), lut.GetPixelData<byte>(0), target);
}
private static void MapColorValues(TextureSettings settings, NativeArray<byte> source, byte[] target)
{
using var targetNative = new NativeArray<byte>(target, Allocator.TempJob);
var job = new MapColorValuesJob
{
settings = settings,
source = source,
target = targetNative
}.Schedule(settings.Resolution * settings.Resolution, settings.Resolution);
job.Complete();
targetNative.CopyTo(target);
}
private static TextureSettings GetTextureSettings(Texture2D lut, int channelCount, bool flipY)
{
int resolution, slicesPerRow;
if (TryGetTextureLayout(lut.width, lut.height, out resolution, out slicesPerRow, out var message))
{
return new TextureSettings(lut.width, lut.height, resolution, slicesPerRow, channelCount, flipY);
}
else
{
throw new System.Exception(message);
}
}
// Supports 2 formats:
// - Square, where the z (blue) planes are arranged in a square (like this https://cdn.streamshark.io/obs-guide/img/neutral-lut.png)
// For that, assuming that x is the edge size of the LUT, width and height must be x * sqrt(x) (-> doesn't work for all edge sizes)
// - Horizontal, where the z (blue) planes are arranged horizontally (like this http://www.thomashourdel.com/lutify/img/tut03.jpg)
internal static bool TryGetTextureLayout(int width, int height, out int resolution, out int slicesPerRow,
out string errorMessage)
{
resolution = -1;
slicesPerRow = -1;
if (width == height)
{
float edgeLengthF = Mathf.Pow(width, 2.0f / 3.0f);
if (Mathf.Abs(edgeLengthF - Mathf.Round(edgeLengthF)) > 0.001)
{
errorMessage = "Texture layout is not compatible for color LUTs: " +
"the dimensions don't result in a power-of-two resolution for the LUT. " +
"Acceptable image sizes are e.g. 64 (for a LUT resolution of 16) or 512 (for a LUT resolution of 64).";
return false;
}
resolution = (int)Mathf.Round(edgeLengthF);
slicesPerRow = (int)Mathf.Sqrt(resolution);
Debug.Assert(width == resolution * slicesPerRow);
}
else
{
if (width != height * height)
{
errorMessage = "Texture layout is not compatible for color LUTs: for horizontal layouts, " +
"the Width is expected to be equal to Height * Height.";
return false;
}
resolution = height;
slicesPerRow = resolution;
}
errorMessage = string.Empty;
return true;
}
private struct MapColorValuesJob : IJobParallelFor
{
public TextureSettings settings;
[NativeDisableParallelForRestriction]
[WriteOnly]
public NativeArray<byte> target;
[NativeDisableParallelForRestriction]
[ReadOnly]
public NativeArray<byte> source;
public void Execute(int index)
{
var bi = index / settings.Resolution;
var gi = index % settings.Resolution;
int bi_row = bi % settings.SlicesPerRow;
int bi_col = (int)Mathf.Floor(bi / settings.SlicesPerRow);
int sY = gi + bi_col * settings.Resolution;
int y = settings.FlipY ? settings.Height - sY - 1 : sY;
int sourceIndex = (bi_row * settings.Resolution + y * settings.Width) * settings.ChannelCount;
int targetIndex = (bi * settings.Resolution * settings.Resolution +
gi * settings.Resolution) * settings.ChannelCount;
for (int i = 0; i < settings.Resolution * settings.ChannelCount; i++)
{
target[targetIndex + i] = source[sourceIndex + i];
}
}
}
private struct TextureSettings
{
public int Width { get; }
public int Height { get; }
public int Resolution { get; }
public int SlicesPerRow { get; }
public int ChannelCount { get; }
public bool FlipY { get; }
public TextureSettings(int width, int height, int resolution, int slicesPerRow, int channelCount,
bool flipY)
{
Width = width;
Height = height;
Resolution = resolution;
SlicesPerRow = slicesPerRow;
ChannelCount = channelCount;
FlipY = flipY;
}
}
}
private enum CreateState
{
Invalid, Pending, Created
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 94060822ffa5e484aa55265db85f6830
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: