/// <summary> /// Copy an <see cref="MLoopTri"/> array into <see cref="Triangles"/> /// /// <para> /// You <b>must</b> ensure <see cref="cachedLoops"/> is up to date via /// <see cref="CopyFromMVerts(MVert[])"/> before performing this /// operation to ensure that vertices can be mapped to. /// </para> /// </summary> /// <param name="loopTris"></param> internal void CopyFromMLoopTris(MLoopTri[] loopTris) { var count = loopTris.Length * 3; if (triangles == null) { triangles = new uint[count]; } Array.Resize(ref triangles, count); // Triangle indices are re-mapped from MLoop vertex index // to the source vertex index using cachedLoops. for (int i = 0; i < loopTris.Length; i++) { var loop = loopTris[i]; var j = i * 3; // TODO: Can this be any faster? This indirect lookup sucks, // but might be the best we can do with the way Blender // organizes data. triangles[j] = cachedLoops[loop.tri[0]].v; triangles[j + 1] = cachedLoops[loop.tri[1]].v; triangles[j + 2] = cachedLoops[loop.tri[2]].v; // Console.WriteLine($" - {triangles[j]}, {triangles[j + 1]}, {triangles[j + 2]}"); } InteropLogger.Debug($"Copied {triangles.Length} triangle indices"); }
/// <summary> /// Process queued messages and write if possible /// </summary> public void ProcessOutboundQueue() { if (outboundQueue.Count() < 1) { return; } if (outboundQueue.Count() > 10) { InteropLogger.Warning($"Outbound queue is at {outboundQueue.Count()} messages"); } // Pump the queue until we fill the outbound buffer int bytesWritten; do { // Only dequeue a message once we have an available node for writing bytesWritten = messageProducer.Write((ptr) => { var next = outboundQueue.Dequeue(); InteropLogger.Debug($" W-> {next.target}:{next.header.type:F}"); return(WriteMessage(next, ptr)); }, 0); } while (bytesWritten > 0 && outboundQueue.Count() > 0); }
internal void Update() { try { if (messages != null) { messages.ProcessOutboundQueue(); ConsumeMessage(); CheckForTimeout(); } } catch (AccessViolationException) { // We run into an access violation when Unity disposes the // shared memory buffer but Blender is still trying to do IO with it. // So we explicitly catch this case and gracefully disconnect. // See: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.exceptionservices.handleprocesscorruptedstateexceptionsattribute#remarks // TODO: IDEALLY - this should happen closer to the IO with the shared memory buffer, // as doing it here might catch access violations elsewhere in the library that weren't // intended since this method essentially wraps EVERYTHING. // Possibly @ RpcMessenger.Read/Write? Or deeper in CircularBuffer.Read/Write. // Actual exception for Read was thrown within CircularBuffer.ReturnNode InteropLogger.Error($"Access Violation doing IO to the shared memory - disconnecting"); Disconnect(); } }
/// <summary> /// /// </summary> /// <typeparam name="T"></typeparam> /// <param name="type"></param> /// <param name="target"></param> /// <param name="buffer"></param> public void QueueArray <T>(RpcRequest type, string target, IArray <T> buffer) where T : struct { var headerSize = FastStructure.SizeOf <InteropMessageHeader>(); var elementSize = FastStructure.SizeOf <T>(); if (headerSize + elementSize * buffer.Length > messageProducer.NodeBufferSize) { throw new Exception($"Cannot queue {buffer.Length} elements of {typeof(T)} - will not fit in a single message"); } // Construct a header with metadata for the array var header = new InteropMessageHeader { type = type, length = buffer.MaxLength, index = buffer.Offset, count = buffer.Length }; // Remove any queued messages with the same outbound header RemoveQueuedMessage(target, ref header); InteropLogger.Debug($" QA-> {target}:{type:F}"); outboundQueue.Enqueue(new InteropMessage { target = target, header = header, producer = (tar, hdr, ptr) => { buffer.CopyTo(ptr, 0, buffer.Length); return(elementSize * buffer.Length); } }); }
int RebuildVertices() { // We resize to the number of vertices from Blender // PLUS the number of split vertices we already calculated // in a prior pass (thus already have the data filled out) vertices.Resize(verts.Length + splitVertices.Count); InteropLogger.Debug($"RebuildVertices name={Name}, Length={vertices.Length}"); for (int i = 0; i < verts.Length; i++) { var vert = verts[i]; // y/z are swizzled here to convert to Unity's coordinate space vertices[i] = new InteropVector3( vert.co_x, vert.co_z, vert.co_y ); } // Also update all existing split vertices to match // the original vertex that may have been updated foreach (var kv in splitVertices) { var vertIndex = (int)loops[kv.Key].v; vertices[kv.Value] = vertices[vertIndex]; } // This will never split return(0); }
int RebuildNormals() { // We assume it always happens AFTER RebuildVertices normals.Resize(vertices.Length); InteropLogger.Debug($"RebuildNormals name={Name}, Length={normals.Length}"); // Normals need to be cast from short -> float from Blender var normalScale = 1f / 32767f; for (int i = 0; i < verts.Length; i++) { var vert = verts[i]; // Like vertex coordinates - we swizzle y/z normals[i] = new InteropVector3( vert.no_x * normalScale, vert.no_z * normalScale, vert.no_y * normalScale ); } // Also update all existing split vertices to match // the original normal that may have been updated foreach (var kv in splitVertices) { var vertIndex = (int)loops[kv.Key].v; normals[kv.Value] = normals[vertIndex]; } // For now we have no splits. Eventually this may // change if we add support for split normals // coming from a CustomData layer return(0); }
/// <summary> /// Read pixel data from <paramref name="intPtr"/> into our local buffer /// and return the total number of bytes read. /// </summary> /// <param name="header"></param> /// <param name="intPtr"></param> /// <returns></returns> internal int ReadPixelData(InteropRenderHeader header, IntPtr intPtr) { var pixelArraySize = header.width * header.height * 3; lock (renderTextureLock) { if (Pixels == IntPtr.Zero) { InteropLogger.Debug($"Allocating {header.width} x {header.height}"); Pixels = Marshal.AllocHGlobal(pixelArraySize); } else if (header.width != Header.width || header.height != Header.height) { // If the inbound data resized - resize our buffer InteropLogger.Debug($"Reallocating {header.width} x {header.height}"); Pixels = Marshal.ReAllocHGlobal(Pixels, new IntPtr(pixelArraySize)); } // Could do a Buffer.MemoryCopy here - but I'm locked to // .NET 4.5 due to the DllExport library we're using. UnsafeNativeMethods.CopyMemory(Pixels, intPtr, (uint)pixelArraySize); Header = header; Frame++; } return(pixelArraySize); }
public static int SetVisibleObjects( #pragma warning disable IDE0060 // Remove unused parameter int viewportId, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] int[] visibleObjectIds, int totalVisibleObjectIds #pragma warning restore IDE0060 // Remove unused parameter ) { InteropLogger.Debug($"Set Visible Objects viewport={viewportId}"); foreach (var i in visibleObjectIds) { InteropLogger.Debug($" - {i}"); } try { var viewport = Bridge.GetViewport(viewportId); viewport.SetVisibleObjects(visibleObjectIds); Bridge.SendArray( RpcRequest.UpdateVisibleObjects, viewport.Name, viewport.VisibleObjectIds, false ); return(1); } catch (Exception e) { SetLastError(e); return(-1); } }
public static int CopyLoopTriangles( #pragma warning disable IDE0060 // Remove unused parameter [MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] MLoopTri[] loopTris, uint loopTrisCount #pragma warning restore IDE0060 // Remove unused parameter ) { InteropLogger.Debug($"Copy {loopTris.Length} loop triangles for `{name}`"); try { var obj = Bridge.GetObject(name); obj.CopyFromMLoopTris(loopTris); // If the vertex count changes, we'll need to push a // change to the object's metadata if (obj.data.triangleCount != obj.Triangles.Length) { obj.data.triangleCount = obj.Triangles.Length; Bridge.SendEntity(RpcRequest.UpdateSceneObject, obj); } Bridge.SendArray(RpcRequest.UpdateTriangles, obj.Name, obj.Triangles, true); return(1); } catch (Exception e) { SetLastError(e); return(-1); } }
private int OnConnect(string target, IntPtr ptr) { unityState = FastStructure.PtrToStructure <InteropUnityState>(ptr); IsConnectedToUnity = true; InteropLogger.Debug($"{target} - {unityState.version} connected. Flavor Blasting."); SendAllSceneData(); return(FastStructure.SizeOf <InteropUnityState>()); }
internal void SendArray <T>(RpcRequest type, string target, IArray <T> buffer) where T : struct { if (!IsConnectedToSharedMemory || buffer.Length < 1) { return; } InteropLogger.Debug( $"SendBuffer target={target} type={type} length={buffer.Length}" ); messages.QueueArray(type, target, buffer); }
void RebuildAll() { InteropLogger.Debug($"RebuildAll name={Name}"); splitVertices.Clear(); RebuildVertices(); RebuildNormals(); RebuildBuffer(loopCols, colors); RebuildBuffer(loopUVs, uvs); // .. and so on RebuildTriangles(); }
public override bool Equals(object obj) { if (obj is InteropString64 interop) { return(Value == interop.Value); } if (obj is string str) { return(Value == str); } InteropLogger.Debug($"Diff str {Value} != {obj}"); return(false); }
/// <summary> /// Consume a single message off the read queue. /// </summary> private void ConsumeMessage() { messages.Read((target, header, ptr) => { lastUpdateFromUnity = DateTime.Now; if (!handlers.ContainsKey(header.type)) { InteropLogger.Warning($"Unhandled request type {header.type} for {target}"); return(0); } return(handlers[header.type](target, ptr)); }); }
/// <summary> /// Queue an outbound message containing a <typeparamref name="T"/> payload /// </summary> /// <typeparam name="T"></typeparam> /// <param name="target"></param> /// <param name="type"></param> /// <param name="data"></param> public void Queue <T>(RpcRequest type, string target, ref T data) where T : struct { InteropLogger.Debug($" Q-> {target}:{type:F}"); outboundQueue.Enqueue(new InteropMessage { target = target, header = new InteropMessageHeader { type = type, index = 0, length = 0, count = 0 }, payload = FastStructure.ToBytes(ref data) }); }
/// <summary> /// Copy an <see cref="MLoop"/> array into managed memory for vertex lookups /// aligned with other MLoop* structures. /// </summary> /// <param name="loops"></param> internal void CopyFromMLoops(MLoop[] loops) { if (cachedLoops == null) { cachedLoops = new MLoop[loops.Length]; } else { Array.Resize(ref cachedLoops, loops.Length); } Array.Copy(loops, cachedLoops, loops.Length); InteropLogger.Debug($"Copy {loops.Length} loops"); }
public static int RemoveViewport(int viewportId) { InteropLogger.Debug($"Removing viewport={viewportId}"); try { Bridge.RemoveViewport(viewportId); return(1); } catch (Exception e) { SetLastError(e); return(-1); } }
public void SendDirty() { if (!dirty) { return; } var b = Bridge.Instance; InteropLogger.Debug($"Pixel buffer is {pixels.Length} elements for {data.width}x{data.height} image"); b.SendEntity(RpcRequest.UpdateTexture, this); b.SendArray(RpcRequest.UpdateTextureData, Name, pixels); dirty = false; }
public static int RemoveObjectFromScene( [MarshalAs(UnmanagedType.LPStr)] string name ) { InteropLogger.Debug($"Removing object {name} from the scene"); try { Bridge.RemoveObject(name); return(1); } catch (Exception e) { SetLastError(e); return(-1); } }
private int OnDisconnect(string target, IntPtr ptr) { InteropLogger.Debug("Unity disconnected"); // Disconnect from invalidated shared memory since Unity was the owner Disconnect(); // Based on Unity's side of things - we may never even see this message. // If unity sends out a Disconnect and then immediately disposes the // shared memory - we won't be able to read the disconnect and instead // just get an access violation on the next read of shared memory // (which is caught in Update() and calls Disconnect() anyway). // If there was some delay though between Unity sending a Disconnect // and memory cleanup, then we can safely catch it here and disconnect // ourselves while avoiding a potential access violation. return(0); }
/// <summary> /// Process queued messages and write if possible /// </summary> public void ProcessOutboundQueue() { if (outboundQueue.Count() < 1) { return; } if (outboundQueue.Count() > 10) { InteropLogger.Warning($"Outbound queue is at {outboundQueue.Count()} messages"); } // Only dequeue a message once we have an available node for writing int bytesWritten = messageProducer.Write((ptr) => { var next = outboundQueue.Dequeue(); return(WriteMessage(next, ptr)); }, 5); }
/// <summary> /// Send all buffers to Unity - regardless of dirtied status /// </summary> internal void SendAll() { var b = Bridge.Instance; InteropLogger.Debug($"SendAll name={Name}"); b.SendArray(RpcRequest.UpdateVertices, Name, vertices); b.SendArray(RpcRequest.UpdateNormals, Name, normals); b.SendArray(RpcRequest.UpdateVertexColors, Name, colors); b.SendArray(RpcRequest.UpdateUV, Name, uvs); // ... and so on b.SendArray(RpcRequest.UpdateTriangles, Name, triangles); SendApplyChanges(); // Clean everything - Unity should be synced. CleanAllBuffers(); }
/// <summary> /// Send an array of <typeparamref name="T"/> to Unity, splitting into multiple /// <see cref="RpcRequest"/> if all the objects cannot fit in a single message. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="type"></param> /// <param name="target"></param> /// <param name="data"></param> /// <param name="allowSplitMessages"></param> internal void SendArray <T>(RpcRequest type, string target, T[] data, bool allowSplitMessages) where T : struct { if (!IsConnectedToSharedMemory) { return; } // TODO: Zero length array support. Makes sense in some use cases // but not others (e.g. don't send RpcRequest.UpdateUVs if there // are no UVs to send) if (data == null || data.Length < 1) { return; } if (messages.ReplaceOrQueueArray(type, target, data, allowSplitMessages)) { InteropLogger.Debug($"Replaced queued {type} for {target}"); } }
public static int CopyVertices( #pragma warning disable IDE0060 // Remove unused parameter [MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] MLoop[] loops, uint loopCount, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)] MVert[] vertices, uint verticesCount #pragma warning restore IDE0060 // Remove unused parameter ) { InteropLogger.Debug($"Copy {loops.Length} loops and {vertices.Length} vertices for `{name}`"); try { var obj = Bridge.GetObject(name); obj.CopyFromMVerts(vertices); obj.CopyFromMLoops(loops); // If the vertex count changes, we'll need to push a // change to the object's metadata if (obj.data.vertexCount != obj.Vertices.Length) { obj.data.vertexCount = obj.Vertices.Length; Bridge.SendEntity(RpcRequest.UpdateSceneObject, obj); } // Followed by changes to the vertex coordinate and normals Bridge.SendArray(RpcRequest.UpdateVertices, obj.Name, obj.Vertices, true); Bridge.SendArray(RpcRequest.UpdateNormals, obj.Name, obj.Normals, true); return(1); } catch (Exception e) { SetLastError(e); return(-1); } }
/// <summary> /// Connect to a shared memory space hosted by Unity and sync scene data /// </summary> /// <param name="connectionName">Common name for the shared memory space between Blender and Unity</param> public bool Connect(string connectionName, string versionInfo) { blenderState = new InteropBlenderState { version = versionInfo }; try { InteropLogger.Debug($"Connecting to `{connectionName + VIEWPORT_IMAGE_BUFFER}`"); // Buffer for render data coming from Unity (consume-only) pixelsConsumer = new CircularBuffer(connectionName + VIEWPORT_IMAGE_BUFFER); InteropLogger.Debug($"Connecting to `{connectionName + UNITY_MESSAGES_BUFFER}` and `{connectionName + BLENDER_MESSAGES_BUFFER}`"); // Two-way channel between Blender and Unity messages = new InteropMessenger(); messages.ConnectAsSlave( connectionName + UNITY_MESSAGES_BUFFER, connectionName + BLENDER_MESSAGES_BUFFER ); } catch (System.IO.FileNotFoundException) { // Shared memory space is not valid - Unity may not have started it. // This is an error that should be gracefully handled by the UI. IsConnectedToSharedMemory = false; return(false); } IsConnectedToSharedMemory = true; // Send an initial connect message to let Unity know we're in messages.Queue(RpcRequest.Connect, versionInfo, ref blenderState); return(true); }
void RebuildTriangles() { triangles.Resize(loopTris.Length * 3); InteropLogger.Debug($"RebuildTriangles name={Name}, Length={triangles.Length}"); for (int t = 0; t < loopTris.Length; t++) { var loopTri = loopTris[t]; // Triangles are flipped due to coordinate space conversions // that happen from Blender to Unity int tri0 = (int)loopTri.tri_2; int tri1 = (int)loopTri.tri_1; int tri2 = (int)loopTri.tri_0; // If the triangle vert has been mapped to a split vertex, // use that instead of the original vertex triangles[t * 3 + 0] = splitVertices.GetValueOrDefault(tri0, (int)loops[tri0].v); triangles[t * 3 + 1] = splitVertices.GetValueOrDefault(tri1, (int)loops[tri1].v); triangles[t * 3 + 2] = splitVertices.GetValueOrDefault(tri2, (int)loops[tri2].v); } }
public static int AddMeshObjectToScene( [MarshalAs(UnmanagedType.LPStr)] string name, InteropMatrix4x4 transform, [MarshalAs(UnmanagedType.LPStr)] string material ) { InteropLogger.Debug($"Adding mesh <name={name}, material={material}>"); try { var obj = new SceneObject(name, SceneObjectType.Mesh); obj.Transform = transform; obj.Material = material; Bridge.AddObject(obj); return(1); } catch (Exception e) { SetLastError(e); return(-1); } }
public bool ReplaceOrQueue <T>(RpcRequest type, string target, ref T data) where T : struct { var header = new InteropMessageHeader { type = type, index = 0, length = 0, count = 0 }; var payload = FastStructure.ToBytes(ref data); // If it's already queued, replace the payload /*var queued = FindQueuedMessage(target, ref header); * if (queued != null) * { * queued.payload = payload; * return true; * }*/ // Remove the old one to then queue up one at the end // This ensures messages that are queued up together // remain in their queued order. RemoveQueuedMessage(target, ref header); InteropLogger.Debug($" ROQ-> {target}:{header.type:F}"); outboundQueue.Enqueue(new InteropMessage { target = target, header = header, payload = payload }); return(false); }
/// <summary> /// Read from the viewport image buffer and copy /// pixel data into the appropriate viewport. /// </summary> internal void ConsumePixels() { if (pixelsConsumer == null || pixelsConsumer.ShuttingDown) { return; } pixelsConsumer.Read((ptr) => { var headerSize = FastStructure.SizeOf <InteropRenderHeader>(); var header = FastStructure.PtrToStructure <InteropRenderHeader>(ptr); if (!viewports.ContainsKey(header.viewportId)) { InteropLogger.Warning($"Got render texture for unknown viewport {header.viewportId}"); return(headerSize); } var viewport = viewports[header.viewportId]; var pixelDataSize = viewport.ReadPixelData(header, ptr + headerSize); return(headerSize + pixelDataSize); }, READ_WAIT); }
public static void SetLastError(Exception e) { InteropLogger.Error(e.ToString()); LastError = e.ToString(); }