initial commit

This commit is contained in:
Jo 2025-01-07 02:06:59 +01:00
parent 6715289efe
commit 788c3389af
37645 changed files with 2526849 additions and 80 deletions

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2f2d3a0f72d55dc43847d723b80f62fe
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);
}
}
}

View file

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

View file

@ -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
{
}
}

View file

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

View file

@ -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,
};
}
}

View file

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

View file

@ -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
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: b3170da2b0ad74e4aad542674030756e
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d10da44e69925b748b71e6e2c052da33
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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): 381387. 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;
}
}
}

View file

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

View file

@ -0,0 +1,4 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("SLZ.CustomStaticBatching.Editor")]
[assembly: InternalsVisibleTo("SLZ.CustomStaticBatching.Unsafe")]

View file

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

View file

@ -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;
}
}
}

View file

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

View file

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

View file

@ -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
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 18839400ad0a58b4cb80e7f1117ebcba
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 783b8b155491a654483e94ca2eedfbda
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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));
}
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5bae5a4c97f51964dbc10d3398312270
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8d8c2081982a9744087b1ed64897360c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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);
}
}
}
}

View file

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

View file

@ -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];
}
}

View file

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

View file

@ -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;
}
}

View file

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

View file

@ -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;
}
}
}
}

View file

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

View file

@ -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>()
);
}
}
}

View file

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

View file

@ -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>());
}
}
}
}

View file

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

View file

@ -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;
}
}
}

View file

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

View file

@ -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
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1a4848c44fae8ec4a9b68efad47aab4e
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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();
}
}
}
}

View file

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