using GameCult.Aetheria.State.Unity; using Unity.Collections; using Unity.Jobs; using Unity.Mathematics; using UnityEngine; public sealed class AetheriaDaemonRenderBuffer : MonoBehaviour { public const string MatrixBufferPropertyName = "_AetheriaObjectToWorld"; public const string InstanceCountPropertyName = "_AetheriaInstanceCount"; private const int MatrixStrideBytes = 64; private const int DrawMeshInstancedIndirectArgsCount = 5; [SerializeField] private AetheriaDaemonObserver observer; [SerializeField] private Mesh indirectArgsMesh; [SerializeField] private int innerLoopBatchCount = 64; [SerializeField] private bool updateInLateUpdate; private readonly uint[] _indirectArgs = new uint[DrawMeshInstancedIndirectArgsCount]; private NativeArray _nativeMatrices; private GraphicsBuffer _matrixBuffer; private GraphicsBuffer _indirectArgsBuffer; private long _allocatedGeneration = long.MinValue; private int _allocatedCount; public GraphicsBuffer MatrixBuffer => _matrixBuffer; public GraphicsBuffer IndirectArgsBuffer => _indirectArgsBuffer; public int InstanceCount { get; private set; } public long LastUploadedFrameId { get; private set; } public long LastUploadedGeneration { get; private set; } public bool HasRenderableView => observer != null && observer.HasRenderNativeView; public bool HasGpuBuffers => _matrixBuffer != null && _matrixBuffer.IsValid() && InstanceCount > 0; private static readonly int MatrixBufferPropertyId = Shader.PropertyToID(MatrixBufferPropertyName); private static readonly int InstanceCountPropertyId = Shader.PropertyToID(InstanceCountPropertyName); private void Reset() { observer = GetComponent(); } private void LateUpdate() { if (updateInLateUpdate) { UploadLatest(); } } public bool UploadLatest() { return UploadLatest(indirectArgsMesh); } public bool UploadLatest(Mesh argsMesh) { return UploadLatest(argsMesh, -1); } public bool UploadLatest(Mesh argsMesh, int renderGroupId) { return UploadLatest(argsMesh, renderGroupId, 0); } public bool UploadLatest(Mesh argsMesh, int renderGroupId, int subMeshIndex) { if (!HasRenderableView) { InstanceCount = 0; UpdateIndirectArgs(null, 0); return false; } var view = observer.LastRenderNativeView; EnsureCapacity(view); if (!_nativeMatrices.IsCreated || _nativeMatrices.Length == 0) { InstanceCount = 0; UpdateIndirectArgs(argsMesh, 0); return false; } var index = observer.LastSoaIndex; var defaultScale = ResolveDefaultScale(index, renderGroupId); var renderLod = ResolveRenderLod(index, renderGroupId); var count = renderGroupId >= 0 && view.HasRenderGroupId ? BuildRenderGroupMatrices(view, renderGroupId, renderLod, defaultScale) : BuildAllMatrices(view, renderLod, defaultScale); _matrixBuffer.SetData(_nativeMatrices, 0, 0, count); InstanceCount = count; LastUploadedFrameId = view.FrameId; LastUploadedGeneration = view.Generation; UpdateIndirectArgs(argsMesh, count, subMeshIndex); return true; } private int BuildAllMatrices(AetheriaDaemonRenderNativeView view, int renderLod, float defaultScale) { if (view.HasRenderVisibility || renderLod >= 0) { return BuildCompactedMatrices(view, -1, renderLod, defaultScale); } var count = math.min(view.Count, _nativeMatrices.Length); var handle = view.ScheduleBuildRenderMatrices( _nativeMatrices, defaultScale, innerLoopBatchCount); handle.Complete(); return count; } private int BuildRenderGroupMatrices( AetheriaDaemonRenderNativeView view, int renderGroupId, int renderLod, float defaultScale) { return BuildCompactedMatrices(view, renderGroupId, renderLod, defaultScale); } private int BuildCompactedMatrices( AetheriaDaemonRenderNativeView view, int renderGroupId, int renderLod, float defaultScale) { using var writtenCount = new NativeArray(1, Allocator.TempJob); var job = new AetheriaDaemonRenderGroupMatrixJob { PositionX = view.PositionX, PositionY = view.PositionY, PositionZ = view.PositionZ, RotationRadians = view.RotationRadians, PhysicsBodyRadius = view.PhysicsBodyRadius, RenderScale = view.RenderScale, RenderVisibility = view.RenderVisibility, RenderLod = view.RenderLod, RenderGroupId = view.RenderGroupId, Matrices = _nativeMatrices, WrittenCount = writtenCount, TargetRenderGroupId = renderGroupId >= 0 ? (uint)renderGroupId : 0, TargetRenderLod = renderLod, HasRotation = view.HasRotation, HasPhysicsRadius = view.HasPhysicsRadius, HasRenderScale = view.HasRenderScale, HasRenderVisibility = view.HasRenderVisibility, HasRenderLod = view.HasRenderLod, HasRenderGroupFilter = renderGroupId >= 0, HasRenderLodFilter = renderLod >= 0, UniformScale = defaultScale }; job.Schedule().Complete(); return math.clamp(writtenCount[0], 0, _nativeMatrices.Length); } private static float ResolveDefaultScale(AetheriaRuntimeDaemonSoaViewIndex index, int renderGroupId) { if (index != null && renderGroupId >= 0 && index.TryGetRenderGroup(renderGroupId, out var renderGroup)) { return math.max(renderGroup.DefaultScale, 0.0001f); } var renderGroups = index?.RenderGroups; if (renderGroups != null && renderGroups.Count == 1) { return math.max(renderGroups[0].DefaultScale, 0.0001f); } return 1.0f; } private static int ResolveRenderLod(AetheriaRuntimeDaemonSoaViewIndex index, int renderGroupId) { if (index != null && renderGroupId >= 0 && index.TryGetRenderGroup(renderGroupId, out var renderGroup)) { return renderGroup.Lod; } var renderGroups = index?.RenderGroups; if (renderGroups != null && renderGroups.Count == 1) { return renderGroups[0].Lod; } return -1; } public void ApplyTo(MaterialPropertyBlock properties) { if (properties == null) { return; } properties.SetInt(InstanceCountPropertyId, InstanceCount); if (HasGpuBuffers) { properties.SetBuffer(MatrixBufferPropertyId, _matrixBuffer); } } private void OnDisable() { ReleaseBuffers(); InstanceCount = 0; LastUploadedFrameId = 0; LastUploadedGeneration = 0; } private void EnsureCapacity(AetheriaDaemonRenderNativeView view) { var count = math.max(0, view.Count); if (_nativeMatrices.IsCreated && _allocatedGeneration == view.Generation && _allocatedCount == count) { return; } ReleaseBuffers(); _allocatedGeneration = view.Generation; _allocatedCount = count; if (count <= 0) { return; } _nativeMatrices = new NativeArray( count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); _matrixBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, count, MatrixStrideBytes); _indirectArgsBuffer = new GraphicsBuffer( GraphicsBuffer.Target.IndirectArguments, DrawMeshInstancedIndirectArgsCount, sizeof(uint)); } private void UpdateIndirectArgs(Mesh mesh, int instanceCount) { UpdateIndirectArgs(mesh, instanceCount, 0); } private void UpdateIndirectArgs(Mesh mesh, int instanceCount, int subMeshIndex) { if (_indirectArgsBuffer == null || !_indirectArgsBuffer.IsValid()) { return; } var safeSubMeshIndex = mesh != null ? math.clamp(subMeshIndex, 0, math.max(0, mesh.subMeshCount - 1)) : 0; _indirectArgs[0] = mesh != null ? mesh.GetIndexCount(safeSubMeshIndex) : 0; _indirectArgs[1] = (uint)math.max(0, instanceCount); _indirectArgs[2] = mesh != null ? mesh.GetIndexStart(safeSubMeshIndex) : 0; _indirectArgs[3] = mesh != null ? mesh.GetBaseVertex(safeSubMeshIndex) : 0; _indirectArgs[4] = 0; _indirectArgsBuffer.SetData(_indirectArgs); } private void ReleaseBuffers() { if (_nativeMatrices.IsCreated) { _nativeMatrices.Dispose(); } _matrixBuffer?.Release(); _matrixBuffer = null; _indirectArgsBuffer?.Release(); _indirectArgsBuffer = null; _allocatedGeneration = long.MinValue; _allocatedCount = 0; } }