initial commit
This commit is contained in:
parent
6715289efe
commit
788c3389af
37645 changed files with 2526849 additions and 80 deletions
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2f2d3a0f72d55dc43847d723b80f62fe
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,69 @@
|
|||
using Unity.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
|
||||
namespace SLZ.CustomStaticBatching
|
||||
{
|
||||
public interface IGenericInt<T>
|
||||
{
|
||||
//[BurstCompatible]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int ToInt(T value);
|
||||
|
||||
//[BurstCompatible]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T ToType(int other);
|
||||
|
||||
//[BurstCompatible]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T Add(T value, int other);
|
||||
}
|
||||
|
||||
public struct GenericInt32 : IGenericInt<int>
|
||||
{
|
||||
//[BurstCompatible]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int ToInt(int value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
//[BurstCompatible]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int ToType(int value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
//[BurstCompatible]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Add(int value, int other)
|
||||
{
|
||||
return value + other;
|
||||
}
|
||||
}
|
||||
|
||||
public struct GenericInt16 : IGenericInt<ushort>
|
||||
{
|
||||
//[BurstCompatible]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int ToInt(ushort value)
|
||||
{
|
||||
return (int)value;
|
||||
}
|
||||
|
||||
//[BurstCompatible]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ushort ToType(int value)
|
||||
{
|
||||
return (ushort)value;
|
||||
}
|
||||
|
||||
//[BurstCompatible]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ushort Add(ushort value, int other)
|
||||
{
|
||||
return (ushort)(value + other);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f74d73f918268d842a6bff9fc2b0dd2e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SLZ.CustomStaticBatching
|
||||
{
|
||||
[Serializable]
|
||||
public struct CombineRendererSettings
|
||||
{
|
||||
public PackedChannel.VtxFormats[] GetVertexFormats()
|
||||
{
|
||||
PackedChannel.VtxFormats[] outp = new PackedChannel.VtxFormats[serializedVtxFormats.Length];
|
||||
for (int i = 0; i < serializedVtxFormats.Length; i++)
|
||||
{
|
||||
outp[i] = (PackedChannel.VtxFormats)serializedVtxFormats[i];
|
||||
}
|
||||
return outp;
|
||||
}
|
||||
|
||||
public byte[] serializedVtxFormats;
|
||||
|
||||
public bool allow32bitIdx;
|
||||
public int maxCombined32Idx;
|
||||
public bool splitMultiMaterialMeshes;
|
||||
public bool[] altStream;
|
||||
|
||||
public CombineRendererSettings(bool initialize)
|
||||
{
|
||||
serializedVtxFormats = new byte[PackedChannel.NUM_VTX_CHANNELS];
|
||||
if (initialize)
|
||||
{
|
||||
for (int i = 0; i < PackedChannel.NUM_VTX_CHANNELS; i++)
|
||||
{
|
||||
serializedVtxFormats[i] = (byte)PackedChannel.VtxFormats.Float32;
|
||||
}
|
||||
}
|
||||
altStream = new bool[PackedChannel.NUM_VTX_CHANNELS];
|
||||
allow32bitIdx = true;
|
||||
splitMultiMaterialMeshes = false;
|
||||
maxCombined32Idx = 1 << 23;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ICSBIndexer
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: dc800bc209466954eb0c07b6362f6bfa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,75 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace SLZ.CustomStaticBatching
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Bit-packed information about a single attribute channel in a vertex buffer. Maps to a single UInt,
|
||||
/// Needs to be kept in sync with the Job and compute shader used for combining.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit, Size = 4)]
|
||||
public struct PackedChannel
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public UInt32 packedData;
|
||||
[FieldOffset(0)]
|
||||
public byte dimension;
|
||||
[FieldOffset(1)]
|
||||
public byte format;
|
||||
[FieldOffset(2)]
|
||||
public byte offset;
|
||||
[FieldOffset(3)]
|
||||
public byte stream;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("Dimension {0}, Format {1}, Offset {2}, Channel {3}, Packed: 0x{3}", (int)dimension, (int)format, (int)offset, packedData.ToString("X8"));
|
||||
}
|
||||
|
||||
// Maximum number of vertex attributes in a mesh
|
||||
public const int NUM_VTX_CHANNELS = 12;
|
||||
|
||||
/// <summary>
|
||||
/// Supported vertex formats. This is hard-coded into the compute shader used for combining, so don't just go adding formats to this list!
|
||||
/// Only commonly used floating point formats are here. There's no logical way to choose a format for the combined mesh's channels if the
|
||||
/// channel is integer on one mesh and floating point on another. Which format do you choose? Cast int to float or treat the bytes of the
|
||||
/// int as a float? You can losslessly store the bytes of an int32 in a float, but it'll get garbled if the output channel is compressed
|
||||
/// to half. Also what if the int is less than 32 bits? You can't put a short or char into a half or unorm, as those will get converted to
|
||||
/// float by the GPU. I'm not supporting 16 bit normalized formats to cut down on shader complexity. Just use half precision instead.
|
||||
/// These need to be in order of increasing precision such that each format can be safely contained in the next format.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public enum VtxFormats : byte
|
||||
{
|
||||
[UnityEngine. HideInInspector]
|
||||
Invalid = 0,
|
||||
UNorm8 = 1,
|
||||
SNorm8 = 2,
|
||||
Float16 = 3,
|
||||
Float32 = 4,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps each VtxFormat enum to the number of bytes in that format
|
||||
/// </summary>
|
||||
public static ReadOnlySpan<byte> VtxFmtToBytes => new byte[5] { 0, 1, 1, 2, 4 };
|
||||
|
||||
/// <summary>
|
||||
/// Maps a VtxFormats enum value to a VertexAttributeFormat enum value
|
||||
/// </summary>
|
||||
public static ReadOnlySpan<VertexAttributeFormat> ToUnityFormatLUT => new VertexAttributeFormat[5] {
|
||||
VertexAttributeFormat.Float32,
|
||||
VertexAttributeFormat.UNorm8,
|
||||
VertexAttributeFormat.SNorm8,
|
||||
VertexAttributeFormat.Float16,
|
||||
VertexAttributeFormat.Float32,
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 33937365e94434c4992b189f6b5e2f47
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "SLZ.CustomStaticBatching.Common",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:2665a8d13d1b3f18800f46e256720795",
|
||||
"GUID:e0cd26848372d4e5c891c569017e11f1",
|
||||
"GUID:d8b63aba1907145bea998dd612889d6b"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b3170da2b0ad74e4aad542674030756e
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d10da44e69925b748b71e6e2c052da33
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,142 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Burst;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Jobs;
|
||||
|
||||
namespace SLZ.CustomStaticBatching
|
||||
{
|
||||
public struct SortIdx : IComparable<SortIdx>
|
||||
{
|
||||
public UInt64 hilbertIdx;
|
||||
public int arrayIdx;
|
||||
|
||||
public int CompareTo(SortIdx other)
|
||||
{
|
||||
if (this.hilbertIdx > other.hilbertIdx) return 1;
|
||||
else if (this.hilbertIdx < other.hilbertIdx) return -1;
|
||||
else return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static class HilbertIndex
|
||||
{
|
||||
|
||||
[BurstCompile]
|
||||
struct HilbertIndexJob : IJobParallelFor
|
||||
{
|
||||
[WriteOnly]
|
||||
public NativeArray<UInt64> indices;
|
||||
[ReadOnly]
|
||||
public NativeArray<Vector3> positions;
|
||||
[ReadOnly]
|
||||
public double3 boundExtent;
|
||||
[ReadOnly]
|
||||
public double3 boundCenter;
|
||||
[ReadOnly]
|
||||
public double3 scale;
|
||||
|
||||
public void Execute(int i)
|
||||
{
|
||||
double3 position = (double3)(float3)positions[i];
|
||||
|
||||
position = (scale * (position - boundCenter) + boundExtent) / (2.0 * boundExtent);
|
||||
position = math.clamp(position, 0.0, 1.0);
|
||||
uint3 intPos = (uint3)((position * 2097152.0d)); // 2 ^ 21
|
||||
intPos = intPos & ((2 << 21) - 1);
|
||||
indices[i] = GetIndex(intPos.xyz);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given an unsigned 21-bit integer coordinate of a point within some bounding volume, return the point's distance along a 21-fold hilbert curve.
|
||||
/// Based on the AxestoTranspose function found in the appendix of John Skilling; Programming the Hilbert curve. AIP Conference Proceedings 21 April 2004; 707 (1): 381–387. https://doi.org/10.1063/1.1751381
|
||||
/// </summary>
|
||||
/// <param name="boundingCoord">3d unsigned integer coordinate of the point inside of a defined bounding volume.</param>
|
||||
/// <returns>The Hilbert index</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
//[BurstCompatible]
|
||||
[BurstCompile]
|
||||
public static UInt64 GetIndex(uint3 boundingCoord)
|
||||
{
|
||||
int bits = 21;
|
||||
uint highestBit = 1U << (bits - 1);
|
||||
for (uint nextBit = highestBit; nextBit > 1; nextBit >>= 1)
|
||||
{
|
||||
uint allBitsSet = nextBit - 1;
|
||||
for (int axis = 0; axis < 3; axis++)
|
||||
{
|
||||
if ((boundingCoord[axis] & nextBit) != 0)
|
||||
{
|
||||
boundingCoord[0] ^= allBitsSet;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint t = (boundingCoord[0] ^ boundingCoord[axis]) & allBitsSet;
|
||||
boundingCoord[0] ^= t;
|
||||
boundingCoord[axis] ^= t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int axis = 1; axis < 3; axis++)
|
||||
{
|
||||
boundingCoord[axis] ^= boundingCoord[axis - 1];
|
||||
}
|
||||
uint t2 = 0;
|
||||
for (uint nextBit = highestBit; nextBit > 1; nextBit >>= 1)
|
||||
{
|
||||
if ((boundingCoord[3 - 1] & nextBit) != 0)
|
||||
t2 ^= nextBit - 1;
|
||||
}
|
||||
for (int axis = 0; axis < 3; axis++)
|
||||
{
|
||||
boundingCoord[axis] ^= t2;
|
||||
}
|
||||
|
||||
return Interleave63Bits(boundingCoord.zyx);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
//[BurstCompatible]
|
||||
[BurstCompile]
|
||||
public static UInt64 Interleave63Bits(uint3 value)
|
||||
{
|
||||
Span<UInt64> longVal = stackalloc UInt64[3];
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
longVal[i] = value[i] & 0x1fffff;
|
||||
longVal[i] = (longVal[i] | longVal[i] << 32) & 0x1f00000000ffff;
|
||||
longVal[i] = (longVal[i] | longVal[i] << 16) & 0x1f0000ff0000ff;
|
||||
longVal[i] = (longVal[i] | longVal[i] << 8) & 0x100f00f00f00f00f;
|
||||
longVal[i] = (longVal[i] | longVal[i] << 4) & 0x10c30c30c30c30c3;
|
||||
longVal[i] = (longVal[i] | longVal[i] << 2) & 0x1249249249249249;
|
||||
}
|
||||
return longVal[0] | longVal[1] << 1 | longVal[2] << 2;
|
||||
}
|
||||
|
||||
public static NativeArray<UInt64> GetHilbertIndices(NativeArray<Vector3> positions, Bounds bounds, Allocator allocator)
|
||||
{
|
||||
return GetHilbertIndices(positions, bounds, Vector3.one, allocator);
|
||||
}
|
||||
public static NativeArray<UInt64> GetHilbertIndices(NativeArray<Vector3> positions, Bounds bounds, Vector3 scale, Allocator allocator)
|
||||
{
|
||||
NativeArray<UInt64> indices = new NativeArray<UInt64>(positions.Length, allocator);
|
||||
HilbertIndexJob indexJob = new HilbertIndexJob()
|
||||
{
|
||||
indices = indices,
|
||||
positions = positions,
|
||||
boundExtent = (double3)(float3)bounds.extents,
|
||||
boundCenter = (double3)(float3)bounds.center,
|
||||
scale = (double3)math.saturate((float3)scale)
|
||||
};
|
||||
JobHandle indexJobHandle = indexJob.Schedule(positions.Length, 8);
|
||||
indexJobHandle.Complete();
|
||||
return indices;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d38371f42b6497f4aa8359634f4c4988
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("SLZ.CustomStaticBatching.Editor")]
|
||||
[assembly: InternalsVisibleTo("SLZ.CustomStaticBatching.Unsafe")]
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 93796faa57a04964f962d7fe3ed9ac45
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,371 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection.Emit;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEditor;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Jobs;
|
||||
using Unity.Profiling;
|
||||
|
||||
namespace SLZ.CustomStaticBatching
|
||||
{
|
||||
/// <summary>
|
||||
/// Data used to sort renderers for static batching.
|
||||
/// Sorts by each property that can break rendering contiguous sections of the static batch in order of decreasing importance.
|
||||
/// 0. 32 bit-index buffer useage. We don't want to upcast 16-bit index buffer meshes to 32 bit in order to combine 16 and 32 bit meshes, as that would dramatically increase memory usage.
|
||||
/// 1. Having multiple materials, this is very important because each multi-material mesh breaks contiguous sections by definition.
|
||||
/// 2. Active state. Assuming the vast majority of static meshes that are off will never be activated, we don't want to create holes in the buffer for meshes that will never be visible
|
||||
/// 3. TODO: Zone ID (ID of the batching volume the renderer is in, 0xFFFF = not in a zone)
|
||||
/// 4. Shader, using the instanceID as a proxy
|
||||
/// 5. Hash of the local keywords on the shader, as these represent different shader programs
|
||||
/// 6. Material, using the instanceID as a proxy
|
||||
/// 7. Lightmap index
|
||||
/// 8. Hilbert index
|
||||
/// </summary>
|
||||
public struct RendererSortItem : IComparable<RendererSortItem>
|
||||
{
|
||||
public int rendererArrayIdx;
|
||||
|
||||
public ushort breakingState; // flags to bin the most important boolean properties. is multi-material in highest bit, is active in lowest
|
||||
public ushort zoneID;
|
||||
public int shaderID;
|
||||
public ulong variantHash;
|
||||
public int materialID;
|
||||
public ushort lightmapIdx;
|
||||
//public ulong probeId; // Pack two int IDs for the two most important probes // Not used for now, seems to cause issues
|
||||
public ulong hilbertIdx;
|
||||
|
||||
//[BurstCompatible]
|
||||
public int CompareTo(RendererSortItem other)
|
||||
{
|
||||
if (breakingState != other.breakingState)
|
||||
{
|
||||
return breakingState > other.breakingState ? 1 : -1;
|
||||
}
|
||||
if (zoneID != other.zoneID)
|
||||
{
|
||||
return zoneID > other.zoneID ? 1 : -1;
|
||||
}
|
||||
if (shaderID != other.shaderID)
|
||||
{
|
||||
return shaderID > other.shaderID ? 1 : -1;
|
||||
}
|
||||
if (variantHash != other.variantHash)
|
||||
{
|
||||
return variantHash > other.variantHash ? 1 : -1;
|
||||
}
|
||||
if (materialID != other.materialID)
|
||||
{
|
||||
return materialID > other.materialID ? 1 : -1;
|
||||
}
|
||||
if (lightmapIdx != other.lightmapIdx)
|
||||
{
|
||||
return lightmapIdx > other.lightmapIdx ? 1 : -1;
|
||||
}
|
||||
//if (probeId != other.probeId)
|
||||
//{
|
||||
// return probeId > other.probeId ? 1 : -1;
|
||||
//}
|
||||
|
||||
if (hilbertIdx != other.hilbertIdx)
|
||||
{
|
||||
return hilbertIdx > other.hilbertIdx ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Utility class to get the internal index for local shader keywords. In editor it uses IL generation to create a method to access the internal field.
|
||||
/// In the player it uses the UnsafeUtility to treat the LocalKeyword as a different struct with a matching layout that has m_Index exposed,
|
||||
/// as we can't do runtime code generation with IL2CPP. It might be better to use this for both, but the IL generation method should be much less brittle.
|
||||
/// </summary>
|
||||
internal static class ReflectKWFields
|
||||
{
|
||||
static bool m_Initialized = false;
|
||||
public static bool Initialized { get { return m_Initialized; } }
|
||||
static Func<LocalKeyword, uint> _localKW_Index;
|
||||
|
||||
public static void GetDelegate()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
FieldInfo fieldInfo = typeof(LocalKeyword).GetField("m_Index", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (fieldInfo == null)
|
||||
{
|
||||
throw new InvalidOperationException("Can't find FieldInfo for m_Index");
|
||||
}
|
||||
_localKW_Index = CreateGetter<LocalKeyword, uint>(fieldInfo);
|
||||
#endif
|
||||
m_Initialized = true;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private static Func<T, R> CreateGetter<T, R>(FieldInfo field)
|
||||
{
|
||||
string methodName = field.ReflectedType.FullName + ".get_" + field.Name;
|
||||
DynamicMethod getterMethod = new DynamicMethod(methodName, typeof(R), new Type[] { typeof(T) }, true);
|
||||
ILGenerator gen = getterMethod.GetILGenerator();
|
||||
|
||||
gen.Emit(OpCodes.Ldarg_0);
|
||||
gen.Emit(OpCodes.Ldfld, field);
|
||||
gen.Emit(OpCodes.Ret);
|
||||
|
||||
return (Func<T, R>)getterMethod.CreateDelegate(typeof(Func<T, R>));
|
||||
}
|
||||
#endif
|
||||
|
||||
public static uint GetIndex(LocalKeyword kw)
|
||||
{
|
||||
if (m_Initialized)
|
||||
{
|
||||
return GetIndexUnsafe(kw);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetDelegate();
|
||||
return GetIndexUnsafe(kw);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the local shader keyword, assuming GetDelegate() has alread been called.
|
||||
/// </summary>
|
||||
/// <param name="kw"></param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static uint GetIndexUnsafe(LocalKeyword kw)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
return _localKW_Index.Invoke(kw);
|
||||
#else
|
||||
return UnsafeUtility.As<LocalKeyword, LocalKeywordInternals>(ref kw).m_Index;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// struct that matches the fields and layout of LocalKeyword, which we can treat a LocalKeyword as to get access to m_Index
|
||||
/// </summary>
|
||||
readonly struct LocalKeywordInternals
|
||||
{
|
||||
internal readonly LocalKeywordSpace m_SpaceInfo;
|
||||
|
||||
internal readonly string m_Name;
|
||||
|
||||
internal readonly uint m_Index;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utilities for getting the hash of a shader's keywords
|
||||
/// </summary>
|
||||
internal static class ShaderKWHash
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets hash of a local keyword array ASSUMING YOU CALLED ReflectKWFields.GetDelegate() FIRST!
|
||||
/// Assumes the shader they belong to has less than 68 keywords, including global keywords!
|
||||
/// </summary>
|
||||
/// <param name="kwArray">Array of keywords to get the hash of</param>
|
||||
/// <returns>Hash of the keyword array</returns>
|
||||
const int DefaultGlobalKWCount = 4; // STEREO_INSTANCING_ON UNITY_SINGLE_PASS_STEREO STEREO_MULTIVIEW_ON STEREO_CUBEMAP_RENDER_ON
|
||||
public static ulong GetHashUnsafe(LocalKeyword[] kwArray)
|
||||
{
|
||||
int kwArrayLength = kwArray.Length;
|
||||
if (kwArrayLength == 0)
|
||||
return 0ul;
|
||||
|
||||
ulong hash = 0ul;
|
||||
|
||||
Span<ushort> kwIdx = kwArrayLength < 17 ? stackalloc ushort[kwArrayLength] : new ushort[kwArrayLength];
|
||||
int j = 0;
|
||||
for (int i = 0; i < kwArrayLength; i++)
|
||||
{
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
if (!kwArray[i].isDynamic)
|
||||
#endif
|
||||
{
|
||||
kwIdx[j] = (ushort)(ReflectKWFields.GetIndexUnsafe(kwArray[i]) - DefaultGlobalKWCount);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < j; i++)
|
||||
{
|
||||
hash += 1ul<<(int)kwIdx[i];
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class that contains the method used to sort a list of renderers for combining in static batched meshes
|
||||
/// </summary>
|
||||
public static class RendererSort
|
||||
{
|
||||
static readonly ProfilerMarker profileSortRenderers = new ProfilerMarker("CustomStaticBatching.SortRenderers");
|
||||
static readonly ProfilerMarker profileGetRenderersSortData = new ProfilerMarker("CustomStaticBatching.GetRenderersSortData");
|
||||
static readonly ProfilerMarker profileGetRenderersHilbertIdx = new ProfilerMarker("CustomStaticBatching.GetRenderersHilbertIdx");
|
||||
static readonly ProfilerMarker profileGetMaterials = new ProfilerMarker("CustomStaticBatching.profileGetMaterials");
|
||||
|
||||
/// <summary>
|
||||
/// Given a list of mesh renderers and a corresponding list of mesh filters, creates an array of renderer data sorted for static batching
|
||||
/// </summary>
|
||||
/// <param name="meshRenderers"> List of mesh renderers</param>
|
||||
/// <param name="filters"> List of mesh filters that correspond to each element of meshRenderers</param>
|
||||
/// <returns></returns>
|
||||
public static RendererData[] GetSortedData(List<MeshRenderer> meshRenderers, List<MeshFilter> filters)
|
||||
{
|
||||
profileSortRenderers.Begin();
|
||||
|
||||
profileGetMaterials.Begin();
|
||||
// Get an array of unique materials referenced by the renderers, and a mapping from a renderer to the index of its first material in the unique array
|
||||
int[] rendererToMaterial;
|
||||
Material[] uniqueMats;
|
||||
GetUniqueMaterials(meshRenderers, out rendererToMaterial, out uniqueMats);
|
||||
profileGetMaterials.End();
|
||||
|
||||
// For each unique material, get a hash of the material, a hash of the shader it uses, and a hash of the enabled keywords on the material
|
||||
MaterialAndShaderID[] matShaderIds = GetMaterialAndShaderIDs(uniqueMats, rendererToMaterial);
|
||||
|
||||
profileGetRenderersHilbertIdx.Begin();
|
||||
// Get the hilbert index of the bounds center of each mesh renderer, in the cubic bounding box that encapsulates all the static meshes to be combined.
|
||||
// TODO: Also pass batching volumes and a BVH tree to accelerate finding the appropriate batching volume for each mesh
|
||||
NativeArray<UInt64> hilbertIdxs = GetHilbertIdxs(meshRenderers);
|
||||
profileGetRenderersHilbertIdx.End();
|
||||
|
||||
profileGetRenderersSortData.Begin();
|
||||
// Generate an array of data for each renderer that will be used to sort the renderers.
|
||||
int numRenderers = meshRenderers.Count;
|
||||
NativeArray<RendererSortItem> rendererSortItems = new NativeArray<RendererSortItem>(numRenderers, Allocator.TempJob);
|
||||
List<ReflectionProbeBlendInfo> closestProbes = new List<ReflectionProbeBlendInfo> ();
|
||||
for (int i = 0; i < numRenderers; i++)
|
||||
{
|
||||
MeshRenderer mr = meshRenderers[i];
|
||||
int materialIdx = rendererToMaterial[i];
|
||||
|
||||
// I was going to sort on probe ID's, but it seems to break batching worse than not? Sorting spatially should ensure the probe indices is also mostly sorted anyways.
|
||||
//mr.GetClosestReflectionProbes(closestProbes);
|
||||
//int numClosest = closestProbes.Count;
|
||||
//ReadOnlySpan<int> probeHash = stackalloc int[2] {
|
||||
// numClosest > 1 ? closestProbes[1].probe.GetHashCode() : -0x7fffffff,
|
||||
// numClosest > 0 ? closestProbes[0].probe.GetHashCode() : -0x7fffffff}; // Assumes little-endian
|
||||
//closestProbes.Clear();
|
||||
ushort breakingState = (ushort)((mr.sharedMaterials.Length > 1 ? 0x4000u : 0u) + (mr.gameObject.activeInHierarchy && mr.enabled ? 0u : 1u));
|
||||
rendererSortItems[i] = new RendererSortItem
|
||||
{
|
||||
rendererArrayIdx = i,
|
||||
breakingState = breakingState, // 0 if single material, 1 otherwise
|
||||
shaderID = matShaderIds[materialIdx].shaderID,
|
||||
variantHash = matShaderIds[materialIdx].keywordHash,
|
||||
materialID = matShaderIds[materialIdx].materialID,
|
||||
lightmapIdx = (ushort)mr.lightmapIndex,
|
||||
//probeId = MemoryMarshal.Cast<int, ulong>(probeHash)[0], // Pack two int IDs for the two most important probes
|
||||
zoneID = 0,
|
||||
hilbertIdx = hilbertIdxs[i]
|
||||
};
|
||||
}
|
||||
hilbertIdxs.Dispose();
|
||||
profileGetRenderersSortData.End();
|
||||
|
||||
// Sort the renderers using the Collection's package extensions for NativeArray sorting
|
||||
var sortJob = NativeSortExtension.SortJob(rendererSortItems);
|
||||
JobHandle sortJobHandle = sortJob.Schedule();
|
||||
sortJobHandle.Complete();
|
||||
|
||||
// Populate an array of RendererData using the sorted items
|
||||
RendererData[] rendererData = new RendererData[numRenderers];
|
||||
for (int i = 0; i < numRenderers; i++)
|
||||
{
|
||||
int rendererIdx = rendererSortItems[i].rendererArrayIdx;
|
||||
MeshFilter filter = filters[rendererIdx];
|
||||
rendererData[i] = new RendererData
|
||||
{
|
||||
mesh = filter.sharedMesh,
|
||||
meshFilter = filter,
|
||||
meshRenderer = meshRenderers[rendererIdx],
|
||||
rendererTransform = filter.transform
|
||||
};
|
||||
}
|
||||
rendererSortItems.Dispose();
|
||||
profileSortRenderers.End();
|
||||
return rendererData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void GetUniqueMaterials(List<MeshRenderer> meshRenderers, out int[] rendererToMaterial, out Material[] uniqueMats)
|
||||
{
|
||||
Dictionary<Material, int> matToIdx = new Dictionary<Material, int>();
|
||||
List<Material> uniqueMatList = new List<Material>();
|
||||
rendererToMaterial = new int[meshRenderers.Count];
|
||||
int uniqueCount = 0;
|
||||
for (int i = 0; i < meshRenderers.Count; i++)
|
||||
{
|
||||
Material mat = meshRenderers[i].sharedMaterial;
|
||||
int index = 0;
|
||||
if (matToIdx.TryGetValue(mat, out index))
|
||||
{
|
||||
rendererToMaterial[i] = index;
|
||||
continue;
|
||||
}
|
||||
rendererToMaterial[i] = uniqueCount;
|
||||
matToIdx.Add(mat, uniqueCount);
|
||||
uniqueMatList.Add(mat);
|
||||
uniqueCount++;
|
||||
}
|
||||
uniqueMats = uniqueMatList.ToArray();
|
||||
}
|
||||
|
||||
struct MaterialAndShaderID
|
||||
{
|
||||
public int shaderID;
|
||||
public int materialID;
|
||||
public ulong keywordHash;
|
||||
}
|
||||
static MaterialAndShaderID[] GetMaterialAndShaderIDs(Material[] uniqueMats, int[] rendererToMaterial)
|
||||
{
|
||||
int matLength = uniqueMats.Length;
|
||||
MaterialAndShaderID[] matShaderIDs = new MaterialAndShaderID[matLength];
|
||||
if (!ReflectKWFields.Initialized) ReflectKWFields.GetDelegate(); // Initialize delegate for getting the shader keyword hash
|
||||
for (int i = 0; i < matLength; i++)
|
||||
{
|
||||
matShaderIDs[i] = new MaterialAndShaderID
|
||||
{
|
||||
shaderID = uniqueMats[i].shader.GetHashCode(),
|
||||
materialID = uniqueMats[i].GetHashCode(),
|
||||
keywordHash = ShaderKWHash.GetHashUnsafe(uniqueMats[i].enabledKeywords)
|
||||
};
|
||||
}
|
||||
return matShaderIDs;
|
||||
}
|
||||
|
||||
static NativeArray<UInt64> GetHilbertIdxs(List<MeshRenderer> meshRenderers)
|
||||
{
|
||||
int length = meshRenderers.Count;
|
||||
NativeArray<Vector3> positions = new NativeArray<Vector3>(length, Allocator.TempJob);
|
||||
Bounds hilbertBounds = meshRenderers[0].bounds;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
Bounds bounds = meshRenderers[i].bounds;
|
||||
positions[i] = bounds.center;
|
||||
hilbertBounds.Encapsulate(bounds);
|
||||
}
|
||||
float maxDim = math.max(math.max(hilbertBounds.extents.x, hilbertBounds.extents.y), hilbertBounds.extents.z);
|
||||
Vector3 minExtent = hilbertBounds.center - hilbertBounds.extents;
|
||||
hilbertBounds.extents = new Vector3(maxDim, maxDim, maxDim);
|
||||
hilbertBounds.center = minExtent + hilbertBounds.extents;
|
||||
NativeArray<UInt64> hilbertIdxs = HilbertIndex.GetHilbertIndices(positions, hilbertBounds, Allocator.TempJob);
|
||||
positions.Dispose();
|
||||
return hilbertIdxs;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e131827d3efe8664f8e8ef24c9c0b63b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 99255177bc8f96d4e92717abb7e1f74e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "SLZ.CustomStaticBatching",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:d8b63aba1907145bea998dd612889d6b",
|
||||
"GUID:e0cd26848372d4e5c891c569017e11f1",
|
||||
"GUID:2665a8d13d1b3f18800f46e256720795",
|
||||
"GUID:1a4848c44fae8ec4a9b68efad47aab4e",
|
||||
"GUID:b3170da2b0ad74e4aad542674030756e"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 18839400ad0a58b4cb80e7f1117ebcba
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 783b8b155491a654483e94ca2eedfbda
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,426 @@
|
|||
#pragma use_dxc
|
||||
|
||||
#pragma kernel CSMain
|
||||
|
||||
#pragma multi_compile _ SPLIT_INPUT
|
||||
#pragma multi_compile _ SPLIT_OUTPUT
|
||||
|
||||
ByteAddressBuffer vertIn;
|
||||
#if defined(SPLIT_INPUT)
|
||||
ByteAddressBuffer vertIn2;
|
||||
#endif
|
||||
RWByteAddressBuffer vertOut;
|
||||
#if defined(SPLIT_OUTPUT)
|
||||
RWByteAddressBuffer vertOut2;
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// Packed Channel info: each uint represents a channel in the vertex struct in this order:
|
||||
// position, normal, tangent, color, and UVs 0-7.
|
||||
// Each int contains several pieces of data bit-packed together
|
||||
// byte 0: vector dimension, with 0 meaning that the struct does not contain this channel
|
||||
// byte 1: data format enum (see below)
|
||||
// byte 2: byte offset of the channel in the vertex struct
|
||||
// byte 3: vertex stream of the channel, can only be 0 or 1
|
||||
// FORMAT: // no handling for other types, if you want to use ints for data just make sure the output channel is not compressed and float32, and asfloat the ints
|
||||
#define FORMAT_FLOAT 4 // 0 - float
|
||||
#define FORMAT_HALF 3 // 1 - half
|
||||
#define FORMAT_SNORM 2 // 2 - snorm
|
||||
#define FORMAT_UNORM 1 // 3 - unorm
|
||||
|
||||
|
||||
cbuffer MeshInBuffer
|
||||
{
|
||||
float4x4 ObjectToWorld;
|
||||
float4x4 WorldToObject;
|
||||
float4 lightmapScaleOffset;
|
||||
float4 dynLightmapScaleOffset;
|
||||
int4 offset_strideIn_TanSign; // offset in the output buffer to write this mesh at in x, vertex struct stride in y, sign of the tangent and the offset of the optional secondary output buffer on z, and stride of the optional secondary input buffer on w
|
||||
uint4 inPackedChannelInfo[3];
|
||||
};
|
||||
|
||||
cbuffer MeshOutBuffer
|
||||
{
|
||||
int4 strideOut; // vertex struct stride in x, stride of the optional second vertex buffer in y, rest empty
|
||||
uint4 outPackedChannelInfo[3];
|
||||
};
|
||||
|
||||
|
||||
uint GetDimension(uint packedInfo)
|
||||
{
|
||||
return (packedInfo & 0x00000ffu);
|
||||
}
|
||||
|
||||
uint GetFormat(uint packedInfo)
|
||||
{
|
||||
return (packedInfo >> 8) & 0x00000ffu;
|
||||
}
|
||||
|
||||
uint GetOffset(uint packedInfo)
|
||||
{
|
||||
return (packedInfo >> 16) & 0x000000ffu;
|
||||
}
|
||||
|
||||
uint GetByteCount(uint packedInfo)
|
||||
{
|
||||
return (packedInfo >> 24) & 0x00000ffu;
|
||||
}
|
||||
|
||||
// UNorm to Float -------------------------------------------------------------
|
||||
|
||||
|
||||
float4 ConvertUNorm4ToFloat4(uint packedValue)
|
||||
{
|
||||
uint4 unpackedVal = uint4(0x000000FF & packedValue, 0x000000FF & (packedValue >> 8), 0x000000FF & (packedValue >> 16), packedValue >> 24);
|
||||
float4 floatVal = (1.0 / 255.0) * float4(unpackedVal);
|
||||
return floatVal;
|
||||
}
|
||||
|
||||
// Float To UNorm -------------------------------------------------------------
|
||||
|
||||
|
||||
uint ConvertFloat4ToUNorm4(float4 value)
|
||||
{
|
||||
uint4 int3Val = uint4(round(255.0 * saturate(value)));
|
||||
//int3Val = int3Val & 0x000000FFu;
|
||||
uint intVal = int3Val.x | (int3Val.y << 8) | (int3Val.z << 16) | (int3Val.w << 24);
|
||||
return intVal;
|
||||
}
|
||||
|
||||
|
||||
// SNorm to Float -------------------------------------------------------------
|
||||
|
||||
|
||||
float4 ConvertSNorm4ToFloat4(uint packedValue)
|
||||
{
|
||||
uint4 unpackedVal = uint4(packedValue << 24, 0xFF000000 & (packedValue << 16), 0xFF000000 & (packedValue << 8), 0xFF000000 & packedValue);
|
||||
int4 unpackedSVal = asint(unpackedVal) / 0x1000000;
|
||||
float4 floatVal = max((1.0 / 127.0) * float4(unpackedSVal), -1.0);
|
||||
return floatVal;
|
||||
}
|
||||
|
||||
// Float To SNorm -------------------------------------------------------------
|
||||
|
||||
uint ConvertFloat4ToSNorm4(float4 value)
|
||||
{
|
||||
int4 valueSign = sign(value);
|
||||
int4 intVal = int4(round(clamp(value, -1.0, 1.0) * 127.0)) * 0x1000000; // multiply by 2^24 to shift the non-sign bits 24 bits to the right, so we have an 8 bit signed int at the end of the 32-bit int
|
||||
uint4 uintVal = asuint(intVal);
|
||||
uint composite = uintVal.x >> 24 | (uintVal.y >> 16) | (uintVal.z >> 8) | (uintVal.w);
|
||||
return composite;
|
||||
}
|
||||
|
||||
// Half to Float --------------------------------------------------------------
|
||||
|
||||
|
||||
float4 ConvertHalf4toFloat4(uint2 packedValue)
|
||||
{
|
||||
uint4 unpackedInt = uint4(packedValue.x, packedValue.x >> 16, packedValue.y, packedValue.y >> 16);
|
||||
return f16tof32(unpackedInt);
|
||||
}
|
||||
|
||||
// Float to Half --------------------------------------------------------------
|
||||
|
||||
uint2 ConvertFloat4ToHalf4(float4 value)
|
||||
{
|
||||
uint4 halfInt = f32tof16(value);
|
||||
return uint2(halfInt.x | (halfInt.y << 16), halfInt.z | (halfInt.w << 16));
|
||||
}
|
||||
|
||||
// Write To Buffer --------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
// Write a float, int, half2, short2, unorm4, or snorm4 to the buffer
|
||||
void Write4Bytes(uint value, uint adr, const RWByteAddressBuffer buffer)
|
||||
{
|
||||
buffer.Store(adr, value);
|
||||
}
|
||||
|
||||
// write a float2, int2, half4 or short4 to the buffer
|
||||
void Write8Bytes(const uint2 value, const uint adr, const RWByteAddressBuffer buffer)
|
||||
{
|
||||
buffer.Store2(adr, value);
|
||||
}
|
||||
|
||||
// write a float3 or int3 to the buffer
|
||||
void Write12Bytes(const uint3 value, const uint adr, const RWByteAddressBuffer buffer)
|
||||
{
|
||||
buffer.Store3(adr, value);
|
||||
}
|
||||
|
||||
// write a float4 or int4 to the buffer
|
||||
void Write16Bytes(const uint4 value, const uint adr, const RWByteAddressBuffer buffer)
|
||||
{
|
||||
buffer.Store4(adr, value);
|
||||
}
|
||||
|
||||
static uint formatToBytes[] = {1, 1, 1, 2, 4};
|
||||
|
||||
void WriteValue(const RWByteAddressBuffer buffer, const uint adr, const uint format, const uint dimension, const uint4 value)
|
||||
{
|
||||
uint byteCount = formatToBytes[format];
|
||||
uint byteCase = byteCount * dimension;
|
||||
switch (byteCase)
|
||||
{
|
||||
case 4:
|
||||
Write4Bytes(value.x, adr, buffer);
|
||||
break;
|
||||
case 8:
|
||||
Write8Bytes(value.xy, adr, buffer);
|
||||
break;
|
||||
case 12:
|
||||
Write12Bytes(value.xyz, adr, buffer);
|
||||
break;
|
||||
case 16:
|
||||
Write16Bytes(value, adr, buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteValueVtx(const RWByteAddressBuffer buffer, const uint adr, const uint format, const uint dimension, const uint4 value)
|
||||
{
|
||||
uint byteCount = formatToBytes[format];
|
||||
uint byteCase = byteCount * dimension;
|
||||
switch (byteCase)
|
||||
{
|
||||
case 8:
|
||||
Write8Bytes(value.xy, adr, buffer);
|
||||
break;
|
||||
case 12:
|
||||
Write12Bytes(value.xyz, adr, buffer);
|
||||
break;
|
||||
default:
|
||||
Write12Bytes(uint3(1, 1, 1), adr, buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteValueNorm(const RWByteAddressBuffer buffer, const uint adr, const uint format, const uint dimension, const uint4 value)
|
||||
{
|
||||
uint byteCount = formatToBytes[format];
|
||||
uint byteCase = byteCount * dimension;
|
||||
switch (byteCase)
|
||||
{
|
||||
case 4:
|
||||
Write4Bytes(value.x, adr, buffer);
|
||||
break;
|
||||
case 8:
|
||||
Write8Bytes(value.xy, adr, buffer);
|
||||
break;
|
||||
case 12:
|
||||
Write12Bytes(value.xyz, adr, buffer);
|
||||
break;
|
||||
default:
|
||||
Write8Bytes(uint2(0xFFFFFFFF, 0xFFFFFFFF), adr, buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteValueTanColor(const RWByteAddressBuffer buffer, const uint adr, const uint format, const uint dimension, const uint4 value)
|
||||
{
|
||||
uint byteCount = formatToBytes[format];
|
||||
uint byteCase = byteCount * dimension;
|
||||
switch (byteCase)
|
||||
{
|
||||
case 4:
|
||||
Write4Bytes(value.x, adr, buffer);
|
||||
break;
|
||||
case 8:
|
||||
Write8Bytes(value.xy, adr, buffer);
|
||||
break;
|
||||
case 16:
|
||||
Write16Bytes(value, adr, buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float4 ConvertRawToFloat(const uint4 value, uint format)
|
||||
{
|
||||
format = clamp(format, FORMAT_UNORM, FORMAT_FLOAT);
|
||||
switch (format)
|
||||
{
|
||||
case FORMAT_UNORM: // unorm
|
||||
return ConvertUNorm4ToFloat4(value.x);
|
||||
break;
|
||||
case FORMAT_SNORM: // snorm
|
||||
return ConvertSNorm4ToFloat4(value.x);
|
||||
break;
|
||||
case FORMAT_HALF: // half
|
||||
return ConvertHalf4toFloat4(value.xy);
|
||||
break;
|
||||
case FORMAT_FLOAT: // float
|
||||
return asfloat(value);
|
||||
break;
|
||||
default:
|
||||
return float4(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
uint4 ConvertFloatToRaw(const float4 value, uint format)
|
||||
{
|
||||
format = clamp(format, FORMAT_UNORM, FORMAT_FLOAT);
|
||||
switch (format)
|
||||
{
|
||||
case FORMAT_FLOAT: // float
|
||||
return asuint(value);
|
||||
break;
|
||||
case FORMAT_HALF: // half
|
||||
return uint4(ConvertFloat4ToHalf4(value), 0, 0);
|
||||
break;
|
||||
case FORMAT_SNORM: // snorm
|
||||
return uint4(ConvertFloat4ToSNorm4(value), 0, 0, 0);
|
||||
break;
|
||||
case FORMAT_UNORM: // unorm
|
||||
return uint4(ConvertFloat4ToUNorm4(value), 0, 0, 0);
|
||||
break;
|
||||
default:
|
||||
return float4(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void WritePositionChannel(const uint inPackedInfo, const uint outPackedInfo, const uint vertInAdr, const uint vertOutAdr)
|
||||
{
|
||||
uint address = vertInAdr; // The vertex position should always be at the start of the struct, don't bother adding the offset
|
||||
uint4 rawData = vertIn.Load4(address);
|
||||
uint inFmt = GetFormat(inPackedInfo);
|
||||
float4 position = ConvertRawToFloat(rawData, inFmt);
|
||||
position.w = 1;
|
||||
position = mul(ObjectToWorld, position);
|
||||
rawData = asuint(position);
|
||||
uint outAddress = vertOutAdr; // The vertex position should always be at the start of the struct, don't bother adding the offset
|
||||
WriteValueVtx(vertOut, outAddress, FORMAT_FLOAT, 3, rawData);
|
||||
}
|
||||
|
||||
void WriteNormalChannel(uint inPackedInfo, uint outPackedInfo, uint vertInAdr, uint vertOutAdr)
|
||||
{
|
||||
[branch]
|
||||
if (GetDimension(inPackedInfo) > 0u) // output mesh guaranteed to have the channel if an input mesh has it
|
||||
{
|
||||
uint address = vertInAdr + GetOffset(inPackedInfo);
|
||||
uint4 rawData = vertIn.Load4(address);
|
||||
uint inFmt = GetFormat(inPackedInfo);
|
||||
|
||||
float3 normal = ConvertRawToFloat(rawData, inFmt).xyz; // normal is always 3 components, if not wtf are you doing?
|
||||
|
||||
// Transform to world space using the appropriate method for normals, but don't bother normalizing the normal. The shader is going to do that anyways, and this ensures the rendering behavior of the combined mesh matches the indiviudal mesh
|
||||
normal = mul(normal, (float3x3) WorldToObject);
|
||||
|
||||
uint outFmt = GetFormat(outPackedInfo);
|
||||
if (outFmt == FORMAT_SNORM)
|
||||
{
|
||||
normal = normalize(normal);
|
||||
}
|
||||
outFmt = clamp(outFmt, FORMAT_SNORM, FORMAT_FLOAT);
|
||||
rawData = ConvertFloatToRaw(float4(normal.xyz, 0), outFmt);
|
||||
uint outAddress = vertOutAdr + GetOffset(outPackedInfo);
|
||||
uint outDimension = GetDimension(outPackedInfo);
|
||||
// uint inByteEnum = clamp(GetByteCountEnum(inPackedInfo), BYTECOUNT_3, BYTECOUNT_12); //always 3 components, can be snorm to float
|
||||
WriteValueNorm(vertOut, outAddress, outFmt, outDimension, rawData);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteTangentChannel(uint inPackedInfo, uint outPackedInfo, uint vertInAdr, uint vertOutAdr)
|
||||
{
|
||||
[branch]
|
||||
if (GetDimension(inPackedInfo) > 0u) // output mesh guaranteed to have the channel if an input mesh has it
|
||||
{
|
||||
uint address = vertInAdr + GetOffset(inPackedInfo);
|
||||
uint4 rawData = vertIn.Load4(address);
|
||||
uint inFmt = GetFormat(inPackedInfo);
|
||||
|
||||
float4 tangent = ConvertRawToFloat(rawData, inFmt); // tangent is always 4 components, if not wtf are you doing?
|
||||
|
||||
// Transform to world space, and flip the direction if the mesh is scaled negatively.
|
||||
tangent.xyz = mul((float3x3) ObjectToWorld, tangent.xyz);
|
||||
tangent.w *= offset_strideIn_TanSign.z < 0 ? -1 : 1;
|
||||
|
||||
uint outFmt = GetFormat(outPackedInfo);
|
||||
outFmt = clamp(outFmt, FORMAT_SNORM, FORMAT_FLOAT);
|
||||
if (outFmt == FORMAT_SNORM)
|
||||
{
|
||||
tangent.xyz = normalize(tangent.xyz);
|
||||
}
|
||||
rawData = ConvertFloatToRaw(tangent, outFmt);
|
||||
uint outAddress = vertOutAdr + GetOffset(outPackedInfo);
|
||||
//uint inByteEnum = clamp(GetByteCountEnum(inPackedInfo), BYTECOUNT_4, BYTECOUNT_16); //always 4 components, can be snorm to float
|
||||
|
||||
WriteValueTanColor(vertOut, outAddress, outFmt, 4, rawData);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteColorChannel(uint inPackedInfo, uint outPackedInfo, uint vertInAdr, uint vertOutAdr)
|
||||
{
|
||||
[branch]
|
||||
if ((outPackedInfo & 0x0000000fu) > 0u) // input mesh not guaranteed to have color even if the output does, thus we have to initialize it to 1,1,1,1 if it doesn't
|
||||
{
|
||||
float4 color;
|
||||
uint4 rawData;
|
||||
uint inDimension = GetDimension(inPackedInfo);
|
||||
if (inDimension > 0u)
|
||||
{
|
||||
uint address = vertInAdr + GetOffset(inPackedInfo);
|
||||
rawData = vertIn.Load4(address);
|
||||
uint inFmt = GetFormat(inPackedInfo);
|
||||
color = ConvertRawToFloat(rawData, inFmt);
|
||||
}
|
||||
else
|
||||
{
|
||||
color = float4(1, 1, 1, 1);
|
||||
}
|
||||
|
||||
uint outFmt = GetFormat(outPackedInfo);
|
||||
rawData = ConvertFloatToRaw(color, outFmt);
|
||||
uint outAddress = vertOutAdr + GetOffset(outPackedInfo);
|
||||
uint outDimension = GetDimension(outPackedInfo);
|
||||
//uint inByteEnum = GetByteCountEnum(inPackedInfo);
|
||||
WriteValueTanColor(vertOut, outAddress, outFmt, outDimension, rawData);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteUVChannel(uint inPackedInfo, uint outPackedInfo, uint vertInAdr, uint vertOutAdr, bool lmScaleOffset, bool dynLmScaleOffset)
|
||||
{
|
||||
[branch]
|
||||
if (GetDimension(inPackedInfo) > 0u) // output mesh guaranteed to have the channel if an input mesh has it
|
||||
{
|
||||
uint address = vertInAdr + GetOffset(inPackedInfo);
|
||||
uint4 rawData = vertIn.Load4(address);
|
||||
uint inFmt = GetFormat(inPackedInfo);
|
||||
|
||||
float4 UV = ConvertRawToFloat(rawData, inFmt);
|
||||
if (lmScaleOffset)
|
||||
UV.xy = UV.xy * lightmapScaleOffset.xy + lightmapScaleOffset.zw;
|
||||
if (lmScaleOffset)
|
||||
UV.xy = UV.xy * dynLightmapScaleOffset.xy + dynLightmapScaleOffset.zw;
|
||||
uint outFmt = GetFormat(outPackedInfo);
|
||||
rawData = ConvertFloatToRaw(UV, outFmt);
|
||||
uint outAddress = vertOutAdr + GetOffset(outPackedInfo);
|
||||
uint inDimension = GetDimension(inPackedInfo);
|
||||
//uint inByteEnum = GetByteCountEnum(inPackedInfo);
|
||||
WriteValue(vertOut, outAddress, outFmt, inDimension, rawData);
|
||||
}
|
||||
}
|
||||
|
||||
[numthreads(32,1,1)]
|
||||
void CSMain (uint3 id : SV_DispatchThreadID)
|
||||
{
|
||||
uint vertInAdr = (id.x * asuint(offset_strideIn_TanSign.y));
|
||||
uint vertOutAdr = (id.x * asuint(strideOut.x) + offset_strideIn_TanSign.x);
|
||||
|
||||
WritePositionChannel(inPackedChannelInfo[0][0], outPackedChannelInfo[0][0], vertInAdr, vertOutAdr);
|
||||
WriteNormalChannel(inPackedChannelInfo[0][1], outPackedChannelInfo[0][1], vertInAdr, vertOutAdr);
|
||||
WriteTangentChannel(inPackedChannelInfo[0][2], outPackedChannelInfo[0][2], vertInAdr, vertOutAdr);
|
||||
WriteColorChannel(inPackedChannelInfo[0][3], outPackedChannelInfo[0][3], vertInAdr, vertOutAdr);
|
||||
|
||||
[unroll] for (int r = 1; r < 3; r++)
|
||||
[unroll] for (int c = 0; c < 4; c++)
|
||||
{
|
||||
WriteUVChannel(inPackedChannelInfo[r][c], outPackedChannelInfo[r][c], vertInAdr, vertOutAdr, (r == 1) && (c == 1), (r == 1) && (c == 2));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5bae5a4c97f51964dbc10d3398312270
|
||||
ComputeShaderImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8d8c2081982a9744087b1ed64897360c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,506 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Burst;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using UnityEngine;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine.UIElements;
|
||||
using System;
|
||||
|
||||
|
||||
namespace SLZ.CustomStaticBatching
|
||||
{
|
||||
public unsafe class TransferVtxDummyCompileGeneric
|
||||
{
|
||||
static IntPtr DummyCompileGeneric(NativeArray<byte> dummy)
|
||||
{
|
||||
IntPtr a = (IntPtr)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks<byte>(dummy);
|
||||
throw new InvalidOperationException("DummyCompileGeneric is not a real method, exists solely to force compilation of a generic method");
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
|
||||
public unsafe struct TransferVtxBuffer : IJobParallelFor
|
||||
{
|
||||
[NativeDisableUnsafePtrRestriction]
|
||||
[ReadOnly]
|
||||
public NativeArray<byte> vertIn;
|
||||
|
||||
[NativeDisableUnsafePtrRestriction]
|
||||
[ReadOnly]
|
||||
public NativeArray<byte> vertIn2;
|
||||
|
||||
[NativeDisableUnsafePtrRestriction]
|
||||
[NativeDisableParallelForRestriction]
|
||||
[WriteOnly]
|
||||
public NativeArray<byte> vertOut;
|
||||
|
||||
[NativeDisableUnsafePtrRestriction]
|
||||
[NativeDisableParallelForRestriction]
|
||||
[WriteOnly]
|
||||
public NativeArray<byte> vertOut2;
|
||||
|
||||
[ReadOnly]
|
||||
public float4x4 ObjectToWorld;
|
||||
[ReadOnly]
|
||||
public float4x4 WorldToObject;
|
||||
[ReadOnly]
|
||||
public float4 lightmapScaleOffset;
|
||||
[ReadOnly]
|
||||
public float4 dynLightmapScaleOffset;
|
||||
[ReadOnly]
|
||||
public int4 offset_strideIn_offset2_strideIn2; // Sign of the tangent is stored in the sign of strideIn2. strideIn2 should be set to 1/-1 if there is no second input buffer.
|
||||
[ReadOnly]
|
||||
public NativeArray<PackedChannel> inPackedChannelInfo;
|
||||
|
||||
[ReadOnly]
|
||||
public int4 strideOut;
|
||||
[ReadOnly]
|
||||
public NativeArray<PackedChannel> outPackedChannelInfo;
|
||||
[ReadOnly]
|
||||
public FixedList32Bytes<uint> formatToBytes;
|
||||
|
||||
public void Execute(int i)
|
||||
{
|
||||
uint id = (uint)i;
|
||||
uint vertInAdr = (id * asuint(offset_strideIn_offset2_strideIn2.y));
|
||||
uint vertOutAdr = (id * asuint(strideOut.x) + asuint(offset_strideIn_offset2_strideIn2.x));
|
||||
uint vertIn2Adr = (id * asuint(abs(offset_strideIn_offset2_strideIn2.w)));
|
||||
uint vertOut2Adr = (id * asuint(strideOut.y) + asuint(offset_strideIn_offset2_strideIn2.z));
|
||||
|
||||
WritePositionChannel(inPackedChannelInfo[0].packedData, outPackedChannelInfo[0].packedData, vertInAdr, vertOutAdr);
|
||||
WriteNormalChannel(inPackedChannelInfo[1].packedData, outPackedChannelInfo[1].packedData, vertInAdr, vertOutAdr, vertIn2Adr, vertOut2Adr);
|
||||
WriteTangentChannel(inPackedChannelInfo[2].packedData, outPackedChannelInfo[2].packedData, vertInAdr, vertOutAdr, vertIn2Adr, vertOut2Adr);
|
||||
WriteColorChannel(inPackedChannelInfo[3].packedData, outPackedChannelInfo[3].packedData, vertInAdr, vertOutAdr, vertIn2Adr, vertOut2Adr);
|
||||
|
||||
for (int r = 4; r < 12; r++)
|
||||
{
|
||||
WriteUVChannel(inPackedChannelInfo[r].packedData, outPackedChannelInfo[r].packedData, vertInAdr, vertOutAdr, vertIn2Adr, vertOut2Adr, (r == 5), (r == 6));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const int
|
||||
FORMAT_FLOAT = 4, // 0 - float
|
||||
FORMAT_HALF = 3, // 1 - half
|
||||
FORMAT_SNORM = 2, // 2 - snorm
|
||||
FORMAT_UNORM = 1; // 3 - unorm
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static uint GetDimension(uint packedInfo)
|
||||
{
|
||||
return (packedInfo & 0x00000ffu);
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static uint GetFormat(uint packedInfo)
|
||||
{
|
||||
return (packedInfo >> 8) & 0x00000ffu;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static uint GetOffset(uint packedInfo)
|
||||
{
|
||||
return (packedInfo >> 16) & 0x000000ffu;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static uint GetStream(uint packedInfo)
|
||||
{
|
||||
return (packedInfo >> 24);
|
||||
}
|
||||
|
||||
// UNorm to Float -------------------------------------------------------------
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static float4 ConvertUNorm4ToFloat4(uint packedValue)
|
||||
{
|
||||
uint4 unpackedVal = new uint4(0x000000FF & packedValue, 0x000000FF & (packedValue >> 8), 0x000000FF & (packedValue >> 16), packedValue >> 24);
|
||||
float4 floatVal = (1.0f / 255.0f) * new float4(unpackedVal);
|
||||
return floatVal;
|
||||
}
|
||||
|
||||
// Float To UNorm -------------------------------------------------------------
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static uint ConvertFloat4ToUNorm4(float4 value)
|
||||
{
|
||||
uint4 int3Val = new uint4(round(255.0f * saturate(value)));
|
||||
//int3Val = int3Val & 0x000000FFu;
|
||||
uint intVal = int3Val.x | (int3Val.y << 8) | (int3Val.z << 16) | (int3Val.w << 24);
|
||||
return intVal;
|
||||
}
|
||||
|
||||
|
||||
// SNorm to Float -------------------------------------------------------------
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static float4 ConvertSNorm4ToFloat4(uint packedValue)
|
||||
{
|
||||
uint4 unpackedVal = new uint4(packedValue << 24, 0xFF000000 & (packedValue << 16), 0xFF000000 & (packedValue << 8), 0xFF000000 & packedValue);
|
||||
int4 unpackedSVal = asint(unpackedVal) / 0x1000000;
|
||||
float4 floatVal = max((1.0f / 127.0f) * new float4(unpackedSVal), -1.0f);
|
||||
return floatVal;
|
||||
}
|
||||
|
||||
// Float To SNorm -------------------------------------------------------------
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static uint ConvertFloat4ToSNorm4(float4 value)
|
||||
{
|
||||
int4 intVal = new int4(round(clamp(value, -1.0f, 1.0f) * 127.0f)) * 0x1000000; // multiply by 2^24 to shift the non-sign bits 24 bits to the right, so we have an 8 bit signed int at the end of the 32-bit int
|
||||
uint4 uintVal = asuint(intVal);
|
||||
uint composite = uintVal.x >> 24 | (uintVal.y >> 16) | (uintVal.z >> 8) | (uintVal.w);
|
||||
return composite;
|
||||
}
|
||||
|
||||
// Half to Float --------------------------------------------------------------
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static float4 ConvertHalf4toFloat4(uint2 packedValue)
|
||||
{
|
||||
uint4 unpackedInt = new uint4(packedValue.x, packedValue.x >> 16, packedValue.y, packedValue.y >> 16);
|
||||
return f16tof32(unpackedInt);
|
||||
}
|
||||
|
||||
// Float to Half --------------------------------------------------------------
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static uint2 ConvertFloat4ToHalf4(float4 value)
|
||||
{
|
||||
uint4 halfInt = f32tof16(value);
|
||||
return new uint2(halfInt.x | (halfInt.y << 16), halfInt.z | (halfInt.w << 16));
|
||||
}
|
||||
// Write a float, int, half2, short2, unorm4, or snorm4 to the buffer
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static void Write4Bytes(uint value, uint adr, ref NativeArray<byte> buffer)
|
||||
{
|
||||
uint* ptr = (uint*)((byte*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(buffer) + adr);
|
||||
*ptr = value;
|
||||
}
|
||||
|
||||
// write a float2, int2, half4 or short4 to the buffer
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static void Write8Bytes(uint2 value, uint adr, ref NativeArray<byte> buffer)
|
||||
{
|
||||
uint* ptr = (uint*)((byte*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(buffer) + adr);
|
||||
ptr[0] = value.x;
|
||||
ptr[1] = value.y;
|
||||
}
|
||||
|
||||
// write a float3 or int3 to the buffer
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static void Write12Bytes(uint3 value, uint adr, ref NativeArray<byte> buffer)
|
||||
{
|
||||
uint* ptr = (uint*)((byte*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(buffer) + adr);
|
||||
ptr[0] = value.x;
|
||||
ptr[1] = value.y;
|
||||
ptr[2] = value.z;
|
||||
}
|
||||
|
||||
// write a float4 or int4 to the buffer
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static void Write16Bytes(uint4 value, uint adr, ref NativeArray<byte> buffer)
|
||||
{
|
||||
uint* ptr = (uint*)((byte*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(buffer) + adr);
|
||||
ptr[0] = value.x;
|
||||
ptr[1] = value.y;
|
||||
ptr[2] = value.z;
|
||||
ptr[3] = value.w;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void WriteValue(NativeArray<byte> buffer, uint adr, uint format, uint dimension, uint4 value)
|
||||
{
|
||||
uint byteCount = formatToBytes[asint(format)];
|
||||
uint byteCase = byteCount * dimension;
|
||||
switch (byteCase)
|
||||
{
|
||||
case 4:
|
||||
Write4Bytes(value.x, adr, ref buffer);
|
||||
break;
|
||||
case 8:
|
||||
Write8Bytes(value.xy, adr, ref buffer);
|
||||
break;
|
||||
case 12:
|
||||
Write12Bytes(value.xyz, adr, ref buffer);
|
||||
break;
|
||||
case 16:
|
||||
Write16Bytes(value, adr, ref buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void WriteValueVtx(NativeArray<byte> buffer, uint adr, uint format, uint dimension, uint4 value)
|
||||
{
|
||||
uint byteCount = formatToBytes[asint(format)];
|
||||
uint byteCase = byteCount * dimension;
|
||||
switch (byteCase)
|
||||
{
|
||||
case 8:
|
||||
Write8Bytes(value.xy, adr, ref buffer);
|
||||
break;
|
||||
case 12:
|
||||
Write12Bytes(value.xyz, adr, ref buffer);
|
||||
break;
|
||||
default:
|
||||
Write12Bytes(uint3(1, 1, 1), adr, ref buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void WriteValueNorm(NativeArray<byte> buffer, uint adr, uint format, uint dimension, uint4 value)
|
||||
{
|
||||
uint byteCount = formatToBytes[asint(format)];
|
||||
uint byteCase = byteCount * dimension;
|
||||
switch (byteCase)
|
||||
{
|
||||
case 4:
|
||||
Write4Bytes(value.x, adr, ref buffer);
|
||||
break;
|
||||
case 8:
|
||||
Write8Bytes(value.xy, adr, ref buffer);
|
||||
break;
|
||||
case 12:
|
||||
Write12Bytes(value.xyz, adr, ref buffer);
|
||||
break;
|
||||
default:
|
||||
Write8Bytes(new uint2(0xFFFFFFFF, 0xFFFFFFFF), adr, ref buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void WriteValueTanColor(NativeArray<byte> buffer, uint adr, uint format, uint dimension, uint4 value)
|
||||
{
|
||||
uint byteCount = formatToBytes[asint(format)];
|
||||
uint byteCase = byteCount * dimension;
|
||||
switch (byteCase)
|
||||
{
|
||||
case 4:
|
||||
Write4Bytes(value.x, adr, ref buffer);
|
||||
break;
|
||||
case 8:
|
||||
Write8Bytes(value.xy, adr, ref buffer);
|
||||
break;
|
||||
case 16:
|
||||
Write16Bytes(value, adr, ref buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static float4 ConvertRawToFloat(uint4 value, uint format)
|
||||
{
|
||||
format = clamp(format, FORMAT_UNORM, FORMAT_FLOAT);
|
||||
switch (format)
|
||||
{
|
||||
case FORMAT_UNORM: // unorm
|
||||
return ConvertUNorm4ToFloat4(value.x);
|
||||
break;
|
||||
case FORMAT_SNORM: // snorm
|
||||
return ConvertSNorm4ToFloat4(value.x);
|
||||
break;
|
||||
case FORMAT_HALF: // half
|
||||
return ConvertHalf4toFloat4(value.xy);
|
||||
break;
|
||||
case FORMAT_FLOAT: // float
|
||||
return asfloat(value);
|
||||
break;
|
||||
default:
|
||||
return float4(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static uint4 ConvertFloatToRaw(float4 value, uint format)
|
||||
{
|
||||
format = clamp(format, FORMAT_UNORM, FORMAT_FLOAT);
|
||||
switch (format)
|
||||
{
|
||||
case FORMAT_FLOAT: // float
|
||||
return asuint(value);
|
||||
break;
|
||||
case FORMAT_HALF: // half
|
||||
return uint4(ConvertFloat4ToHalf4(value), 0, 0);
|
||||
break;
|
||||
case FORMAT_SNORM: // snorm
|
||||
return uint4(ConvertFloat4ToSNorm4(value), 0, 0, 0);
|
||||
break;
|
||||
case FORMAT_UNORM: // unorm
|
||||
return uint4(ConvertFloat4ToUNorm4(value), 0, 0, 0);
|
||||
break;
|
||||
default:
|
||||
return uint4(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static uint4 Load4(NativeArray<byte> array, uint address)
|
||||
{
|
||||
byte* baseAddress = (byte*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(array);
|
||||
int alignedAddress = (int)(address >> 2); // divide by 4 to get the 4 byte aligned address
|
||||
byte* ptr = baseAddress + (alignedAddress << 2);
|
||||
|
||||
|
||||
int arrayLength = array.Length >> 2;
|
||||
int remainingSize = arrayLength - alignedAddress;
|
||||
|
||||
if (remainingSize >= 4)
|
||||
{
|
||||
uint4* uPtr = (uint4*)ptr;
|
||||
uint4 outp = *uPtr;
|
||||
return outp;
|
||||
}
|
||||
|
||||
switch (remainingSize)
|
||||
{
|
||||
case 3:
|
||||
uint3* uPtr3 = (uint3*)ptr;
|
||||
uint4 outp3 = new uint4(uPtr3->x, uPtr3->y, uPtr3->z, 0u);
|
||||
return outp3;
|
||||
case 2:
|
||||
uint2* uPtr2 = (uint2*)ptr;
|
||||
uint4 outp2 = new uint4(uPtr2->x, uPtr2->y, 0u, 0u);
|
||||
return outp2;
|
||||
case 1:
|
||||
uint* uPtr1 = (uint*)ptr;
|
||||
uint4 outp1 = new uint4(*uPtr1, 0u, 0u, 0u);
|
||||
return outp1;
|
||||
default:
|
||||
return new uint4(1, 2, 3, 4);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WritePositionChannel(uint inPackedInfo, uint outPackedInfo, uint vertInAdr, uint vertOutAdr)
|
||||
{
|
||||
uint address = vertInAdr; // The vertex position should always be at the start of the struct, don't bother adding the offset
|
||||
uint4 rawData = Load4(vertIn, address);
|
||||
uint inFmt = GetFormat(inPackedInfo);
|
||||
float4 position = ConvertRawToFloat(rawData, inFmt);
|
||||
position.w = 1;
|
||||
position = mul(ObjectToWorld, position);
|
||||
rawData = asuint(position);
|
||||
uint outAddress = vertOutAdr; // The vertex position should always be at the start of the struct, don't bother adding the offset
|
||||
WriteValueVtx(vertOut, outAddress, FORMAT_FLOAT, 3, rawData);
|
||||
}
|
||||
|
||||
void WriteNormalChannel(uint inPackedInfo, uint outPackedInfo, uint vertInAdr, uint vertOutAdr, uint vertInAdr2, uint vertOutAdr2)
|
||||
{
|
||||
|
||||
if (GetDimension(inPackedInfo) > 0u) // output mesh guaranteed to have the channel if an input mesh has it
|
||||
{
|
||||
bool altIn = false;// GetStream(inPackedInfo) > 0;
|
||||
uint address = (altIn ? vertInAdr2 : vertInAdr) + GetOffset(inPackedInfo);
|
||||
uint4 rawData = Load4(altIn ? vertIn2 : vertIn, address);
|
||||
uint inFmt = GetFormat(inPackedInfo);
|
||||
|
||||
float3 normal = ConvertRawToFloat(rawData, inFmt).xyz; // normal is always 3 components, if not wtf are you doing?
|
||||
|
||||
// Transform to world space using the appropriate method for normals, but don't bother normalizing the normal. The shader is going to do that anyways, and this ensures the rendering behavior of the combined mesh matches the indiviudal mesh
|
||||
normal = mul(normal, (float3x3)WorldToObject);
|
||||
|
||||
uint outFmt = GetFormat(outPackedInfo);
|
||||
if (outFmt == FORMAT_SNORM)
|
||||
{
|
||||
normal = normalize(normal);
|
||||
}
|
||||
outFmt = clamp(outFmt, FORMAT_SNORM, FORMAT_FLOAT);
|
||||
rawData = ConvertFloatToRaw(float4(normal.xyz, 0), outFmt);
|
||||
bool altOut = GetStream(outPackedInfo) > 0;
|
||||
uint outAddress = (altOut ? vertOutAdr2 : vertOutAdr) + GetOffset(outPackedInfo);
|
||||
uint outDimension = GetDimension(outPackedInfo);
|
||||
// uint inByteEnum = clamp(GetByteCountEnum(inPackedInfo), BYTECOUNT_3, BYTECOUNT_12); //always 3 components, can be snorm to float
|
||||
|
||||
WriteValueNorm(altOut ? vertOut2 : vertOut, outAddress, outFmt, outDimension, rawData);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteTangentChannel(uint inPackedInfo, uint outPackedInfo, uint vertInAdr, uint vertOutAdr, uint vertInAdr2, uint vertOutAdr2)
|
||||
{
|
||||
if (GetDimension(inPackedInfo) > 0u) // output mesh guaranteed to have the channel if an input mesh has it
|
||||
{
|
||||
bool altIn = GetStream(inPackedInfo) > 0;
|
||||
uint address = (altIn ? vertInAdr2 : vertInAdr) + GetOffset(inPackedInfo);
|
||||
uint4 rawData = Load4(altIn ? vertIn2 : vertIn, address);
|
||||
uint inFmt = GetFormat(inPackedInfo);
|
||||
|
||||
float4 tangent = ConvertRawToFloat(rawData, inFmt); // tangent is always 4 components, if not wtf are you doing?
|
||||
|
||||
// Transform to world space, and flip the direction if the mesh is scaled negatively.
|
||||
tangent.xyz = mul((float3x3)ObjectToWorld, tangent.xyz);
|
||||
tangent.w *= offset_strideIn_offset2_strideIn2.w < 0 ? -1 : 1; // sign of tanget stored in sign of strideIn2
|
||||
|
||||
uint outFmt = GetFormat(outPackedInfo);
|
||||
outFmt = clamp(outFmt, FORMAT_SNORM, FORMAT_FLOAT);
|
||||
if (outFmt == FORMAT_SNORM)
|
||||
{
|
||||
tangent.xyz = normalize(tangent.xyz);
|
||||
}
|
||||
rawData = ConvertFloatToRaw(tangent, outFmt);
|
||||
bool altOut = GetStream(outPackedInfo) > 0;
|
||||
uint outAddress = (altOut ? vertOutAdr2 : vertOutAdr) + GetOffset(outPackedInfo);
|
||||
//uint inByteEnum = clamp(GetByteCountEnum(inPackedInfo), BYTECOUNT_4, BYTECOUNT_16); //always 4 components, can be snorm to float
|
||||
WriteValueTanColor(altOut ? vertOut2 : vertOut, outAddress, outFmt, 4, rawData);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteColorChannel(uint inPackedInfo, uint outPackedInfo, uint vertInAdr, uint vertOutAdr, uint vertInAdr2, uint vertOutAdr2)
|
||||
{
|
||||
if ((outPackedInfo & 0x0000000fu) > 0u) // input mesh not guaranteed to have color even if the output does, so we have to initialize the color to 1,1,1,1 if the input is missing the attribute
|
||||
{
|
||||
float4 color;
|
||||
uint4 rawData;
|
||||
uint inDimension = GetDimension(inPackedInfo);
|
||||
if (inDimension > 0u)
|
||||
{
|
||||
bool altIn = GetStream(inPackedInfo) > 0;
|
||||
uint address = (altIn ? vertInAdr2 : vertInAdr) + GetOffset(inPackedInfo);
|
||||
rawData = Load4(altIn ? vertIn2 : vertIn, address);
|
||||
uint inFmt = GetFormat(inPackedInfo);
|
||||
color = ConvertRawToFloat(rawData, inFmt);
|
||||
}
|
||||
else
|
||||
{
|
||||
color = float4(1, 1, 1, 1);
|
||||
}
|
||||
|
||||
uint outFmt = GetFormat(outPackedInfo);
|
||||
rawData = ConvertFloatToRaw(color, outFmt);
|
||||
bool altOut = GetStream(outPackedInfo) > 0;
|
||||
uint outAddress = (altOut ? vertOutAdr2 : vertOutAdr) + GetOffset(outPackedInfo);
|
||||
uint outDimension = GetDimension(outPackedInfo);
|
||||
//uint inByteEnum = GetByteCountEnum(inPackedInfo);
|
||||
WriteValueTanColor(altOut ? vertOut2 : vertOut, outAddress, outFmt, outDimension, rawData);
|
||||
}
|
||||
}
|
||||
|
||||
void WriteUVChannel(uint inPackedInfo, uint outPackedInfo, uint vertInAdr, uint vertOutAdr, uint vertInAdr2, uint vertOutAdr2, bool lmScaleOffset, bool dynLmScaleOffset)
|
||||
{
|
||||
if (GetDimension(inPackedInfo) > 0u) // output mesh guaranteed to have the channel if an input mesh has it
|
||||
{
|
||||
bool altIn = GetStream(inPackedInfo) > 0;
|
||||
uint address = (altIn ? vertInAdr2 : vertInAdr) + GetOffset(inPackedInfo);
|
||||
uint4 rawData = Load4(altIn ? vertIn2 : vertIn, address);
|
||||
uint inFmt = GetFormat(inPackedInfo);
|
||||
|
||||
float4 UV = ConvertRawToFloat(rawData, inFmt);
|
||||
if (lmScaleOffset)
|
||||
UV.xy = UV.xy * lightmapScaleOffset.xy + lightmapScaleOffset.zw;
|
||||
if (lmScaleOffset)
|
||||
UV.xy = UV.xy * dynLightmapScaleOffset.xy + dynLightmapScaleOffset.zw;
|
||||
uint outFmt = GetFormat(outPackedInfo);
|
||||
rawData = ConvertFloatToRaw(UV, outFmt);
|
||||
bool altOut = GetStream(outPackedInfo) > 0;
|
||||
uint outAddress = (altOut ? vertOutAdr2 : vertOutAdr) + GetOffset(outPackedInfo);
|
||||
uint inDimension = GetDimension(inPackedInfo);
|
||||
//uint inByteEnum = GetByteCountEnum(inPackedInfo);
|
||||
WriteValue(altOut ? vertOut2 : vertOut, outAddress, outFmt, inDimension, rawData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 16a63a8e8ff53de46b9d0e058e78f824
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,145 @@
|
|||
// Derived from https://github.com/keijiro/Akvfx/blob/master/Packages/jp.keijiro.akvfx/Runtime/Internal/Extensions.cs
|
||||
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace SLZ.CustomStaticBatching
|
||||
{
|
||||
public static class CSBBufferExt
|
||||
{
|
||||
// SetData with ReadOnlySpan
|
||||
public unsafe static void SetFromSpan<T>
|
||||
(ComputeBuffer buffer, ReadOnlySpan<T> data) where T : unmanaged
|
||||
{
|
||||
fixed (T* pData = &data.GetPinnableReference())
|
||||
SetData(buffer, (IntPtr)pData, data.Length, sizeof(T));
|
||||
}
|
||||
|
||||
public unsafe static void SetFromSpan<T>
|
||||
(ComputeBuffer buffer, Span<T> data) where T : unmanaged
|
||||
{
|
||||
fixed (T* pData = &data.GetPinnableReference())
|
||||
SetData(buffer, (IntPtr)pData, data.Length, sizeof(T));
|
||||
}
|
||||
|
||||
public unsafe static void SetFromSpan<T>
|
||||
(ComputeBuffer buffer, ReadOnlySpan<T> data, int spanStartIndex, int bufferStartIndex, int count) where T : unmanaged
|
||||
{
|
||||
fixed (T* pData = &data.GetPinnableReference())
|
||||
SetCBNativeDelegate(buffer, (IntPtr)pData, spanStartIndex, bufferStartIndex, count, sizeof(T));
|
||||
}
|
||||
|
||||
public unsafe static void SetFromSpan<T>
|
||||
(ComputeBuffer buffer, Span<T> data, int spanStartIndex, int bufferStartIndex, int count) where T : unmanaged
|
||||
{
|
||||
fixed (T* pData = &data.GetPinnableReference())
|
||||
SetCBNativeDelegate(buffer, (IntPtr)pData, spanStartIndex, bufferStartIndex, count, sizeof(T));
|
||||
}
|
||||
|
||||
public unsafe static void CmdSetFromSpan<T>
|
||||
(CommandBuffer cmd, ComputeBuffer buffer, ReadOnlySpan<T> data) where T : unmanaged
|
||||
{
|
||||
fixed (T* pData = &data.GetPinnableReference())
|
||||
CmdSetData(cmd, buffer, (IntPtr)pData, data.Length, sizeof(T));
|
||||
}
|
||||
|
||||
public unsafe static void CmdSetFromSpan<T>
|
||||
(CommandBuffer cmd, ComputeBuffer buffer, Span<T> data) where T : unmanaged
|
||||
{
|
||||
fixed (T* pData = &data.GetPinnableReference())
|
||||
CmdSetData(cmd, buffer, (IntPtr)pData, data.Length, sizeof(T));
|
||||
}
|
||||
|
||||
public unsafe static void CmdSetFromSpan<T>
|
||||
(CommandBuffer cmd, ComputeBuffer buffer, ReadOnlySpan<T> data, int spanStartIndex, int bufferStartIndex, int count) where T : unmanaged
|
||||
{
|
||||
fixed (T* pData = &data.GetPinnableReference())
|
||||
SetCmdNativeDelegate(cmd, buffer, (IntPtr)pData, spanStartIndex, bufferStartIndex, count, sizeof(T));
|
||||
}
|
||||
|
||||
public unsafe static void CmdSetFromSpan<T>
|
||||
(CommandBuffer cmd, ComputeBuffer buffer, Span<T> data, int spanStartIndex, int bufferStartIndex, int count) where T : unmanaged
|
||||
{
|
||||
fixed (T* pData = &data.GetPinnableReference())
|
||||
SetCmdNativeDelegate(cmd, buffer, (IntPtr)pData, spanStartIndex, bufferStartIndex, count, sizeof(T));
|
||||
}
|
||||
|
||||
// Directly load an unmanaged data array to a compute buffer via an
|
||||
// Intptr. This is not a public interface so will be broken one day.
|
||||
// DO NOT TRY AT HOME.
|
||||
static void SetData
|
||||
(ComputeBuffer buffer, IntPtr pointer, int count, int stride)
|
||||
{
|
||||
/*
|
||||
_args5[0] = pointer;
|
||||
_args5[1] = 0; // source offset
|
||||
_args5[2] = 0; // buffer offset
|
||||
_args5[3] = count;
|
||||
_args5[4] = stride;
|
||||
|
||||
SetNativeData.Invoke(buffer, _args5);
|
||||
*/
|
||||
SetCBNativeDelegate(buffer, pointer, 0, 0, count, stride);
|
||||
}
|
||||
|
||||
static void CmdSetData
|
||||
(CommandBuffer cmd, ComputeBuffer buffer, IntPtr pointer, int count, int stride)
|
||||
{
|
||||
/*
|
||||
_args6[0] = buffer;
|
||||
_args6[1] = pointer;
|
||||
_args6[2] = 0; // source offset
|
||||
_args6[3] = 0; // buffer offset
|
||||
_args6[4] = count;
|
||||
_args6[5] = stride;
|
||||
|
||||
CmdSetNativeData.Invoke(cmd, _args6);
|
||||
*/
|
||||
SetCmdNativeDelegate(cmd, buffer, pointer, 0, 0, count, stride);
|
||||
}
|
||||
|
||||
static MethodInfo _setNativeData;
|
||||
static MethodInfo _cmdSetNativeData;
|
||||
|
||||
static MethodInfo SetNativeData
|
||||
=> _setNativeData ?? (_setNativeData = GetSetNativeDataMethod());
|
||||
|
||||
static MethodInfo CmdSetNativeData
|
||||
=> _cmdSetNativeData ?? (_cmdSetNativeData = GetCmdSetNativeDataMethod());
|
||||
|
||||
static MethodInfo GetSetNativeDataMethod()
|
||||
=> typeof(ComputeBuffer).GetMethod("InternalSetNativeData",
|
||||
BindingFlags.InvokeMethod |
|
||||
BindingFlags.NonPublic |
|
||||
BindingFlags.Instance);
|
||||
static MethodInfo GetCmdSetNativeDataMethod()
|
||||
=> typeof(CommandBuffer).GetMethod("InternalSetComputeBufferNativeData",
|
||||
BindingFlags.InvokeMethod |
|
||||
BindingFlags.NonPublic |
|
||||
BindingFlags.Instance);
|
||||
|
||||
static Action<ComputeBuffer, IntPtr, int, int, int, int> _setCBNativeDelegate;
|
||||
static Action<CommandBuffer, ComputeBuffer, IntPtr, int, int, int, int> _setCmdNativeDelegate;
|
||||
|
||||
static Action<ComputeBuffer, IntPtr, int, int, int, int> GetCBNativeDelegate()
|
||||
{
|
||||
MethodInfo method = GetSetNativeDataMethod();
|
||||
return (Action<ComputeBuffer, IntPtr, int, int, int, int>)Delegate.CreateDelegate(typeof(Action<ComputeBuffer, IntPtr, int, int, int, int>), method);
|
||||
}
|
||||
|
||||
static Action<CommandBuffer, ComputeBuffer, IntPtr, int, int, int, int> GetCmdNativeDelegate()
|
||||
{
|
||||
MethodInfo method = GetCmdSetNativeDataMethod();
|
||||
return (Action<CommandBuffer, ComputeBuffer, IntPtr, int, int, int, int>)Delegate.CreateDelegate(typeof(Action<CommandBuffer, ComputeBuffer, IntPtr, int, int, int, int>), method);
|
||||
}
|
||||
|
||||
static Action<ComputeBuffer, IntPtr, int, int, int, int> SetCBNativeDelegate => _setCBNativeDelegate ?? (_setCBNativeDelegate = GetCBNativeDelegate());
|
||||
static Action<CommandBuffer, ComputeBuffer, IntPtr, int, int, int, int> SetCmdNativeDelegate => _setCmdNativeDelegate ?? (_setCmdNativeDelegate = GetCmdNativeDelegate());
|
||||
|
||||
//static object[] _args5 = new object[5];
|
||||
//static object[] _args6 = new object[6];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5970ca602db4c0142a9f093446f61387
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,27 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
|
||||
public unsafe static class CSBListExt
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T[] GetInternalArray<T>(List<T> list)
|
||||
{
|
||||
if (list == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
ListInternals<T> tListAccess = UnsafeUtility.As<List<T>, ListInternals<T>>(ref list);
|
||||
return tListAccess._items;
|
||||
}
|
||||
|
||||
// Copied from 2023's version of the NoAllocHelpers, names and order are magic and should not be changed
|
||||
private class ListInternals<T>
|
||||
{
|
||||
internal T[] _items;
|
||||
internal int _size;
|
||||
internal int _version;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: aa24622bb4d79604ea1ceebd6e6f6b16
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,261 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Collections;
|
||||
using UnityEngine.Rendering;
|
||||
using Unity.Jobs;
|
||||
using Unity.Burst;
|
||||
using Unity.Profiling;
|
||||
using System.Threading;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Mathematics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace SLZ.CustomStaticBatching
|
||||
{
|
||||
public class MeshUtilities
|
||||
{
|
||||
static ProfilerMarker profilerGetSubMesh = new ProfilerMarker("MeshUtilities.GetSplitMesh");
|
||||
static ProfilerMarker profilerGetSubMeshMarkUsed = new ProfilerMarker("MeshUtilities.GetSplitMesh.MarkUsed");
|
||||
static ProfilerMarker profilerGetSubMeshHashMap = new ProfilerMarker("MeshUtilities.GetSplitMesh.HashMap");
|
||||
static ProfilerMarker profilerGetSubMeshInitMesh = new ProfilerMarker("MeshUtilities.GetSplitMesh.InitMesh");
|
||||
static ProfilerMarker profilerSplitBySubMesh = new ProfilerMarker("MeshUtilities.SplitBySubmesh");
|
||||
// Start is called before the first frame update
|
||||
static Mesh GetSplitMesh(
|
||||
Mesh originalMesh,
|
||||
ref SubMeshDescriptor oldSmDesc,
|
||||
ref NativeArray<ushort> indexBuffer,
|
||||
ref NativeArray<int> oldVertexBuffer,
|
||||
ref NativeArray<int> tempVertexBuffer,
|
||||
ref NativeArray<int> hashMap,
|
||||
int vertexByteStride)
|
||||
{
|
||||
profilerGetSubMesh.Begin();
|
||||
int vertexStride = vertexByteStride / sizeof(int); // Each channel in a mesh buffer MUST be a multiple of 4 bytes!
|
||||
NativeArrayClear.Clear(ref hashMap, hashMap.Length);
|
||||
int indexCount = indexBuffer.Length;
|
||||
int maxIndex = 0;
|
||||
profilerGetSubMeshMarkUsed.Begin();
|
||||
|
||||
|
||||
NativeReference<int> maxIdxRef = new NativeReference<int>(Allocator.TempJob);
|
||||
maxIdxRef.Value = 0;
|
||||
FlagUsedVerticesSerial flagJob = new FlagUsedVerticesSerial() { hashMap = hashMap, maxIdx = maxIdxRef, indexBuffer = indexBuffer };
|
||||
flagJob.Run();
|
||||
maxIndex = flagJob.maxIdx.Value;
|
||||
maxIdxRef.Dispose();
|
||||
|
||||
|
||||
profilerGetSubMeshMarkUsed.End();
|
||||
|
||||
profilerGetSubMeshHashMap.Begin();
|
||||
ushort currentVtx = 0;
|
||||
|
||||
NativeReference<int> vtxCountRef = new NativeReference<int>(Allocator.TempJob);
|
||||
vtxCountRef.Value = 0;
|
||||
PopulateVtxBufferSerial popVtxBuffer = new PopulateVtxBufferSerial()
|
||||
{
|
||||
hashMap = hashMap,
|
||||
oldVertexBuffer = oldVertexBuffer,
|
||||
tempVertexBuffer = tempVertexBuffer,
|
||||
vtxCount = vtxCountRef,
|
||||
maxIndex = maxIndex,
|
||||
vertexIntStride = vertexStride
|
||||
};
|
||||
popVtxBuffer.Run();
|
||||
currentVtx = (ushort)vtxCountRef.Value;
|
||||
vtxCountRef.Dispose();
|
||||
profilerGetSubMeshHashMap.End();
|
||||
|
||||
|
||||
ReIndexBuffer16 reindexJob = new ReIndexBuffer16() { indexBuffer = indexBuffer, hashMap = hashMap };
|
||||
JobHandle reindexJobHandle = reindexJob.Schedule(indexCount, 32);
|
||||
reindexJobHandle.Complete();
|
||||
|
||||
profilerGetSubMeshInitMesh.Begin();
|
||||
Mesh splitMesh = new Mesh();
|
||||
|
||||
MeshUpdateFlags noMeshUpdate = MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontResetBoneBounds | MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontNotifyMeshUsers;
|
||||
splitMesh.SetVertexBufferParams(currentVtx, originalMesh.GetVertexAttributes());
|
||||
splitMesh.SetVertexBufferData<int>(tempVertexBuffer, 0, 0, currentVtx * vertexStride, 0, noMeshUpdate);
|
||||
splitMesh.SetIndexBufferParams(indexCount, IndexFormat.UInt16);
|
||||
SubMeshDescriptor newSmDesc = new SubMeshDescriptor()
|
||||
{
|
||||
indexStart = 0,
|
||||
baseVertex = 0,
|
||||
firstVertex = 0,
|
||||
vertexCount = oldSmDesc.vertexCount,
|
||||
bounds = oldSmDesc.bounds,
|
||||
indexCount = indexCount,
|
||||
topology = oldSmDesc.topology
|
||||
};
|
||||
|
||||
splitMesh.SetSubMesh(0, newSmDesc, noMeshUpdate);
|
||||
splitMesh.bounds = oldSmDesc.bounds;
|
||||
splitMesh.SetIndexBufferData(indexBuffer, 0, 0, indexCount, noMeshUpdate);
|
||||
profilerGetSubMeshInitMesh.End();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
profilerGetSubMesh.End();
|
||||
return splitMesh;
|
||||
}
|
||||
[BurstCompile]
|
||||
struct ReIndexBuffer16 : IJobParallelFor
|
||||
{
|
||||
public NativeArray<ushort> indexBuffer;
|
||||
[ReadOnly]
|
||||
public NativeArray<int> hashMap;
|
||||
|
||||
public void Execute(int i)
|
||||
{
|
||||
indexBuffer[i] = (ushort)hashMap[indexBuffer[i]];
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
struct FlagUsedVertices : IJobParallelFor
|
||||
{
|
||||
[NativeDisableParallelForRestriction]
|
||||
public NativeArray<int> hashMap;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<ushort> indexBuffer;
|
||||
|
||||
public void Execute(int i)
|
||||
{
|
||||
|
||||
unsafe {
|
||||
Interlocked.Increment(ref ((int*)hashMap.GetUnsafePtr())[indexBuffer[i]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
struct FlagUsedVerticesSerial : IJob
|
||||
{
|
||||
[NativeDisableParallelForRestriction]
|
||||
public NativeArray<int> hashMap;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<ushort> indexBuffer;
|
||||
|
||||
public NativeReference<int> maxIdx;
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
int numIdx = indexBuffer.Length;
|
||||
int maxIdxLocal = 0;
|
||||
for (int i = 0; i < numIdx; i++)
|
||||
{
|
||||
int idx = indexBuffer[i];
|
||||
hashMap[idx] = 1;
|
||||
maxIdxLocal = math.max(maxIdxLocal, idx);
|
||||
}
|
||||
maxIdx.Value = maxIdxLocal;
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
struct PopulateVtxBufferSerial : IJob
|
||||
{
|
||||
[NativeDisableParallelForRestriction]
|
||||
public NativeArray<int> hashMap;
|
||||
|
||||
public NativeArray<int> tempVertexBuffer;
|
||||
[ReadOnly]
|
||||
public NativeArray<int> oldVertexBuffer;
|
||||
|
||||
public NativeReference<int> vtxCount;
|
||||
|
||||
public int vertexIntStride;
|
||||
public int maxIndex;
|
||||
|
||||
public unsafe void Execute()
|
||||
{
|
||||
int vtxCountLocal = 0;
|
||||
int* tempVertexBufferPtr = (int*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(tempVertexBuffer);
|
||||
int* oldVertexBufferPtr = (int*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(oldVertexBuffer);
|
||||
long vtxStructSize = sizeof(int) * vertexIntStride;
|
||||
for (int i = 0; i <= maxIndex; i++)
|
||||
{
|
||||
if (hashMap[i] > 0)
|
||||
{
|
||||
|
||||
hashMap[i] = vtxCountLocal;
|
||||
//CopyUnsafe<int>(oldVertexBuffer, i * vertexStride, tempVertexBuffer, vtxIdx * vertexStride, vertexStride);
|
||||
UnsafeUtility.MemCpy(tempVertexBufferPtr + vtxCountLocal * vertexIntStride, oldVertexBufferPtr + i * vertexIntStride, vtxStructSize);
|
||||
vtxCountLocal++;
|
||||
}
|
||||
}
|
||||
vtxCount.Value = vtxCountLocal;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe static void CopyUnsafe<T>(NativeArray<T> src, int srcIndex, NativeArray<T> dst, int dstIndex, int length) where T : struct
|
||||
{
|
||||
UnsafeUtility.MemCpy((byte*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(dst) + dstIndex * UnsafeUtility.SizeOf<T>(),
|
||||
(byte*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(src) + srcIndex * UnsafeUtility.SizeOf<T>(), length * UnsafeUtility.SizeOf<T>());
|
||||
}
|
||||
|
||||
static Mesh[] SplitBySubmesh16(Mesh mesh, Mesh.MeshData meshData)
|
||||
{
|
||||
profilerSplitBySubMesh.Begin();
|
||||
int subMeshCount = meshData.subMeshCount;
|
||||
if (subMeshCount <= 1)
|
||||
{
|
||||
return new Mesh[1] { mesh };
|
||||
}
|
||||
Mesh[] outMeshes = new Mesh[subMeshCount];
|
||||
int vertexBufferStride = meshData.GetVertexBufferStride(0);
|
||||
NativeArray<int> oldVertexBuffer = meshData.GetVertexData<int>(0);
|
||||
NativeArray<int> tempVertexBuffer = new NativeArray<int>(meshData.vertexCount * (vertexBufferStride / sizeof(int)), Allocator.TempJob);
|
||||
NativeArray<int> hashMap = new NativeArray<int>(meshData.vertexCount, Allocator.TempJob);
|
||||
try
|
||||
{
|
||||
for (int smIdx = 0; smIdx < subMeshCount; smIdx++)
|
||||
{
|
||||
SubMeshDescriptor subMeshDescriptor = meshData.GetSubMesh(smIdx);
|
||||
NativeArray<ushort> indexBuffer = new NativeArray<ushort>(subMeshDescriptor.indexCount, Allocator.TempJob);
|
||||
meshData.GetIndices(indexBuffer, smIdx, true);
|
||||
outMeshes[smIdx] = GetSplitMesh(
|
||||
mesh,
|
||||
ref subMeshDescriptor,
|
||||
ref indexBuffer,
|
||||
ref oldVertexBuffer,
|
||||
ref tempVertexBuffer,
|
||||
ref hashMap,
|
||||
vertexBufferStride
|
||||
);
|
||||
indexBuffer.Dispose();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
tempVertexBuffer.Dispose();
|
||||
hashMap.Dispose();
|
||||
}
|
||||
profilerSplitBySubMesh.End();
|
||||
return outMeshes;
|
||||
}
|
||||
|
||||
public static Mesh[] SplitBySubmesh(Mesh mesh, Mesh.MeshData meshData)
|
||||
{
|
||||
|
||||
if (meshData.indexFormat == IndexFormat.UInt16)
|
||||
{
|
||||
return SplitBySubmesh16(mesh, meshData);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Could not split mesh, 32 bit index buffer not implemented yet");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c10da140626dac84594172d21696e0a9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,19 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace SLZ.CustomStaticBatching
|
||||
{
|
||||
public unsafe static class NativeArrayClear
|
||||
{
|
||||
unsafe public static void Clear<T>(ref NativeArray<T> array, long count, long start = 0) where T : struct
|
||||
{
|
||||
UnsafeUtility.MemClear(
|
||||
(byte*)NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(array) + start * UnsafeUtility.SizeOf<T>(),
|
||||
count * UnsafeUtility.SizeOf<T>()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 62e8b5071153e944485b48cb6f80b125
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SLZ.CustomStaticBatching
|
||||
{
|
||||
public static class CSBNativeArraySpanExt
|
||||
{
|
||||
public unsafe static void Copy<T>(Span<T> src, int srcIndex, NativeArray<T> dst, int dstIndex, int length) where T : unmanaged
|
||||
{
|
||||
fixed (T* pData = &src.GetPinnableReference())
|
||||
{
|
||||
//AtomicSafetyHandle.CheckWriteAndThrow(dst.m_Safety);
|
||||
UnsafeUtility.MemCpy((byte*)dst.GetUnsafePtr<T>() + dstIndex * UnsafeUtility.SizeOf<T>(), (byte*)(void*)pData + srcIndex * UnsafeUtility.SizeOf<T>(), length * UnsafeUtility.SizeOf<T>());
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe static void Copy<T>(ReadOnlySpan<T> src, int srcIndex, NativeArray<T> dst, int dstIndex, int length) where T : unmanaged
|
||||
{
|
||||
fixed (T* pData = &src.GetPinnableReference())
|
||||
{
|
||||
//AtomicSafetyHandle.CheckWriteAndThrow(dst.m_Safety);
|
||||
UnsafeUtility.MemCpy((byte*)dst.GetUnsafePtr<T>() + dstIndex * UnsafeUtility.SizeOf<T>(), (byte*)(void*)pData + srcIndex * UnsafeUtility.SizeOf<T>(), length * UnsafeUtility.SizeOf<T>());
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe static void Copy<T>(NativeArray<T> src, int srcIndex, Span<T> dst, int dstIndex, int length) where T : unmanaged
|
||||
{
|
||||
fixed (T* pData = &dst.GetPinnableReference())
|
||||
{
|
||||
//AtomicSafetyHandle.CheckWriteAndThrow(dst.m_Safety);
|
||||
UnsafeUtility.MemCpy((byte*)(void*)pData + dstIndex * UnsafeUtility.SizeOf<T>(), (byte*)src.GetUnsafePtr<T>() + srcIndex * UnsafeUtility.SizeOf<T>(), length * UnsafeUtility.SizeOf<T>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 64629eefdeabffc49b82f6587560c82d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SLZ.CustomStaticBatching
|
||||
{
|
||||
public static unsafe class NativeArraySubArray
|
||||
{
|
||||
public static unsafe NativeArray<T2> GetSubArrayAlias<T, T2>(NativeArray<T> array, int start, int length)
|
||||
where T : unmanaged
|
||||
where T2 : unmanaged
|
||||
{
|
||||
void* dataPointer = ((byte*)array.GetUnsafePtr()) + start * sizeof(T);
|
||||
NativeArray<T2> outp = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T2>(dataPointer, length, Allocator.None);
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref outp, AtomicSafetyHandle.GetTempMemoryHandle());
|
||||
#endif
|
||||
return outp;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1a0167058eeee0a49920258bf784a54b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "SLZ.CustomStaticBatching.Unsafe",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:e0cd26848372d4e5c891c569017e11f1",
|
||||
"GUID:2665a8d13d1b3f18800f46e256720795",
|
||||
"GUID:d8b63aba1907145bea998dd612889d6b",
|
||||
"GUID:b3170da2b0ad74e4aad542674030756e"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1a4848c44fae8ec4a9b68efad47aab4e
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SLZ.CustomStaticBatching
|
||||
{
|
||||
public unsafe static class SpanSortExt
|
||||
{
|
||||
public static void Sort<T>(ref Span<T> span) where T : unmanaged, IComparable<T>
|
||||
{
|
||||
fixed (T* pData = &span.GetPinnableReference())
|
||||
{
|
||||
NativeArray<T> nativeCast = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>((void*)pData, span.Length, Allocator.None);
|
||||
nativeCast.Sort();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 411179256dc538f41a2a5bfff6f4ab55
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Add table
Add a link
Reference in a new issue