From e70d36399836c0aa2b35aa347e865230a0e1e8de Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Thu, 22 Apr 2021 16:08:21 +0200 Subject: [PATCH] Implemented uploading of skin texture data to Unity, and linking loaded alias models to their primary skin texture --- Assets/Scripts/Data/QConstants.cs | 6 ++ Assets/Scripts/Data/QConstants.cs.meta | 3 + Assets/Scripts/Data/QModel.cs | 27 +++++---- Assets/Scripts/Data/QTexture.cs | 57 +++++++++++++++++++ Assets/Scripts/Data/QTexture.cs.meta | 3 + Assets/Scripts/Modules/AliasModel.cs | 2 +- .../Scripts/Modules/RenderModule.Interop.cs | 24 +++++++- Assets/Scripts/Modules/RenderModule.cs | 48 ++++++++++++++-- engine/Quake/gl_texmgr.c | 2 + engine/Quake/gl_texmgr.h | 2 + engine/UniQuake/gl_uniquake.c | 7 +++ 11 files changed, 161 insertions(+), 20 deletions(-) create mode 100644 Assets/Scripts/Data/QConstants.cs create mode 100644 Assets/Scripts/Data/QConstants.cs.meta create mode 100644 Assets/Scripts/Data/QTexture.cs create mode 100644 Assets/Scripts/Data/QTexture.cs.meta diff --git a/Assets/Scripts/Data/QConstants.cs b/Assets/Scripts/Data/QConstants.cs new file mode 100644 index 0000000..b66a1c2 --- /dev/null +++ b/Assets/Scripts/Data/QConstants.cs @@ -0,0 +1,6 @@ +public static class QConstants +{ + public const int MaxQPath = 64; // Should correspond to MAX_QPATH + public const int MaxMapHulls = 4; // Should correspond to MAX_MAP_HULLS + public const int MaxSkins = 32; // Should correspond to MAX_SKINS +} diff --git a/Assets/Scripts/Data/QConstants.cs.meta b/Assets/Scripts/Data/QConstants.cs.meta new file mode 100644 index 0000000..b65dafd --- /dev/null +++ b/Assets/Scripts/Data/QConstants.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d507e6306af34f16916b95a6b6427762 +timeCreated: 1619100138 \ No newline at end of file diff --git a/Assets/Scripts/Data/QModel.cs b/Assets/Scripts/Data/QModel.cs index 1d571fe..d00c7d8 100644 --- a/Assets/Scripts/Data/QModel.cs +++ b/Assets/Scripts/Data/QModel.cs @@ -1,6 +1,4 @@ using System; -using System.Collections; -using System.Collections.Generic; using System.Runtime.InteropServices; /// @@ -9,10 +7,7 @@ using System.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential, Pack = 0, CharSet = CharSet.Ansi)] public class QModel { - private const int MaxPath = 64; // Should correspond to MAX_QPATH - private const int MaxMapHulls = 4; // Should correspond to MAX_MAP_HULLS - - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MaxPath)] public string name; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = QConstants.MaxQPath)] public string name; public uint pathId; public bool needLoad; @@ -67,7 +62,7 @@ public class QModel public int numMarkSurfaces; public IntPtr markSurfaces; // Array of msurface_t pointers - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MaxMapHulls)] public QHull[] hulls; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = QConstants.MaxMapHulls)] public QHull[] hulls; public int numTextures; public IntPtr textures; // Array of texture_t pointers @@ -79,6 +74,7 @@ public class QModel public bool visWarn; public int bspVersion; + // VBO data from QuakeSpasm; unused by UniQuake public uint meshvbo; public uint meshIndexesVbo; public int vboIndexOfs; @@ -94,8 +90,6 @@ public class QModel [StructLayout(LayoutKind.Sequential, Pack = 0)] public class QAliasHeader { - private const int MaxSkins = 32; // Should correspond to MAX_SKINS - public int ident; public int version; public QVec3 scale; @@ -112,15 +106,20 @@ public class QAliasHeader public int flags; public float size; + // VBO data from QuakeSpasm; unused by UniQuake + public int numVertsVbo; + public IntPtr meshDesc; + public int numIndexes; + public IntPtr indexes; + public IntPtr vertexes; + public int numPoses; public int poseVerts; public int poseData; public int commands; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MaxSkins)] public QVec4i[] glTextureNum; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MaxSkins)] public int[] texels; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MaxSkins)] public bool[] skinLuma; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MaxSkins)] public bool[] skinLuma8bit; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = QConstants.MaxSkins)] public IntPtr[] glTextures; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = QConstants.MaxSkins)] public IntPtr[] fbTextures; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = QConstants.MaxSkins)] public int[] texels; // Actually variable sized, but we receive an additional pointer to the first element so we can manually marshal the entire array [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] public QAliasFrameDesc[] frames; diff --git a/Assets/Scripts/Data/QTexture.cs b/Assets/Scripts/Data/QTexture.cs new file mode 100644 index 0000000..9e85928 --- /dev/null +++ b/Assets/Scripts/Data/QTexture.cs @@ -0,0 +1,57 @@ +using System; +using System.Runtime.InteropServices; + +/// +/// Managed equivalent of gltexture_t +/// +[StructLayout(LayoutKind.Sequential, Pack = 0, CharSet = CharSet.Ansi)] +public class QGLTexture +{ + public uint texNum; + public IntPtr next; + public IntPtr owner; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] public string name; + public uint width; + public uint height; + public QTexPrefs flags; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = QConstants.MaxQPath)] public string sourceFile; + public UIntPtr sourceOffset; + public QTexSourceFormat sourceFormat; + public uint sourceWidth; + public uint sourceHeight; + public ushort sourceCrc; + public char shirt; + public char pants; + public int visFrame; +} + +/// +/// Managed equivalent of srcformat +/// +public enum QTexSourceFormat +{ + Indexed, + Lightmap, + Rgba, +} + +/// +/// Managed equivalent of TEXPRF_ defines +/// +[Flags] +public enum QTexPrefs: uint +{ + None = 0x0000, + Mipmap = 0x0001, + Linear = 0x0002, + Nearest = 0x0004, + Alpha = 0x0008, + Pad = 0x0010, + Persist = 0x0020, + Overwrite = 0x0040, + NoPicMip = 0x0080, + FullBright = 0x0100, + NoBright = 0x0200, + ConChars = 0x0400, + WarpImage = 0x0800, +} diff --git a/Assets/Scripts/Data/QTexture.cs.meta b/Assets/Scripts/Data/QTexture.cs.meta new file mode 100644 index 0000000..7258baf --- /dev/null +++ b/Assets/Scripts/Data/QTexture.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 08aab07be00d450fb56bcfa0c24a1b99 +timeCreated: 1619095043 \ No newline at end of file diff --git a/Assets/Scripts/Modules/AliasModel.cs b/Assets/Scripts/Modules/AliasModel.cs index 8c72ef2..ab63bde 100644 --- a/Assets/Scripts/Modules/AliasModel.cs +++ b/Assets/Scripts/Modules/AliasModel.cs @@ -248,7 +248,7 @@ public class AliasModel for (int i = 0; i < numVerts; ++i) { - uvs[i] = Vector2.Scale(new Vector2(stVerts[i].s, skinHeight - stVerts[i].t), scale); + uvs[i] = Vector2.Scale(new Vector2(stVerts[i].s, stVerts[i].t), scale); } } diff --git a/Assets/Scripts/Modules/RenderModule.Interop.cs b/Assets/Scripts/Modules/RenderModule.Interop.cs index 8112b44..eb10ad3 100644 --- a/Assets/Scripts/Modules/RenderModule.Interop.cs +++ b/Assets/Scripts/Modules/RenderModule.Interop.cs @@ -15,6 +15,7 @@ public partial class RenderModule: CallbackHandler UploadAliasModel = CreateCallback(Callback_UploadAliasModel), UploadBrushModel = CreateCallback(Callback_UploadBrushModel), + UploadTexture = CreateCallback(Callback_UploadTexture), }; RegisterCallbacks(callbacks); @@ -30,6 +31,7 @@ public partial class RenderModule: CallbackHandler public IntPtr UploadAliasModel; public IntPtr UploadBrushModel; + public IntPtr UploadTexture; } [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] @@ -66,10 +68,19 @@ public partial class RenderModule: CallbackHandler 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].ToStructArray(4); + fbTextures[i] = header.fbTextures[i].ToStructArray(4); + } + return GetSelf(target).UploadAliasModel( name, header, frameType, poseVertices, triangles.ToStructArray(header.numTriangles), - stVerts.ToStructArray(header.numVerts)); + stVerts.ToStructArray(header.numVerts), + glTextures, fbTextures); } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -80,4 +91,15 @@ public partial class RenderModule: CallbackHandler { return GetSelf(target).UploadBrushModel(model); } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate bool UploadTextureCallback(IntPtr target, QGLTexture texture, IntPtr data, ref uint texNum); + + [MonoPInvokeCallback(typeof(UploadTextureCallback))] + private static bool Callback_UploadTexture(IntPtr target, QGLTexture texture, IntPtr data, ref uint texNum) + { + byte[] dataBytes = new byte[texture.width * texture.height * 4]; // 32 bits per pixel + Marshal.Copy(data, dataBytes, 0, dataBytes.Length); + return GetSelf(target).UploadTexture(texture, dataBytes, ref texNum); + } } diff --git a/Assets/Scripts/Modules/RenderModule.cs b/Assets/Scripts/Modules/RenderModule.cs index 6d33350..8a9f1b7 100644 --- a/Assets/Scripts/Modules/RenderModule.cs +++ b/Assets/Scripts/Modules/RenderModule.cs @@ -6,7 +6,8 @@ public partial class RenderModule { private readonly UniQuake uq; private readonly List aliasModels = new List(); - + private readonly Dictionary textures = new Dictionary(); + public RenderModule(UniQuake uniQuake) { uq = uniQuake; @@ -23,12 +24,20 @@ public partial class RenderModule } aliasModels.Clear(); + + foreach (var texture in textures.Values) + { + // Object.Destroy(texture); // TODO: reactivate when done testing in editor + } + + textures.Clear(); } private float xPos = -8f; private int UploadAliasModel(string name, QAliasHeader header, QAliasFrameType frameType, - QTriVertex[][] poseVertices, QTriangle[] triangles, QSTVert[] stVertices) + QTriVertex[][] poseVertices, QTriangle[] triangles, QSTVert[] stVertices, + QGLTexture[][] glTextures, QGLTexture[][] fbTextures) { var sb = new System.Text.StringBuilder(); foreach (var frame in header.frames) @@ -48,10 +57,17 @@ public partial class RenderModule aliasModel.Animate(0, out Mesh mesh, out float blendWeight); + var material = new Material(Shader.Find("Universal Render Pipeline/Simple Lit")); + uint texNum = glTextures[0][0].texNum; + if (texNum > 0 && textures.ContainsKey(texNum)) + { + material.mainTexture = textures[texNum]; + } + if (header.numPoses > 1) { var mr = go.AddComponent(); - mr.material = new Material(Shader.Find("Universal Render Pipeline/Simple Lit")); + mr.material = material; mr.sharedMesh = mesh; mr.shadowCastingMode = ShadowCastingMode.Off; mr.receiveShadows = false; @@ -66,7 +82,7 @@ public partial class RenderModule var mf = go.AddComponent(); mf.sharedMesh = mesh; var mr = go.AddComponent(); - mr.material = new Material(Shader.Find("Universal Render Pipeline/Simple Lit")); + mr.material = material; mr.shadowCastingMode = ShadowCastingMode.Off; mr.receiveShadows = false; mr.lightProbeUsage = LightProbeUsage.Off; @@ -85,4 +101,28 @@ public partial class RenderModule Debug.Log($"Brush model '{model.name}' with {model.numLeafs} leafs, {model.numVertices} vertices, {model.numSurfaces} surfaces"); return 1; } + + private uint nextTexNum = 0x10001; + + private bool UploadTexture(QGLTexture texture, byte[] data, ref uint texNum) + { + Debug.Log($"Texture '{texture.name}' with dimensions {texture.width}x{texture.height}, data size = {data.Length} bytes"); + + if (texNum == 0) + { + // Assign a new texture number + while (textures.ContainsKey(nextTexNum)) + ++nextTexNum; + + texNum = nextTexNum++; + } + + var tex = new Texture2D((int)texture.width, (int)texture.height, TextureFormat.RGBA32, texture.flags.HasFlag(QTexPrefs.Mipmap)); + tex.name = texture.name; + tex.SetPixelData(data, 0); + tex.Apply(); + + textures[texNum] = tex; + return true; + } } diff --git a/engine/Quake/gl_texmgr.c b/engine/Quake/gl_texmgr.c index 188907d..4cd6d77 100644 --- a/engine/Quake/gl_texmgr.c +++ b/engine/Quake/gl_texmgr.c @@ -1071,6 +1071,8 @@ static void TexMgr_LoadImage32 (gltexture_t *glt, unsigned *data) // set filter modes TexMgr_SetFilterModes (glt); + + UQ_GL_UploadTexture(glt, data); } /* diff --git a/engine/Quake/gl_texmgr.h b/engine/Quake/gl_texmgr.h index e58ff03..b39b09c 100644 --- a/engine/Quake/gl_texmgr.h +++ b/engine/Quake/gl_texmgr.h @@ -106,5 +106,7 @@ void GL_EnableMultitexture (void); //selects texture unit 1 void GL_Bind (gltexture_t *texture); void GL_ClearBindings (void); +extern qboolean UQ_GL_UploadTexture(gltexture_t *texture, unsigned *data); + #endif /* _GL_TEXMAN_H */ diff --git a/engine/UniQuake/gl_uniquake.c b/engine/UniQuake/gl_uniquake.c index c311e1f..2e45500 100644 --- a/engine/UniQuake/gl_uniquake.c +++ b/engine/UniQuake/gl_uniquake.c @@ -9,6 +9,7 @@ typedef struct unity_glcalls_s int(*UploadAliasModel)(void *target, const char *name, aliashdr_t *aliashdr, aliasframetype_t frametype, maliasframedesc_t *frames, trivertx_t **poseVerts, mtriangle_t *triangles, stvert_t *stVerts); int(*UploadBrushModel)(void *target, qmodel_t *model); + qboolean(*UploadTexture)(void *target, gltexture_t *texture, unsigned *data, GLuint *texnum); } unity_glcalls_t; const unity_glcalls_t *unity_glcalls; @@ -24,3 +25,9 @@ int UQ_GL_UploadBrushModel(qmodel_t *model) { return unity_glcalls->UploadBrushModel(unity_glcalls->target, model); } + +qboolean UQ_GL_UploadTexture(gltexture_t *texture, unsigned *data) +{ + // Allow UniQuake to either sync up its internal texture reference number, or assign a new one. + return unity_glcalls->UploadTexture(unity_glcalls->target, texture, data, &texture->texnum); +}