Adaug proiectul Unity

This commit is contained in:
2026-11-01 23:39:55 +02:00
parent cb14d9d37d
commit 863eac8f2c
23463 changed files with 5412601 additions and 0 deletions
@@ -0,0 +1,37 @@
#if !UNITY_WEBGL_RENDERER_ONLY
using System;
using Unity.Mathematics;
namespace UnityEngine.Rendering
{
[Serializable]
internal partial struct AABB
{
public float3 center;
public float3 extents; // half the size, must be 0 or greater
public float3 min { get { return center - extents; } }
public float3 max { get { return center + extents; } }
/// <summary>Returns a string representation of the AABB.</summary>
public override string ToString()
{
return $"AABB(Center:{center}, Extents:{extents}";
}
}
internal static class AABBExtensions
{
public static AABB ToAABB(this Bounds bounds)
{
return new AABB { center = bounds.center, extents = bounds.extents };
}
public static Bounds ToBounds(this AABB aabb)
{
return new Bounds { center = aabb.center, extents = aabb.extents };
}
}
}
#endif // !UNITY_WEBGL_RENDERER_ONLY
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bd65ba16b646b304eb2663c40bc69c8c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,134 @@
#if !UNITY_WEBGL_RENDERER_ONLY
using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.Assertions;
namespace UnityEngine.Rendering
{
internal unsafe struct EmbeddedArray32<T> : IDisposable where T : unmanaged
{
FixedList32Bytes<T> m_FixedArray;
UnsafeList<T> m_List;
int m_Length;
bool m_Created;
bool m_IsEmbedded;
public int Length => m_Length;
public EmbeddedArray32(NativeArray<T> array, Allocator allocator)
{
m_FixedArray = default;
m_List = default;
m_Length = array.Length;
m_Created = true;
if (array.Length <= m_FixedArray.Capacity)
{
m_FixedArray.AddRangeNoResize(array.GetUnsafeReadOnlyPtr(), array.Length);
m_IsEmbedded = true;
}
else
{
m_List = new UnsafeList<T>(array.Length, allocator, NativeArrayOptions.UninitializedMemory);
m_List.AddRangeNoResize(array.GetUnsafeReadOnlyPtr(), array.Length);
m_IsEmbedded = false;
}
}
public T this[int index]
{
get
{
Assert.IsTrue(m_Created && index < Length);
if (m_IsEmbedded)
return m_FixedArray[index];
else
return m_List[index];
}
set
{
Assert.IsTrue(m_Created && index < Length);
if (m_IsEmbedded)
m_FixedArray[index] = value;
else
m_List[index] = value;
}
}
public unsafe void Dispose()
{
if (!m_Created)
return;
m_List.Dispose();
m_Created = false;
}
}
internal unsafe struct EmbeddedArray64<T> : IDisposable where T : unmanaged
{
FixedList64Bytes<T> m_FixedArray;
UnsafeList<T> m_List;
int m_Length;
bool m_Created;
bool m_IsEmbedded;
public int Length => m_Length;
public EmbeddedArray64(NativeArray<T> array, Allocator allocator)
{
m_FixedArray = default;
m_List = default;
m_Length = array.Length;
m_Created = true;
if (array.Length <= m_FixedArray.Capacity)
{
m_FixedArray.AddRangeNoResize(array.GetUnsafeReadOnlyPtr(), array.Length);
m_IsEmbedded = true;
}
else
{
m_List = new UnsafeList<T>(array.Length, allocator, NativeArrayOptions.UninitializedMemory);
m_List.AddRangeNoResize(array.GetUnsafeReadOnlyPtr(), array.Length);
m_IsEmbedded = false;
}
}
public T this[int index]
{
get
{
Assert.IsTrue(m_Created && index < Length);
if (m_IsEmbedded)
return m_FixedArray[index];
else
return m_List[index];
}
set
{
Assert.IsTrue(m_Created && index < Length);
if (m_IsEmbedded)
m_FixedArray[index] = value;
else
m_List[index] = value;
}
}
public unsafe void Dispose()
{
if (!m_Created)
return;
m_List.Dispose();
m_Created = false;
}
}
}
#endif // !UNITY_WEBGL_RENDERER_ONLY
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a722f3be8f8fbb146a033ed4785f895e
@@ -0,0 +1,483 @@
#if !UNITY_WEBGL_RENDERER_ONLY
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
namespace UnityEngine.Rendering
{
// 6-component representation of a (infinite length) line in 3D space
internal struct Line
{
// for the line to be valid, dot(m, t) == 0
public float3 m;
public float3 t;
internal static Line LineOfPlaneIntersectingPlane(float4 a, float4 b)
{
// planes do not need to have a unit length normal
return new Line {
m = a.w*b.xyz - b.w*a.xyz,
t = math.cross(a.xyz, b.xyz),
};
}
internal static float4 PlaneContainingLineAndPoint(Line a, float3 b)
{
// the resulting plane will not have a unit length normal (and the normal will be approximately zero when no plane exists)
return new float4(a.m + math.cross(a.t, b), -math.dot(a.m, b));
}
internal static float4 PlaneContainingLineWithNormalPerpendicularToVector(Line a, float3 b)
{
// the resulting plane will not have a unit length normal (and the normal will be approximately zero when no plane exists)
return new float4(math.cross(a.t, b), -math.dot(a.m, b));
}
}
internal struct ReceiverPlanes
{
public NativeList<Plane> planes;
public int lightFacingPlaneCount;
private static bool IsSignBitSet(float x)
{
uint i = math.asuint(x);
return (i >> 31) != 0;
}
internal NativeArray<Plane> LightFacingFrustumPlaneSubArray()
{
return planes.AsArray().GetSubArray(0, lightFacingPlaneCount);
}
internal NativeArray<Plane> SilhouettePlaneSubArray()
{
return planes.AsArray().GetSubArray(lightFacingPlaneCount, planes.Length - lightFacingPlaneCount);
}
internal static ReceiverPlanes CreateEmptyForTesting(Allocator allocator)
{
return new ReceiverPlanes()
{
planes = new NativeList<Plane>(allocator),
lightFacingPlaneCount = 0,
};
}
internal JobHandle Dispose(JobHandle job)
{
return planes.Dispose(job);
}
internal static ReceiverPlanes Create(in BatchCullingContext cc, Allocator allocator)
{
var result = new ReceiverPlanes()
{
planes = new NativeList<Plane>(allocator),
lightFacingPlaneCount = 0,
};
if (cc.viewType == BatchCullingViewType.Light && cc.receiverPlaneCount != 0)
{
bool isLightOrthographic = false;
if (cc.cullingSplits.Length > 0)
{
Matrix4x4 m = cc.cullingSplits[0].cullingMatrix;
isLightOrthographic = m[15] == 1.0f && m[11] == 0.0f && m[7] == 0.0f && m[3] == 0.0f;
}
if (isLightOrthographic)
{
Vector3 lightDir = -cc.localToWorldMatrix.GetColumn(2);
// cache result for each plane, add planes facing towards the light
int planeSignBits = 0;
for (int i = 0; i < cc.receiverPlaneCount; ++i)
{
var plane = cc.cullingPlanes[cc.receiverPlaneOffset + i];
float facingTerm = Vector3.Dot(plane.normal, lightDir);
if (IsSignBitSet(facingTerm))
planeSignBits |= (1 << i);
else
result.planes.Add(plane);
}
result.lightFacingPlaneCount = result.planes.Length;
// assume ordering +/-x, +/-y, +/-z for frustum planes, test pairs for silhouette edges
if (cc.receiverPlaneCount == 6)
{
for (int i = 0; i < cc.receiverPlaneCount; ++i)
{
for (int j = i + 1; j < cc.receiverPlaneCount; ++j)
{
// skip pairs that are from the same frustum axis (i.e. both xs, both ys or both zs)
if ((i / 2) == (j / 2))
continue;
// silhouette edges occur when the planes have opposing signs
int signCheck = ((planeSignBits >> i) ^ (planeSignBits >> j)) & 1;
if (signCheck == 0)
continue;
// process in consistent order for consistent plane normal in the result
var (indexA, indexB) = (((planeSignBits >> i) & 1) == 0) ? (i, j) : (j, i);
var planeA = cc.cullingPlanes[cc.receiverPlaneOffset + indexA];
var planeB = cc.cullingPlanes[cc.receiverPlaneOffset + indexB];
// construct a plane that contains the light origin and this silhouette edge
var planeEqA = new float4(planeA.normal, planeA.distance);
var planeEqB = new float4(planeB.normal, planeB.distance);
var silhouetteEdge = Line.LineOfPlaneIntersectingPlane(planeEqA, planeEqB);
var silhouettePlaneEq = Line.PlaneContainingLineWithNormalPerpendicularToVector(silhouetteEdge, lightDir);
// try to normalize
silhouettePlaneEq = silhouettePlaneEq / math.length(silhouettePlaneEq.xyz);
if (!math.any(math.isnan(silhouettePlaneEq)))
result.planes.Add(new Plane(silhouettePlaneEq.xyz, silhouettePlaneEq.w));
}
}
}
}
else
{
var lightPos = cc.localToWorldMatrix.GetPosition();
// cache result for each plane, add planes facing towards the light
int planeSignBits = 0;
for (int i = 0; i < cc.receiverPlaneCount; ++i)
{
var plane = cc.cullingPlanes[cc.receiverPlaneOffset + i];
float distance = plane.GetDistanceToPoint(lightPos);
if (IsSignBitSet(distance))
planeSignBits |= (1 << i);
else
result.planes.Add(plane);
}
result.lightFacingPlaneCount = result.planes.Length;
// assume ordering +/-x, +/-y, +/-z for frustum planes, test pairs for silhouette edges
if (cc.receiverPlaneCount == 6)
{
for (int i = 0; i < cc.receiverPlaneCount; ++i)
{
for (int j = i + 1; j < cc.receiverPlaneCount; ++j)
{
// skip pairs that are from the same frustum axis (i.e. both xs, both ys or both zs)
if ((i / 2) == (j / 2))
continue;
// silhouette edges occur when the planes have opposing signs
int signCheck = ((planeSignBits >> i) ^ (planeSignBits >> j)) & 1;
if (signCheck == 0)
continue;
// process in consistent order for consistent plane normal in the result
var (indexA, indexB) = (((planeSignBits >> i) & 1) == 0) ? (i, j) : (j, i);
var planeA = cc.cullingPlanes[cc.receiverPlaneOffset + indexA];
var planeB = cc.cullingPlanes[cc.receiverPlaneOffset + indexB];
// construct a plane that contains the light origin and this silhouette edge
var planeEqA = new float4(planeA.normal, planeA.distance);
var planeEqB = new float4(planeB.normal, planeB.distance);
var silhouetteEdge = Line.LineOfPlaneIntersectingPlane(planeEqA, planeEqB);
var silhouettePlaneEq = Line.PlaneContainingLineAndPoint(silhouetteEdge, lightPos);
// try to normalize
silhouettePlaneEq = silhouettePlaneEq / math.length(silhouettePlaneEq.xyz);
if (!math.any(math.isnan(silhouettePlaneEq)))
result.planes.Add(new Plane(silhouettePlaneEq.xyz, silhouettePlaneEq.w));
}
}
}
}
}
return result;
}
}
internal struct FrustumPlaneCuller
{
internal struct PlanePacket4
{
public float4 nx;
public float4 ny;
public float4 nz;
public float4 d;
// Store absolute values of plane normals to avoid recalculating per instance
public float4 nxAbs;
public float4 nyAbs;
public float4 nzAbs;
public PlanePacket4(NativeArray<Plane> planes, int offset, int limit)
{
Plane p0 = planes[Mathf.Min(offset + 0, limit)];
Plane p1 = planes[Mathf.Min(offset + 1, limit)];
Plane p2 = planes[Mathf.Min(offset + 2, limit)];
Plane p3 = planes[Mathf.Min(offset + 3, limit)];
nx = new float4(p0.normal.x, p1.normal.x, p2.normal.x, p3.normal.x);
ny = new float4(p0.normal.y, p1.normal.y, p2.normal.y, p3.normal.y);
nz = new float4(p0.normal.z, p1.normal.z, p2.normal.z, p3.normal.z);
d = new float4(p0.distance, p1.distance, p2.distance, p3.distance);
nxAbs = math.abs(nx);
nyAbs = math.abs(ny);
nzAbs = math.abs(nz);
}
}
internal struct SplitInfo
{
public int packetCount;
}
public NativeList<PlanePacket4> planePackets;
public NativeList<SplitInfo> splitInfos;
internal JobHandle Dispose(JobHandle job)
{
return JobHandle.CombineDependencies(planePackets.Dispose(job), splitInfos.Dispose(job));
}
internal static FrustumPlaneCuller Create(in BatchCullingContext cc, NativeArray<Plane> receiverPlanes, in ReceiverSphereCuller receiverSphereCuller, Allocator allocator)
{
int splitCount = cc.cullingSplits.Length;
int totalPacketCount = 0;
for (int splitIndex = 0; splitIndex < splitCount; ++splitIndex)
{
int planeCount = receiverPlanes.Length + cc.cullingSplits[splitIndex].cullingPlaneCount;
totalPacketCount += (planeCount + 3)/4;
}
FrustumPlaneCuller result = new FrustumPlaneCuller()
{
planePackets = new NativeList<PlanePacket4>(totalPacketCount, allocator),
splitInfos = new NativeList<SplitInfo>(splitCount, allocator),
};
result.planePackets.ResizeUninitialized(totalPacketCount);
result.splitInfos.ResizeUninitialized(splitCount);
var tmpPlanes = new NativeList<Plane>(Allocator.Temp);
int packetBase = 0;
for (int splitIndex = 0; splitIndex < splitCount; ++splitIndex)
{
CullingSplit split = cc.cullingSplits[splitIndex];
tmpPlanes.Clear();
// use all culling planes
for (int i = 0; i < split.cullingPlaneCount; ++i)
tmpPlanes.Add(cc.cullingPlanes[split.cullingPlaneOffset + i]);
// conditionally use receiver planes
if (receiverSphereCuller.UseReceiverPlanes())
tmpPlanes.AddRange(receiverPlanes);
int packetCount = (tmpPlanes.Length + 3)/4;
result.splitInfos[splitIndex] = new SplitInfo()
{
packetCount = packetCount,
};
for (int i = 0; i < packetCount; ++i)
result.planePackets[packetBase + i] = new PlanePacket4(tmpPlanes.AsArray(), 4*i, tmpPlanes.Length - 1);
packetBase += packetCount;
}
tmpPlanes.Dispose();
return result;
}
internal static uint ComputeSplitVisibilityMask(NativeArray<PlanePacket4> planePackets, NativeArray<SplitInfo> splitInfos, in AABB bounds)
{
float4 cx = bounds.center.xxxx;
float4 cy = bounds.center.yyyy;
float4 cz = bounds.center.zzzz;
float4 ex = bounds.extents.xxxx;
float4 ey = bounds.extents.yyyy;
float4 ez = bounds.extents.zzzz;
uint splitVisibilityMask = 0;
int packetBase = 0;
int splitCount = splitInfos.Length;
for (int splitIndex = 0; splitIndex < splitCount; ++splitIndex)
{
SplitInfo splitInfo = splitInfos[splitIndex];
bool4 isCulled = new bool4(false);
for (int i = 0; i < splitInfo.packetCount; ++i)
{
PlanePacket4 p = planePackets[packetBase + i];
float4 distances = p.nx*cx + p.ny*cy + p.nz*cz + p.d;
float4 radii = p.nxAbs*ex + p.nyAbs*ey + p.nzAbs*ez;
isCulled = isCulled | (distances + radii < float4.zero);
}
if (!math.any(isCulled))
splitVisibilityMask |= 1U << splitIndex;
packetBase += splitInfo.packetCount;
}
return splitVisibilityMask;
}
}
internal struct ReceiverSphereCuller
{
internal struct SplitInfo
{
public float4 receiverSphereLightSpace;
public float cascadeBlendCullingFactor;
}
public NativeList<SplitInfo> splitInfos;
public float3x3 worldToLightSpaceRotation;
internal static ReceiverSphereCuller CreateEmptyForTesting(Allocator allocator)
{
return new ReceiverSphereCuller()
{
splitInfos = new NativeList<SplitInfo>(0, allocator),
worldToLightSpaceRotation = float3x3.identity,
};
}
internal JobHandle Dispose(JobHandle job)
{
return splitInfos.Dispose(job);
}
internal bool UseReceiverPlanes()
{
// only use receiver planes if there are no receiver spheres
// (if spheres are present, then this is directional light cascades and Unity has already added receiver planes to the culling planes)
return splitInfos.Length == 0;
}
internal static ReceiverSphereCuller Create(in BatchCullingContext cc, Allocator allocator)
{
int splitCount = cc.cullingSplits.Length;
// only set up sphere culling when there are multiple splits and all splits have valid spheres
bool allSpheresValid = (splitCount > 1);
for (int splitIndex = 0; splitIndex < splitCount; ++splitIndex)
{
// ensure that NaN is not considered as valid
if (!(cc.cullingSplits[splitIndex].sphereRadius > 0.0f))
allSpheresValid = false;
}
if (!allSpheresValid)
splitCount = 0;
float3x3 lightToWorldSpaceRotation = (float3x3)(float4x4)cc.localToWorldMatrix;
ReceiverSphereCuller result = new ReceiverSphereCuller()
{
splitInfos = new NativeList<SplitInfo>(splitCount, allocator),
worldToLightSpaceRotation = math.transpose(lightToWorldSpaceRotation),
};
result.splitInfos.ResizeUninitialized(splitCount);
for (int splitIndex = 0; splitIndex < splitCount; ++splitIndex)
{
var cullingSplit = cc.cullingSplits[splitIndex];
float4 receiverSphereLightSpace = new float4(
math.mul(result.worldToLightSpaceRotation, cullingSplit.sphereCenter),
cullingSplit.sphereRadius);
result.splitInfos[splitIndex] = new SplitInfo()
{
receiverSphereLightSpace = receiverSphereLightSpace,
cascadeBlendCullingFactor = cullingSplit.cascadeBlendCullingFactor,
};
}
return result;
}
internal static float DistanceUntilCylinderFullyCrossesPlane(
float3 cylinderCenter,
float3 cylinderDirection,
float cylinderRadius,
Plane plane)
{
float cosEpsilon = 0.001f; // clamp the cosine of glancing angles
// compute the distance until the center intersects the plane
float cosTheta = math.max(math.abs(math.dot(plane.normal, cylinderDirection)), cosEpsilon);
float heightAbovePlane = math.dot(plane.normal, cylinderCenter) + plane.distance;
float centerDistanceToPlane = heightAbovePlane/cosTheta;
// compute the additional distance until the edge of the cylinder intersects the plane
float sinTheta = math.sqrt(math.max(1.0f - cosTheta*cosTheta, 0.0f));
float edgeDistanceToPlane = cylinderRadius*sinTheta/cosTheta;
return centerDistanceToPlane + edgeDistanceToPlane;
}
internal static uint ComputeSplitVisibilityMask(
NativeArray<Plane> lightFacingFrustumPlanes,
NativeArray<SplitInfo> splitInfos,
float3x3 worldToLightSpaceRotation,
in AABB bounds)
{
float3 casterCenterWorldSpace = bounds.center;
float3 casterCenterLightSpace = math.mul(worldToLightSpaceRotation, bounds.center);
float casterRadius = math.length(bounds.extents);
// push the (light-facing) frustum planes back by the caster radius, then intersect with a line through the caster capsule center,
// to compute the length of the shadow that will cover all possible receivers within the whole frustum (not just this split)
float3 shadowDirection = math.transpose(worldToLightSpaceRotation).c2;
float shadowLength = math.INFINITY;
for (int i = 0; i < lightFacingFrustumPlanes.Length; ++i)
{
shadowLength = math.min(shadowLength, DistanceUntilCylinderFullyCrossesPlane(
casterCenterWorldSpace,
shadowDirection,
casterRadius,
lightFacingFrustumPlanes[i]));
}
shadowLength = math.max(shadowLength, 0.0f);
uint splitVisibilityMask = 0;
int splitCount = splitInfos.Length;
for (int splitIndex = 0; splitIndex < splitCount; ++splitIndex)
{
SplitInfo splitInfo = splitInfos[splitIndex];
float3 receiverCenterLightSpace = splitInfo.receiverSphereLightSpace.xyz;
float receiverRadius = splitInfo.receiverSphereLightSpace.w;
float3 receiverToCasterLightSpace = casterCenterLightSpace - receiverCenterLightSpace;
// compute the light space z coordinate where the caster sphere and receiver sphere just intersect
float sphereIntersectionMaxDistance = casterRadius + receiverRadius;
float zSqAtSphereIntersection = math.lengthsq(sphereIntersectionMaxDistance) - math.lengthsq(receiverToCasterLightSpace.xy);
// if this is negative, the spheres do not overlap as circles in the XY plane, so cull the caster
if (zSqAtSphereIntersection < 0.0f)
continue;
// if the caster is outside of the receiver sphere in the light direction, it cannot cast a shadow on it, so cull it
if (receiverToCasterLightSpace.z > 0.0f && math.lengthsq(receiverToCasterLightSpace.z) > zSqAtSphereIntersection)
continue;
// render the caster in this split
splitVisibilityMask |= 1U << splitIndex;
// culling assumes that shaders will always sample from the cascade with the lowest index,
// so if the caster capsule is fully contained within the "core" sphere where only this split index is sampled,
// then cull this caster from all the larger index splits (break from this loop)
// (it is sufficient to test that only the capsule start and end spheres are within the "core" receiver sphere)
float coreRadius = receiverRadius * splitInfo.cascadeBlendCullingFactor;
float3 receiverToShadowEndLightSpace = receiverToCasterLightSpace + new float3(0.0f, 0.0f, shadowLength);
float capsuleMaxDistance = coreRadius - casterRadius;
float capsuleDistanceSq = math.max(math.lengthsq(receiverToCasterLightSpace), math.lengthsq(receiverToShadowEndLightSpace));
if (capsuleMaxDistance > 0.0f && capsuleDistanceSq < math.lengthsq(capsuleMaxDistance))
break;
}
return splitVisibilityMask;
}
}
}
#endif // !UNITY_WEBGL_RENDERER_ONLY
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: deb0d018a7f3e9a41b689860783e6d56
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,121 @@
#if !UNITY_WEBGL_RENDERER_ONLY
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine.Assertions;
namespace UnityEngine.Rendering
{
internal static class GPUResidentUtils
{
public static void RunParallelByRef<T>(this ref T jobData, int arrayLength, int innerloopBatchCount, JobHandle dependsOn = default) where T : unmanaged, IJobParallelFor
{
if (arrayLength > innerloopBatchCount)
{
jobData.ScheduleByRef(arrayLength, innerloopBatchCount, dependsOn).Complete();
}
else
{
dependsOn.Complete();
jobData.RunByRef(arrayLength);
}
}
public static void RunParallel<T>(this T jobData, int arrayLength, int innerloopBatchCount, JobHandle dependsOn = default) where T : unmanaged, IJobParallelFor
{
RunParallelByRef(ref jobData, arrayLength, innerloopBatchCount, dependsOn);
}
public static void RunBatchParallelByRef<T>(this ref T jobData, int arrayLength, int innerloopBatchCount, JobHandle dependsOn = default) where T : unmanaged, IJobParallelForBatch
{
if (arrayLength > innerloopBatchCount)
{
jobData.ScheduleBatchByRef(arrayLength, innerloopBatchCount, dependsOn).Complete();
}
else
{
dependsOn.Complete();
jobData.RunByRef(arrayLength, innerloopBatchCount);
}
}
public static void RunBatchParallel<T>(this T jobData, int arrayLength, int innerloopBatchCount, JobHandle dependsOn = default) where T : unmanaged, IJobParallelForBatch
{
RunBatchParallelByRef(ref jobData, arrayLength, innerloopBatchCount, dependsOn);
}
public static unsafe ref T ElementAtRW<T>(this NativeArray<T> array, int index) where T : unmanaged
{
return ref UnsafeUtility.ArrayElementAsRef<T>(array.GetUnsafePtr(), index);
}
public static unsafe ref readonly T ElementAt<T>(this NativeArray<T> array, int index) where T : unmanaged
{
return ref UnsafeUtility.ArrayElementAsRef<T>(array.GetUnsafeReadOnlyPtr(), index);
}
public static unsafe NativeArray<T> AsNativeArray<T>(this UnsafeList<T> list) where T : unmanaged
{
var array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>(list.Ptr,
list.Length,
Allocator.None);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array, AtomicSafetyHandle.Create());
#endif
return array;
}
public static unsafe UnsafeList<T> AsUnsafeList<T>(this NativeArray<T> array) where T : unmanaged
{
return new UnsafeList<T>((T*)array.GetUnsafePtr(), array.Length);
}
public static unsafe UnsafeList<T> AsUnsafeListReadOnly<T>(this NativeArray<T> array) where T : unmanaged
{
return new UnsafeList<T>((T*)array.GetUnsafeReadOnlyPtr(), array.Length);
}
public static unsafe UnsafeList<UntypedUnsafeList> AsUntypedUnsafeList<T>(this UnsafeList<UnsafeList<T>> list) where T : unmanaged
{
Assert.IsTrue(UnsafeUtility.SizeOf<UnsafeList<T>>() == UnsafeUtility.SizeOf<UntypedUnsafeList>());
return *(UnsafeList<UntypedUnsafeList>*)&list;
}
public static unsafe UnsafeBitArray AsUnsafeBitArray(this in NativeBitArray section) => *section.m_BitArray;
public static unsafe ref T GetRef<T>(this NativeReference<T> reference) where T : unmanaged => ref *reference.GetUnsafePtr();
public static unsafe ref readonly T GetRefRO<T>(this NativeReference<T> reference) where T : unmanaged => ref *reference.GetUnsafeReadOnlyPtr();
public static bool HasAnyBit(this MeshRendererComponentMask mask, MeshRendererComponentMask bits) => (mask & bits) != 0;
public static bool HasAnyBit(this LODGroupComponentMask mask, LODGroupComponentMask bits) => (mask & bits) != 0;
}
internal static class LightmapUtils
{
// See doc for more infos on lightmap index special values -1 and -2
// https://docs.unity3d.com/Documentation/ScriptReference/Renderer-lightmapIndex.html
// Object doesn't use lightmaps and don't influence them.
public const short LightmapIndexNull = -1;
// Object only influences lightmaps, but does not use them itself.
public const short LightmapIndexInfluenceOnly = -2;
public static readonly float4 kDefaultLightmapST = new float4(1.0f, 1.0f, 0.0f, 0.0f);
public static bool IsNull(int lightmapIndex) => ((short)lightmapIndex) == LightmapIndexNull;
public static bool IsInfluenceOnly(int lightmapIndex) => ((short)lightmapIndex) == LightmapIndexInfluenceOnly;
public static bool UsesLightmaps(int lightmapIndex) => ((short)lightmapIndex) >= 0;
// If the object is light mapped, or has the special influence-only value, it affects lightmaps
public static bool AffectsLightmaps(int lightmapIndex) => ((short)lightmapIndex) >= 0 || ((short)lightmapIndex) == LightmapIndexInfluenceOnly;
}
}
#endif // !UNITY_WEBGL_RENDERER_ONLY
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c996da3d8d7bf4046a6be511a477e3ee
@@ -0,0 +1,83 @@
#ifndef GEO_UTILITIES_H
#define GEO_UTILITIES_H
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Packing.hlsl"
struct CylinderBound
{
float3 center;
float3 axis;
float halfHeight;
float capRadius;
};
struct SphereBound
{
float3 center;
float radius;
};
float CompleteSinCos(float sinOrCos)
{
return sqrt(max(1.0f - sinOrCos*sinOrCos, 0.0f));
}
float2 ProjectedHalfLengths(CylinderBound cylinder, float3 planeNormal)
{
float absCosTheta = abs(dot(planeNormal, cylinder.axis));
float sinTheta = CompleteSinCos(absCosTheta);
float h = cylinder.halfHeight;
float r = cylinder.capRadius;
float halfLengthAlongNormal = absCosTheta * h + sinTheta * r;
float halfLengthInPlane = max(sinTheta * h + absCosTheta * r, r); // ellipse, so use max of two axis lengths
return float2(halfLengthInPlane, halfLengthAlongNormal);
}
struct BoundingObjectData
{
float3 frontCenterPosRWS;
float2 centerPosNDC;
float2 radialPosNDC;
};
BoundingObjectData CalculateBoundingObjectData(SphereBound boundingSphere,
float4x4 viewProjMatrix,
float4 viewOriginWorldSpace,
float4 radialDirWorldSpace,
float4 facingDirWorldSpace)
{
const float3 centerPosRWS = boundingSphere.center - viewOriginWorldSpace.xyz;
const float3 radialVec = abs(boundingSphere.radius) * radialDirWorldSpace.xyz;
const float3 facingVec = abs(boundingSphere.radius) * facingDirWorldSpace.xyz;
BoundingObjectData data;
data.centerPosNDC = ComputeNormalizedDeviceCoordinates(centerPosRWS, viewProjMatrix);
data.radialPosNDC = ComputeNormalizedDeviceCoordinates(centerPosRWS + radialVec, viewProjMatrix);
data.frontCenterPosRWS = centerPosRWS + facingVec;
return data;
}
BoundingObjectData CalculateBoundingObjectData(CylinderBound cylinderBound,
float4x4 viewProjMatrix,
float4 viewOriginWorldSpace,
float4 radialDirWorldSpace,
float4 facingDirWorldSpace)
{
const float3 centerPosRWS = cylinderBound.center - viewOriginWorldSpace.xyz;
const float2 halfLengths = ProjectedHalfLengths(cylinderBound, facingDirWorldSpace.xyz);
const float3 radialVec = halfLengths.x * radialDirWorldSpace.xyz;
BoundingObjectData data;
data.centerPosNDC = ComputeNormalizedDeviceCoordinates(centerPosRWS, viewProjMatrix);
data.radialPosNDC = ComputeNormalizedDeviceCoordinates(centerPosRWS + radialVec, viewProjMatrix);
data.frontCenterPosRWS = centerPosRWS + halfLengths.y * facingDirWorldSpace.xyz;
return data;
}
#endif
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 8ae16c27ed16096458605a6fc56994c8
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,79 @@
#if !UNITY_WEBGL_RENDERER_ONLY
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Jobs.LowLevel.Unsafe;
using Unity.Mathematics;
using UnityEngine.Assertions;
using UnityEngine.Profiling;
namespace UnityEngine.Rendering
{
internal struct JaggedJobRange
{
public int sectionIndex;
public int localStart;
public int absoluteStart;
public int length;
public int localEnd => localStart + length;
public int absoluteEnd => absoluteStart + length;
public static NativeList<JaggedJobRange> FromSpanWithRelaxedBatchSize<T>(JaggedSpan<T> jaggedSpan, int batchSizeHint, Allocator allocator) where T : unmanaged
{
return ComputeRanges(jaggedSpan, batchSizeHint, canExceedBatchSizeHint: true, allocator);
}
public static NativeList<JaggedJobRange> FromSpanWithMaxBatchSize<T>(JaggedSpan<T> jaggedSpan, int maxBatchSize, Allocator allocator) where T : unmanaged
{
return ComputeRanges(jaggedSpan, maxBatchSize, canExceedBatchSizeHint: false, allocator);
}
private static NativeList<JaggedJobRange> ComputeRanges<T>(JaggedSpan<T> jaggedSpan, int batchSizeHint, bool canExceedBatchSizeHint, Allocator allocator) where T : unmanaged
{
Assert.IsTrue(batchSizeHint > 0);
Assert.IsTrue(allocator == Allocator.TempJob || allocator == Allocator.Persistent,
"Allocator must be either TempJob or Persistent");
var jobRanges = new NativeList<JaggedJobRange>(allocator);
// Allocated before the check because callers use the result without testing IsCreated.
if (jaggedSpan.isEmpty)
return jobRanges;
JaggedJobRangeBurst.ComputeRanges(JobsUtility.JobWorkerCount, batchSizeHint, jaggedSpan.totalLength, canExceedBatchSizeHint,
jaggedSpan.untypedSections, ref jobRanges);
return jobRanges;
}
}
internal static class JaggedJobRangeExtensions
{
public static JobHandle Schedule<T>(this T job, in NativeList<JaggedJobRange> jobRanges, JobHandle dependsOn = default) where T : unmanaged, IJobParallelFor
{
return jobRanges.IsEmpty ? dependsOn : job.ScheduleByRef(jobRanges.Length, 1, dependsOn);
}
public static JobHandle ScheduleByRef<T>(ref this T job, in NativeList<JaggedJobRange> jobRanges, JobHandle dependsOn = default) where T : unmanaged, IJobParallelFor
{
return jobRanges.IsEmpty ? dependsOn : job.ScheduleByRef(jobRanges.Length, 1, dependsOn);
}
public static void RunParallel<T>(this T job, in NativeList<JaggedJobRange> jobRanges, JobHandle dependsOn = default) where T : unmanaged, IJobParallelFor
{
if (jobRanges.Length == 1)
{
dependsOn.Complete();
job.RunByRef(1);
}
else
{
job.ScheduleByRef(jobRanges, dependsOn).Complete();
}
}
}
}
#endif // !UNITY_WEBGL_RENDERER_ONLY
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1e113f4d261f3b74b818176f34f8c2f1
@@ -0,0 +1,78 @@
#if !UNITY_WEBGL_RENDERER_ONLY
using Unity.Collections;
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
using UnityEngine.Assertions;
namespace UnityEngine.Rendering
{
[BurstCompile]
internal static class JaggedJobRangeBurst
{
private static int ComputeIdealJobCount(int totalLength, int batchSizeHint, int workerThreadCount)
{
Assert.IsTrue(totalLength > 0);
Assert.IsTrue(batchSizeHint > 0);
// If there are no worker threads then it should run on the main thread.
if (workerThreadCount == 0)
return 1;
int jobCountBasedOnSizeHint = CoreUtils.DivRoundUp(totalLength, batchSizeHint);
// Most jobs are waited for on the thread that schedules them. So to even it out (workerThreadCount + 1).
// Then * 2 because often jobs are not executed perfectly even so if some jobs are longer at least it distributes a bit more.
// This is no perfect math, just empirically tweaked on profiling data.
int jobCountBasedOnWorkerThreads = (workerThreadCount + 1) * 2;
return math.min(jobCountBasedOnSizeHint, jobCountBasedOnWorkerThreads);
}
[BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
public static void ComputeRanges(int workerThreadCount, int batchSizeHint, int totalLength, bool canExceedBatchSizeHint,
in NativeArray<UntypedUnsafeList> sections, ref NativeList<JaggedJobRange> jobRanges)
{
int idealJobCount = ComputeIdealJobCount(totalLength, batchSizeHint, workerThreadCount);
Assert.IsTrue(idealJobCount > 0);
int idealBatchSize = CoreUtils.DivRoundUp(totalLength, idealJobCount);
Assert.IsTrue(idealBatchSize > 0);
// Try to account for the jobs being possibly split when they would otherwise span over more than one chunk
int jobRangesInitialCapacity = idealJobCount * 2;
jobRanges.SetCapacity(jobRangesInitialCapacity);
int absoluteStart = 0;
for (int sectionIndex = 0; sectionIndex < sections.Length; sectionIndex++)
{
int localStart = 0;
int remainingLength = sections.ElementAt(sectionIndex).m_length;
while (remainingLength > 0)
{
// Check for idealBatchSize * 1.5 so that if there is small batch left a the end it is merged with the previous job.
int batchSizeUncapped = remainingLength >= (int)(idealBatchSize * 1.5) ? idealBatchSize : remainingLength;
int batchSize = batchSizeUncapped;
if (!canExceedBatchSizeHint && batchSize > batchSizeHint)
batchSize = batchSizeHint;
JaggedJobRange jobRange = default;
jobRange.sectionIndex = sectionIndex;
jobRange.absoluteStart = absoluteStart;
jobRange.localStart = localStart;
jobRange.length = batchSize;
jobRanges.Add(jobRange);
localStart += batchSize;
absoluteStart += batchSize;
remainingLength -= batchSize;
}
Assert.AreEqual(remainingLength, 0);
}
}
}
}
#endif // !UNITY_WEBGL_RENDERER_ONLY
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 35f8971d8d5e7b74ba94c42615c1e65e
@@ -0,0 +1,169 @@
#if !UNITY_WEBGL_RENDERER_ONLY
using System;
using System.Runtime.InteropServices;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
namespace UnityEngine.Rendering
{
[StructLayout(LayoutKind.Sequential)]
internal struct JaggedSpan<T> : IDisposable where T : unmanaged
{
UnsafeList<UnsafeList<T>> m_Sections;
int m_TotalLength;
public bool isCreated => m_Sections.IsCreated;
public int sectionCount => m_Sections.IsCreated ? m_Sections.Length : 0;
public int totalLength => m_TotalLength;
public bool isEmpty => totalLength == 0;
public NativeArray<UnsafeList<T>> sections => m_Sections.IsCreated ? m_Sections.AsNativeArray() : default;
public NativeArray<UntypedUnsafeList> untypedSections => m_Sections.IsCreated ? m_Sections.AsUntypedUnsafeList().AsNativeArray() : default;
public JaggedSpan(int initialCapacity, Allocator allocator)
{
m_Sections = new UnsafeList<UnsafeList<T>>(initialCapacity, allocator);
m_TotalLength = 0;
}
public void Dispose()
{
m_Sections.Dispose();
}
public JobHandle Dispose(JobHandle jobHandle)
{
return m_Sections.Dispose(jobHandle);
}
// NativeArray lifetime must match or exceed the lifetime of the JaggedSpan
public void Add(in NativeArray<T> section)
{
m_Sections.Add(SectionAsUnsafeList(section));
m_TotalLength += section.Length;
}
public bool HasSameLayout<U>(in JaggedSpan<U> other)
where U : unmanaged
{
if (sectionCount != other.sectionCount)
return false;
for (int i = 0; i < sectionCount; i++)
{
if (this[i].Length != other[i].Length)
return false;
}
return true;
}
public NativeArray<T> this[int index]
{
get => SectionAsArray(m_Sections[index]);
set
{
ref UnsafeList<T> section = ref m_Sections.ElementAt(index);
int deltaLength = value.Length - section.Length;
section = SectionAsUnsafeList(value);
m_TotalLength += deltaLength;
}
}
private static NativeArray<T> SectionAsArray(in UnsafeList<T> section) => section.AsNativeArray();
private static UnsafeList<T> SectionAsUnsafeList(in NativeArray<T> section) => section.AsUnsafeList();
}
internal struct JaggedBitSpan
{
UnsafeList<UnsafeBitArray> m_Sections;
int m_TotalLength;
public bool isCreated => m_Sections.IsCreated;
public int sectionCount => m_Sections.IsCreated ? m_Sections.Length : 0;
public int totalLength => m_TotalLength;
public bool isEmpty => totalLength == 0;
public NativeArray<UnsafeBitArray> sections => m_Sections.IsCreated ? m_Sections.AsNativeArray() : default;
public JaggedBitSpan(int initialCapacity, Allocator allocator)
{
m_Sections = new UnsafeList<UnsafeBitArray>(initialCapacity, allocator);
m_TotalLength = 0;
}
public void Dispose()
{
m_Sections.Dispose();
}
public JobHandle Dispose(JobHandle jobHandle)
{
return m_Sections.Dispose(jobHandle);
}
public void Add(in NativeBitArray section)
{
m_Sections.Add(section.AsUnsafeBitArray());
m_TotalLength += section.Length;
}
public bool HasSameLayout<U>(in JaggedSpan<U> other)
where U : unmanaged
{
if (sectionCount != other.sectionCount)
return false;
for (int i = 0; i < sectionCount; i++)
{
if (this[i].Length != other[i].Length)
return false;
}
return true;
}
public UnsafeBitArray this[int index]
{
get => m_Sections[index];
set
{
ref UnsafeBitArray section = ref m_Sections.ElementAt(index);
int deltaLength = value.Length - section.Length;
section = value;
m_TotalLength += deltaLength;
}
}
}
internal static class JaggedSpanExtensions
{
public static JaggedSpan<T> ToJaggedSpan<T>(this NativeArray<T> array, Allocator allocator) where T : unmanaged
{
var jaggedSpan = new JaggedSpan<T>(1, allocator);
jaggedSpan.Add(array);
return jaggedSpan;
}
public static bool HasDuplicates(this JaggedSpan<EntityId> jaggedSpan)
{
var uniqueItems = new NativeHashSet<EntityId>(jaggedSpan.totalLength, Allocator.Temp);
for (int s = 0; s < jaggedSpan.sectionCount; s++)
{
NativeArray<EntityId> section = jaggedSpan[s];
for (int i = 0; i < section.Length; i++)
{
if (!uniqueItems.Add(section[i]))
return true;
}
}
return false;
}
}
}
#endif // !UNITY_WEBGL_RENDERER_ONLY
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 97a69240652682c46913f30d9e721493
@@ -0,0 +1,66 @@
#if !UNITY_WEBGL_RENDERER_ONLY
namespace UnityEngine.Rendering
{
// Based on C++ LODGroupManager code.
internal static class LODRenderingUtils
{
public static float CalculateFOVHalfAngle(float fieldOfView)
{
return Mathf.Tan(Mathf.Deg2Rad * fieldOfView * 0.5f);
}
public static float CalculateScreenRelativeMetricNoBias(LODParameters lodParams)
{
if (lodParams.isOrthographic)
{
return 2.0F * lodParams.orthoSize;
}
// Half angle at 90 degrees is 1.0 (So we skip halfAngle / 1.0 calculation)
float halfAngle = CalculateFOVHalfAngle(lodParams.fieldOfView);
return 2.0f * halfAngle;
}
public static float CalculateMeshLodConstant(LODParameters lodParams, float screenRelativeMetric, float meshLodThreshold)
{
return meshLodThreshold * screenRelativeMetric / lodParams.cameraPixelHeight;
}
public static float CalculatePerspectiveDistance(Vector3 objPosition, Vector3 camPosition, float sqrScreenRelativeMetric)
{
return Mathf.Sqrt(CalculateSqrPerspectiveDistance(objPosition, camPosition, sqrScreenRelativeMetric));
}
public static float CalculateSqrPerspectiveDistance(Vector3 objPosition, Vector3 camPosition, float sqrScreenRelativeMetric)
{
return (objPosition - camPosition).sqrMagnitude * sqrScreenRelativeMetric;
}
public static Vector3 GetWorldReferencePoint(this LODGroup lodGroup)
{
return lodGroup.transform.TransformPoint(lodGroup.localReferencePoint);
}
public static float GetWorldSpaceScale(this LODGroup lodGroup)
{
Vector3 scale = lodGroup.transform.lossyScale;
float largestAxis = Mathf.Abs(scale.x);
largestAxis = Mathf.Max(largestAxis, Mathf.Abs(scale.y));
largestAxis = Mathf.Max(largestAxis, Mathf.Abs(scale.z));
return largestAxis;
}
public static float GetWorldSpaceSize(this LODGroup lodGroup)
{
return lodGroup.GetWorldSpaceScale() * lodGroup.size;
}
public static float CalculateLODDistance(float relativeScreenHeight, float size)
{
return size / relativeScreenHeight;
}
}
}
#endif // !UNITY_WEBGL_RENDERER_ONLY
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: df4d72ce4630d5a4388434b70060c6f7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,24 @@
#if !UNITY_WEBGL_RENDERER_ONLY
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
namespace UnityEngine.Rendering
{
internal static class MemoryUtilities
{
public unsafe static T* Malloc<T>(int count, Allocator allocator) where T : unmanaged
{
return (T*)UnsafeUtility.Malloc(
UnsafeUtility.SizeOf<T>() * count,
UnsafeUtility.AlignOf<T>(),
allocator);
}
public unsafe static void Free<T>(T* p, Allocator allocator) where T : unmanaged
{
UnsafeUtility.Free(p, allocator);
}
}
}
#endif // !UNITY_WEBGL_RENDERER_ONLY
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ce2eb19ed182ce44292cdf33f8191ec8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,209 @@
#if !UNITY_WEBGL_RENDERER_ONLY
using System;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
using UnityEngine.Assertions;
namespace UnityEngine.Rendering
{
internal struct NativeHandleAllocator
{
private const int InvalidChunkIndex = -1;
private const int BitChunkSize = 32;
// Store length/freeCount inside an UnsafeList<int> of size 2 so that they can be modified from Burst jobs while the struct is passed by value.
private UnsafeList<int> m_StructData;
private UnsafeList<FreeBitsChunk> m_FreeBitChunksDense;
private UnsafeList<int> m_FreeChunkIndicesSparse;
public int length { get => m_StructData[0]; private set => m_StructData[0] = value; }
public int freeCount { get => m_StructData[1]; private set => m_StructData[1] = value; }
public int allocatedCount => isValid ? length - freeCount : 0;
public bool isValid => m_StructData.IsCreated;
public void Initialize(int initialCapacity = 128)
{
Assert.IsTrue(math.ispow2(BitChunkSize));
int bitChunkCount = CoreUtils.DivRoundUp(initialCapacity, BitChunkSize);
m_StructData = new UnsafeList<int>(2, Allocator.Persistent) { 0, 0 };
m_FreeBitChunksDense = new UnsafeList<FreeBitsChunk>(bitChunkCount, Allocator.Persistent);
m_FreeChunkIndicesSparse = new UnsafeList<int>(bitChunkCount, Allocator.Persistent);
}
public void Dispose()
{
if (isValid)
{
m_StructData.Dispose();
m_FreeBitChunksDense.Dispose();
m_FreeChunkIndicesSparse.Dispose();
}
}
public int Allocate()
{
Assert.IsTrue(isValid, "Allocator not initialized.");
if (freeCount == 0)
{
Assert.IsTrue(m_FreeBitChunksDense.Length == 0, "Found non empty handles chunks while freeHandles is zero.");
return length++;
}
Assert.IsTrue(m_FreeBitChunksDense.Length > 0, "No free chunks available.");
int lastChunkIndex = m_FreeBitChunksDense.Length - 1;
FreeBitsChunk freeBitsChunk = m_FreeBitChunksDense[lastChunkIndex];
Assert.IsTrue(freeBitsChunk.freeBits != 0, "No free bits in the chunk.");
int freeBitIndex = math.tzcnt(freeBitsChunk.freeBits);
freeBitsChunk.freeBits &= ~(1u << freeBitIndex);
m_FreeBitChunksDense[lastChunkIndex] = freeBitsChunk;
int handle = freeBitsChunk.chunk * BitChunkSize + freeBitIndex;
Assert.IsTrue(handle < length, "Handle exceeds length.");
if (freeBitsChunk.freeBits == 0)
{
m_FreeChunkIndicesSparse[freeBitsChunk.chunk] = InvalidChunkIndex;
m_FreeBitChunksDense.Resize(m_FreeBitChunksDense.Length - 1);
}
freeCount -= 1;
return handle;
}
public void Free(int handle)
{
Assert.IsTrue(math.ispow2(BitChunkSize));
Assert.IsTrue(isValid, "Allocator not initialized.");
Assert.IsTrue(handle >= 0 && handle < length, "Handle is out of allocated range.");
int chunk = handle / BitChunkSize;
int bitIndex = handle & (BitChunkSize - 1);
uint bitMask = 1u << bitIndex;
int chunkIndex;
if (chunk >= m_FreeChunkIndicesSparse.Length)
{
m_FreeChunkIndicesSparse.AddReplicate(InvalidChunkIndex, chunk - m_FreeChunkIndicesSparse.Length + 1);
chunkIndex = InvalidChunkIndex;
}
else
{
chunkIndex = m_FreeChunkIndicesSparse[chunk];
}
if (chunkIndex != InvalidChunkIndex)
{
FreeBitsChunk freeBitsChunk = m_FreeBitChunksDense[chunkIndex];
Assert.IsTrue((freeBitsChunk.freeBits & bitMask) == 0, "Handle is freed already.");
Assert.IsTrue(freeBitsChunk.chunk == chunk, "Chunk index mismatch.");
freeBitsChunk.freeBits |= bitMask;
m_FreeBitChunksDense[chunkIndex] = freeBitsChunk;
}
else
{
m_FreeChunkIndicesSparse[chunk] = m_FreeBitChunksDense.Length;
m_FreeBitChunksDense.Add(new FreeBitsChunk(chunk, bitMask));
}
freeCount += 1;
}
internal void TrimLengthImpl()
{
if (!isValid || length == 0)
return;
int lastChunk = (length - 1) / BitChunkSize;
if (lastChunk != m_FreeChunkIndicesSparse.Length - 1)
return;
int usedBitsInChunk = Math.Max(length - BitChunkSize * lastChunk, 0);
int nonUsedBitsInChunk = BitChunkSize - usedBitsInChunk;
uint usedBitsMask = 0xFFFFFFFF >> nonUsedBitsInChunk;
while (lastChunk >= 0)
{
int lastChunkIndex = m_FreeChunkIndicesSparse[lastChunk];
if (lastChunkIndex == InvalidChunkIndex)
return;
FreeBitsChunk lastBitsChunk = m_FreeBitChunksDense[lastChunkIndex];
Assert.IsTrue(lastBitsChunk.chunk == lastChunk, "Chunk index mismatch.");
int freeLeadBits = math.lzcnt(~lastBitsChunk.freeBits & usedBitsMask) - nonUsedBitsInChunk;
lastBitsChunk.freeBits &= (uint)((ulong)usedBitsMask >> freeLeadBits);
if (lastBitsChunk.freeBits == 0)
{
int lastDenseChunk = m_FreeBitChunksDense[m_FreeBitChunksDense.Length - 1].chunk;
m_FreeBitChunksDense.RemoveAtSwapBack(lastChunkIndex);
m_FreeChunkIndicesSparse[lastDenseChunk] = lastChunkIndex;
m_FreeChunkIndicesSparse.Resize(m_FreeChunkIndicesSparse.Length - 1);
}
else
{
m_FreeBitChunksDense[lastChunkIndex] = lastBitsChunk;
}
length -= freeLeadBits;
freeCount -= freeLeadBits;
Assert.IsTrue(length >= 0 && freeCount >= 0, "Length or freeCount went negative.");
if (freeLeadBits < usedBitsInChunk)
return;
if (usedBitsInChunk < BitChunkSize)
{
usedBitsInChunk = BitChunkSize;
nonUsedBitsInChunk = 0;
usedBitsMask = 0xFFFFFFFF;
}
lastChunk -= 1;
}
}
public unsafe void TrimLength()
{
fixed (NativeHandleAllocator* thisPtr = &this)
{
NativeHandleAllocatorBurst.TrimLength(thisPtr);
}
}
struct FreeBitsChunk
{
public readonly int chunk;
public uint freeBits;
public FreeBitsChunk(int chunk, uint freeBits)
{
this.chunk = chunk;
this.freeBits = freeBits;
}
}
}
[BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
internal static unsafe class NativeHandleAllocatorBurst
{
public static void TrimLength(NativeHandleAllocator* allocator) => allocator->TrimLengthImpl();
}
}
#endif // !UNITY_WEBGL_RENDERER_ONLY
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f6607226450929441ace28f2ff712695
@@ -0,0 +1,177 @@
#if !UNITY_WEBGL_RENDERER_ONLY
using System;
using System.Threading;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using UnityEngine.Assertions;
namespace UnityEngine.Rendering
{
internal struct ParallelBitArray : IDisposable
{
private Allocator m_Allocator;
private NativeArray<long> m_Bits;
private int m_Length;
public int Length => m_Length;
public bool IsCreated
{
get { return m_Bits.IsCreated; }
}
public ParallelBitArray(int length, Allocator allocator, NativeArrayOptions options = NativeArrayOptions.ClearMemory)
{
m_Allocator = allocator;
m_Bits = new NativeArray<long>((length + 63) / 64, allocator, options);
m_Length = length;
}
public void Dispose()
{
m_Bits.Dispose();
m_Length = 0;
}
public void Dispose(JobHandle inputDeps)
{
m_Bits.Dispose(inputDeps);
m_Length = 0;
}
public void Resize(int newLength)
{
int oldLength = m_Length;
if (newLength == oldLength)
return;
int oldBitsLength = m_Bits.Length;
int newBitsLength = (newLength + 63) / 64;
if (newBitsLength != oldBitsLength)
{
var newBits = new NativeArray<long>(newBitsLength, m_Allocator, NativeArrayOptions.UninitializedMemory);
if (m_Bits.IsCreated)
{
NativeArray<long>.Copy(m_Bits, newBits, m_Bits.Length);
m_Bits.Dispose();
}
m_Bits = newBits;
}
// mask off bits past the length
int validLength = Math.Min(oldLength, newLength);
int validBitsLength = Math.Min(oldBitsLength, newBitsLength);
for (int chunkIndex = validBitsLength; chunkIndex < m_Bits.Length; ++chunkIndex)
{
int validBitCount = Math.Max(validLength - 64 * chunkIndex, 0);
if (validBitCount < 64)
{
ulong validMask = (1ul << validBitCount) - 1;
m_Bits[chunkIndex] &= (long)validMask;
}
}
m_Length = newLength;
}
public void Set(int index, bool value)
{
unsafe
{
Assert.IsTrue(0 <= index && index < m_Length);
int entry_index = index >> 6;
long* entries = (long*)m_Bits.GetUnsafePtr();
ulong bit = 1ul << (index & 0x3f);
long and_mask = (long)(~bit);
long or_mask = value ? (long)bit : 0;
long old_entry, new_entry;
do
{
old_entry = Interlocked.Read(ref entries[entry_index]);
new_entry = (old_entry & and_mask) | or_mask;
} while (Interlocked.CompareExchange(ref entries[entry_index], new_entry, old_entry) != old_entry);
}
}
public bool Get(int index)
{
unsafe
{
Assert.IsTrue(0 <= index && index < m_Length);
int entry_index = index >> 6;
long* entries = (long*)m_Bits.GetUnsafeReadOnlyPtr();
ulong bit = 1ul << (index & 0x3f);
long check_mask = (long)bit;
return (entries[entry_index] & check_mask) != 0;
}
}
public ulong GetChunk(int chunk_index)
{
return (ulong)m_Bits[chunk_index];
}
public void SetChunk(int chunk_index, ulong chunk_bits)
{
m_Bits[chunk_index] = (long)chunk_bits;
}
public unsafe ulong InterlockedReadChunk(int chunk_index)
{
long* entries = (long*)m_Bits.GetUnsafeReadOnlyPtr();
return (ulong)Interlocked.Read(ref entries[chunk_index]);
}
public unsafe void InterlockedOrChunk(int chunk_index, ulong chunk_bits)
{
long* entries = (long*)m_Bits.GetUnsafePtr();
long old_entry, new_entry;
do
{
old_entry = Interlocked.Read(ref entries[chunk_index]);
new_entry = old_entry | (long)chunk_bits;
} while (Interlocked.CompareExchange(ref entries[chunk_index], new_entry, old_entry) != old_entry);
}
public int ChunkCount()
{
return m_Bits.Length;
}
public ParallelBitArray GetSubArray(int length)
{
ParallelBitArray array = new ParallelBitArray();
array.m_Bits = m_Bits.GetSubArray(0, (length + 63) / 64);
array.m_Length = length;
return array;
}
public NativeArray<long> GetBitsArray()
{
return m_Bits;
}
public void FillZeroes(int length)
{
length = Math.Min(length, m_Length);
int chunkIndex = length / 64;
int remainder = length & 63;
m_Bits.FillArray(0, 0, chunkIndex);
if(remainder > 0)
{
long lastChunkMask = (1L << remainder) - 1;
m_Bits[chunkIndex] &= ~lastChunkMask;
}
}
}
}
#endif // !UNITY_WEBGL_RENDERER_ONLY
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c0bd11b715efbd24e95cb49f31c3cbeb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,359 @@
#if !UNITY_WEBGL_RENDERER_ONLY
using System;
using System.Runtime.InteropServices;
using System.Threading;
using UnityEngine.Assertions;
using Unity.Mathematics;
using Unity.Collections;
using Unity.Jobs;
using Unity.Jobs.LowLevel.Unsafe;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Burst;
[assembly: RegisterGenericJobType(typeof(UnityEngine.Rendering.ParallelSortExtensions.RadixSortBucketCountJob<int>))]
[assembly: RegisterGenericJobType(typeof(UnityEngine.Rendering.ParallelSortExtensions.RadixSortBucketCountJob<ulong>))]
[assembly: RegisterGenericJobType(typeof(UnityEngine.Rendering.ParallelSortExtensions.RadixSortBatchPrefixSumJob<int>))]
[assembly: RegisterGenericJobType(typeof(UnityEngine.Rendering.ParallelSortExtensions.RadixSortBatchPrefixSumJob<ulong>))]
[assembly: RegisterGenericJobType(typeof(UnityEngine.Rendering.ParallelSortExtensions.RadixSortBucketSortJob<int>))]
[assembly: RegisterGenericJobType(typeof(UnityEngine.Rendering.ParallelSortExtensions.RadixSortBucketSortJob<ulong>))]
[assembly: RegisterGenericJobType(typeof(SortJob<int, NativeSortExtension.DefaultComparer<int>>))]
[assembly: RegisterGenericJobType(typeof(SortJob<ulong, NativeSortExtension.DefaultComparer<ulong>>))]
namespace UnityEngine.Rendering
{
internal static class ParallelSortExtensions
{
// This constant is used in the ParallelSort unit test to make sure we're hitting the parallel code path.
internal const int kMinRadixSortArraySize = 2048;
const int kMinRadixSortBatchSize = 256;
internal enum ParallelSortValueType
{
Int,
ULong
}
private static int GetBucketIndex(int value, int radix)
{
return (value >> radix * 8) & 0xFF;
}
private static int GetBucketIndex(ulong value, int radix)
{
return (int)((value >> radix * 8) & 0xFF);
}
private static void Swap<T>(ref NativeArray<T> a, ref NativeArray<T> b) where T : unmanaged
{
NativeArray<T> temp = a;
a = b;
b = temp;
}
// The method supports for the moment only keys of type int or ulong.
internal static JobHandle ParallelSort<T>(this NativeArray<T> array) where T : unmanaged, IComparable<T>
{
// Only these two integer types are supported
Assert.IsTrue(typeof(T) == typeof(ulong) || typeof(T) == typeof(int));
if (array.Length <= 1)
return new JobHandle();
var jobHandle = new JobHandle();
if (array.Length >= kMinRadixSortArraySize)
{
int workersCount = Mathf.Max(JobsUtility.JobWorkerCount + 1, 1);
int batchSize = Mathf.Max(kMinRadixSortBatchSize, Mathf.CeilToInt((float)array.Length / workersCount));
int jobsCount = Mathf.CeilToInt((float)array.Length / batchSize);
int keyByteSize = Marshal.SizeOf<T>();
int signBitRadixIndex = keyByteSize - 1;
Assert.IsTrue(jobsCount * batchSize >= array.Length);
var supportArray = new NativeArray<T>(array.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
var counter = new NativeArray<int>(1, Allocator.TempJob);
var buckets = new NativeArray<int>(jobsCount * 256, Allocator.TempJob);
var indices = new NativeArray<int>(jobsCount * 256, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
var indicesSum = new NativeArray<int>(16, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
var arraySource = array;
var arrayDest = supportArray;
ParallelSortValueType valueType = typeof(T) == typeof(int) ? ParallelSortValueType.Int : ParallelSortValueType.ULong;
// Add any unsigned value type to this condition
if (valueType == ParallelSortValueType.ULong)
{
// There are no sign bits in this type.
signBitRadixIndex = -1;
}
for (int radix = 0; radix < keyByteSize; ++radix)
{
var bucketCountJobData = new RadixSortBucketCountJob<T>
{
radix = radix,
batchSize = batchSize,
buckets = buckets,
array = arraySource,
valueType = valueType
};
var batchPrefixSumJobData = new RadixSortBatchPrefixSumJob<T>
{
radix = radix,
jobsCount = jobsCount,
array = arraySource,
counter = counter,
buckets = buckets,
indices = indices,
indicesSum = indicesSum,
signBitRadixIndex = signBitRadixIndex
};
var prefixSumJobData = new RadixSortPrefixSumJob
{
jobsCount = jobsCount,
indices = indices,
indicesSum = indicesSum
};
var bucketSortJobData = new RadixSortBucketSortJob<T>
{
radix = radix,
batchSize = batchSize,
indices = indices,
array = arraySource,
arraySorted = arrayDest,
valueType = valueType
};
jobHandle = bucketCountJobData.ScheduleParallel(jobsCount, 1, jobHandle);
jobHandle = batchPrefixSumJobData.ScheduleParallel(16, 1, jobHandle);
jobHandle = prefixSumJobData.ScheduleParallel(16, 1, jobHandle);
jobHandle = bucketSortJobData.ScheduleParallel(jobsCount, 1, jobHandle);
JobHandle.ScheduleBatchedJobs();
Swap(ref arraySource, ref arrayDest);
}
supportArray.Dispose(jobHandle);
counter.Dispose(jobHandle);
buckets.Dispose(jobHandle);
indices.Dispose(jobHandle);
indicesSum.Dispose(jobHandle);
}
else
{
jobHandle = NativeSortExtension.SortJob(array).Schedule();
}
return jobHandle;
}
[BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
internal struct RadixSortBucketCountJob<T> : IJobFor where T : unmanaged
{
[ReadOnly] public int radix;
[ReadOnly] public int batchSize;
[ReadOnly] public ParallelSortValueType valueType;
[ReadOnly] [NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<T> array;
[NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<int> buckets;
public void Execute(int index)
{
int start = index * batchSize;
int end = math.min(start + batchSize, array.Length);
int jobBuckets = index * 256;
// This hacky system instead of relying solely on C# generics is because our current version of C# doesn't support IBinaryWriter
// which would let us restrain the generic type to types that accept the binary shift and "and" operations
// used in GetBucketIndex.
if (valueType == ParallelSortValueType.Int)
{
NativeArray<int> intArray = array.Reinterpret<int>(4);
for (int i = start; i < end; ++i)
{
int value = intArray[i];
int bucket = GetBucketIndex(value, radix);
buckets[jobBuckets + bucket] += 1;
}
}
else
{
NativeArray<ulong> ulongArray = array.Reinterpret<ulong>(8);
for (int i = start; i < end; ++i)
{
ulong value = ulongArray[i];
int bucket = GetBucketIndex(value, radix);
buckets[jobBuckets + bucket] += 1;
}
}
}
}
[BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
internal struct RadixSortBatchPrefixSumJob<T> : IJobFor where T : unmanaged
{
[ReadOnly] public int radix;
[ReadOnly] public int jobsCount;
[ReadOnly] public int signBitRadixIndex;
[ReadOnly] [NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<T> array;
[NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<int> counter;
[NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<int> indicesSum;
[NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<int> buckets;
[NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<int> indices;
private unsafe static int AtomicIncrement(NativeArray<int> counter)
{
return Interlocked.Increment(ref UnsafeUtility.AsRef<int>((int*)counter.GetUnsafePtr()));
}
private int JobIndexPrefixSum(int sum, int i)
{
for (int j = 0; j < jobsCount; ++j)
{
int k = i + j * 256;
indices[k] = sum;
sum += buckets[k];
buckets[k] = 0;
}
return sum;
}
public void Execute(int index)
{
int start = index * 16;
int end = start + 16;
int jobSum = 0;
for (int i = start; i < end; ++i)
jobSum = JobIndexPrefixSum(jobSum, i);
indicesSum[index] = jobSum;
if (AtomicIncrement(counter) == 16)
{
int sum = 0;
if (radix != signBitRadixIndex)
{
for (int i = 0; i < 16; ++i)
{
int indexSum = indicesSum[i];
indicesSum[i] = sum;
sum += indexSum;
}
}
// The radix contains the sign bit so might be negative
else
{
for (int i = 8; i < 16; ++i)
{
int indexSum = indicesSum[i];
indicesSum[i] = sum;
sum += indexSum;
}
for (int i = 0; i < 8; ++i)
{
int indexSum = indicesSum[i];
indicesSum[i] = sum;
sum += indexSum;
}
}
Assert.AreEqual(sum, array.Length);
counter[0] = 0;
}
}
}
[BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
internal struct RadixSortPrefixSumJob : IJobFor
{
[ReadOnly] public int jobsCount;
[NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<int> indicesSum;
[NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<int> indices;
public void Execute(int index)
{
int start = index * 16;
int end = start + 16;
int jobSum = indicesSum[index];
for (int j = 0; j < jobsCount; ++j)
{
for (int i = start; i < end; ++i)
{
int k = j * 256 + i;
indices[k] += jobSum;
}
}
}
}
[BurstCompile(DisableSafetyChecks = true, OptimizeFor = OptimizeFor.Performance)]
internal struct RadixSortBucketSortJob<T> : IJobFor where T : unmanaged
{
[ReadOnly] public int radix;
[ReadOnly] public int batchSize;
[ReadOnly] public ParallelSortValueType valueType;
[ReadOnly] [NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<T> array;
[NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<int> indices;
[NativeDisableContainerSafetyRestriction, NoAlias] public NativeArray<T> arraySorted;
public void Execute(int index)
{
int start = index * batchSize;
int end = math.min(start + batchSize, array.Length);
int jobIndices = index * 256;
// This hacky system instead of relying solely on C# generics is because our current version of C# doesn't support IBinaryWriter
// which would let us restrain the generic type to types that accept the binary shift and "and" operations
// used in GetBucketIndex.
if (valueType == ParallelSortValueType.Int)
{
NativeArray<int> inArray = array.Reinterpret<int>(4);
NativeArray<int> outArray = arraySorted.Reinterpret<int>(4);
for (int i = start; i < end; ++i)
{
int value = inArray[i];
int bucket = GetBucketIndex(value, radix);
int sortedIndex = indices[jobIndices + bucket]++;
outArray[sortedIndex] = value;
}
}
else
{
NativeArray<ulong> inArray = array.Reinterpret<ulong>(8);
NativeArray<ulong> outArray = arraySorted.Reinterpret<ulong>(8);
for (int i = start; i < end; ++i)
{
ulong value = inArray[i];
int bucket = GetBucketIndex(value, radix);
int sortedIndex = indices[jobIndices + bucket]++;
outArray[sortedIndex] = value;
}
}
}
}
}
}
#endif // !UNITY_WEBGL_RENDERER_ONLY
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 08852b5d707b78442bda3ca091091ce8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: