Adaug proiectul Unity

This commit is contained in:
2026-11-01 23:39:55 +02:00
parent cb14d9d37d
commit 863eac8f2c
23463 changed files with 5412601 additions and 0 deletions
@@ -0,0 +1,544 @@
using System;
using System.Collections.Generic;
using UnityEngine.Assertions;
using UnityEngine.Rendering;
using Unity.Mathematics;
using Unity.Mathematics.Geometry;
using Unity.Collections;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UnityEngine.Rendering.UnifiedRayTracing
{
internal sealed class AccelStructAdapter : IDisposable
{
private IRayTracingAccelStruct _accelStruct;
AccelStructInstances _instances;
Texture2DArray _terrainTextureArray;
readonly List<GraphicsBuffer> _aabbBuffers = new();
int _terrainCount;
// Width (and height) of the heightmap atlas in texels — the running max of every added
// terrain's resolution. Smaller terrains are padded into the upper-left of their slice.
int _maxTerrainResolution;
static RenderTexture s_EmptyTerrainTexture;
internal static readonly int _terrainTileWidth = 8;
static readonly int s_TerrainTextureId = Shader.PropertyToID("_TerrainTexture");
static readonly int s_TerrainTextureInvWidthId = Shader.PropertyToID("_TerrainTextureInvWidth");
static RenderTexture GetEmptyTerrainTexture()
{
if (s_EmptyTerrainTexture == null)
{
s_EmptyTerrainTexture = new RenderTexture(new RenderTextureDescriptor(1, 1)
{
dimension = TextureDimension.Tex2DArray,
depthBufferBits = 0,
volumeDepth = 1,
msaaSamples = 1,
graphicsFormat = Experimental.Rendering.GraphicsFormat.R16_SNorm,
enableRandomWrite = true,
});
s_EmptyTerrainTexture.Create();
}
return s_EmptyTerrainTexture;
}
internal AccelStructInstances Instances { get => _instances; }
internal Texture2DArray TerrainTextureArray { get => _terrainTextureArray; }
internal int TerrainCount { get => _terrainCount; }
struct IdsOfInstances
{
public int IdOfInstance;
public int AccelStructID;
public Mesh OwnedMesh;
}
private readonly Dictionary<UInt64, IdsOfInstances[]> _objectHandleToInstances = new();
public AccelStructAdapter(IRayTracingAccelStruct accelStruct, GeometryPool geometryPool)
{
_accelStruct = accelStruct;
_instances = new AccelStructInstances(geometryPool);
}
public AccelStructAdapter(IRayTracingAccelStruct accelStruct, RayTracingResources resources)
: this(accelStruct, new GeometryPool(GeometryPoolDesc.NewDefault(), resources.geometryPoolKernels, resources.copyBuffer))
{ }
public IRayTracingAccelStruct GetAccelerationStructure()
{
return _accelStruct;
}
public GeometryPool GeometryPool => _instances.geometryPool;
public void Bind(CommandBuffer cmd, string propertyName, IRayTracingShader shader)
{
shader.SetAccelerationStructure(cmd, propertyName, _accelStruct);
_instances.Bind(cmd, shader);
}
public void BindTerrainResources(CommandBuffer cmd, IRayTracingShader shader)
{
// Always bind something — Unity's ray tracing dispatch validates that all
// declared shader textures/buffers are bound, even if not accessed at runtime.
shader.SetTextureParam(cmd, s_TerrainTextureId, _terrainTextureArray != null ? _terrainTextureArray : GetEmptyTerrainTexture());
// Inverse of the heightmap atlas width in texels. Shaders use it to convert per-
// terrain texel indices into atlas-normalized UVs — the divisor differs from a
// terrain's own resolution when smaller terrains share the atlas with larger ones.
// Defaults to 1.0 against the 1×1 fallback texture above.
int atlasW = _maxTerrainResolution > 0 ? _maxTerrainResolution : 1;
shader.SetFloatParam(cmd, s_TerrainTextureInvWidthId, 1.0f / atlasW);
}
public void Dispose()
{
_instances?.Dispose();
_instances = null;
_accelStruct?.Dispose();
_accelStruct = null;
foreach (var instances in _objectHandleToInstances.Values)
foreach (var instance in instances)
CoreUtils.Destroy(instance.OwnedMesh);
_objectHandleToInstances.Clear();
foreach (var buf in _aabbBuffers)
buf?.Dispose();
_aabbBuffers.Clear();
_terrainCount = 0;
_maxTerrainResolution = 0;
if (_terrainTextureArray != null)
{
Object.DestroyImmediate(_terrainTextureArray);
_terrainTextureArray = null;
}
}
public void AddInstance(UInt64 objectHandle, Component meshRendererOrTerrain, Span<uint> perSubMeshMask, Span<uint> perSubMeshMaterialIDs, Span<bool> perSubMeshIsOpaque, uint renderingLayerMask)
{
#if ENABLE_TERRAIN_MODULE
if (meshRendererOrTerrain is Terrain terrain)
{
Debug.Assert(terrain.enabled, "Terrains are expected to be enabled.");
TerrainDesc terrainDesc;
terrainDesc.terrain = terrain;
terrainDesc.localToWorldMatrix = terrain.transform.localToWorldMatrix;
terrainDesc.mask = perSubMeshMask[0];
terrainDesc.renderingLayerMask = renderingLayerMask;
terrainDesc.materialID = perSubMeshMaterialIDs[0];
terrainDesc.enableTriangleCulling = true;
terrainDesc.frontTriangleCounterClockwise = false;
AddInstance(objectHandle, terrainDesc);
}
else
#endif
{
var meshRenderer = (MeshRenderer)meshRendererOrTerrain;
Debug.Assert(meshRenderer.enabled, "Mesh renderers are expected to be enabled.");
Debug.Assert(!meshRenderer.isPartOfStaticBatch, "Mesh renderers are expected to not be part of static batch.");
var mesh = meshRenderer.GetComponent<MeshFilter>().sharedMesh;
AddInstance(objectHandle, mesh, meshRenderer.transform.localToWorldMatrix, perSubMeshMask, perSubMeshMaterialIDs, perSubMeshIsOpaque, renderingLayerMask);
}
}
public void AddInstance(UInt64 objectHandle, Mesh mesh, Matrix4x4 localToWorldMatrix, Span<uint> perSubMeshMask, Span<uint> perSubMeshMaterialIDs, Span<bool> perSubMeshIsOpaque, uint renderingLayerMask)
{
int subMeshCount = mesh.subMeshCount;
var instances = new IdsOfInstances[subMeshCount];
for (int i = 0; i < subMeshCount; ++i)
{
var instanceDesc = new MeshInstanceDesc(mesh, i)
{
localToWorldMatrix = localToWorldMatrix,
mask = perSubMeshMask[i],
opaqueGeometry = perSubMeshIsOpaque[i]
};
instances[i].IdOfInstance = _instances.AddInstance(instanceDesc, perSubMeshMaterialIDs[i], renderingLayerMask);
instanceDesc.instanceID = (uint)instances[i].IdOfInstance;
instances[i].AccelStructID = _accelStruct.AddInstance(instanceDesc);
}
_objectHandleToInstances.Add(objectHandle, instances);
}
#if ENABLE_TERRAIN_MODULE
private void AddInstance(UInt64 objectHandle, TerrainDesc terrainDesc)
{
List<IdsOfInstances> instanceHandles = new List<IdsOfInstances>();
try
{
AddHeightmap(terrainDesc, ref instanceHandles);
AddTrees(terrainDesc, ref instanceHandles);
}
catch
{
foreach (var instance in instanceHandles)
CoreUtils.Destroy(instance.OwnedMesh);
throw;
}
_objectHandleToInstances.Add(objectHandle, instanceHandles.ToArray());
}
void AddHeightmap(TerrainDesc terrainDesc, ref List<IdsOfInstances> instanceHandles)
{
var terrainMesh = TerrainToMesh.Convert(terrainDesc.terrain);
var instanceDesc = new MeshInstanceDesc(terrainMesh);
instanceDesc.localToWorldMatrix = terrainDesc.localToWorldMatrix;
instanceDesc.mask = terrainDesc.mask;
instanceDesc.enableTriangleCulling = terrainDesc.enableTriangleCulling;
instanceDesc.frontTriangleCounterClockwise = terrainDesc.frontTriangleCounterClockwise;
IdsOfInstances ids;
try
{
ids = AddInstance(instanceDesc, terrainDesc.materialID, terrainDesc.renderingLayerMask);
}
catch
{
CoreUtils.Destroy(terrainMesh);
throw;
}
ids.OwnedMesh = terrainMesh;
instanceHandles.Add(ids);
}
void AddTrees(TerrainDesc terrainDesc, ref List<IdsOfInstances> instanceHandles)
{
TerrainData terrainData = terrainDesc.terrain.terrainData;
Matrix4x4 terrainLocalToWorld = terrainDesc.localToWorldMatrix;
Vector3 positionScale = Vector3.Scale(new Vector3(terrainData.heightmapResolution, 1.0f, terrainData.heightmapResolution), terrainData.heightmapScale);
Vector3 positionOffset = terrainLocalToWorld.GetPosition();
foreach (var treeInstance in terrainData.treeInstances)
{
var localToWorld = Matrix4x4.TRS(
positionOffset + Vector3.Scale(treeInstance.position, positionScale),
Quaternion.AngleAxis(treeInstance.rotation, Vector3.up),
new Vector3(treeInstance.widthScale, treeInstance.heightScale, treeInstance.widthScale));
var prefab = terrainData.treePrototypes[treeInstance.prototypeIndex].prefab;
GameObject go = prefab.gameObject;
if (prefab.TryGetComponent<LODGroup>(out var lodGroup))
{
var groups = lodGroup.GetLODs();
if (groups.Length != 0 && groups[0].renderers.Length != 0)
go = (groups[0].renderers[0] as MeshRenderer).gameObject;
}
if (!go.TryGetComponent<MeshFilter>(out var filter))
continue;
var mesh = filter.sharedMesh;
for (int i = 0; i < mesh.subMeshCount; ++i)
{
var instanceDesc = new MeshInstanceDesc(mesh, i);
instanceDesc.localToWorldMatrix = localToWorld;
instanceDesc.mask = terrainDesc.mask;
instanceDesc.enableTriangleCulling = terrainDesc.enableTriangleCulling;
instanceDesc.frontTriangleCounterClockwise = terrainDesc.frontTriangleCounterClockwise;
instanceHandles.Add(AddInstance(instanceDesc, terrainDesc.materialID, 1u << prefab.gameObject.layer));
}
}
}
#endif
static MinMaxAABB TileAabb(short[] heightData, int resolution, float3 heightmapScale, int2 topLeftCorner)
{
MinMaxAABB tileAabb = new MinMaxAABB(float.PositiveInfinity, float.NegativeInfinity);
for (int x = 0; x <= _terrainTileWidth; ++x)
for (int y = 0; y <= _terrainTileWidth; ++y)
{
int2 coord = new int2(topLeftCorner.x + x, topLeftCorner.y + y);
float h = (float)heightData[coord.y * resolution + coord.x] / 32767.0f;
float3 pos = new float3(coord.x, h, coord.y) * heightmapScale;
tileAabb.Encapsulate(pos);
}
return tileAabb;
}
static GraphicsBuffer CreateTerrainAabbBuffer(short[] heightData, int resolution, float3 heightmapScale)
{
int tilesPerAxis = (resolution - 1) / _terrainTileWidth;
int tileCount = tilesPerAxis * tilesPerAxis;
var aabbs = new MinMaxAABB[tileCount];
for (int i = 0; i < tileCount; ++i)
{
int x = i % tilesPerAxis;
int y = i / tilesPerAxis;
aabbs[i] = TileAabb(heightData, resolution, heightmapScale, new int2(x, y) * _terrainTileWidth);
}
var buffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, tileCount, 6 * sizeof(float));
buffer.SetData(aabbs);
return buffer;
}
internal static Texture2D CreateTerrainTextureForTest(short[] heightData, int resolution, byte[] holeData, int holeResolution)
=> CreateTerrainTexture(heightData, resolution, holeData, holeResolution);
static Texture2D CreateTerrainTexture(short[] heightData, int resolution, byte[] holeData, int holeResolution)
{
var texture = new Texture2D(resolution, resolution, Experimental.Rendering.GraphicsFormat.R16_SNorm, Experimental.Rendering.TextureCreationFlags.None);
// The Engine's terrain values are always in the range [0-32766].
// We store them in the range [1-32767] so the shader can uniformly subtract 1/32767
// to recover the original value. For holes, we negate the biased value.
// The +1 bias is always applied (even without holes) so that height=0 doesn't
// become negative after the shader's subtraction and get misdetected as a hole.
var biasedHeights = new NativeArray<short>(resolution * resolution, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
for (int i = 0; i < resolution * resolution; ++i)
{
short v = (short)(heightData[i] + 1);
if (holeData != null)
{
int x = i % resolution;
int y = i / resolution;
bool isHole = x < holeResolution && y < holeResolution && holeData[y * holeResolution + x] == 0;
if (isHole)
v = (short)(-v);
}
biasedHeights[i] = v;
}
texture.SetPixelData(biasedHeights, 0);
texture.Apply();
biasedHeights.Dispose();
return texture;
}
// Append `newSlice` (sized newSliceResolution × newSliceResolution) into the heightmap
// atlas. The atlas is sized to _maxTerrainResolution; smaller slices live in the
// upper-left of their slot and the rest is zero-padded. If the atlas needs to grow,
// existing slices are partial-copied into the upper-left of correspondingly larger
// slots in a freshly allocated array.
void GrowTextureArray(Texture2D newSlice, int newSliceResolution, int previousAtlasWidth)
{
int atlasW = _maxTerrainResolution;
int newCount = _terrainCount + 1;
var newArray = new Texture2DArray(atlasW, atlasW, newCount,
Experimental.Rendering.GraphicsFormat.R16_SNorm, Experimental.Rendering.TextureCreationFlags.None);
// Zero-initialize every slot so padding regions are well-defined.
// Upload one zeroed slice from the CPU and broadcast it to every slot via
// GPU-local CopyTexture, avoiding an O(N) PCIe transfer per grow.
var zeroSlice = new Texture2D(atlasW, atlasW, Experimental.Rendering.GraphicsFormat.R16_SNorm, Experimental.Rendering.TextureCreationFlags.None);
var zeros = new NativeArray<short>(atlasW * atlasW, Allocator.Temp, NativeArrayOptions.ClearMemory);
zeroSlice.SetPixelData(zeros, 0);
zeroSlice.Apply(updateMipmaps: false, makeNoLongerReadable: true);
zeros.Dispose();
for (int slot = 0; slot < newCount; slot++)
Graphics.CopyTexture(zeroSlice, 0, 0, newArray, slot, 0);
Object.DestroyImmediate(zeroSlice);
if (_terrainTextureArray != null)
{
for (int i = 0; i < _terrainCount; i++)
Graphics.CopyTexture(_terrainTextureArray, i, 0, 0, 0, previousAtlasWidth, previousAtlasWidth, newArray, i, 0, 0, 0);
Object.DestroyImmediate(_terrainTextureArray);
}
Graphics.CopyTexture(newSlice, 0, 0, 0, 0, newSliceResolution, newSliceResolution, newArray, _terrainCount, 0, 0, 0);
_terrainTextureArray = newArray;
}
public void AddTerrainInstance(
UInt64 objectHandle,
short[] heightData,
int resolution,
float3 heightmapScale,
byte[] holeData,
int holeResolution,
Matrix4x4 localToWorldMatrix,
uint materialID,
uint renderingLayerMask,
uint instanceMask = 0xFFFFFFFF)
{
int previousAtlasWidth = _maxTerrainResolution;
_maxTerrainResolution = math.max(_maxTerrainResolution, resolution);
var aabbBuffer = CreateTerrainAabbBuffer(heightData, resolution, heightmapScale);
_aabbBuffers.Add(aabbBuffer);
var sliceTexture = CreateTerrainTexture(heightData, resolution, holeData, holeResolution);
GrowTextureArray(sliceTexture, resolution, previousAtlasWidth);
Object.DestroyImmediate(sliceTexture);
int tilesPerAxis = (resolution - 1) / _terrainTileWidth;
int log2TilesPerAxis = (int)math.log2(tilesPerAxis);
var terrainData = new AccelStructInstances.RTTerrain
{
terrainScale = heightmapScale,
heightmapWidthInTexels = resolution,
invTerrainScale = 1.0f / heightmapScale,
invHeightmapWidthInTexels = 1.0f / resolution,
pow2DivideTileCountX = log2TilesPerAxis,
pow2ModuloTileCountX = (1 << log2TilesPerAxis) - 1,
tileWidthInCells = _terrainTileWidth,
invTerrainWidthInCells = 1.0f / (resolution - 1),
};
var instanceDesc = new ProceduralInstanceDesc(aabbBuffer, (uint)aabbBuffer.count);
instanceDesc.localToWorldMatrix = localToWorldMatrix;
instanceDesc.mask = instanceMask;
var instances = new IdsOfInstances[1];
instances[0].IdOfInstance = _instances.AddInstance(instanceDesc, materialID, renderingLayerMask, terrainData);
instanceDesc.instanceID = (uint)instances[0].IdOfInstance;
instances[0].AccelStructID = _accelStruct.AddInstance(instanceDesc);
_objectHandleToInstances.Add(objectHandle, instances);
_terrainCount++;
}
IdsOfInstances AddInstance(MeshInstanceDesc instanceDesc, uint materialID, uint renderingLayerMask)
{
IdsOfInstances res = new IdsOfInstances();
res.IdOfInstance = _instances.AddInstance(instanceDesc, materialID, renderingLayerMask);
instanceDesc.instanceID = (uint)res.IdOfInstance;
res.AccelStructID = _accelStruct.AddInstance(instanceDesc);
return res;
}
public void RemoveInstance(UInt64 objectHandle)
{
bool success = _objectHandleToInstances.TryGetValue(objectHandle, out var instances);
Assert.IsTrue(success);
foreach (var instance in instances)
{
_instances.RemoveInstance(instance.IdOfInstance);
_accelStruct.RemoveInstance(instance.AccelStructID);
CoreUtils.Destroy(instance.OwnedMesh);
}
_objectHandleToInstances.Remove(objectHandle);
}
public void UpdateInstanceTransform(UInt64 objectHandle, Matrix4x4 localToWorldMatrix)
{
bool success = _objectHandleToInstances.TryGetValue(objectHandle, out var instances);
Assert.IsTrue(success);
foreach(var instance in instances)
{
_instances.UpdateInstanceTransform(instance.IdOfInstance, localToWorldMatrix);
_accelStruct.UpdateInstanceTransform(instance.AccelStructID, localToWorldMatrix);
}
}
public void UpdateInstanceMaterialIDs(UInt64 objectHandle, Span<uint> perSubMeshMaterialIDs)
{
bool success = _objectHandleToInstances.TryGetValue(objectHandle, out var instances);
Assert.IsTrue(success);
Assert.IsTrue(perSubMeshMaterialIDs.Length >= instances.Length);
int i = 0;
foreach (var instance in instances)
{
_instances.UpdateInstanceMaterialID(instance.IdOfInstance, perSubMeshMaterialIDs[i++]);
}
}
public void UpdateInstanceMask(UInt64 objectHandle, Span<uint> perSubMeshMask)
{
bool success = _objectHandleToInstances.TryGetValue(objectHandle, out var instances);
Assert.IsTrue(success);
Assert.IsTrue(perSubMeshMask.Length >= instances.Length);
int i = 0;
foreach (var instance in instances)
{
_instances.UpdateInstanceMask(instance.IdOfInstance, perSubMeshMask[i]);
_accelStruct.UpdateInstanceMask(instance.AccelStructID, perSubMeshMask[i]);
i++;
}
}
public void UpdateInstanceMask(UInt64 objectHandle, uint mask)
{
bool success = _objectHandleToInstances.TryGetValue(objectHandle, out var instances);
Assert.IsTrue(success);
var perSubMeshMask = new uint[instances.Length];
Array.Fill(perSubMeshMask, mask);
int i = 0;
foreach (var instance in instances)
{
_instances.UpdateInstanceMask(instance.IdOfInstance, perSubMeshMask[i]);
_accelStruct.UpdateInstanceMask(instance.AccelStructID, perSubMeshMask[i]);
i++;
}
}
public void Build(CommandBuffer cmd, ref GraphicsBuffer scratchBuffer)
{
RayTracingHelper.ResizeScratchBufferForBuild(_accelStruct, ref scratchBuffer);
_accelStruct.Build(cmd, scratchBuffer);
}
public void NextFrame()
{
_instances.NextFrame();
}
public bool GetInstanceIDs(UInt64 rendererID, out int[] instanceIDs)
{
if (!_objectHandleToInstances.TryGetValue(rendererID, out IdsOfInstances[] instIDs))
{
// This should never happen as long as the renderer was already added to the acceleration structure
instanceIDs = null;
return false;
}
instanceIDs = Array.ConvertAll(instIDs, item => item.IdOfInstance);
return true;
}
}
#if ENABLE_TERRAIN_MODULE
internal struct TerrainDesc
{
public Terrain terrain;
public Matrix4x4 localToWorldMatrix;
public uint mask;
public uint renderingLayerMask;
public uint materialID;
public bool enableTriangleCulling;
public bool frontTriangleCounterClockwise;
public TerrainDesc(Terrain terrain)
{
this.terrain = terrain;
localToWorldMatrix = Matrix4x4.identity;
mask = 0xFFFFFFFF;
renderingLayerMask = 0xFFFFFFFF;
materialID = 0;
enableTriangleCulling = true;
frontTriangleCounterClockwise = false;
}
}
#endif
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: be387ad7b61cdc84c81dbdd3d936722c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,309 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Unity.Mathematics;
using UnityEngine.Assertions;
namespace UnityEngine.Rendering.UnifiedRayTracing
{
internal sealed class AccelStructInstances : IDisposable
{
internal AccelStructInstances(GeometryPool geometryPool)
{
m_GeometryPool = geometryPool;
}
public void Dispose()
{
foreach (InstanceEntry instanceEntry in m_Instances.Values)
{
GeometryPoolHandle geomHandle = instanceEntry.geometryPoolHandle;
if (geomHandle.valid)
m_GeometryPool.Unregister(geomHandle);
}
m_GeometryPool.SendGpuCommands();
m_InstanceBuffer?.Dispose();
m_TerrainBuffer?.Dispose();
m_GeometryPool.Dispose();
}
public PersistentGpuArray<RTInstance> instanceBuffer { get => m_InstanceBuffer; }
public IReadOnlyCollection<InstanceEntry> instances { get => m_Instances.Values; }
public GeometryPool geometryPool { get => m_GeometryPool; }
public int AddInstance(MeshInstanceDesc meshInstance, uint materialID, uint renderingLayerMask)
{
var slot = m_InstanceBuffer.Add(1)[0];
AddInstance(slot, meshInstance, materialID, renderingLayerMask);
return slot.block.offset;
}
public int AddInstances(Span<MeshInstanceDesc> meshInstances, Span<uint> materialIDs, Span<uint> renderingLayerMask)
{
Assert.IsTrue(meshInstances.Length == materialIDs.Length);
var slots = m_InstanceBuffer.Add(meshInstances.Length);
for (int i = 0; i < meshInstances.Length; ++i)
AddInstance(slots[i], meshInstances[i], materialIDs[i], renderingLayerMask[i]);
return slots[0].block.offset;
}
public int AddInstance(in ProceduralInstanceDesc procInstance, uint materialID, uint renderingLayerMask, RTTerrain terrainData)
{
var terrainSlot = m_TerrainBuffer.Add(1)[0];
m_TerrainBuffer.Set(terrainSlot, terrainData);
var instanceSlot = m_InstanceBuffer.Add(1)[0];
m_InstanceBuffer.Set(instanceSlot,
new RTInstance
{
localToWorld = ToFloat4x3(procInstance.localToWorldMatrix),
localToWorldNormals = NormalMatrix(procInstance.localToWorldMatrix),
previousLocalToWorld = ToFloat4x3(procInstance.localToWorldMatrix),
userTerrainIndex = terrainSlot.block.offset,
userMaterialID = materialID,
instanceMask = procInstance.mask,
renderingLayerMask = renderingLayerMask,
geometryIndex = 0xFFFFFFFF
});
var instanceEntry = new InstanceEntry
{
geometryPoolHandle = GeometryPoolHandle.Invalid,
indexInTerrainBuffer = terrainSlot,
indexInInstanceBuffer = instanceSlot,
instanceMask = procInstance.mask,
vertexOffset = 0xFFFFFFFF,
indexOffset = 0xFFFFFFFF,
};
m_Instances.Add(instanceSlot.block.offset, instanceEntry);
return instanceSlot.block.offset;
}
void AddInstance(BlockAllocator.Allocation slotAllocation, in MeshInstanceDesc meshInstance, uint materialID, uint renderingLayerMask)
{
Debug.Assert(meshInstance.mesh != null, "targetRenderer.mesh is null");
GeometryPoolHandle geometryHandle;
if (!m_GeometryPool.Register(meshInstance.mesh, out geometryHandle))
throw new System.InvalidOperationException("Failed to allocate geometry data for instance");
m_GeometryPool.SendGpuCommands();
float localToWorldDet = meshInstance.localToWorldMatrix.determinant;
m_InstanceBuffer.Set(slotAllocation,
new RTInstance
{
localToWorld = ToFloat4x3(meshInstance.localToWorldMatrix),
localToWorldDeterminant = localToWorldDet,
localToWorldDetSign = localToWorldDet > 0 ? 1.0f : -1.0f,
userTerrainIndex = -1,
localToWorldNormals = NormalMatrix(meshInstance.localToWorldMatrix),
previousLocalToWorld = ToFloat4x3(meshInstance.localToWorldMatrix),
userMaterialID = materialID,
instanceMask = meshInstance.mask,
renderingLayerMask = renderingLayerMask,
geometryIndex = (uint)(m_GeometryPool.GetEntryGeomAllocation(geometryHandle).meshChunkTableAlloc.block.offset + meshInstance.subMeshIndex)
});
var allocInfo = m_GeometryPool.GetEntryGeomAllocation(geometryHandle).meshChunks[meshInstance.subMeshIndex];
var instanceEntry = new InstanceEntry
{
geometryPoolHandle = geometryHandle,
indexInTerrainBuffer = BlockAllocator.Allocation.Invalid,
indexInInstanceBuffer = slotAllocation,
instanceMask = meshInstance.mask,
vertexOffset = (uint)(allocInfo.vertexAlloc.block.offset) * ((uint)GeometryPool.GetVertexByteSize() / 4),
indexOffset = (uint)allocInfo.indexAlloc.block.offset,
};
m_Instances.Add(slotAllocation.block.offset, instanceEntry);
}
public GeometryPool.MeshChunk GetEntryGeomAllocation(GeometryPoolHandle handle, int submeshIndex)
{
return m_GeometryPool.GetEntryGeomAllocation(handle).meshChunks[submeshIndex];
}
public GraphicsBuffer indexBuffer { get { return m_GeometryPool.globalIndexBuffer; } }
public GraphicsBuffer vertexBuffer { get { return m_GeometryPool.globalVertexBuffer; } }
public void RemoveInstance(int instanceHandle)
{
bool success = m_Instances.TryGetValue(instanceHandle, out InstanceEntry removedEntry);
Assert.IsTrue(success);
m_Instances.Remove(instanceHandle);
m_InstanceBuffer.Remove(removedEntry.indexInInstanceBuffer);
var terrainHandle = removedEntry.indexInTerrainBuffer;
if (terrainHandle.valid)
m_TerrainBuffer.Remove(terrainHandle);
var geomHandle = removedEntry.geometryPoolHandle;
if (geomHandle.valid)
{
m_GeometryPool.Unregister(geomHandle);
m_GeometryPool.SendGpuCommands();
}
}
public void ClearInstances()
{
foreach (InstanceEntry instanceEntry in m_Instances.Values)
{
GeometryPoolHandle geomHandle = instanceEntry.geometryPoolHandle;
if (geomHandle.valid)
m_GeometryPool.Unregister(geomHandle);
}
m_GeometryPool.SendGpuCommands();
m_Instances.Clear();
m_TerrainBuffer.Clear();
m_InstanceBuffer.Clear();
}
public void UpdateInstanceTransform(int instanceHandle, Matrix4x4 localToWorldMatrix)
{
bool success = m_Instances.TryGetValue(instanceHandle, out InstanceEntry instanceEntry);
Assert.IsTrue(success);
var instanceInfo = m_InstanceBuffer.Get(instanceEntry.indexInInstanceBuffer);
instanceInfo.localToWorld = ToFloat4x3(localToWorldMatrix);
instanceInfo.localToWorldNormals = NormalMatrix(localToWorldMatrix);
m_InstanceBuffer.Set(instanceEntry.indexInInstanceBuffer, instanceInfo);
m_TransformTouchedLastTimestamp = m_FrameTimestamp;
}
public void UpdateInstanceMaterialID(int instanceHandle, uint materialID)
{
InstanceEntry instanceEntry;
bool success = m_Instances.TryGetValue(instanceHandle, out instanceEntry);
Assert.IsTrue(success);
var instanceInfo = m_InstanceBuffer.Get(instanceEntry.indexInInstanceBuffer);
instanceInfo.userMaterialID = materialID;
m_InstanceBuffer.Set(instanceEntry.indexInInstanceBuffer, instanceInfo);
}
public void UpdateRenderingLayerMask(int instanceHandle, uint renderingLayerMask)
{
InstanceEntry instanceEntry;
bool success = m_Instances.TryGetValue(instanceHandle, out instanceEntry);
Assert.IsTrue(success);
var instanceInfo = m_InstanceBuffer.Get(instanceEntry.indexInInstanceBuffer);
instanceInfo.renderingLayerMask = renderingLayerMask;
m_InstanceBuffer.Set(instanceEntry.indexInInstanceBuffer, instanceInfo);
}
public void UpdateInstanceMask(int instanceHandle, uint mask)
{
bool success = m_Instances.TryGetValue(instanceHandle, out InstanceEntry instanceEntry);
Assert.IsTrue(success);
instanceEntry.instanceMask = mask;
var instanceInfo = m_InstanceBuffer.Get(instanceEntry.indexInInstanceBuffer);
instanceInfo.instanceMask = mask;
m_InstanceBuffer.Set(instanceEntry.indexInInstanceBuffer, instanceInfo);
}
public void NextFrame()
{
if ((m_FrameTimestamp - m_TransformTouchedLastTimestamp) <= 1)
{
m_InstanceBuffer.ModifyForEach(
instance =>
{
instance.previousLocalToWorld = instance.localToWorld;
return instance;
});
}
m_FrameTimestamp++;
}
public bool instanceListValid => m_InstanceBuffer != null;
public void Bind(CommandBuffer cmd, IRayTracingShader shader)
{
shader.SetBufferParam(cmd, Shader.PropertyToID("g_AccelStructInstanceList"), m_InstanceBuffer.GetGpuBuffer(cmd));
shader.SetBufferParam(cmd, Shader.PropertyToID("g_TerrainList"), m_TerrainBuffer.GetGpuBuffer(cmd));
shader.SetBufferParam(cmd, Shader.PropertyToID("g_globalIndexBuffer"), m_GeometryPool.globalIndexBuffer);
shader.SetBufferParam(cmd, Shader.PropertyToID("g_globalVertexBuffer"), m_GeometryPool.globalVertexBuffer);
shader.SetIntParam(cmd, Shader.PropertyToID("g_globalVertexBufferStride"), m_GeometryPool.globalVertexBufferStrideBytes/4);
shader.SetBufferParam(cmd, Shader.PropertyToID("g_MeshList"), m_GeometryPool.globalMeshChunkTableEntryBuffer);
}
public int GetInstanceCount()
{
return m_Instances.Count;
}
static float4x3 NormalMatrix(float4x4 m)
{
float3x3 t = new float3x3(m);
var res = math.inverse(math.transpose(t));
return new float4x3(new float4(res.c0, 0.0f), new float4(res.c1, 0.0f), new float4(res.c2, 0.0f));
}
static float4x3 ToFloat4x3(in float4x4 m)
{
return new float4x3(m.c0.x, m.c0.y, m.c0.z, m.c1.x, m.c1.y, m.c1.z, m.c2.x, m.c2.y, m.c2.z, m.c3.x, m.c3.y, m.c3.z);
}
readonly GeometryPool m_GeometryPool;
readonly PersistentGpuArray<RTInstance> m_InstanceBuffer = new PersistentGpuArray<RTInstance>(100);
readonly PersistentGpuArray<RTTerrain> m_TerrainBuffer = new PersistentGpuArray<RTTerrain>(20);
[StructLayout(LayoutKind.Sequential)]
public struct RTInstance
{
public float4x3 localToWorld;
public float localToWorldDeterminant;
public float localToWorldDetSign;
public int userTerrainIndex;
public uint padding1;
public float4x3 previousLocalToWorld;
public float4x3 localToWorldNormals;
public uint renderingLayerMask;
public uint instanceMask;
public uint userMaterialID;
public uint geometryIndex;
};
[StructLayout(LayoutKind.Sequential)]
public struct RTTerrain
{
public float3 terrainScale;
public float heightmapWidthInTexels;
public float3 invTerrainScale;
public float invHeightmapWidthInTexels;
public int pow2DivideTileCountX;
public int pow2ModuloTileCountX;
public int tileWidthInCells;
public float invTerrainWidthInCells;
}
public class InstanceEntry
{
public GeometryPoolHandle geometryPoolHandle;
public BlockAllocator.Allocation indexInTerrainBuffer;
public BlockAllocator.Allocation indexInInstanceBuffer;
public uint instanceMask;
public uint vertexOffset;
public uint indexOffset;
}
readonly Dictionary<int, InstanceEntry> m_Instances = new Dictionary<int, InstanceEntry>();
uint m_FrameTimestamp = 0;
uint m_TransformTouchedLastTimestamp = 0;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f9b589fa00bef23408b114d1af9b44be
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c92be10cd223d054b8314f7445705554
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,869 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine.Assertions;
// This file is a fork of the GeometryPool used by the GPU Driven Pipeline
// TODO: remove that file and use GeometryPool v2 (written in C++)
namespace UnityEngine.Rendering.UnifiedRayTracing
{
// Initial description of geometry pool, contains memory limits to hold cluster / index & vertex data.
internal struct GeometryPoolDesc
{
public int vertexPoolByteSize;
public int indexPoolByteSize;
public int meshChunkTablesByteSize;
public static GeometryPoolDesc NewDefault()
{
return new GeometryPoolDesc()
{
vertexPoolByteSize = 256 * 1024 * 1024, //256 mb
indexPoolByteSize = 32 * 1024 * 1024, //32 mb
meshChunkTablesByteSize = 4 * 1024 * 1024
};
}
}
// Handle to a piece of geo. Geometry meshes registered are ref counted.
// Each handle allocation must be deallocated manually.
internal struct GeometryPoolHandle : IEquatable<GeometryPoolHandle>
{
public int index;
public static readonly GeometryPoolHandle Invalid = new GeometryPoolHandle() { index = -1 };
public readonly bool valid => index != -1;
public bool Equals(GeometryPoolHandle other) => index == other.index;
}
// Entry information of a geometry handle.
// Use this helper to check validity, material hashes and active refcounts.
internal struct GeometryPoolEntryInfo
{
public bool valid;
public uint refCount;
public static GeometryPoolEntryInfo NewDefault()
{
return new GeometryPoolEntryInfo()
{
valid = false,
refCount = 0
};
}
}
// Descriptor of piece of geometry (the submesh information).
internal struct GeometryPoolSubmeshData
{
public int submeshIndex;
public Material material;
}
// Description of the geometry pool entry.
// Contains master list and the submesh data information.
internal struct GeometryPoolEntryDesc
{
public Mesh mesh;
public GeometryPoolSubmeshData[] submeshData;
}
// Geometry pool container. Contains a global set of geometry accessible from the GPU.
internal sealed class GeometryPool : IDisposable
{
private const int kMaxThreadGroupsPerDispatch = 65535; // Counted in groups, not threads.
private const int kThreadGroupSize = 256; // Counted in threads
private static class GeoPoolShaderIDs
{
// MainUpdateIndexBuffer32 and MainUpdateIndexBuffer16 Kernel Strings
public static readonly int _InputIBBaseOffset = Shader.PropertyToID("_InputIBBaseOffset");
public static readonly int _DispatchIndexOffset = Shader.PropertyToID("_DispatchIndexOffset");
public static readonly int _InputIBCount = Shader.PropertyToID("_InputIBCount");
public static readonly int _OutputIBOffset = Shader.PropertyToID("_OutputIBOffset");
public static readonly int _InputFirstVertex = Shader.PropertyToID("_InputFirstVertex");
public static readonly int _InputIndexBuffer = Shader.PropertyToID("_InputIndexBuffer");
public static readonly int _OutputIndexBuffer = Shader.PropertyToID("_OutputIndexBuffer");
// MainUpdateVertexBuffer Kernel Strings
public static readonly int _InputVBCount = Shader.PropertyToID("_InputVBCount");
public static readonly int _InputBaseVertexOffset = Shader.PropertyToID("_InputBaseVertexOffset");
public static readonly int _DispatchVertexOffset = Shader.PropertyToID("_DispatchVertexOffset");
public static readonly int _OutputVBSize = Shader.PropertyToID("_OutputVBSize");
public static readonly int _OutputVBOffset = Shader.PropertyToID("_OutputVBOffset");
public static readonly int _InputPosBufferStride = Shader.PropertyToID("_InputPosBufferStride");
public static readonly int _InputPosBufferOffset = Shader.PropertyToID("_InputPosBufferOffset");
public static readonly int _InputUv0BufferStride = Shader.PropertyToID("_InputUv0BufferStride");
public static readonly int _InputUv0BufferOffset = Shader.PropertyToID("_InputUv0BufferOffset");
public static readonly int _InputUv1BufferStride = Shader.PropertyToID("_InputUv1BufferStride");
public static readonly int _InputUv1BufferOffset = Shader.PropertyToID("_InputUv1BufferOffset");
public static readonly int _InputNormalBufferStride = Shader.PropertyToID("_InputNormalBufferStride");
public static readonly int _InputNormalBufferOffset = Shader.PropertyToID("_InputNormalBufferOffset");
public static readonly int _PosBuffer = Shader.PropertyToID("_PosBuffer");
public static readonly int _Uv0Buffer = Shader.PropertyToID("_Uv0Buffer");
public static readonly int _Uv1Buffer = Shader.PropertyToID("_Uv1Buffer");
public static readonly int _NormalBuffer = Shader.PropertyToID("_NormalBuffer");
public static readonly int _OutputVB = Shader.PropertyToID("_OutputVB");
public static readonly int _AttributesMask = Shader.PropertyToID("_AttributesMask");
}
// Geometry slot represents a set of pointers to the blobs of vertex, index and cluster information
private const int InvalidHandle = -1;
public struct MeshChunk
{
public BlockAllocator.Allocation vertexAlloc;
public BlockAllocator.Allocation indexAlloc;
public GeoPoolMeshChunk EncodeGPUEntry()
{
return new GeoPoolMeshChunk() {
indexOffset = indexAlloc.block.offset,
indexCount = indexAlloc.block.count,
vertexOffset = vertexAlloc.block.offset,
vertexCount = vertexAlloc.block.count,
};
}
public static MeshChunk Invalid => new MeshChunk()
{
vertexAlloc = BlockAllocator.Allocation.Invalid,
indexAlloc = BlockAllocator.Allocation.Invalid
};
}
public struct GeometrySlot
{
public uint refCount;
public uint hash;
public BlockAllocator.Allocation meshChunkTableAlloc;
public NativeArray<MeshChunk> meshChunks;
public bool hasGPUData;
public static readonly GeometrySlot Invalid = new GeometrySlot()
{
meshChunkTableAlloc = BlockAllocator.Allocation.Invalid,
hasGPUData = false,
};
public bool valid => meshChunkTableAlloc.valid;
}
private struct GeoPoolEntrySlot
{
public uint refCount;
public uint hash;
public int geoSlotHandle;
public static readonly GeoPoolEntrySlot Invalid = new GeoPoolEntrySlot()
{
refCount = 0u,
hash = 0u,
geoSlotHandle = InvalidHandle,
};
public bool valid => geoSlotHandle != InvalidHandle;
}
private struct VertexBufferAttribInfo
{
public GraphicsBuffer buffer;
public int stride;
public int offset;
public int byteCount;
public bool valid => buffer != null;
}
public static int GetVertexByteSize() => GeometryPoolConstants.GeoPoolVertexByteSize;
public static int GetIndexByteSize() => GeometryPoolConstants.GeoPoolIndexByteSize;
public static int GetMeshChunkTableEntryByteSize() => System.Runtime.InteropServices.Marshal.SizeOf<GeoPoolMeshChunk>();
private int GetFormatByteCount(VertexAttributeFormat format)
{
switch (format)
{
case VertexAttributeFormat.Float32: return 4;
case VertexAttributeFormat.Float16: return 2;
case VertexAttributeFormat.UNorm8: return 1;
case VertexAttributeFormat.SNorm8: return 1;
case VertexAttributeFormat.UNorm16: return 2;
case VertexAttributeFormat.SNorm16: return 2;
case VertexAttributeFormat.UInt8: return 1;
case VertexAttributeFormat.SInt8: return 1;
case VertexAttributeFormat.UInt16: return 2;
case VertexAttributeFormat.SInt16: return 2;
case VertexAttributeFormat.UInt32: return 4;
case VertexAttributeFormat.SInt32: return 4;
}
return 4;
}
private static int DivUp(int x, int y) => (x + y - 1) / y;
private const GraphicsBuffer.Target VertexBufferTarget = GraphicsBuffer.Target.Structured;
private const GraphicsBuffer.Target IndexBufferTarget = GraphicsBuffer.Target.Structured;
public GraphicsBuffer globalIndexBuffer { get { return m_GlobalIndexBuffer; } }
public GraphicsBuffer globalVertexBuffer { get { return m_GlobalVertexBuffer; } }
public int globalVertexBufferStrideBytes { get { return GetVertexByteSize(); } }
public GraphicsBuffer globalMeshChunkTableEntryBuffer { get { return m_GlobalMeshChunkTableEntryBuffer; } }
public int indicesCount => m_MaxIndexCounts;
public int verticesCount => m_MaxVertCounts;
public int meshChunkTablesEntryCount => m_MaxMeshChunkTableEntriesCount;
GraphicsBuffer m_GlobalIndexBuffer = null;
GraphicsBuffer m_GlobalVertexBuffer = null;
GraphicsBuffer m_GlobalMeshChunkTableEntryBuffer = null;
readonly GraphicsBuffer m_DummyBuffer = null;
int m_MaxVertCounts;
int m_MaxIndexCounts;
int m_MaxMeshChunkTableEntriesCount;
BlockAllocator m_VertexAllocator;
BlockAllocator m_IndexAllocator;
BlockAllocator m_MeshChunkTableAllocator;
NativeParallelHashMap<uint, int> m_MeshHashToGeoSlot;
List<GeometrySlot> m_GeoSlots;
NativeList<int> m_FreeGeoSlots;
NativeParallelHashMap<uint, GeometryPoolHandle> m_GeoPoolEntryHashToSlot;
NativeList<GeoPoolEntrySlot> m_GeoPoolEntrySlots;
NativeList<GeometryPoolHandle> m_FreeGeoPoolEntrySlots;
readonly List<GraphicsBuffer> m_InputBufferReferences;
readonly ComputeShader m_CopyShader;
ComputeShader m_GeometryPoolKernelsCS;
int m_KernelMainUpdateIndexBuffer16;
int m_KernelMainUpdateIndexBuffer32;
int m_KernelMainUpdateVertexBuffer;
readonly CommandBuffer m_CmdBuffer;
bool m_MustClearCmdBuffer;
int m_PendingCmds;
public GeometryPool(in GeometryPoolDesc desc, ComputeShader geometryPoolShader, ComputeShader copyShader)
{
m_CopyShader = copyShader;
LoadKernels(geometryPoolShader);
m_CmdBuffer = new CommandBuffer();
m_InputBufferReferences = new List<GraphicsBuffer>();
m_MustClearCmdBuffer = false;
m_PendingCmds = 0;
m_MaxVertCounts = CalcVertexCount(desc.vertexPoolByteSize);
m_MaxIndexCounts = CalcIndexCount(desc.indexPoolByteSize);
m_MaxMeshChunkTableEntriesCount = CalcMeshChunkTablesCount(desc.meshChunkTablesByteSize);
m_GlobalVertexBuffer = new GraphicsBuffer(VertexBufferTarget, DivUp(m_MaxVertCounts * GetVertexByteSize(), 4), 4);
m_GlobalIndexBuffer = new GraphicsBuffer(IndexBufferTarget, m_MaxIndexCounts, 4);
m_GlobalMeshChunkTableEntryBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, m_MaxMeshChunkTableEntriesCount, GetMeshChunkTableEntryByteSize());
m_DummyBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 16, 4);
var initialCapacity = 4096;
m_MeshHashToGeoSlot = new NativeParallelHashMap<uint, int>(initialCapacity, Allocator.Persistent);
m_GeoSlots = new List<GeometrySlot>();
m_FreeGeoSlots = new NativeList<int>(Allocator.Persistent);
m_GeoPoolEntryHashToSlot = new NativeParallelHashMap<uint, GeometryPoolHandle>(initialCapacity, Allocator.Persistent);
m_GeoPoolEntrySlots = new NativeList<GeoPoolEntrySlot>(Allocator.Persistent);
m_FreeGeoPoolEntrySlots = new NativeList<GeometryPoolHandle>(Allocator.Persistent);
m_VertexAllocator = new BlockAllocator();
m_VertexAllocator.Initialize(m_MaxVertCounts);
m_IndexAllocator = new BlockAllocator();
m_IndexAllocator.Initialize(m_MaxIndexCounts);
m_MeshChunkTableAllocator = new BlockAllocator();
m_MeshChunkTableAllocator.Initialize(m_MaxMeshChunkTableEntriesCount);
}
void DisposeInputBuffers()
{
if (m_InputBufferReferences.Count == 0)
return;
foreach (var b in m_InputBufferReferences)
b.Dispose();
m_InputBufferReferences.Clear();
}
public void Dispose()
{
m_IndexAllocator.Dispose();
m_VertexAllocator.Dispose();
m_MeshChunkTableAllocator.Dispose();
m_DummyBuffer.Dispose();
m_MeshHashToGeoSlot.Dispose();
foreach (var geoSlot in m_GeoSlots)
{
if (geoSlot.valid)
geoSlot.meshChunks.Dispose();
}
m_GeoSlots = null;
m_FreeGeoSlots.Dispose();
m_GeoPoolEntryHashToSlot.Dispose();
m_GeoPoolEntrySlots.Dispose();
m_FreeGeoPoolEntrySlots.Dispose();
m_GlobalIndexBuffer.Dispose();
m_GlobalVertexBuffer.Release();
m_GlobalMeshChunkTableEntryBuffer.Dispose();
m_CmdBuffer.Release();
DisposeInputBuffers();
}
private void LoadKernels(ComputeShader geometryPoolShader)
{
m_GeometryPoolKernelsCS = geometryPoolShader;
m_KernelMainUpdateIndexBuffer16 = m_GeometryPoolKernelsCS.FindKernel("MainUpdateIndexBuffer16");
m_KernelMainUpdateIndexBuffer32 = m_GeometryPoolKernelsCS.FindKernel("MainUpdateIndexBuffer32");
m_KernelMainUpdateVertexBuffer = m_GeometryPoolKernelsCS.FindKernel("MainUpdateVertexBuffer");
}
private int CalcVertexCount(int bufferByteSize) => DivUp(bufferByteSize, GetVertexByteSize());
private int CalcIndexCount(int bufferByteSize) => DivUp(bufferByteSize, GetIndexByteSize());
private int CalcMeshChunkTablesCount(int bufferByteSize) => DivUp(bufferByteSize, GetMeshChunkTableEntryByteSize());
private void DeallocateGeometrySlot(ref GeometrySlot slot)
{
if (slot.meshChunkTableAlloc.valid)
{
m_MeshChunkTableAllocator.FreeAllocation(slot.meshChunkTableAlloc);
if (slot.meshChunks.IsCreated)
{
for (int i = 0; i < slot.meshChunks.Length; ++i)
{
var meshChunk = slot.meshChunks[i];
if (meshChunk.vertexAlloc.valid)
m_VertexAllocator.FreeAllocation(meshChunk.vertexAlloc);
if (meshChunk.indexAlloc.valid)
m_IndexAllocator.FreeAllocation(meshChunk.indexAlloc);
}
slot.meshChunks.Dispose();
}
}
slot = GeometrySlot.Invalid;
}
private void DeallocateGeometrySlot(int geoSlotHandle)
{
var geoSlot = m_GeoSlots[geoSlotHandle];
Assertions.Assert.IsTrue(geoSlot.valid);
--geoSlot.refCount;
if (geoSlot.refCount == 0)
{
m_MeshHashToGeoSlot.Remove(geoSlot.hash);
DeallocateGeometrySlot(ref geoSlot);
m_FreeGeoSlots.Add(geoSlotHandle);
}
m_GeoSlots[geoSlotHandle] = geoSlot;
}
private bool AllocateGeo(Mesh mesh, out int allocationHandle)
{
uint meshHash = (uint)mesh.GetHashCode();
int indexCount = 0;
for (int submeshIndex = 0; submeshIndex < mesh.subMeshCount; ++submeshIndex)
{
indexCount += (int)mesh.GetIndexCount(submeshIndex);
}
if (m_MeshHashToGeoSlot.TryGetValue(meshHash, out allocationHandle))
{
var geoSlot = m_GeoSlots[allocationHandle];
Assertions.Assert.IsTrue(geoSlot.hash == meshHash);
Assertions.Assert.IsTrue(geoSlot.meshChunkTableAlloc.block.count == mesh.subMeshCount);
++geoSlot.refCount;
m_GeoSlots[allocationHandle] = geoSlot;
return true;
}
allocationHandle = InvalidHandle;
var newSlot = GeometrySlot.Invalid;
newSlot.refCount = 1;
newSlot.hash = meshHash;
bool allocationSuccess = true;
if (mesh.subMeshCount > 0)
{
newSlot.meshChunkTableAlloc = m_MeshChunkTableAllocator.Allocate(mesh.subMeshCount);
if (!newSlot.meshChunkTableAlloc.valid)
{
newSlot.meshChunkTableAlloc = m_MeshChunkTableAllocator.GrowAndAllocate(mesh.subMeshCount, GraphicsHelpers.MaxElementCount(GetMeshChunkTableEntryByteSize()), out int oldCapacity, out int newCapacity);
if (!newSlot.meshChunkTableAlloc.valid)
throw new UnifiedRayTracingException($"Can't allocate a GraphicsBuffer bigger than {GraphicsHelpers.MaxGraphicsBufferSizeInGigaBytes:F1}GB", UnifiedRayTracingError.GraphicsBufferAllocationFailed);
GraphicsHelpers.ReallocateBuffer(m_CopyShader, oldCapacity, newCapacity, GetMeshChunkTableEntryByteSize(), ref m_GlobalMeshChunkTableEntryBuffer);
m_MaxMeshChunkTableEntriesCount = newCapacity;
}
newSlot.meshChunks = new NativeArray<MeshChunk>(mesh.subMeshCount, Allocator.Persistent);
for (int submeshIndex = 0; submeshIndex < mesh.subMeshCount; ++submeshIndex)
{
SubMeshDescriptor submeshDescriptor = mesh.GetSubMesh(submeshIndex);
var newMeshChunk = MeshChunk.Invalid;
newMeshChunk.vertexAlloc = m_VertexAllocator.Allocate(submeshDescriptor.vertexCount);
if (!newMeshChunk.vertexAlloc.valid)
{
newMeshChunk.vertexAlloc = m_VertexAllocator.GrowAndAllocate(submeshDescriptor.vertexCount, GraphicsHelpers.MaxElementCount(GetVertexByteSize()), out int oldCapacity, out int newCapacity);
if (!newMeshChunk.vertexAlloc.valid)
throw new UnifiedRayTracingException($"Can't allocate a GraphicsBuffer bigger than {GraphicsHelpers.MaxGraphicsBufferSizeInGigaBytes:F1}GB", UnifiedRayTracingError.GraphicsBufferAllocationFailed);
GraphicsHelpers.ReallocateBuffer(m_CopyShader, oldCapacity, newCapacity, GetVertexByteSize(), ref m_GlobalVertexBuffer);
m_MaxVertCounts = newCapacity;
}
newMeshChunk.indexAlloc = m_IndexAllocator.Allocate(submeshDescriptor.indexCount);
if (!newMeshChunk.indexAlloc.valid)
{
newMeshChunk.indexAlloc = m_IndexAllocator.GrowAndAllocate(submeshDescriptor.indexCount, GraphicsHelpers.MaxElementCount(sizeof(int)), out int oldCapacity, out int newCapacity);
if (!newMeshChunk.indexAlloc.valid)
throw new UnifiedRayTracingException($"Can't allocate a GraphicsBuffer bigger than {GraphicsHelpers.MaxGraphicsBufferSizeInGigaBytes:F1}GB", UnifiedRayTracingError.GraphicsBufferAllocationFailed);
GraphicsHelpers.ReallocateBuffer(m_CopyShader, oldCapacity, newCapacity, sizeof(int), ref m_GlobalIndexBuffer);
m_MaxIndexCounts = newCapacity;
}
newSlot.meshChunks[submeshIndex] = newMeshChunk;
}
}
if (!allocationSuccess)
{
DeallocateGeometrySlot(ref newSlot);
return false;
}
if (m_FreeGeoSlots.IsEmpty)
{
allocationHandle = m_GeoSlots.Count;
m_GeoSlots.Add(newSlot);
}
else
{
allocationHandle = m_FreeGeoSlots[m_FreeGeoSlots.Length - 1];
m_FreeGeoSlots.RemoveAtSwapBack(m_FreeGeoSlots.Length - 1);
Assertions.Assert.IsTrue(!m_GeoSlots[allocationHandle].valid);
m_GeoSlots[allocationHandle] = newSlot;
}
m_MeshHashToGeoSlot.Add(newSlot.hash, allocationHandle);
return true;
}
private void DeallocateGeoPoolEntrySlot(GeometryPoolHandle handle)
{
var slot = m_GeoPoolEntrySlots[handle.index];
--slot.refCount;
if (slot.refCount == 0)
{
m_GeoPoolEntryHashToSlot.Remove(slot.hash);
DeallocateGeoPoolEntrySlot(ref slot);
m_FreeGeoPoolEntrySlots.Add(handle);
}
m_GeoPoolEntrySlots[handle.index] = slot;
}
private void DeallocateGeoPoolEntrySlot(ref GeoPoolEntrySlot geoPoolEntrySlot)
{
if (geoPoolEntrySlot.geoSlotHandle != InvalidHandle)
DeallocateGeometrySlot(geoPoolEntrySlot.geoSlotHandle);
geoPoolEntrySlot = GeoPoolEntrySlot.Invalid;
}
public GeometryPoolEntryInfo GetEntryInfo(GeometryPoolHandle handle)
{
if (!handle.valid)
return GeometryPoolEntryInfo.NewDefault();
GeoPoolEntrySlot slot = m_GeoPoolEntrySlots[handle.index];
if (!slot.valid)
return GeometryPoolEntryInfo.NewDefault();
if (slot.geoSlotHandle == -1)
Debug.LogErrorFormat("Found invalid geometry slot handle with handle id {0}.", handle.index);
return new GeometryPoolEntryInfo()
{
valid = slot.valid,
refCount = slot.refCount
};
}
public GeometrySlot GetEntryGeomAllocation(GeometryPoolHandle handle)
{
var slot = m_GeoPoolEntrySlots[handle.index];
Assertions.Assert.IsTrue(slot.valid);
var geoSlot = m_GeoSlots[slot.geoSlotHandle];
Assertions.Assert.IsTrue(geoSlot.valid);
return geoSlot;
}
public int GetInstanceGeometryIndex(Mesh mesh)
{
return GetEntryGeomAllocation(GetHandle(mesh)).meshChunkTableAlloc.block.offset;
}
private void UpdateGeoGpuState(Mesh mesh, GeometryPoolHandle handle)
{
var entrySlot = m_GeoPoolEntrySlots[handle.index];
var geoSlot = m_GeoSlots[entrySlot.geoSlotHandle];
CommandBuffer cmdBuffer = AllocateCommandBuffer(); //clear any previous cmd buffers.
//Upload mesh information.
if (!geoSlot.hasGPUData)
{
//Load index buffer
GraphicsBuffer buffer = LoadIndexBuffer(mesh);
Assertions.Assert.IsTrue((buffer.target & GraphicsBuffer.Target.Raw) != 0);
// Load attribute buffers
VertexBufferAttribInfo posAttrib;
LoadVertexAttribInfo(mesh, VertexAttribute.Position, out posAttrib);
VertexBufferAttribInfo uv0Attrib;
LoadVertexAttribInfo(mesh, VertexAttribute.TexCoord0, out uv0Attrib);
VertexBufferAttribInfo uv1Attrib;
LoadVertexAttribInfo(mesh, VertexAttribute.TexCoord1, out uv1Attrib);
VertexBufferAttribInfo normalAttrib;
LoadVertexAttribInfo(mesh, VertexAttribute.Normal, out normalAttrib);
var meshChunkAllocationTable = new NativeArray<GeoPoolMeshChunk>(geoSlot.meshChunks.Length, Allocator.Temp);
for (int submeshIndex = 0; submeshIndex < mesh.subMeshCount; ++submeshIndex)
{
SubMeshDescriptor submeshDescriptor = mesh.GetSubMesh(submeshIndex);
MeshChunk targetMeshChunk = geoSlot.meshChunks[submeshIndex];
//Update mesh chunk vertex offset
AddVertexUpdateCommand(
cmdBuffer, submeshDescriptor.baseVertex + submeshDescriptor.firstVertex,
posAttrib, uv0Attrib, uv1Attrib, normalAttrib,
targetMeshChunk.vertexAlloc, m_GlobalVertexBuffer);
//Update mesh chunk index offset
AddIndexUpdateCommand(
cmdBuffer,
mesh.indexFormat, buffer, targetMeshChunk.indexAlloc, submeshDescriptor.firstVertex,
submeshDescriptor.indexStart, submeshDescriptor.indexCount, 0,
m_GlobalIndexBuffer);
meshChunkAllocationTable[submeshIndex] = targetMeshChunk.EncodeGPUEntry();
}
cmdBuffer.SetBufferData(m_GlobalMeshChunkTableEntryBuffer, meshChunkAllocationTable, 0, geoSlot.meshChunkTableAlloc.block.offset, meshChunkAllocationTable.Length);
meshChunkAllocationTable.Dispose();
geoSlot.hasGPUData = true;
m_GeoSlots[entrySlot.geoSlotHandle] = geoSlot;
}
}
private uint FNVHash(uint prevHash, uint dword)
{
//https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
const uint fnvPrime = 0x811C9DC5;
for (int i = 0; i < 4; ++i)
{
prevHash ^= ((dword >> (i * 8)) & 0xFF);
prevHash *= fnvPrime;
}
return prevHash;
}
private uint CalculateClusterHash(Mesh mesh, GeometryPoolSubmeshData[] submeshData)
{
uint meshHash = (uint)mesh.GetHashCode();
uint clusterHash = meshHash;
if (submeshData != null)
{
foreach (var data in submeshData)
{
clusterHash = FNVHash(clusterHash, (uint)data.submeshIndex);
clusterHash = FNVHash(clusterHash, (uint)(data.material == null ? 0 : data.material.GetHashCode()));
}
}
return clusterHash;
}
public GeometryPoolHandle GetHandle(Mesh mesh)
{
uint geoPoolEntryHash = CalculateClusterHash(mesh, null);
if (m_GeoPoolEntryHashToSlot.TryGetValue(geoPoolEntryHash, out GeometryPoolHandle outHandle))
return outHandle;
else
return GeometryPoolHandle.Invalid;
}
private static int FindSubmeshEntryInDesc(int submeshIndex, in GeometryPoolSubmeshData[] submeshData)
{
if (submeshData == null)
return -1;
for (int i = 0; i < submeshData.Length; ++i)
{
if (submeshData[i].submeshIndex == submeshIndex)
return i;
}
return -1;
}
public bool Register(Mesh mesh, out GeometryPoolHandle outHandle)
{
return Register(new GeometryPoolEntryDesc()
{
mesh = mesh,
submeshData = null
}, out outHandle);
}
public bool Register(in GeometryPoolEntryDesc entryDesc, out GeometryPoolHandle outHandle)
{
outHandle = GeometryPoolHandle.Invalid;
if (entryDesc.mesh == null)
{
return false;
}
Mesh mesh = entryDesc.mesh;
uint geoPoolEntryHash = CalculateClusterHash(entryDesc.mesh, entryDesc.submeshData);
if (m_GeoPoolEntryHashToSlot.TryGetValue(geoPoolEntryHash, out outHandle))
{
GeoPoolEntrySlot geoPoolEntrySlot = m_GeoPoolEntrySlots[outHandle.index];
Assertions.Assert.IsTrue(geoPoolEntrySlot.hash == geoPoolEntryHash);
GeometrySlot geoSlot = m_GeoSlots[geoPoolEntrySlot.geoSlotHandle];
Assertions.Assert.IsTrue(geoSlot.hash == (uint)mesh.GetHashCode());
++geoPoolEntrySlot.refCount;
m_GeoPoolEntrySlots[outHandle.index] = geoPoolEntrySlot;
return true;
}
var newSlot = GeoPoolEntrySlot.Invalid;
newSlot.refCount = 1;
newSlot.hash = geoPoolEntryHash;
// Validate submesh information
var validSubmeshData = new List<GeometryPoolSubmeshData>(mesh.subMeshCount);
if (mesh.subMeshCount > 0 && entryDesc.submeshData != null)
{
for (int submeshIndex = 0; submeshIndex < mesh.subMeshCount; ++submeshIndex)
{
int entryIndex = FindSubmeshEntryInDesc(submeshIndex, entryDesc.submeshData);
if (entryIndex == -1)
{
Debug.LogErrorFormat("Could not find submesh index {0} for mesh entry descriptor of mesh {1}.", submeshIndex, mesh.name);
continue;
}
validSubmeshData.Add(entryDesc.submeshData[entryIndex]);
}
}
if (!AllocateGeo(mesh, out newSlot.geoSlotHandle))
{
DeallocateGeoPoolEntrySlot(ref newSlot);
return false;
}
if (m_FreeGeoPoolEntrySlots.IsEmpty)
{
outHandle = new GeometryPoolHandle() { index = m_GeoPoolEntrySlots.Length };
m_GeoPoolEntrySlots.Add(newSlot);
}
else
{
outHandle = m_FreeGeoPoolEntrySlots[m_FreeGeoPoolEntrySlots.Length - 1];
m_FreeGeoPoolEntrySlots.RemoveAtSwapBack(m_FreeGeoPoolEntrySlots.Length - 1);
Assertions.Assert.IsTrue(!m_GeoPoolEntrySlots[outHandle.index].valid);
m_GeoPoolEntrySlots[outHandle.index] = newSlot;
}
m_GeoPoolEntryHashToSlot.Add(newSlot.hash, outHandle);
UpdateGeoGpuState(mesh, outHandle);
return true;
}
public void Unregister(GeometryPoolHandle handle)
{
var slot = m_GeoPoolEntrySlots[handle.index];
Assertions.Assert.IsTrue(slot.valid);
DeallocateGeoPoolEntrySlot(handle);
}
public void SendGpuCommands()
{
if (m_PendingCmds != 0)
{
Graphics.ExecuteCommandBuffer(m_CmdBuffer);
m_MustClearCmdBuffer = true;
m_PendingCmds = 0;
}
DisposeInputBuffers();
}
private GraphicsBuffer LoadIndexBuffer(Mesh mesh)
{
Debug.Assert((mesh.indexBufferTarget & GraphicsBuffer.Target.Raw) != 0 || (mesh.GetIndices(0) != null && mesh.GetIndices(0).Length != 0),
"Cant use a mesh buffer that is not raw and has no CPU index information.");
mesh.indexBufferTarget |= GraphicsBuffer.Target.Raw;
mesh.vertexBufferTarget |= GraphicsBuffer.Target.Raw;
var idxBuffer = mesh.GetIndexBuffer();
m_InputBufferReferences.Add(idxBuffer);
return idxBuffer;
}
void LoadVertexAttribInfo(Mesh mesh, VertexAttribute attribute, out VertexBufferAttribInfo output)
{
if (!mesh.HasVertexAttribute(attribute))
{
output.buffer = null;
output.stride = output.offset = output.byteCount = 0;
return;
}
int stream = mesh.GetVertexAttributeStream(attribute);
output.stride = mesh.GetVertexBufferStride(stream);
output.offset = mesh.GetVertexAttributeOffset(attribute);
output.byteCount = GetFormatByteCount(mesh.GetVertexAttributeFormat(attribute)) * mesh.GetVertexAttributeDimension(attribute);
output.buffer = mesh.GetVertexBuffer(stream);
m_InputBufferReferences.Add(output.buffer);
Assertions.Assert.IsTrue((output.buffer.target & GraphicsBuffer.Target.Raw) != 0);
}
private CommandBuffer AllocateCommandBuffer()
{
if (m_MustClearCmdBuffer)
{
m_CmdBuffer.Clear();
m_MustClearCmdBuffer = false;
}
++m_PendingCmds;
return m_CmdBuffer;
}
private void AddIndexUpdateCommand(
CommandBuffer cmdBuffer,
IndexFormat inputFormat,
in GraphicsBuffer inputBuffer,
in BlockAllocator.Allocation location,
int firstVertex,
int inputOffset, int indexCount, int outputOffset,
GraphicsBuffer outputIdxBuffer)
{
if (location.block.count == 0)
return;
Assertions.Assert.IsTrue(indexCount <= location.block.count);
Assertions.Assert.IsTrue(outputOffset < location.block.count);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._InputIBBaseOffset, inputOffset);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._InputIBCount, indexCount);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._InputFirstVertex, firstVertex);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._OutputIBOffset, location.block.offset + outputOffset);
int kernel = inputFormat == IndexFormat.UInt16 ? m_KernelMainUpdateIndexBuffer16 : m_KernelMainUpdateIndexBuffer32;
cmdBuffer.SetComputeBufferParam(m_GeometryPoolKernelsCS, kernel, GeoPoolShaderIDs._InputIndexBuffer, inputBuffer);
cmdBuffer.SetComputeBufferParam(m_GeometryPoolKernelsCS, kernel, GeoPoolShaderIDs._OutputIndexBuffer, outputIdxBuffer);
int totalGroupCount = DivUp(location.block.count, kThreadGroupSize);
int dispatchCount = DivUp(totalGroupCount, kMaxThreadGroupsPerDispatch);
for (int dispatchIndex = 0; dispatchIndex < dispatchCount; ++dispatchIndex)
{
int indexOffset = dispatchIndex * kMaxThreadGroupsPerDispatch * kThreadGroupSize;
int dispatchGroupCount = Math.Min(kMaxThreadGroupsPerDispatch, totalGroupCount - dispatchIndex * kMaxThreadGroupsPerDispatch);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._DispatchIndexOffset, indexOffset);
cmdBuffer.DispatchCompute(m_GeometryPoolKernelsCS, kernel, dispatchGroupCount, 1, 1);
}
}
private void AddVertexUpdateCommand(
CommandBuffer cmdBuffer, int baseVertexOffset,
in VertexBufferAttribInfo pos, in VertexBufferAttribInfo uv0, in VertexBufferAttribInfo uv1, in VertexBufferAttribInfo n,
in BlockAllocator.Allocation location,
GraphicsBuffer outputVertexBuffer)
{
if (location.block.count == 0)
return;
GeoPoolVertexAttribs attributes = 0;
if (pos.valid)
attributes |= GeoPoolVertexAttribs.Position;
if (uv0.valid)
attributes |= GeoPoolVertexAttribs.Uv0;
if (uv1.valid)
attributes |= GeoPoolVertexAttribs.Uv1;
if (n.valid)
attributes |= GeoPoolVertexAttribs.Normal;
int vertexCount = location.block.count;
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._InputVBCount, vertexCount);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._InputBaseVertexOffset, baseVertexOffset);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._OutputVBSize, m_MaxVertCounts);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._OutputVBOffset, location.block.offset);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._InputPosBufferStride, pos.stride);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._InputPosBufferOffset, pos.offset);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._InputUv0BufferStride, uv0.stride);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._InputUv0BufferOffset, uv0.offset);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._InputUv1BufferStride, uv1.stride);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._InputUv1BufferOffset, uv1.offset);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._InputNormalBufferStride, n.stride);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._InputNormalBufferOffset, n.offset);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._AttributesMask, (int)attributes);
int kernel = m_KernelMainUpdateVertexBuffer;
cmdBuffer.SetComputeBufferParam(m_GeometryPoolKernelsCS, kernel, GeoPoolShaderIDs._PosBuffer, pos.valid ? pos.buffer : m_DummyBuffer);
cmdBuffer.SetComputeBufferParam(m_GeometryPoolKernelsCS, kernel, GeoPoolShaderIDs._Uv0Buffer, uv0.valid ? uv0.buffer : m_DummyBuffer);
cmdBuffer.SetComputeBufferParam(m_GeometryPoolKernelsCS, kernel, GeoPoolShaderIDs._Uv1Buffer, uv1.valid ? uv1.buffer : m_DummyBuffer);
cmdBuffer.SetComputeBufferParam(m_GeometryPoolKernelsCS, kernel, GeoPoolShaderIDs._NormalBuffer, n.valid ? n.buffer : m_DummyBuffer);
cmdBuffer.SetComputeBufferParam(m_GeometryPoolKernelsCS, kernel, GeoPoolShaderIDs._OutputVB, outputVertexBuffer);
int totalGroupCount = DivUp(vertexCount, kThreadGroupSize);
int dispatchCount = DivUp(totalGroupCount, kMaxThreadGroupsPerDispatch);
for (int dispatchIndex = 0; dispatchIndex < dispatchCount; ++dispatchIndex)
{
int vertexOffset = dispatchIndex * kMaxThreadGroupsPerDispatch * kThreadGroupSize;
int dispatchGroupCount = Math.Min(kMaxThreadGroupsPerDispatch, totalGroupCount - dispatchIndex * kMaxThreadGroupsPerDispatch);
cmdBuffer.SetComputeIntParam(m_GeometryPoolKernelsCS, GeoPoolShaderIDs._DispatchVertexOffset, vertexOffset);
cmdBuffer.DispatchCompute(m_GeometryPoolKernelsCS, kernel, dispatchGroupCount, 1, 1);
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 352c33fc019547446a50f48d3badcecb
@@ -0,0 +1,113 @@
#ifndef GEOMETRY_POOL_H
#define GEOMETRY_POOL_H
namespace GeometryPool
{
float2 msign(float2 v)
{
return float2(
(v.x >= 0.0) ? 1.0 : -1.0,
(v.y >= 0.0) ? 1.0 : -1.0);
}
uint NormalToOctahedral32(float3 normal)
{
normal.xy /= (abs(normal.x) + abs(normal.y) + abs(normal.z));
normal.xy = (normal.z >= 0.0) ? normal.xy : (1.0 - abs(normal.yx)) * msign(normal.xy);
uint2 d = uint2(round(32767.5 + normal.xy * 32767.5));
return d.x | (d.y << 16u);
}
float3 Octahedral32ToNormal(uint data)
{
uint2 iv = uint2(data, data >> 16u) & 65535u;
float2 v = float2(iv) / 32767.5 - 1.0;
float3 normal = float3(v, 1.0 - abs(v.x) - abs(v.y));
float t = max(-normal.z, 0.0);
normal.x += (normal.x > 0.0) ? -t : t;
normal.y += (normal.y > 0.0) ? -t : t;
return normalize(normal);
}
uint UvsToUint32(float2 uv)
{
return (uint(uv.x * 65535.0f) & 0xFFFF) | (uint(uv.y * 65535.0f) << 16);
}
float2 Uint32ToUvs(uint data)
{
return float2((data & 0xFFFF) * (1.0f/65535.0f), (data >> 16u) * (1.0f/65535.0f));
}
void StoreUvs(RWStructuredBuffer<uint> output, uint index, float2 uv)
{
#ifdef GEOMETRY_POOL_USE_COMPRESSED_UVS
output[index] = UvsToUint32(uv);
#else
output[index] = asuint(uv.x);
output[index + 1] = asuint(uv.y);
#endif
}
float2 LoadUvs(StructuredBuffer<uint> vertexBuffer, uint index)
{
#ifdef GEOMETRY_POOL_USE_COMPRESSED_UVS
return Uint32ToUvs(vertexBuffer[index]);
#else
return asfloat(uint2(vertexBuffer[index], vertexBuffer[index + 1]));
#endif
}
void StoreVertex(
uint vertexIndex,
in GeoPoolVertex vertex,
int outputBufferSize,
RWStructuredBuffer<uint> output)
{
uint posIndex = vertexIndex * GEO_POOL_VERTEX_BYTE_SIZE / 4;
output[posIndex] = asuint(vertex.pos.x);
output[posIndex+1] = asuint(vertex.pos.y);
output[posIndex+2] = asuint(vertex.pos.z);
uint uv0Index = (vertexIndex * GEO_POOL_VERTEX_BYTE_SIZE + GEO_POOL_UV0BYTE_OFFSET) / 4;
StoreUvs(output, uv0Index, vertex.uv0);
uint uv1Index = (vertexIndex * GEO_POOL_VERTEX_BYTE_SIZE + GEO_POOL_UV1BYTE_OFFSET) / 4;
StoreUvs(output, uv1Index, vertex.uv1);
uint normalIndex = (vertexIndex * GEO_POOL_VERTEX_BYTE_SIZE + GEO_POOL_NORMAL_BYTE_OFFSET) / 4;
output[normalIndex] = NormalToOctahedral32(vertex.N);
}
void LoadVertex(
uint vertexIndex,
int vertexFlags,
StructuredBuffer<uint> vertexBuffer,
out GeoPoolVertex outputVertex)
{
uint posIndex = vertexIndex * GEO_POOL_VERTEX_BYTE_SIZE / 4;
float3 pos = asfloat(uint3(vertexBuffer[posIndex], vertexBuffer[posIndex + 1], vertexBuffer[posIndex + 2]));
uint uv0Index = (vertexIndex * GEO_POOL_VERTEX_BYTE_SIZE + GEO_POOL_UV0BYTE_OFFSET) / 4;
float2 uv0 = LoadUvs(vertexBuffer, uv0Index);
uint uv1Index = (vertexIndex * GEO_POOL_VERTEX_BYTE_SIZE + GEO_POOL_UV1BYTE_OFFSET) / 4;
float2 uv1 = LoadUvs(vertexBuffer, uv1Index);
uint normalIndex = (vertexIndex * GEO_POOL_VERTEX_BYTE_SIZE + GEO_POOL_NORMAL_BYTE_OFFSET) / 4;
uint normal = uint(vertexBuffer[normalIndex]);
outputVertex.pos = pos;
outputVertex.uv0 = uv0;
outputVertex.uv1 = uv1;
outputVertex.N = Octahedral32ToNormal(normal);
}
}
#endif
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 839bff092489da0498be737d665c0665
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,40 @@
using System;
namespace UnityEngine.Rendering.UnifiedRayTracing
{
internal static class GeometryPoolConstants
{
public const int GeoPoolPosByteSize = 3 * 4;
const int UvFieldSizeInDWords = 2;
public const int GeoPoolUV0ByteSize = UvFieldSizeInDWords * 4;
public const int GeoPoolUV1ByteSize = UvFieldSizeInDWords * 4;
public const int GeoPoolNormalByteSize = 1 * 4;
public const int GeoPoolPosByteOffset = 0;
public const int GeoPoolUV0ByteOffset = GeoPoolPosByteOffset + GeoPoolPosByteSize;
public const int GeoPoolUV1ByteOffset = GeoPoolUV0ByteOffset + GeoPoolUV0ByteSize;
public const int GeoPoolNormalByteOffset = GeoPoolUV1ByteOffset + GeoPoolUV1ByteSize;
public const int GeoPoolIndexByteSize = 4;
public const int GeoPoolVertexByteSize = GeoPoolPosByteSize + GeoPoolUV0ByteSize + GeoPoolUV1ByteSize + GeoPoolNormalByteSize;
}
internal struct GeoPoolVertex
{
public Vector3 pos;
public Vector2 uv0;
public Vector2 uv1;
public Vector3 N;
}
internal struct GeoPoolMeshChunk
{
public int indexOffset;
public int indexCount;
public int vertexOffset;
public int vertexCount;
}
[Flags]
internal enum GeoPoolVertexAttribs { Position = 1, Normal = 2, Uv0 = 4, Uv1 = 8 }
}
@@ -0,0 +1,53 @@
#ifndef GEOMETRYPOOLDEFS_CS_HLSL
#define GEOMETRYPOOLDEFS_CS_HLSL
//#define GEOMETRY_POOL_USE_COMPRESSED_UVS
//
// UnityEngine.Rendering.UnifiedRayTracing.GeoPoolVertexAttribs: static fields
//
#define GEOPOOLVERTEXATTRIBS_POSITION (1)
#define GEOPOOLVERTEXATTRIBS_NORMAL (2)
#define GEOPOOLVERTEXATTRIBS_UV0 (4)
#define GEOPOOLVERTEXATTRIBS_UV1 (8)
//
// UnityEngine.Rendering.UnifiedRayTracing.GeometryPoolConstants: static fields
//
#define GEO_POOL_POS_BYTE_SIZE (12)
#ifdef GEOMETRY_POOL_USE_COMPRESSED_UVS
#define GEO_POOL_UV0BYTE_SIZE (4)
#define GEO_POOL_UV1BYTE_SIZE (4)
#else
#define GEO_POOL_UV0BYTE_SIZE (8)
#define GEO_POOL_UV1BYTE_SIZE (8)
#endif
#define GEO_POOL_NORMAL_BYTE_SIZE (4)
#define GEO_POOL_POS_BYTE_OFFSET (0)
#define GEO_POOL_UV0BYTE_OFFSET (GEO_POOL_POS_BYTE_SIZE)
#define GEO_POOL_UV1BYTE_OFFSET (GEO_POOL_POS_BYTE_SIZE+GEO_POOL_UV0BYTE_SIZE)
#define GEO_POOL_NORMAL_BYTE_OFFSET (GEO_POOL_POS_BYTE_SIZE+GEO_POOL_UV0BYTE_SIZE+GEO_POOL_UV1BYTE_SIZE)
#define GEO_POOL_INDEX_BYTE_SIZE (4)
#define GEO_POOL_VERTEX_BYTE_SIZE (GEO_POOL_NORMAL_BYTE_OFFSET+GEO_POOL_NORMAL_BYTE_SIZE)
// Generated from UnityEngine.Rendering.UnifiedRayTracing.GeoPoolMeshChunk
struct GeoPoolMeshChunk
{
int indexOffset;
int indexCount;
int vertexOffset;
int vertexCount;
};
// Generated from UnityEngine.Rendering.UnifiedRayTracing.GeoPoolVertex
struct GeoPoolVertex
{
float3 pos;
float2 uv0;
float2 uv1;
float3 N;
};
#endif
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: eca78e9f751408043bef5b9d52db9d39
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1e9c09a61faef3046bcf416dbb9c9d88
@@ -0,0 +1,111 @@
#pragma kernel MainUpdateIndexBuffer16
#pragma kernel MainUpdateIndexBuffer32
#pragma kernel MainUpdateVertexBuffer
#include "GeometryPoolDefs.cs.hlsl"
#include "GeometryPool.hlsl"
#define GROUP_SIZE_X 256
#define GROUP_SIZE_X_HALF (GROUP_SIZE_X >> 1)
int _DispatchIndexOffset;
int _InputIBBaseOffset;
int _InputIBCount;
int _InputFirstVertex;
int _OutputIBOffset;
ByteAddressBuffer _InputIndexBuffer;
RWStructuredBuffer<uint> _OutputIndexBuffer;
[numthreads(GROUP_SIZE_X,1,1)]
void MainUpdateIndexBuffer32(uint3 dispatchThreadID : SV_DispatchThreadID, int3 groupID : SV_GroupID)
{
uint bufferOffset = _DispatchIndexOffset + dispatchThreadID.x;
if (bufferOffset >= (uint)_InputIBCount)
return;
uint indexVal = _InputIndexBuffer.Load((_InputIBBaseOffset + bufferOffset) << 2u);
_OutputIndexBuffer[(uint)_OutputIBOffset + bufferOffset] = indexVal - _InputFirstVertex;
}
groupshared uint _ldsIndexCache[GROUP_SIZE_X_HALF + 1];
[numthreads(GROUP_SIZE_X,1,1)]
void MainUpdateIndexBuffer16(uint3 dispatchThreadID : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex, uint3 groupID : SV_GroupID)
{
uint uintPos = GROUP_SIZE_X_HALF * groupID.x + groupIndex + (uint)(_InputIBBaseOffset + _DispatchIndexOffset) / 2;
uint indexPos = uintPos * 2; // There are two indices per word
if (groupIndex < (GROUP_SIZE_X_HALF + 1) && indexPos < (uint)(_InputIBBaseOffset + _InputIBCount))
{
uint bytePos = uintPos << 2u;
_ldsIndexCache[groupIndex] = _InputIndexBuffer.Load(bytePos);
}
GroupMemoryBarrierWithGroupSync();
uint bufferOffset = _DispatchIndexOffset + dispatchThreadID.x;
if (bufferOffset >= (uint)_InputIBCount)
return;
uint localOffset = groupIndex + ((_InputIBBaseOffset + _DispatchIndexOffset) & 0x1);
uint pair = _ldsIndexCache[localOffset >> 1u];
uint value = (localOffset & 0x1) ? (pair >> 16) : (pair & 0xffff);
_OutputIndexBuffer[(uint)_OutputIBOffset + bufferOffset] = value - _InputFirstVertex;
}
int _InputVBCount;
int _InputBaseVertexOffset;
int _DispatchVertexOffset;
int _OutputVBSize;
int _OutputVBOffset;
int _InputPosBufferStride;
int _InputPosBufferOffset;
int _InputUv0BufferStride;
int _InputUv0BufferOffset;
int _InputUv1BufferStride;
int _InputUv1BufferOffset;
int _InputNormalBufferStride;
int _InputNormalBufferOffset;
ByteAddressBuffer _PosBuffer;
ByteAddressBuffer _Uv0Buffer;
ByteAddressBuffer _Uv1Buffer;
ByteAddressBuffer _NormalBuffer;
RWStructuredBuffer<uint> _OutputVB;
int _AttributesMask;
[numthreads(GROUP_SIZE_X, 1, 1)]
void MainUpdateVertexBuffer(uint3 dispatchThreadID : SV_DispatchThreadID)
{
int inputVertexOffset = _DispatchVertexOffset + (int)dispatchThreadID.x;
if ((int)inputVertexOffset >= _InputVBCount)
return;
inputVertexOffset += _InputBaseVertexOffset;
GeoPoolVertex vtx = (GeoPoolVertex)0;
if (_AttributesMask & GEOPOOLVERTEXATTRIBS_POSITION)
vtx.pos = asfloat(_PosBuffer.Load3(_InputPosBufferOffset + (inputVertexOffset * _InputPosBufferStride)));
if (_AttributesMask & GEOPOOLVERTEXATTRIBS_UV0)
vtx.uv0.xy = asfloat(_Uv0Buffer.Load2(_InputUv0BufferOffset + (inputVertexOffset * _InputUv0BufferStride)));
if (_AttributesMask & GEOPOOLVERTEXATTRIBS_UV1)
vtx.uv1.xy = asfloat(_Uv1Buffer.Load2(_InputUv1BufferOffset + (inputVertexOffset * _InputUv1BufferStride)));
if (_AttributesMask & GEOPOOLVERTEXATTRIBS_NORMAL)
vtx.N = asfloat(_NormalBuffer.Load3(_InputNormalBufferOffset + (inputVertexOffset * _InputNormalBufferStride)));
uint outputVertexOffset = _DispatchVertexOffset + (int)dispatchThreadID.x;
GeometryPool::StoreVertex(_OutputVBOffset + outputVertexOffset, vtx, _OutputVBSize, _OutputVB);
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 98e3d58cae7210c4786f67f504c9e899
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,252 @@
using System.Collections.Generic;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
namespace UnityEngine.Rendering.UnifiedRayTracing
{
internal static class TerrainToMesh
{
static private AsyncTerrainToMeshRequest MakeAsyncTerrainToMeshRequest(int width, int height, Vector3 heightmapScale, float[,] heightmap, bool[,] holes)
{
int vertexCount = width * height;
var job = new ComputeTerrainMeshJob();
job.heightmap = new NativeArray<float>(vertexCount, Allocator.Persistent);
for (int i = 0; i < vertexCount; ++i)
job.heightmap[i] = heightmap[i / (width), i % (width)];
job.holes = new NativeArray<bool>((width - 1) * (height - 1), Allocator.Persistent);
for (int i = 0; i < (width - 1) * (height - 1); ++i)
job.holes[i] = holes[i / (width - 1), i % (width - 1)];
job.width = width;
job.height = height;
job.heightmapScale = heightmapScale;
job.positions = new NativeArray<float3>(vertexCount, Allocator.Persistent);
job.uvs = new NativeArray<float2>(vertexCount, Allocator.Persistent);
job.normals = new NativeArray<float3>(vertexCount, Allocator.Persistent);
job.indices = new NativeArray<int>((width - 1) * (height - 1) * 6, Allocator.Persistent);
JobHandle jobHandle = job.Schedule(vertexCount, math.max(width, 128));
return new AsyncTerrainToMeshRequest(job, jobHandle);
}
static public AsyncTerrainToMeshRequest ConvertAsync(int heightmapWidth, int heightmapHeight, short[] heightmapData, Vector3 heightmapScale, int holeWidth, int holeHeight, byte[] holedata)
{
float[,] heightmap = new float[heightmapWidth,heightmapHeight];
for (int y = 0; y < heightmapHeight; ++y)
for (int x = 0; x < heightmapWidth; ++x)
heightmap[y, x] = (float)heightmapData[y * heightmapWidth + x] / (float)32766;
bool[,] holes = new bool[heightmapWidth - 1, heightmapHeight - 1];
if (holedata != null)
{
for (int y = 0; y < heightmapHeight - 1; ++y)
for (int x = 0; x < heightmapWidth - 1; ++x)
holes[y, x] = holedata[y * holeWidth + x] != 0;
}
else
{
for (int y = 0; y < heightmapHeight - 1; ++y)
for (int x = 0; x < heightmapWidth - 1; ++x)
holes[x, y] = true;
}
return MakeAsyncTerrainToMeshRequest(heightmapWidth, heightmapHeight, heightmapScale, heightmap, holes);
}
static public Mesh Convert(int heightmapWidth, int heightmapHeight, short[] heightmapData, Vector3 heightmapScale, int holeWidth, int holeHeight, byte[] holedata)
{
var request = ConvertAsync(heightmapWidth, heightmapHeight, heightmapData, heightmapScale, holeWidth, holeHeight, holedata);
request.WaitForCompletion();
return request.GetMesh();
}
#if ENABLE_TERRAIN_MODULE
static public AsyncTerrainToMeshRequest ConvertAsync(Terrain terrain)
{
TerrainData terrainData = terrain.terrainData;
int width = terrainData.heightmapTexture.width;
int height = terrainData.heightmapTexture.height;
float[,] heightmap = terrain.terrainData.GetHeights(0, 0, width, height);
bool[,] holes = terrain.terrainData.GetHoles(0, 0, width - 1, height - 1);
return MakeAsyncTerrainToMeshRequest(width, height, terrainData.heightmapScale, heightmap, holes);
}
static public Mesh Convert(Terrain terrain)
{
var request = ConvertAsync(terrain);
request.WaitForCompletion();
return request.GetMesh();
}
#endif
}
internal struct AsyncTerrainToMeshRequest
{
internal AsyncTerrainToMeshRequest(ComputeTerrainMeshJob job, JobHandle jobHandle)
{
m_Job = job;
m_JobHandle = jobHandle;
}
public bool done { get { return m_JobHandle.IsCompleted; } }
public Mesh GetMesh()
{
if (!done)
return null;
Mesh mesh = new Mesh();
mesh.indexFormat = IndexFormat.UInt32;
mesh.SetVertices(m_Job.positions);
mesh.SetUVs(0, m_Job.uvs);
mesh.SetUVs(1, m_Job.uvs);
mesh.SetNormals(m_Job.normals);
mesh.SetIndices(TriangleIndicesWithoutHoles().ToArray(), MeshTopology.Triangles, 0);
m_Job.DisposeArrays();
return mesh;
}
public void WaitForCompletion()
{
m_JobHandle.Complete();
}
List<int> TriangleIndicesWithoutHoles()
{
var trianglesWithoutHoles = new List<int>((m_Job.width - 1) * (m_Job.height - 1) * 6);
for (int i = 0; i < m_Job.indices.Length; i += 3)
{
int i1 = m_Job.indices[i];
int i2 = m_Job.indices[i + 1];
int i3 = m_Job.indices[i + 2];
if (i1 != i2 || i2 != i3)
{
trianglesWithoutHoles.Add(i1);
trianglesWithoutHoles.Add(i2);
trianglesWithoutHoles.Add(i3);
}
}
if (trianglesWithoutHoles.Count == 0)
{
trianglesWithoutHoles.Add(0);
trianglesWithoutHoles.Add(0);
trianglesWithoutHoles.Add(0);
}
return trianglesWithoutHoles;
}
JobHandle m_JobHandle;
ComputeTerrainMeshJob m_Job;
}
[BurstCompile]
internal struct ComputeTerrainMeshJob : IJobParallelFor
{
[ReadOnly]
public NativeArray<float> heightmap;
[ReadOnly]
public NativeArray<bool> holes;
public int width;
public int height;
public float3 heightmapScale;
public NativeArray<float3> positions;
public NativeArray<float2> uvs;
public NativeArray<float3> normals;
[NativeDisableParallelForRestriction]
public NativeArray<int> indices;
public void DisposeArrays()
{
heightmap.Dispose();
holes.Dispose();
positions.Dispose();
uvs.Dispose();
normals.Dispose();
indices.Dispose();
}
public void Execute(int index)
{
int vertexIndex = index;
int x = vertexIndex % width;
int y = vertexIndex / height;
float3 v = new float3(x, heightmap[y*width +x], y);
positions[vertexIndex] = v * heightmapScale;
// Extent convention: vertex.xz / (W-1) spans [0, 1] across the terrain extent.
// Matches Progressive GPU's HeightmapJobData.cpp:83 so the lightmap atlas region
// is fully covered (no ~1/W unbaked strip at the right/top edges).
uvs[vertexIndex] = v.xz / new float2(width - 1, height - 1);
normals[vertexIndex] = CalculateTerrainNormal(heightmap, x, y, width, height, heightmapScale);
if (x < width - 1 && y < height - 1)
{
int i1 = y * width + x;
int i2 = i1 + 1;
int i3 = i1 + width;
int i4 = i3 + 1;
int faceIndex = x + y * (width - 1);
if (!holes[faceIndex])
{
i1 = i2 = i3 = i4 = 0;
}
indices[6* faceIndex + 0] = i1;
indices[6* faceIndex + 1] = i4;
indices[6* faceIndex + 2] = i2;
indices[6* faceIndex + 3] = i1;
indices[6* faceIndex + 4] = i3;
indices[6* faceIndex + 5] = i4;
}
}
static float3 CalculateTerrainNormal(NativeArray<float> heightmap, int x, int y, int width, int height, float3 scale)
{
float dY, dX;
dX = SampleHeight(x - 1, y - 1, width, height, heightmap, scale.y) * -1.0F;
dX += SampleHeight(x - 1, y, width, height, heightmap, scale.y) * -2.0F;
dX += SampleHeight(x - 1, y + 1, width, height, heightmap, scale.y) * -1.0F;
dX += SampleHeight(x + 1, y - 1, width, height, heightmap, scale.y) * 1.0F;
dX += SampleHeight(x + 1, y, width, height, heightmap, scale.y) * 2.0F;
dX += SampleHeight(x + 1, y + 1, width, height, heightmap, scale.y) * 1.0F;
dX /= scale.x;
dY = SampleHeight(x - 1, y - 1, width, height, heightmap, scale.y) * -1.0F;
dY += SampleHeight(x, y - 1, width, height, heightmap, scale.y) * -2.0F;
dY += SampleHeight(x + 1, y - 1, width, height, heightmap, scale.y) * -1.0F;
dY += SampleHeight(x - 1, y + 1, width, height, heightmap, scale.y) * 1.0F;
dY += SampleHeight(x, y + 1, width, height, heightmap, scale.y) * 2.0F;
dY += SampleHeight(x + 1, y + 1, width, height, heightmap, scale.y) * 1.0F;
dY /= scale.z;
// Cross Product of components of gradient reduces to
return math.normalize(new float3(-dX, 8, -dY));
}
static float SampleHeight(int x, int y, int width, int height, NativeArray<float> heightmap, float scale)
{
x = math.clamp(x, 0, width - 1);
y = math.clamp(y, 0, height - 1);
return heightmap[x + y* width] * scale;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f0cd7c2492f60ae4a9e06a436208c7de
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d87eae04416aa5b4faeeb9dc46c9c6b6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,43 @@
using System;
namespace UnityEngine.Rendering.UnifiedRayTracing
{
/// <summary>
/// Helper functions for querying the shader type and filename for a given backend.
/// </summary>
public static class BackendHelpers
{
/// <summary>
/// Builds a file path with the right extension for the given <see cref="RayTracingBackend"/>.
/// </summary>
/// <param name="backend">Backend for which the shader will be loaded.</param>
/// <param name="fileName">Path to the shader file, without any extension.</param>
/// <returns>The file path.</returns>
public static string GetFileNameOfShader(RayTracingBackend backend, string fileName)
{
string postFix = backend switch
{
RayTracingBackend.Hardware => "raytrace",
RayTracingBackend.Compute => "compute",
_ => throw new ArgumentOutOfRangeException(nameof(backend), backend, null)
};
return $"{fileName}.{postFix}";
}
/// <summary>
/// Returns the <see cref="System.Type"/> of shader used by the given <see cref="RayTracingBackend"/>.
/// </summary>
/// <param name="backend">Backend for which the shader will be loaded.</param>
/// <returns>The Type of the shader.</returns>
public static Type GetTypeOfShader(RayTracingBackend backend)
{
Type shaderType = backend switch
{
RayTracingBackend.Hardware => typeof(RayTracingShader),
RayTracingBackend.Compute => typeof(ComputeShader),
_ => throw new ArgumentOutOfRangeException(nameof(backend), backend, null)
};
return shaderType;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d3167d2685f567343836d6d5ee2e115e
@@ -0,0 +1,284 @@
using System;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine.Assertions;
namespace UnityEngine.Rendering.UnifiedRayTracing
{
internal struct BlockAllocator : IDisposable
{
public struct Block
{
public int offset;
public int count;
public static readonly Block Invalid = new Block() { offset = 0, count = 0 };
}
public struct Allocation
{
public int handle;
public Block block;
public static readonly Allocation Invalid = new Allocation() { handle = -1 };
public readonly bool valid => handle != -1;
}
private int m_FreeElementCount;
private int m_MaxElementCount;
private NativeList<Block> m_freeBlocks;
private NativeList<Block> m_usedBlocks;
private NativeList<int> m_freeSlots;
public int freeElementsCount => m_FreeElementCount;
public int freeBlocks => m_freeBlocks.Length;
public int capacity => m_MaxElementCount;
public int allocatedSize => m_MaxElementCount - m_FreeElementCount;
public void Initialize(int maxElementCounts)
{
m_MaxElementCount = maxElementCounts;
m_FreeElementCount = maxElementCounts;
if (!m_freeBlocks.IsCreated)
m_freeBlocks = new NativeList<Block>(Allocator.Persistent);
else
m_freeBlocks.Clear();
m_freeBlocks.Add(new Block() { offset = 0, count = m_FreeElementCount });
if (!m_usedBlocks.IsCreated)
m_usedBlocks = new NativeList<Block>(Allocator.Persistent);
else
m_usedBlocks.Clear();
if (!m_freeSlots.IsCreated)
m_freeSlots = new NativeList<int>(Allocator.Persistent);
else
m_freeSlots.Clear();
}
private int CalculateGeometricGrowthCapacity(int desiredNewCapacity, int maxAllowedNewCapacity)
{
var oldCapacity = capacity;
if (oldCapacity > maxAllowedNewCapacity - oldCapacity / 2)
{
return maxAllowedNewCapacity; // geometric growth would overflow
}
var geometricNewCapacity = oldCapacity + oldCapacity / 2;
if (geometricNewCapacity < desiredNewCapacity)
return desiredNewCapacity; // geometric growth would be insufficient
else
return geometricNewCapacity;
}
public int Grow(int newDesiredCapacity, int maxAllowedCapacity = Int32.MaxValue)
{
Debug.Assert(newDesiredCapacity > 0, "newDesiredCapacity must be positive");
Debug.Assert(maxAllowedCapacity > 0, "maxAllowedCapacity must be positive");
Debug.Assert(capacity < newDesiredCapacity, "newDesiredCapacity must be greater than curent capacity");
Debug.Assert(maxAllowedCapacity >= newDesiredCapacity, "newDesiredCapacity must be smaller than maxAllowedCapacity");
var newCapacity = CalculateGeometricGrowthCapacity(newDesiredCapacity, maxAllowedCapacity);
var oldCapacity = m_MaxElementCount;
var addedElements = newCapacity - oldCapacity;
Debug.Assert(addedElements > 0);
m_FreeElementCount += addedElements;
m_MaxElementCount = newCapacity;
int blockToMerge = m_freeBlocks.Length;
m_freeBlocks.Add(new Block() { offset = oldCapacity, count = addedElements });
while (blockToMerge != -1)
blockToMerge = MergeBlockFrontBack(blockToMerge);
return m_MaxElementCount;
}
public bool GetExpectedGrowthToFitAllocation(int elementCounts, int maxAllowedCapacity, out int newCapacity)
{
newCapacity = 0;
var additionalRequiredElements = m_freeBlocks.IsEmpty ? elementCounts : math.max(elementCounts - m_freeBlocks[m_freeBlocks.Length - 1].count, 0);
if (maxAllowedCapacity < capacity || (maxAllowedCapacity - capacity) < additionalRequiredElements)
return false;
newCapacity = additionalRequiredElements > 0 ? CalculateGeometricGrowthCapacity(capacity + additionalRequiredElements, maxAllowedCapacity) : capacity;
return true;
}
public Allocation GrowAndAllocate(int elementCounts, out int oldCapacity, out int newCapacity)
{
return GrowAndAllocate(elementCounts, Int32.MaxValue, out oldCapacity, out newCapacity);
}
public Allocation GrowAndAllocate(int elementCounts, int maxAllowedCapacity, out int oldCapacity, out int newCapacity)
{
oldCapacity = capacity;
var additionalRequiredElements = m_freeBlocks.IsEmpty ? elementCounts : math.max(elementCounts - m_freeBlocks[m_freeBlocks.Length - 1].count, 0);
if (maxAllowedCapacity < capacity || (maxAllowedCapacity - capacity) < additionalRequiredElements)
{
newCapacity = capacity;
return Allocation.Invalid;
}
newCapacity = additionalRequiredElements > 0 ? Grow(capacity + additionalRequiredElements, maxAllowedCapacity) : capacity;
Debug.Assert(newCapacity >= oldCapacity + additionalRequiredElements);
var alloc = Allocate(elementCounts);
Assert.IsTrue(alloc.valid);
return alloc;
}
public void Dispose()
{
m_MaxElementCount = 0;
m_FreeElementCount = 0;
if (m_freeBlocks.IsCreated)
m_freeBlocks.Dispose();
if (m_usedBlocks.IsCreated)
m_usedBlocks.Dispose();
if (m_freeSlots.IsCreated)
m_freeSlots.Dispose();
}
public Allocation Allocate(int elementCounts)
{
if (elementCounts > m_FreeElementCount || m_freeBlocks.IsEmpty)
return Allocation.Invalid;
int selectedBlock = -1;
int currentBlockCount = 0;
for (int b = 0; b < m_freeBlocks.Length; ++b)
{
Block block = m_freeBlocks[b];
//simple naive allocator, we find the smallest possible space to allocate in our blocks.
if (elementCounts <= block.count && (selectedBlock == -1 || block.count < currentBlockCount))
{
currentBlockCount = block.count;
selectedBlock = b;
}
}
if (selectedBlock == -1)
return Allocation.Invalid;
Block allocationBlock = m_freeBlocks[selectedBlock];
Block split = allocationBlock;
split.offset += elementCounts;
split.count -= elementCounts;
allocationBlock.count = elementCounts;
if (split.count > 0)
m_freeBlocks[selectedBlock] = split;
else
m_freeBlocks.RemoveAtSwapBack(selectedBlock);
int allocationHandle;
if (m_freeSlots.IsEmpty)
{
allocationHandle = m_usedBlocks.Length;
m_usedBlocks.Add(allocationBlock);
}
else
{
allocationHandle = m_freeSlots[m_freeSlots.Length - 1];
m_freeSlots.RemoveAtSwapBack(m_freeSlots.Length - 1);
m_usedBlocks[allocationHandle] = allocationBlock;
}
m_FreeElementCount -= elementCounts;
return new Allocation() { handle = allocationHandle, block = allocationBlock };
}
private int MergeBlockFrontBack(int freeBlockId)
{
Block targetBlock = m_freeBlocks[freeBlockId];
for (int i = 0; i < m_freeBlocks.Length; ++i)
{
if (i == freeBlockId)
continue;
Block freeBlock = m_freeBlocks[i];
bool mergeTargetBlock = false;
if (targetBlock.offset == (freeBlock.offset + freeBlock.count))
{
freeBlock.count += targetBlock.count;
mergeTargetBlock = true;
}
else if (freeBlock.offset == (targetBlock.offset + targetBlock.count))
{
freeBlock.offset = targetBlock.offset;
freeBlock.count += targetBlock.count;
mergeTargetBlock = true;
}
if (mergeTargetBlock)
{
m_freeBlocks[i] = freeBlock;
m_freeBlocks.RemoveAtSwapBack(freeBlockId);
return i == m_freeBlocks.Length ? freeBlockId : i;
}
}
return -1;
}
public void FreeAllocation(in Allocation allocation)
{
Debug.Assert(allocation.valid, "Cannot free invalid allocation");
m_freeSlots.Add(allocation.handle);
m_usedBlocks[allocation.handle] = Block.Invalid;
int blockToMerge = m_freeBlocks.Length;
m_freeBlocks.Add(allocation.block);
while (blockToMerge != -1)
blockToMerge = MergeBlockFrontBack(blockToMerge);
m_FreeElementCount += allocation.block.count;
}
public Allocation[] SplitAllocation(in Allocation allocation, int count)
{
Debug.Assert(allocation.valid, "Invalid allocation");
var newAllocs = new Allocation[count];
var newAllocsSize = allocation.block.count / count;
var newBlock0 = new Block { offset = allocation.block.offset, count = newAllocsSize };
m_usedBlocks[allocation.handle] = newBlock0;
newAllocs[0] = new Allocation() { handle = allocation.handle, block = newBlock0 };
for (int i = 1; i < count; ++i)
{
Block block = new Block { offset = allocation.block.offset + i * newAllocsSize, count = newAllocsSize };
int allocationHandle;
if (m_freeSlots.IsEmpty)
{
allocationHandle = m_usedBlocks.Length;
m_usedBlocks.Add(block);
}
else
{
allocationHandle = m_freeSlots[m_freeSlots.Length - 1];
m_freeSlots.RemoveAtSwapBack(m_freeSlots.Length - 1);
m_usedBlocks[allocationHandle] = block;
}
newAllocs[i] = new Allocation() { handle = allocationHandle, block = block };
}
return newAllocs;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 56a7556d9033d1f43b3825829da0505f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,24 @@
#define GROUP_SIZE 256
#define ELEMENTS_PER_THREAD 8
StructuredBuffer<uint> _SrcBuffer;
int _SrcOffset;
RWStructuredBuffer<uint> _DstBuffer;
int _DstOffset;
int _Size;
#pragma kernel CopyBuffer
[numthreads(GROUP_SIZE,1,1)]
void CopyBuffer(uint gidx : SV_DispatchThreadID)
{
for (int i = 0; i < ELEMENTS_PER_THREAD; ++i)
{
int elemIndex = gidx * ELEMENTS_PER_THREAD + i;
if (elemIndex >= _Size)
return;
uint value = _SrcBuffer[elemIndex + _SrcOffset];
_DstBuffer[elemIndex + _DstOffset] = value;
}
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1b95b5dcf48d1914c9e1e7405c7660e3
ComputeShaderImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,84 @@
using Unity.Mathematics;
namespace UnityEngine.Rendering.UnifiedRayTracing
{
internal static class GraphicsHelpers
{
static public void CopyBuffer(ComputeShader copyShader, CommandBuffer cmd, GraphicsBuffer src, int srcOffsetInDWords, GraphicsBuffer dst, int dstOffsetInDwords, int sizeInDWords)
{
const int groupSize = 256;
const int elementsPerThread = 8;
const int maxThreadGroups = 65535; // gfx device limitation
const int maxBatchSizeInDWords = groupSize * elementsPerThread * maxThreadGroups;
int remainingDWords = sizeInDWords;
cmd.SetComputeBufferParam(copyShader, 0, "_SrcBuffer", src);
cmd.SetComputeBufferParam(copyShader, 0, "_DstBuffer", dst);
while (remainingDWords > 0)
{
int batchSize = math.min(remainingDWords, maxBatchSizeInDWords);
cmd.SetComputeIntParam(copyShader, "_SrcOffset", srcOffsetInDWords);
cmd.SetComputeIntParam(copyShader, "_DstOffset", dstOffsetInDwords);
cmd.SetComputeIntParam(copyShader, "_Size", batchSize);
cmd.DispatchCompute(copyShader, 0, DivUp(batchSize, elementsPerThread * groupSize), 1, 1);
remainingDWords -= batchSize;
srcOffsetInDWords += batchSize;
dstOffsetInDwords += batchSize;
}
}
static public void CopyBuffer(ComputeShader copyShader, GraphicsBuffer src, int srcOffsetInDWords, GraphicsBuffer dst, int dstOffsetInDwords, int sizeInDwords)
{
CommandBuffer cmd = new CommandBuffer();
CopyBuffer(copyShader, cmd, src, srcOffsetInDWords, dst, dstOffsetInDwords, sizeInDwords);
Graphics.ExecuteCommandBuffer(cmd);
}
static public bool ReallocateBuffer(ComputeShader copyShader, int oldCapacity, int newCapacity, int elementSizeInBytes, ref GraphicsBuffer buffer)
{
int bufferStrideInBytes = buffer.stride;
var newBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, (int)((long)newCapacity * elementSizeInBytes / bufferStrideInBytes), bufferStrideInBytes);
if (!newBuffer.IsValid())
return false;
CopyBuffer(copyShader, buffer, 0, newBuffer, 0, (int)((long)oldCapacity * elementSizeInBytes / 4));
buffer.Dispose();
buffer = newBuffer;
return true;
}
public static long MaxGraphicsBufferSizeInBytes => SystemInfo.maxGraphicsBufferSize;
public static float MaxGraphicsBufferSizeInGigaBytes => MaxGraphicsBufferSizeInBytes / 1024f / 1024f / 1024f;
// Element capacity of a GraphicsBuffer / BlockAllocator-backed buffer for a given byte budget.
// The capacity is an int (BlockAllocator's backing NativeList is int-indexed), so we compute the
// budget in long and clamp to int.MaxValue.
static public int MaxElementCount(long maxBufferSizeInBytes, int elementSizeInBytes)
=> (int)math.min((long)int.MaxValue, maxBufferSizeInBytes / (long)elementSizeInBytes);
static public int MaxElementCount(int elementSizeInBytes)
=> MaxElementCount(MaxGraphicsBufferSizeInBytes, elementSizeInBytes);
static public int DivUp(int x, int y) => (x + y - 1) / y;
static public int DivUp(int x, uint y) => (x + (int)y - 1) / (int)y;
static public uint DivUp(uint x, uint y) => (x + y - 1) / y;
static public uint3 DivUp(uint3 x, uint3 y) => (x + y - 1) / y;
/// <summary>
/// Immediately executes the pending work on the command buffer.
/// This is useful for preventing TDR, which can happen when scheduling too much work in one CommandBuffer.
/// </summary>
/// <param name="cmd">Command buffer to execute.</param>
static public void Flush(CommandBuffer cmd)
{
Graphics.ExecuteCommandBuffer(cmd);
cmd.Clear();
GL.Flush();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bfd0ff47a7084174ba1c6b5a11903a1b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,163 @@
using System;
using System.Collections;
using System.Runtime.InteropServices;
using Unity.Collections;
using UnityEngine.Assertions;
namespace UnityEngine.Rendering.UnifiedRayTracing
{
internal sealed class PersistentGpuArray<Tstruct> : IDisposable
where Tstruct : struct
{
BlockAllocator m_SlotAllocator;
ComputeBuffer m_GpuBuffer;
NativeArray<Tstruct> m_CpuList;
BitArray m_Updates;
bool m_gpuBufferDirty = true;
int m_ElementCount = 0;
public int elementCount { get { return m_ElementCount; } }
public PersistentGpuArray(int initialSize)
{
m_SlotAllocator.Initialize(initialSize);
m_GpuBuffer = new ComputeBuffer(initialSize, Marshal.SizeOf<Tstruct>());
m_CpuList = new NativeArray<Tstruct>(initialSize, Allocator.Persistent);
m_Updates = new BitArray(initialSize);
m_ElementCount = 0;
}
public void Dispose()
{
m_ElementCount = 0;
m_SlotAllocator.Dispose();
m_GpuBuffer.Dispose();
m_CpuList.Dispose();
}
public BlockAllocator.Allocation Add(Tstruct element)
{
m_ElementCount++;
var slotAllocation = m_SlotAllocator.Allocate(1);
if (!slotAllocation.valid)
{
Grow();
slotAllocation = m_SlotAllocator.Allocate(1);
Assert.IsTrue(slotAllocation.valid);
}
m_CpuList[slotAllocation.block.offset] = element;
m_Updates[slotAllocation.block.offset] = true;
m_gpuBufferDirty = true;
return slotAllocation;
}
public BlockAllocator.Allocation[] Add(int elementCount)
{
m_ElementCount+= elementCount;
var slotAllocation = m_SlotAllocator.Allocate(elementCount);
if (!slotAllocation.valid)
{
Grow();
slotAllocation = m_SlotAllocator.Allocate(elementCount);
Assert.IsTrue(slotAllocation.valid);
}
return m_SlotAllocator.SplitAllocation(slotAllocation, elementCount);
}
public void Remove(BlockAllocator.Allocation allocation)
{
m_ElementCount--;
m_SlotAllocator.FreeAllocation(allocation);
}
public void Clear()
{
m_ElementCount = 0;
var currentCapacity = m_SlotAllocator.capacity;
m_SlotAllocator.Dispose();
m_SlotAllocator = new BlockAllocator();
m_SlotAllocator.Initialize(currentCapacity);
m_Updates = new BitArray(currentCapacity);
m_gpuBufferDirty = false;
}
public void Set(BlockAllocator.Allocation allocation, Tstruct element)
{
m_CpuList[allocation.block.offset] = element;
m_Updates[allocation.block.offset] = true;
m_gpuBufferDirty = true;
}
public Tstruct Get(BlockAllocator.Allocation allocation)
{
return m_CpuList[allocation.block.offset];
}
public void ModifyForEach(Func<Tstruct, Tstruct> lambda)
{
for (int i = 0; i < m_CpuList.Length; ++i)
{
m_CpuList[i] = lambda(m_CpuList[i]);
m_Updates[i] = true;
}
m_gpuBufferDirty = true;
}
// Note: this should ideally be used with only one command buffer. If used with more than one cmd buffers, the order of their execution is important.
public ComputeBuffer GetGpuBuffer(CommandBuffer cmd)
{
if (m_gpuBufferDirty)
{
int copyStartIndex = -1;
for (int i = 0; i < m_Updates.Length; ++i)
{
if (m_Updates[i])
{
if (copyStartIndex == -1)
copyStartIndex = i;
m_Updates[i] = false;
}
else if (copyStartIndex != -1)
{
int copyEndIndex = i;
cmd.SetBufferData(m_GpuBuffer, m_CpuList, copyStartIndex, copyStartIndex, copyEndIndex - copyStartIndex);
copyStartIndex = -1;
}
}
if (copyStartIndex != -1)
{
int copyEndIndex = m_Updates.Length;
cmd.SetBufferData(m_GpuBuffer, m_CpuList, copyStartIndex, copyStartIndex, copyEndIndex - copyStartIndex);
}
m_gpuBufferDirty = false;
}
return m_GpuBuffer;
}
private void Grow()
{
var oldCapacity = m_SlotAllocator.capacity;
m_SlotAllocator.Grow(m_SlotAllocator.capacity + 1);
m_GpuBuffer.Dispose();
m_GpuBuffer = new ComputeBuffer(m_SlotAllocator.capacity, Marshal.SizeOf<Tstruct>());
var oldList = m_CpuList;
m_CpuList = new NativeArray<Tstruct>(m_SlotAllocator.capacity, Allocator.Persistent);
NativeArray<Tstruct>.Copy(oldList, m_CpuList, oldCapacity);
oldList.Dispose();
var oldUpdates = m_Updates;
m_Updates = new BitArray(m_SlotAllocator.capacity);
for (int i = 0; i < oldCapacity; ++i)
m_Updates[i] = oldUpdates[i];
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a6af8b249ef94724681ddfb4d682f0b7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,49 @@
using System;
using System.Diagnostics;
namespace UnityEngine.Rendering.UnifiedRayTracing
{
internal static class Utils
{
public static void Destroy(UnityEngine.Object obj)
{
if (obj != null)
{
#if UNITY_EDITOR
if (Application.isPlaying && !UnityEditor.EditorApplication.isPaused)
UnityEngine.Object.Destroy(obj);
else
UnityEngine.Object.DestroyImmediate(obj);
#else
UnityEngine.Object.Destroy(obj);
#endif
}
}
[Conditional("UNITY_ASSERTIONS")]
public static void CheckArgIsNotNull(System.Object obj, string argName)
{
if (obj == null)
throw new ArgumentNullException(argName);
}
[Conditional("UNITY_ASSERTIONS")]
public static void CheckArg(bool condition, string message)
{
if (!condition)
throw new ArgumentException(message);
}
[Conditional("UNITY_ASSERTIONS")]
public static void CheckArgRange<T>(T value, T minIncluded, T maxExcluded, string argName) where T: IComparable
{
if (value.CompareTo(minIncluded) < 0 || value.CompareTo(maxExcluded) >= 0)
{
var message = $"{argName}={value}, it must be in the range [{minIncluded}, {maxExcluded}[";
throw new ArgumentOutOfRangeException(argName, message);
}
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8787039c0fe98374a9815ac59d9878ef