using Unity.Collections.LowLevel.Unsafe; using Unity.Mathematics; using Unity.Jobs.LowLevel.Unsafe; using UnityEngine; using Unity.Burst; using UnityEngine.Profiling; using Unity.Collections; using Unity.Jobs; namespace Pathfinding.Drawing { using static CommandBuilder; [BurstCompile] internal struct StreamSplitter : IJob { public NativeArray inputBuffers; [NativeDisableUnsafePtrRestriction] public unsafe UnsafeAppendBuffer* staticBuffer, dynamicBuffer, persistentBuffer; internal static readonly int PushCommands = (1 << (int)Command.PushColor) | (1 << (int)Command.PushMatrix) | (1 << (int)Command.PushSetMatrix) | (1 << (int)Command.PushPersist) | (1 << (int)Command.PushLineWidth); internal static readonly int PopCommands = (1 << (int)Command.PopColor) | (1 << (int)Command.PopMatrix) | (1 << (int)Command.PopPersist) | (1 << (int)Command.PopLineWidth); internal static readonly int MetaCommands = PushCommands | PopCommands; internal static readonly int DynamicCommands = (1 << (int)Command.SphereOutline) | (1 << (int)Command.CircleXZ) | (1 << (int)Command.Circle) | (1 << (int)Command.DiscXZ) | (1 << (int)Command.Disc) | (1 << (int)Command.Text) | (1 << (int)Command.Text3D) | (1 << (int)Command.CaptureState) | MetaCommands; internal static readonly int StaticCommands = (1 << (int)Command.Line) | (1 << (int)Command.Box) | (1 << (int)Command.WirePlane) | (1 << (int)Command.WireBox) | (1 << (int)Command.SolidTriangle) | MetaCommands; internal static readonly int[] CommandSizes; static StreamSplitter() { // Size of all commands in bytes CommandSizes = new int[22]; CommandSizes[(int)Command.PushColor] = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); CommandSizes[(int)Command.PopColor] = UnsafeUtility.SizeOf() + 0; CommandSizes[(int)Command.PushMatrix] = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); CommandSizes[(int)Command.PushSetMatrix] = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); CommandSizes[(int)Command.PopMatrix] = UnsafeUtility.SizeOf() + 0; CommandSizes[(int)Command.Line] = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); CommandSizes[(int)Command.CircleXZ] = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); CommandSizes[(int)Command.SphereOutline] = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); CommandSizes[(int)Command.Circle] = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); CommandSizes[(int)Command.Disc] = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); CommandSizes[(int)Command.DiscXZ] = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); CommandSizes[(int)Command.Box] = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); CommandSizes[(int)Command.WirePlane] = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); CommandSizes[(int)Command.WireBox] = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); CommandSizes[(int)Command.SolidTriangle] = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); CommandSizes[(int)Command.PushPersist] = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); CommandSizes[(int)Command.PopPersist] = UnsafeUtility.SizeOf(); CommandSizes[(int)Command.Text] = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); // Dynamically sized CommandSizes[(int)Command.Text3D] = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); // Dynamically sized CommandSizes[(int)Command.PushLineWidth] = UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf(); CommandSizes[(int)Command.PopLineWidth] = UnsafeUtility.SizeOf(); CommandSizes[(int)Command.CaptureState] = UnsafeUtility.SizeOf(); } public void Execute () { var lastWriteStatic = -1; var lastWriteDynamic = -1; var lastWritePersist = -1; var stackStatic = new NativeArray(GeometryBuilderJob.MaxStackSize, Allocator.Temp, NativeArrayOptions.ClearMemory); var stackDynamic = new NativeArray(GeometryBuilderJob.MaxStackSize, Allocator.Temp, NativeArrayOptions.ClearMemory); var stackPersist = new NativeArray(GeometryBuilderJob.MaxStackSize, Allocator.Temp, NativeArrayOptions.ClearMemory); unsafe { // Store in local variables for performance (makes it possible to use registers for a lot of fields) var bufferStatic = *staticBuffer; var bufferDynamic = *dynamicBuffer; var bufferPersist = *persistentBuffer; bufferStatic.Reset(); bufferDynamic.Reset(); bufferPersist.Reset(); for (int i = 0; i < inputBuffers.Length; i++) { int stackSize = 0; int persist = 0; var reader = inputBuffers[i].AsReader(); // Guarantee we have enough space for copying the whole buffer if (bufferStatic.Capacity < bufferStatic.Length + reader.Size) bufferStatic.SetCapacity(math.ceilpow2(bufferStatic.Length + reader.Size)); if (bufferDynamic.Capacity < bufferDynamic.Length + reader.Size) bufferDynamic.SetCapacity(math.ceilpow2(bufferDynamic.Length + reader.Size)); if (bufferPersist.Capacity < bufferPersist.Length + reader.Size) bufferPersist.SetCapacity(math.ceilpow2(bufferPersist.Length + reader.Size)); // To ensure that even if exceptions are thrown the output buffers still point to valid memory regions *staticBuffer = bufferStatic; *dynamicBuffer = bufferDynamic; *persistentBuffer = bufferPersist; while (reader.Offset < reader.Size) { var cmd = *(Command*)((byte*)reader.Ptr + reader.Offset); var cmdBit = 1 << ((int)cmd & 0xFF); int size = CommandSizes[(int)cmd & 0xFF] + ((cmd & Command.PushColorInline) != 0 ? UnsafeUtility.SizeOf() : 0); bool isMeta = (cmdBit & MetaCommands) != 0; if ((cmd & (Command)0xFF) == Command.Text) { // Very pretty way of reading the TextData struct right after the command label and optional Color32 var data = *((TextData*)((byte*)reader.Ptr + reader.Offset + size) - 1); // Add the size of the embedded string in the buffer // TODO: Unaligned memory access performance penalties?? Update: Doesn't seem to be so bad on Intel at least. size += data.numCharacters * UnsafeUtility.SizeOf(); } else if ((cmd & (Command)0xFF) == Command.Text3D) { // Very pretty way of reading the TextData struct right after the command label and optional Color32 var data = *((TextData3D*)((byte*)reader.Ptr + reader.Offset + size) - 1); // Add the size of the embedded string in the buffer // TODO: Unaligned memory access performance penalties?? Update: Doesn't seem to be so bad on Intel at least. size += data.numCharacters * UnsafeUtility.SizeOf(); } #if ENABLE_UNITY_COLLECTIONS_CHECKS UnityEngine.Assertions.Assert.IsTrue(reader.Offset + size <= reader.Size); #endif if ((cmdBit & DynamicCommands) != 0 && persist == 0) { if (!isMeta) lastWriteDynamic = bufferDynamic.Length; #if ENABLE_UNITY_COLLECTIONS_CHECKS UnityEngine.Assertions.Assert.IsTrue(bufferDynamic.Length + size <= bufferDynamic.Capacity); #endif UnsafeUtility.MemCpy((byte*)bufferDynamic.Ptr + bufferDynamic.Length, (byte*)reader.Ptr + reader.Offset, size); bufferDynamic.Length = bufferDynamic.Length + size; } if ((cmdBit & StaticCommands) != 0 && persist == 0) { if (!isMeta) lastWriteStatic = bufferStatic.Length; #if ENABLE_UNITY_COLLECTIONS_CHECKS UnityEngine.Assertions.Assert.IsTrue(bufferStatic.Length + size <= bufferStatic.Capacity); #endif UnsafeUtility.MemCpy((byte*)bufferStatic.Ptr + bufferStatic.Length, (byte*)reader.Ptr + reader.Offset, size); bufferStatic.Length = bufferStatic.Length + size; } if ((cmdBit & MetaCommands) != 0 || persist > 0) { if (persist > 0 && !isMeta) lastWritePersist = bufferPersist.Length; #if ENABLE_UNITY_COLLECTIONS_CHECKS UnityEngine.Assertions.Assert.IsTrue(bufferPersist.Length + size <= bufferPersist.Capacity); #endif UnsafeUtility.MemCpy((byte*)bufferPersist.Ptr + bufferPersist.Length, (byte*)reader.Ptr + reader.Offset, size); bufferPersist.Length = bufferPersist.Length + size; } if ((cmdBit & PushCommands) != 0) { stackStatic[stackSize] = bufferStatic.Length - size; stackDynamic[stackSize] = bufferDynamic.Length - size; stackPersist[stackSize] = bufferPersist.Length - size; stackSize++; if ((cmd & (Command)0xFF) == Command.PushPersist) { persist++; } #if ENABLE_UNITY_COLLECTIONS_CHECKS if (stackSize >= GeometryBuilderJob.MaxStackSize) throw new System.Exception("Push commands are too deeply nested. This can happen if you have deeply nested WithMatrix or WithColor scopes."); #else if (stackSize >= GeometryBuilderJob.MaxStackSize) { return; } #endif } else if ((cmdBit & PopCommands) != 0) { stackSize--; #if ENABLE_UNITY_COLLECTIONS_CHECKS if (stackSize < 0) throw new System.Exception("Trying to issue a pop command but there is no corresponding push command"); #else if (stackSize < 0) return; #endif // If a scope was pushed and later popped, but no actual draw commands were written to the buffers // inside that scope then we erase the whole scope. if (lastWriteStatic < stackStatic[stackSize]) { bufferStatic.Length = stackStatic[stackSize]; } if (lastWriteDynamic < stackDynamic[stackSize]) { bufferDynamic.Length = stackDynamic[stackSize]; } if (lastWritePersist < stackPersist[stackSize]) { bufferPersist.Length = stackPersist[stackSize]; } if ((cmd & (Command)0xFF) == Command.PopPersist) { persist--; #if ENABLE_UNITY_COLLECTIONS_CHECKS if (persist < 0) throw new System.Exception("Too many PopPersist commands. Are your PushPersist/PopPersist calls matched?"); #else if (persist < 0) return; #endif } } reader.Offset += size; } #if ENABLE_UNITY_COLLECTIONS_CHECKS if (stackSize != 0) throw new System.Exception("Too few pop commands and too many push commands. Are your push and pop commands properly matched?"); if (reader.Offset != reader.Size) throw new System.Exception("Did not end up at the end of the buffer. This is a bug."); #else if (stackSize != 0) return; if (reader.Offset != reader.Size) return; #endif } #if ENABLE_UNITY_COLLECTIONS_CHECKS if (bufferStatic.Length > bufferStatic.Capacity) throw new System.Exception("Buffer overrun. This is a bug"); if (bufferDynamic.Length > bufferDynamic.Capacity) throw new System.Exception("Buffer overrun. This is a bug"); if (bufferPersist.Length > bufferPersist.Capacity) throw new System.Exception("Buffer overrun. This is a bug"); #endif *staticBuffer = bufferStatic; *dynamicBuffer = bufferDynamic; *persistentBuffer = bufferPersist; } } } }