using System; using System.Runtime.InteropServices; using AOT; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using UnityEngine; using UnityEngine.Profiling; public partial class RenderModule: CallbackHandler { private const int MaxAliasFrames = 256; // Should match MAXALIASFRAMES private void BuildCallbacks() { var callbacks = new Callbacks { UploadAliasModel = CreateCallback(Callback_UploadAliasModel), UploadBrushModel = CreateCallback(Callback_UploadBrushModel), UploadWorldModel = CreateCallback(Callback_UploadWorldModel), UploadTexture = CreateCallback(Callback_UploadTexture), UploadLightmap = CreateCallback(Callback_UploadLightmap), SetupView = CreateCallback(Callback_SetupView), }; RegisterCallbacks(callbacks); } /// /// This matches unity_modcalls_t from mod_uniquake.c in native code. /// [StructLayout(LayoutKind.Sequential, Pack = 0)] private class Callbacks { public IntPtr UploadAliasModel; public IntPtr UploadBrushModel; public IntPtr UploadWorldModel; public IntPtr UploadTexture; public IntPtr UploadLightmap; public IntPtr SetupView; } [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private delegate int UploadAliasModelCallback(IntPtr context, [MarshalAs(UnmanagedType.LPStr)] string name, QAliasHeader header, QAliasFrameType frameType, IntPtr frames, [MarshalAs(UnmanagedType.LPArray, SizeConst = MaxAliasFrames)] IntPtr[] poseVerts, IntPtr triangles, IntPtr stVerts); [MonoPInvokeCallback(typeof(UploadAliasModelCallback))] private static int Callback_UploadAliasModel(IntPtr context, string name, QAliasHeader header, QAliasFrameType frameType, IntPtr frames, IntPtr[] poseVerts, IntPtr triangles, IntPtr stVerts) { if (header == null) { Debug.LogWarning($"Uploading invalid alias model, name = {name}"); return 0; } Profiler.BeginSample("UploadAliasModel"); if (header.numFrames > MaxAliasFrames) header.numFrames = MaxAliasFrames; if (frames != IntPtr.Zero) header.frames = frames.ToStructArray(header.numFrames); header.numPoses = 0; for (int i = 0; i < header.numFrames; ++i) { header.numPoses += header.frames[i].numPoses; } var poseVertices = new QTriVertex[header.numPoses][]; for (int i = 0; i < header.numPoses; ++i) { poseVertices[i] = poseVerts[i].ToStructArray(header.numVerts); } var glTextures = new QGLTexture[QConstants.MaxSkins][]; var fbTextures = new QGLTexture[QConstants.MaxSkins][]; for (int i = 0; i < QConstants.MaxSkins; ++i) { glTextures[i] = header.glTextures[i * 4].ToStructArray(4); fbTextures[i] = header.fbTextures[i * 4].ToStructArray(4); } int result = GetSelf(context).UploadAliasModel( name, header, frameType, poseVertices, triangles.ToStructArray(header.numTriangles), stVerts.ToStructArray(header.numVerts), glTextures, fbTextures); Profiler.EndSample(); return result; } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int UploadBrushModelCallback(IntPtr context, QModel model); [MonoPInvokeCallback(typeof(UploadBrushModelCallback))] private static int Callback_UploadBrushModel(IntPtr context, QModel model) { if (model == null || model.type != QModelType.Brush) return -1; Profiler.BeginSample("UploadBrushModel"); int result = GetSelf(context).UploadBrushModel(model); Profiler.EndSample(); return result; } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int UploadWorldModelCallback(IntPtr context, QModel model); [MonoPInvokeCallback(typeof(UploadBrushModelCallback))] private static int Callback_UploadWorldModel(IntPtr context, QModel model) { if (model == null || model.type != QModelType.Brush) return -1; Profiler.BeginSample("UploadWorldModel"); int result = GetSelf(context).UploadWorldModel(model); Profiler.EndSample(); return result; } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate bool UploadTextureCallback(IntPtr context, QGLTexture texture, IntPtr data, ref uint texNum); [MonoPInvokeCallback(typeof(UploadTextureCallback))] private static bool Callback_UploadTexture(IntPtr context, QGLTexture texture, IntPtr data, ref uint texNum) { Profiler.BeginSample("UploadTexture"); byte[] dataBytes = new byte[texture.width * texture.height * 4]; // 32 bits per pixel Marshal.Copy(data, dataBytes, 0, dataBytes.Length); bool result = GetSelf(context).UploadTexture(texture, dataBytes, ref texNum); Profiler.EndSample(); return result; } #if UNITY_EDITOR private readonly byte[] lightmapBytes = new byte[QConstants.LightmapBlockWidth * QConstants.LightmapBlockHeight * 4]; // 32 bits per pixel #endif [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate bool UploadLightmapCallback(IntPtr context, int lmap, IntPtr data); [MonoPInvokeCallback(typeof(UploadLightmapCallback))] private static bool Callback_UploadLightmap(IntPtr context, int lmap, IntPtr data) { bool result; Profiler.BeginSample("UploadLightmap"); #if UNITY_EDITOR // This is fairly pointless additional data copy step; the below NativeArray approach is faster and doesn't // produce any garbage, but it doesn't work inside the Unity Editor for some reason. var self = GetSelf(context); Marshal.Copy(data, self.lightmapBytes, 0, self.lightmapBytes.Length); result = self.UploadLightmap(lmap, QConstants.LightmapBlockWidth, QConstants.LightmapBlockHeight, self.lightmapBytes); #else unsafe { // More efficient code path that passes the native byte buffer directly to the Texture2D's pixel data var dataArray = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(data.ToPointer(), QConstants.LightmapBlockWidth * QConstants.LightmapBlockHeight * 4, Allocator.None); result = GetSelf(context).UploadLightmap(lmap, QConstants.LightmapBlockWidth, QConstants.LightmapBlockHeight, dataArray); } #endif Profiler.EndSample(); return result; } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SetupViewCallback(IntPtr context, ref QVec3 origin, ref QVec3 angles, ref QLeaf viewLeaf); [MonoPInvokeCallback(typeof(SetupViewCallback))] private static void Callback_SetupView(IntPtr context, ref QVec3 origin, ref QVec3 angles, ref QLeaf viewLeaf) { Profiler.BeginSample("SetupView"); GetSelf(context).SetupView(origin, angles, viewLeaf); Profiler.EndSample(); } }