From 08f520f1c6e76a7f4cf68b921266b9cfea846892 Mon Sep 17 00:00:00 2001 From: Shiny Metagross <30511800+ShinyMetagross@users.noreply.github.com> Date: Fri, 12 Aug 2022 13:17:08 -0700 Subject: [PATCH] IQM Support Implemented More info to come --- src/CMakeLists.txt | 2 + src/common/models/model.cpp | 5 + src/common/models/model.h | 7 +- src/common/models/model_iqm.h | 219 +++++++ src/common/models/model_kvx.h | 11 +- src/common/models/model_md2.h | 5 +- src/common/models/model_md3.h | 5 +- src/common/models/model_obj.h | 3 +- src/common/models/model_ue1.h | 5 +- src/common/models/modelrenderer.h | 2 +- src/common/models/models_iqm.cpp | 599 ++++++++++++++++++ src/common/models/models_md2.cpp | 18 +- src/common/models/models_md3.cpp | 18 +- src/common/models/models_obj.cpp | 17 +- src/common/models/models_ue1.cpp | 11 +- src/common/models/models_voxel.cpp | 19 +- src/common/rendering/gl/gl_buffers.cpp | 13 +- src/common/rendering/gl/gl_buffers.h | 2 + src/common/rendering/gl/gl_framebuffer.cpp | 4 + src/common/rendering/gl/gl_renderstate.cpp | 17 + src/common/rendering/gl/gl_renderstate.h | 1 + src/common/rendering/gl/gl_shader.cpp | 28 +- src/common/rendering/gl/gl_shader.h | 1 + .../rendering/gles/gles_framebuffer.cpp | 4 + src/common/rendering/gles/gles_renderer.h | 1 + .../rendering/hwrenderer/data/buffers.h | 3 + .../hwrenderer/data/hw_bonebuffer.cpp | 112 ++++ .../rendering/hwrenderer/data/hw_bonebuffer.h | 43 ++ .../hwrenderer/data/hw_modelvertexbuffer.cpp | 4 +- .../hwrenderer/data/hw_renderstate.h | 7 + .../hwrenderer/data/shaderuniforms.h | 3 +- src/common/rendering/i_modelvertexbuffer.h | 18 + src/common/rendering/v_video.h | 2 + .../vulkan/renderer/vk_descriptorset.cpp | 4 +- .../vulkan/renderer/vk_renderpass.cpp | 9 +- .../vulkan/renderer/vk_renderstate.cpp | 1 + .../rendering/vulkan/shaders/vk_shader.cpp | 11 +- .../rendering/vulkan/shaders/vk_shader.h | 3 + .../rendering/vulkan/system/vk_buffer.cpp | 3 +- .../rendering/vulkan/system/vk_buffer.h | 1 + .../vulkan/system/vk_framebuffer.cpp | 3 + src/common/utility/matrix.cpp | 38 ++ src/common/utility/matrix.h | 1 + src/r_data/models.cpp | 33 +- src/rendering/hwrenderer/hw_entrypoint.cpp | 3 + src/rendering/hwrenderer/hw_models.cpp | 5 +- src/rendering/hwrenderer/hw_models.h | 2 +- .../hwrenderer/scene/hw_drawinfo.cpp | 4 + wadsrc/static/shaders/glsl/main.vp | 88 ++- 49 files changed, 1356 insertions(+), 62 deletions(-) create mode 100644 src/common/models/model_iqm.h create mode 100644 src/common/models/models_iqm.cpp create mode 100644 src/common/rendering/hwrenderer/data/hw_bonebuffer.cpp create mode 100644 src/common/rendering/hwrenderer/data/hw_bonebuffer.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 09c9da0ec6..e6769c06e2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1084,6 +1084,7 @@ set (PCH_SOURCES common/models/models_voxel.cpp common/models/models_ue1.cpp common/models/models_obj.cpp + common/models/models_iqm.cpp common/models/model.cpp common/models/voxels.cpp common/console/c_commandline.cpp @@ -1179,6 +1180,7 @@ set (PCH_SOURCES common/rendering/hwrenderer/data/hw_cvars.cpp common/rendering/hwrenderer/data/hw_vrmodes.cpp common/rendering/hwrenderer/data/hw_lightbuffer.cpp + common/rendering/hwrenderer/data/hw_bonebuffer.cpp common/rendering/hwrenderer/data/hw_aabbtree.cpp common/rendering/hwrenderer/data/hw_shadowmap.cpp common/rendering/hwrenderer/data/hw_shaderpatcher.cpp diff --git a/src/common/models/model.cpp b/src/common/models/model.cpp index 21f5c9f41c..c4b184f0c2 100644 --- a/src/common/models/model.cpp +++ b/src/common/models/model.cpp @@ -36,6 +36,7 @@ #include "model_md2.h" #include "model_md3.h" #include "model_kvx.h" +#include "model_iqm.h" #include "i_time.h" #include "voxels.h" #include "texturemanager.h" @@ -208,6 +209,10 @@ unsigned FindModel(const char * path, const char * modelfile) { model = new FMD3Model; } + else if (!memcmp(buffer, "INTERQUAKEMODEL\0", 16)) + { + model = new IQMModel; + } if (model != nullptr) { diff --git a/src/common/models/model.h b/src/common/models/model.h index edbcc48419..bb2699a1a7 100644 --- a/src/common/models/model.h +++ b/src/common/models/model.h @@ -3,6 +3,7 @@ #include #include "textureid.h" #include "i_modelvertexbuffer.h" +#include "matrix.h" class FModelRenderer; class FGameTexture; @@ -26,6 +27,7 @@ struct FSpriteModelFrame TArray skinIDs; TArray surfaceskinIDs; TArray modelframes; + TArray animationIDs; float xscale, yscale, zscale; // [BB] Added zoffset, rotation parameters and flags. // Added xoffset, yoffset @@ -67,11 +69,12 @@ public: virtual ~FModel(); virtual bool Load(const char * fn, int lumpnum, const char * buffer, int length) = 0; - virtual int FindFrame(const char * name, bool nodefault = false) = 0; - virtual void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation, const FTextureID* surfaceskinids) = 0; + virtual int FindFrame(const char* name, bool nodefault = false) = 0; + virtual void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation, const FTextureID* surfaceskinids, const TArray& animationData) = 0; virtual void BuildVertexBuffer(FModelRenderer *renderer) = 0; virtual void AddSkins(uint8_t *hitlist, const FTextureID* surfaceskinids) = 0; virtual float getAspectFactor(float vscale) { return 1.f; } + virtual const TArray* AttachAnimationData() = 0; void SetVertexBuffer(int type, IModelVertexBuffer *buffer) { mVBuf[type] = buffer; } IModelVertexBuffer *GetVertexBuffer(int type) const { return mVBuf[type]; } diff --git a/src/common/models/model_iqm.h b/src/common/models/model_iqm.h new file mode 100644 index 0000000000..6c347d0e21 --- /dev/null +++ b/src/common/models/model_iqm.h @@ -0,0 +1,219 @@ +#pragma once + +#include +#include "model.h" +#include "vectors.h" +#include "matrix.h" +#include "common/rendering/i_modelvertexbuffer.h" + +struct IQMMesh +{ + FString Name; + FString Material; + uint32_t FirstVertex; + uint32_t NumVertices; + uint32_t FirstTriangle; + uint32_t NumTriangles; + FTextureID Skin; +}; + +enum IQMVertexArrayType +{ + IQM_POSITION = 0, // float, 3 + IQM_TEXCOORD = 1, // float, 2 + IQM_NORMAL = 2, // float, 3 + IQM_TANGENT = 3, // float, 4 + IQM_BLENDINDEXES = 4, // ubyte, 4 + IQM_BLENDWEIGHTS = 5, // ubyte, 4 + IQM_COLOR = 6, // ubyte, 4 + IQM_CUSTOM = 0x10 +}; + +enum IQMVertexArrayFormat +{ + IQM_BYTE = 0, + IQM_UBYTE = 1, + IQM_SHORT = 2, + IQM_USHORT = 3, + IQM_INT = 4, + IQM_UINT = 5, + IQM_HALF = 6, + IQM_FLOAT = 7, + IQM_DOUBLE = 8, +}; + +struct IQMVertexArray +{ + IQMVertexArrayType Type; + uint32_t Flags; + IQMVertexArrayFormat Format; + uint32_t Size; + uint32_t Offset; +}; + +struct IQMTriangle +{ + uint32_t Vertex[3]; +}; + +struct IQMAdjacency +{ + uint32_t Triangle[3]; +}; + +struct IQMJoint +{ + FString Name; + int32_t Parent; // parent < 0 means this is a root bone + FVector3 Translate; + FVector4 Quaternion; + FVector3 Scale; +}; + +struct IQMPose +{ + int32_t Parent; // parent < 0 means this is a root bone + uint32_t ChannelMask; // mask of which 10 channels are present for this joint pose + float ChannelOffset[10]; + float ChannelScale[10]; + // channels 0..2 are translation and channels 3..6 are quaternion rotation + // rotation is in relative/parent local space + // channels 7..9 are scale + // output = (input*scale)*rotation + translation +}; + +struct IQMAnim +{ + FString Name; + uint32_t FirstFrame; + uint32_t NumFrames; + float Framerate; + bool Loop; +}; + +struct IQMBounds +{ + float BBMins[3]; + float BBMaxs[3]; + float XYRadius; + float Radius; +}; + +class IQMFileReader; + +class IQMModel : public FModel +{ +public: + IQMModel(); + ~IQMModel(); + + bool Load(const char* fn, int lumpnum, const char* buffer, int length) override; + int FindFrame(const char* name, bool nodefault) override; + void RenderFrame(FModelRenderer* renderer, FGameTexture* skin, int frame, int frame2, double inter, int translation, const FTextureID* surfaceskinids, const TArray& animationData) override; + void BuildVertexBuffer(FModelRenderer* renderer) override; + void AddSkins(uint8_t* hitlist, const FTextureID* surfaceskinids) override; + const TArray* AttachAnimationData() override; + +private: + void LoadGeometry(); + void UnloadGeometry(); + + void LoadPosition(IQMFileReader& reader, const IQMVertexArray& vertexArray); + void LoadTexcoord(IQMFileReader& reader, const IQMVertexArray& vertexArray); + void LoadNormal(IQMFileReader& reader, const IQMVertexArray& vertexArray); + void LoadBlendIndexes(IQMFileReader& reader, const IQMVertexArray& vertexArray); + void LoadBlendWeights(IQMFileReader& reader, const IQMVertexArray& vertexArray); + + int mLumpNum = -1; + TArray Meshes; + TArray Triangles; + TArray Adjacency; + TArray Joints; + TArray Poses; + TArray Anims; + TArray FrameTransforms; + TArray Bounds; + TArray VertexArrays; + uint32_t NumVertices = 0; + + TArray Vertices; + + TArray baseframe; + TArray inversebaseframe; +}; + +struct IQMReadErrorException { }; + +class IQMFileReader +{ +public: + IQMFileReader(const void* buffer, int length) : buffer((const char*)buffer), length(length) { } + + uint8_t ReadUByte() + { + uint8_t value; + Read(&value, sizeof(uint8_t)); + return value; + } + + int32_t ReadInt32() + { + int32_t value; + Read(&value, sizeof(int32_t)); + value = LittleLong(value); + return value; + } + + int16_t ReadInt16() + { + int16_t value; + Read(&value, sizeof(int16_t)); + value = LittleShort(value); + return value; + } + + uint32_t ReadUInt32() + { + return ReadInt32(); + } + + uint16_t ReadUInt16() + { + return ReadInt16(); + } + + float ReadFloat() + { + float value; + Read(&value, sizeof(float)); + return value; + } + + FString ReadName(const TArray& textBuffer) + { + uint32_t nameOffset = ReadUInt32(); + if (nameOffset >= textBuffer.Size()) + throw IQMReadErrorException(); + return textBuffer.Data() + nameOffset; + } + + void Read(void* data, int size) + { + if (pos + size > length || size < 0 || size > 0x0fffffff) + throw IQMReadErrorException(); + memcpy(data, buffer + pos, size); + pos += size; + } + + void SeekTo(int newPos) + { + if (newPos < 0 || newPos > length) + throw IQMReadErrorException(); + pos = newPos; + } + +private: + const char* buffer = nullptr; + int length = 0; + int pos = 0; +}; diff --git a/src/common/models/model_kvx.h b/src/common/models/model_kvx.h index 1491b03b48..fa10401f0a 100644 --- a/src/common/models/model_kvx.h +++ b/src/common/models/model_kvx.h @@ -15,9 +15,9 @@ struct FVoxelVertexHash // Returns the hash value for a key. hash_t Hash(const FModelVertex &key) { - int ix = int(key.x); - int iy = int(key.y); - int iz = int(key.z); + int ix = int(key.x); + int iy = int(key.y); + int iz = int(key.z); return (hash_t)(ix + (iy<<9) + (iz<<18)); } @@ -58,12 +58,13 @@ public: ~FVoxelModel(); bool Load(const char * fn, int lumpnum, const char * buffer, int length) override; void Initialize(); - virtual int FindFrame(const char * name, bool nodefault) override; - virtual void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation, const FTextureID* surfaceskinids) override; + virtual int FindFrame(const char* name, bool nodefault) override; + virtual void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation, const FTextureID* surfaceskinids, const TArray& animationData) override; virtual void AddSkins(uint8_t *hitlist, const FTextureID* surfaceskinids) override; FTextureID GetPaletteTexture() const { return mPalette; } void BuildVertexBuffer(FModelRenderer *renderer) override; float getAspectFactor(float vscale) override; + const TArray* AttachAnimationData() override; }; diff --git a/src/common/models/model_md2.h b/src/common/models/model_md2.h index b1feef645a..8be0497337 100644 --- a/src/common/models/model_md2.h +++ b/src/common/models/model_md2.h @@ -112,10 +112,11 @@ public: virtual ~FDMDModel(); virtual bool Load(const char * fn, int lumpnum, const char * buffer, int length) override; - virtual int FindFrame(const char * name, bool nodefault) override; - virtual void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation, const FTextureID* surfaceskinids) override; + virtual int FindFrame(const char* name, bool nodefault) override; + virtual void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation, const FTextureID* surfaceskinids, const TArray& animationData) override; virtual void LoadGeometry(); virtual void AddSkins(uint8_t *hitlist, const FTextureID* surfaceskinids) override; + const TArray* AttachAnimationData() override; void UnloadGeometry(); void BuildVertexBuffer(FModelRenderer *renderer); diff --git a/src/common/models/model_md3.h b/src/common/models/model_md3.h index 3d81cb1c39..84dda60af5 100644 --- a/src/common/models/model_md3.h +++ b/src/common/models/model_md3.h @@ -66,10 +66,11 @@ public: FMD3Model() = default; virtual bool Load(const char * fn, int lumpnum, const char * buffer, int length) override; - virtual int FindFrame(const char * name, bool nodefault) override; - virtual void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation, const FTextureID* surfaceskinids) override; + virtual int FindFrame(const char* name, bool nodefault) override; + virtual void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation, const FTextureID* surfaceskinids, const TArray& animationData) override; void LoadGeometry(); void BuildVertexBuffer(FModelRenderer *renderer); virtual void AddSkins(uint8_t *hitlist, const FTextureID* surfaceskinids) override; + const TArray* AttachAnimationData() override; }; diff --git a/src/common/models/model_obj.h b/src/common/models/model_obj.h index 55a47363cb..97fb1c15a9 100644 --- a/src/common/models/model_obj.h +++ b/src/common/models/model_obj.h @@ -98,9 +98,10 @@ public: ~FOBJModel(); bool Load(const char* fn, int lumpnum, const char* buffer, int length) override; int FindFrame(const char* name, bool nodefault) override; - void RenderFrame(FModelRenderer* renderer, FGameTexture* skin, int frame, int frame2, double inter, int translation, const FTextureID* surfaceskinids) override; + void RenderFrame(FModelRenderer* renderer, FGameTexture* skin, int frame, int frame2, double inter, int translation, const FTextureID* surfaceskinids, const TArray& animationData) override; void BuildVertexBuffer(FModelRenderer* renderer) override; void AddSkins(uint8_t* hitlist, const FTextureID* surfaceskinids) override; + const TArray* AttachAnimationData() override; }; #endif diff --git a/src/common/models/model_ue1.h b/src/common/models/model_ue1.h index f2ed1952c4..7ae9d11df8 100644 --- a/src/common/models/model_ue1.h +++ b/src/common/models/model_ue1.h @@ -25,10 +25,11 @@ public: }; bool Load(const char * fn, int lumpnum, const char * buffer, int length) override; - int FindFrame(const char * name, bool nodefault) override; - void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation, const FTextureID* surfaceskinids) override; + int FindFrame(const char* name, bool nodefault) override; + void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation, const FTextureID* surfaceskinids, const TArray& animationData) override; void BuildVertexBuffer(FModelRenderer *renderer) override; void AddSkins(uint8_t *hitlist, const FTextureID* surfaceskinids) override; + const TArray* AttachAnimationData() override; void LoadGeometry(); void UnloadGeometry(); FUE1Model() diff --git a/src/common/models/modelrenderer.h b/src/common/models/modelrenderer.h index 38786afe6f..9a91cad4f2 100644 --- a/src/common/models/modelrenderer.h +++ b/src/common/models/modelrenderer.h @@ -24,6 +24,6 @@ public: virtual void SetMaterial(FGameTexture *skin, bool clampNoFilter, int translation) = 0; virtual void DrawArrays(int start, int count) = 0; virtual void DrawElements(int numIndices, size_t offset) = 0; - virtual void SetupFrame(FModel *model, unsigned int frame1, unsigned int frame2, unsigned int size) = 0; + virtual void SetupFrame(FModel* model, unsigned int frame1, unsigned int frame2, unsigned int size, const TArray& bones) = 0; }; diff --git a/src/common/models/models_iqm.cpp b/src/common/models/models_iqm.cpp new file mode 100644 index 0000000000..a3354e04b5 --- /dev/null +++ b/src/common/models/models_iqm.cpp @@ -0,0 +1,599 @@ + +#include "filesystem.h" +#include "cmdlib.h" +#include "model_iqm.h" +#include "texturemanager.h" +#include "modelrenderer.h" +#include "engineerrors.h" +#include "r_utility.h" + +IQMModel::IQMModel() +{ +} + +IQMModel::~IQMModel() +{ +} + +bool IQMModel::Load(const char* path, int lumpnum, const char* buffer, int length) +{ + mLumpNum = lumpnum; + + try + { + IQMFileReader reader(buffer, length); + + char magic[16]; + reader.Read(magic, 16); + if (memcmp(magic, "INTERQUAKEMODEL\0", 16) != 0) + return false; + + uint32_t version = reader.ReadUInt32(); + if (version != 2) + return false; + + uint32_t filesize = reader.ReadUInt32(); + uint32_t flags = reader.ReadUInt32(); + uint32_t num_text = reader.ReadUInt32(); + uint32_t ofs_text = reader.ReadUInt32(); + uint32_t num_meshes = reader.ReadUInt32(); + uint32_t ofs_meshes = reader.ReadUInt32(); + uint32_t num_vertexarrays = reader.ReadUInt32(); + uint32_t num_vertices = reader.ReadUInt32(); + uint32_t ofs_vertexarrays = reader.ReadUInt32(); + uint32_t num_triangles = reader.ReadUInt32(); + uint32_t ofs_triangles = reader.ReadUInt32(); + uint32_t ofs_adjacency = reader.ReadUInt32(); + uint32_t num_joints = reader.ReadUInt32(); + uint32_t ofs_joints = reader.ReadUInt32(); + uint32_t num_poses = reader.ReadUInt32(); + uint32_t ofs_poses = reader.ReadUInt32(); + uint32_t num_anims = reader.ReadUInt32(); + uint32_t ofs_anims = reader.ReadUInt32(); + uint32_t num_frames = reader.ReadUInt32(); + uint32_t num_framechannels = reader.ReadUInt32(); + uint32_t ofs_frames = reader.ReadUInt32(); + uint32_t ofs_bounds = reader.ReadUInt32(); + uint32_t num_comment = reader.ReadUInt32(); + uint32_t ofs_comment = reader.ReadUInt32(); + uint32_t num_extensions = reader.ReadUInt32(); + uint32_t ofs_extensions = reader.ReadUInt32(); + + if (num_text == 0) + return false; + + TArray text(num_text, true); + reader.SeekTo(ofs_text); + reader.Read(text.Data(), text.Size()); + text[text.Size() - 1] = 0; + + Meshes.Resize(num_meshes); + Triangles.Resize(num_triangles); + Adjacency.Resize(num_triangles); + Joints.Resize(num_joints); + Poses.Resize(num_poses); + Anims.Resize(num_anims); + Bounds.Resize(num_frames); + VertexArrays.Resize(num_vertexarrays); + NumVertices = num_vertices; + + reader.SeekTo(ofs_meshes); + for (IQMMesh& mesh : Meshes) + { + mesh.Name = reader.ReadName(text); + mesh.Material = reader.ReadName(text); + mesh.FirstVertex = reader.ReadUInt32(); + mesh.NumVertices = reader.ReadUInt32(); + mesh.FirstTriangle = reader.ReadUInt32(); + mesh.NumTriangles = reader.ReadUInt32(); + mesh.Skin = LoadSkin(path, mesh.Material.GetChars()); + } + + reader.SeekTo(ofs_triangles); + for (IQMTriangle& triangle : Triangles) + { + triangle.Vertex[0] = reader.ReadUInt32(); + triangle.Vertex[1] = reader.ReadUInt32(); + triangle.Vertex[2] = reader.ReadUInt32(); + } + + reader.SeekTo(ofs_adjacency); + for (IQMAdjacency& adj : Adjacency) + { + adj.Triangle[0] = reader.ReadUInt32(); + adj.Triangle[1] = reader.ReadUInt32(); + adj.Triangle[2] = reader.ReadUInt32(); + } + + reader.SeekTo(ofs_joints); + for (IQMJoint& joint : Joints) + { + joint.Name = reader.ReadName(text); + joint.Parent = reader.ReadInt32(); + joint.Translate.X = reader.ReadFloat(); + joint.Translate.Y = reader.ReadFloat(); + joint.Translate.Z = reader.ReadFloat(); + joint.Quaternion.X = reader.ReadFloat(); + joint.Quaternion.Y = reader.ReadFloat(); + joint.Quaternion.Z = reader.ReadFloat(); + joint.Quaternion.W = reader.ReadFloat(); + joint.Quaternion.MakeUnit(); + joint.Scale.X = reader.ReadFloat(); + joint.Scale.Y = reader.ReadFloat(); + joint.Scale.Z = reader.ReadFloat(); + } + + reader.SeekTo(ofs_poses); + for (IQMPose& pose : Poses) + { + pose.Parent = reader.ReadInt32(); + pose.ChannelMask = reader.ReadUInt32(); + for (int i = 0; i < 10; i++) pose.ChannelOffset[i] = reader.ReadFloat(); + for (int i = 0; i < 10; i++) pose.ChannelScale[i] = reader.ReadFloat(); + } + + reader.SeekTo(ofs_anims); + for (IQMAnim& anim : Anims) + { + anim.Name = reader.ReadName(text); + anim.FirstFrame = reader.ReadUInt32(); + anim.NumFrames = reader.ReadUInt32(); + anim.Framerate = reader.ReadFloat(); + anim.Loop = !!(reader.ReadUInt32() & 1); + } + + baseframe.Resize(num_joints); + inversebaseframe.Resize(num_joints); + + for (uint32_t i = 0; i < num_joints; i++) + { + const IQMJoint& j = Joints[i]; + + VSMatrix m, invm; + m.loadIdentity(); + m.translate(j.Translate.X, j.Translate.Y, j.Translate.Z); + m.multQuaternion(j.Quaternion); + m.scale(j.Scale.X, j.Scale.Y, j.Scale.Z); + m.inverseMatrix(invm); + if (j.Parent >= 0) + { + baseframe[i] = baseframe[j.Parent]; + baseframe[i].multMatrix(m); + inversebaseframe[i] = invm; + inversebaseframe[i].multMatrix(inversebaseframe[j.Parent]); + } + else + { + baseframe[i] = m; + inversebaseframe[i] = invm; + } + } + + // Swap YZ axis as we did that with the vertices down in LoadGeometry. + // This is an unfortunate side effect of the coordinate system in the gzdoom model rendering system + float swapYZ[16] = { 0.0f }; + swapYZ[0 + 0 * 4] = 1.0f; + swapYZ[1 + 2 * 4] = 1.0f; + swapYZ[2 + 1 * 4] = 1.0f; + swapYZ[3 + 3 * 4] = 1.0f; + + FrameTransforms.Resize(num_frames * num_poses); + reader.SeekTo(ofs_frames); + for (uint32_t i = 0; i < num_frames; i++) + { + for (uint32_t j = 0; j < num_poses; j++) + { + const IQMPose& p = Poses[j]; + + FVector3 translate; + translate.X = p.ChannelOffset[0]; if (p.ChannelMask & 0x01) translate.X += reader.ReadUInt16() * p.ChannelScale[0]; + translate.Y = p.ChannelOffset[1]; if (p.ChannelMask & 0x02) translate.Y += reader.ReadUInt16() * p.ChannelScale[1]; + translate.Z = p.ChannelOffset[2]; if (p.ChannelMask & 0x04) translate.Z += reader.ReadUInt16() * p.ChannelScale[2]; + + FVector4 quaternion; + quaternion.X = p.ChannelOffset[3]; if (p.ChannelMask & 0x08) quaternion.X += reader.ReadUInt16() * p.ChannelScale[3]; + quaternion.Y = p.ChannelOffset[4]; if (p.ChannelMask & 0x10) quaternion.Y += reader.ReadUInt16() * p.ChannelScale[4]; + quaternion.Z = p.ChannelOffset[5]; if (p.ChannelMask & 0x20) quaternion.Z += reader.ReadUInt16() * p.ChannelScale[5]; + quaternion.W = p.ChannelOffset[6]; if (p.ChannelMask & 0x40) quaternion.W += reader.ReadUInt16() * p.ChannelScale[6]; + quaternion.MakeUnit(); + + FVector3 scale; + scale.X = p.ChannelOffset[7]; if (p.ChannelMask & 0x80) scale.X += reader.ReadUInt16() * p.ChannelScale[7]; + scale.Y = p.ChannelOffset[8]; if (p.ChannelMask & 0x100) scale.Y += reader.ReadUInt16() * p.ChannelScale[8]; + scale.Z = p.ChannelOffset[9]; if (p.ChannelMask & 0x200) scale.Z += reader.ReadUInt16() * p.ChannelScale[9]; + + VSMatrix m; + m.loadIdentity(); + m.translate(translate.X, translate.Y, translate.Z); + m.multQuaternion(quaternion); + m.scale(scale.X, scale.Y, scale.Z); + + // Concatenate each pose with the inverse base pose to avoid doing this at animation time. + // If the joint has a parent, then it needs to be pre-concatenated with its parent's base pose. + // Thus it all negates at animation time like so: + // (parentPose * parentInverseBasePose) * (parentBasePose * childPose * childInverseBasePose) => + // parentPose * (parentInverseBasePose * parentBasePose) * childPose * childInverseBasePose => + // parentPose * childPose * childInverseBasePose + VSMatrix& result = FrameTransforms[i * num_poses + j]; + if (p.Parent >= 0) + { + result = baseframe[p.Parent]; + result.multMatrix(m); + result.multMatrix(inversebaseframe[j]); + } + else + { + result = m; + result.multMatrix(inversebaseframe[j]); + } + } + + for (uint32_t j = 0; j < num_poses; j++) + { + VSMatrix m; + m.loadMatrix(swapYZ); + m.multMatrix(FrameTransforms[i * num_poses + j]); + m.multMatrix(swapYZ); + FrameTransforms[i * num_poses + j] = m; + } + } + + //If a model doesn't have an animation loaded, it will crash. We don't want that! + if (num_frames <= 0) + { + num_frames = 1; + FrameTransforms.Resize(num_joints); + + for (uint32_t j = 0; j < num_joints; j++) + { + FVector3 translate; + translate.X = Joints[j].Translate.X; + translate.Y = Joints[j].Translate.Y; + translate.Z = Joints[j].Translate.Z; + + FVector4 quaternion; + quaternion.X = Joints[j].Quaternion.X; + quaternion.Y = Joints[j].Quaternion.Y; + quaternion.Z = Joints[j].Quaternion.Z; + quaternion.W = Joints[j].Quaternion.W; + quaternion.MakeUnit(); + + FVector3 scale; + scale.X = Joints[j].Scale.X; + scale.Y = Joints[j].Scale.Y; + scale.Z = Joints[j].Scale.Z; + + VSMatrix m; + m.loadIdentity(); + m.translate(translate.X, translate.Y, translate.Z); + m.multQuaternion(quaternion); + m.scale(scale.X, scale.Y, scale.Z); + + VSMatrix& result = FrameTransforms[j]; + if (Joints[j].Parent >= 0) + { + result = baseframe[Joints[j].Parent]; + result.multMatrix(m); + result.multMatrix(inversebaseframe[j]); + } + else + { + result = m; + result.multMatrix(inversebaseframe[j]); + } + } + + for (uint32_t j = 0; j < num_joints; j++) + { + VSMatrix m; + m.loadMatrix(swapYZ); + m.multMatrix(FrameTransforms[j]); + m.multMatrix(swapYZ); + FrameTransforms[j] = m; + } + } + + reader.SeekTo(ofs_bounds); + for (IQMBounds& bound : Bounds) + { + bound.BBMins[0] = reader.ReadFloat(); + bound.BBMins[1] = reader.ReadFloat(); + bound.BBMins[2] = reader.ReadFloat(); + bound.BBMaxs[0] = reader.ReadFloat(); + bound.BBMaxs[1] = reader.ReadFloat(); + bound.BBMaxs[2] = reader.ReadFloat(); + bound.XYRadius = reader.ReadFloat(); + bound.Radius = reader.ReadFloat(); + } + + reader.SeekTo(ofs_vertexarrays); + for (IQMVertexArray& vertexArray : VertexArrays) + { + vertexArray.Type = (IQMVertexArrayType)reader.ReadUInt32(); + vertexArray.Flags = reader.ReadUInt32(); + vertexArray.Format = (IQMVertexArrayFormat)reader.ReadUInt32(); + vertexArray.Size = reader.ReadUInt32(); + vertexArray.Offset = reader.ReadUInt32(); + } + + return true; + } + catch (IQMReadErrorException) + { + return false; + } +} + +void IQMModel::LoadGeometry() +{ + try + { + FileData lumpdata = fileSystem.ReadFile(mLumpNum); + IQMFileReader reader(lumpdata.GetMem(), (int)lumpdata.GetSize()); + + Vertices.Resize(NumVertices); + for (IQMVertexArray& vertexArray : VertexArrays) + { + reader.SeekTo(vertexArray.Offset); + if (vertexArray.Type == IQM_POSITION) + { + LoadPosition(reader, vertexArray); + } + else if (vertexArray.Type == IQM_TEXCOORD) + { + LoadTexcoord(reader, vertexArray); + } + else if (vertexArray.Type == IQM_NORMAL) + { + LoadNormal(reader, vertexArray); + } + else if (vertexArray.Type == IQM_BLENDINDEXES) + { + LoadBlendIndexes(reader, vertexArray); + } + else if (vertexArray.Type == IQM_BLENDWEIGHTS) + { + LoadBlendWeights(reader, vertexArray); + } + } + } + catch (IQMReadErrorException) + { + } +} + +void IQMModel::LoadPosition(IQMFileReader& reader, const IQMVertexArray& vertexArray) +{ + float lu = 0.0f, lv = 0.0f, lindex = -1.0f; + if (vertexArray.Format == IQM_FLOAT && vertexArray.Size == 3) + { + for (FModelVertex& v : Vertices) + { + v.x = reader.ReadFloat(); + v.z = reader.ReadFloat(); + v.y = reader.ReadFloat(); + + v.lu = lu; + v.lv = lv; + v.lindex = lindex; + } + } + else + { + I_FatalError("Unsupported IQM_POSITION vertex format"); + } +} + +void IQMModel::LoadTexcoord(IQMFileReader& reader, const IQMVertexArray& vertexArray) +{ + if (vertexArray.Format == IQM_FLOAT && vertexArray.Size == 2) + { + for (FModelVertex& v : Vertices) + { + v.u = reader.ReadFloat(); + v.v = reader.ReadFloat(); + } + } + else + { + I_FatalError("Unsupported IQM_TEXCOORD vertex format"); + } +} + +void IQMModel::LoadNormal(IQMFileReader& reader, const IQMVertexArray& vertexArray) +{ + if (vertexArray.Format == IQM_FLOAT && vertexArray.Size == 3) + { + for (FModelVertex& v : Vertices) + { + float x = reader.ReadFloat(); + float y = reader.ReadFloat(); + float z = reader.ReadFloat(); + + v.SetNormal(x, z, y); + } + } + else + { + I_FatalError("Unsupported IQM_NORMAL vertex format"); + } +} + +void IQMModel::LoadBlendIndexes(IQMFileReader& reader, const IQMVertexArray& vertexArray) +{ + if (vertexArray.Format == IQM_UBYTE && vertexArray.Size == 4) + { + for (FModelVertex& v : Vertices) + { + int x = reader.ReadUByte(); + int y = reader.ReadUByte(); + int z = reader.ReadUByte(); + int w = reader.ReadUByte(); + v.SetBoneSelector(x, y, z, w); + } + } + else if (vertexArray.Format == IQM_INT && vertexArray.Size == 4) + { + for (FModelVertex& v : Vertices) + { + int x = reader.ReadInt32(); + int y = reader.ReadInt32(); + int z = reader.ReadInt32(); + int w = reader.ReadInt32(); + v.SetBoneSelector(x, y, z, w); + } + } + else + { + I_FatalError("Unsupported IQM_BLENDINDEXES vertex format"); + } +} + +void IQMModel::LoadBlendWeights(IQMFileReader& reader, const IQMVertexArray& vertexArray) +{ + if (vertexArray.Format == IQM_UBYTE && vertexArray.Size == 4) + { + for (FModelVertex& v : Vertices) + { + v.SetBoneWeight(reader.ReadUByte(), reader.ReadUByte(), reader.ReadUByte(), reader.ReadUByte()); + } + } + else if (vertexArray.Format == IQM_FLOAT && vertexArray.Size == 4) + { + for (FModelVertex& v : Vertices) + { + uint8_t x = (int)clamp(reader.ReadFloat() * 255.0f, 0.0f, 255.0f); + uint8_t y = (int)clamp(reader.ReadFloat() * 255.0f, 0.0f, 255.0f); + uint8_t z = (int)clamp(reader.ReadFloat() * 255.0f, 0.0f, 255.0f); + uint8_t w = (int)clamp(reader.ReadFloat() * 255.0f, 0.0f, 255.0f); + v.SetBoneWeight(x, y, z, w); + } + } + else + { + I_FatalError("Unsupported IQM_BLENDWEIGHTS vertex format"); + } +} + +void IQMModel::UnloadGeometry() +{ + Vertices.Reset(); +} + +int IQMModel::FindFrame(const char* name, bool nodefault) +{ + // This doesn't really mean all that much for IQM + for (unsigned i = 0; i < Anims.Size(); i++) + { + if (!stricmp(name, Anims[i].Name.GetChars())) return i; + } + return FErr_NotFound; +} + +void IQMModel::RenderFrame(FModelRenderer* renderer, FGameTexture* skin, int frame1, int frame2, double inter, int translation, const FTextureID* surfaceskinids, const TArray& animationData) +{ + const TArray& animationFrames = &animationData ? animationData : FrameTransforms; + + int numbones = Joints.Size(); + + frame1 = clamp(frame1, 0, ((int)animationFrames.Size() - 1)/numbones); + frame2 = clamp(frame2, 0, ((int)animationFrames.Size() - 1)/numbones); + + int offset1 = frame1 * numbones; + int offset2 = frame2 * numbones; + float t = (float)inter; + float invt = 1.0f - t; + + TArray bones(numbones, true); + for (int i = 0; i < numbones; i++) + { + const float* from = animationFrames[offset1 + i].get(); + const float* to = animationFrames[offset2 + i].get(); + + // Interpolate bone between the two frames + float bone[16]; + for (int i = 0; i < 16; i++) + { + bone[i] = from[i] * invt + to[i] * t; + } + + // Apply parent bone + if (Joints[i].Parent >= 0) + { + bones[i] = bones[Joints[i].Parent]; + bones[i].multMatrix(bone); + } + else + { + bones[i].loadMatrix(bone); + } + } + + renderer->SetupFrame(this, 0, 0, NumVertices, bones); + + FGameTexture* lastSkin = nullptr; + for (int i = 0; i < Meshes.Size(); i++) + { + FGameTexture* meshSkin = skin; + + if (!meshSkin) + { + if (surfaceskinids && surfaceskinids[i].isValid()) + { + meshSkin = TexMan.GetGameTexture(surfaceskinids[i], true); + } + else if (!Meshes[i].Skin.isValid()) + { + continue; + } + else + { + meshSkin = TexMan.GetGameTexture(Meshes[i].Skin, true); + } + if (!meshSkin) continue; + } + + if (meshSkin != lastSkin) + { + renderer->SetMaterial(meshSkin, false, translation); + lastSkin = meshSkin; + } + + renderer->DrawElements(Meshes[i].NumTriangles * 3, Meshes[i].FirstTriangle * 3 * sizeof(unsigned int)); + } +} + +void IQMModel::BuildVertexBuffer(FModelRenderer* renderer) +{ + if (!GetVertexBuffer(renderer->GetType())) + { + LoadGeometry(); + + auto vbuf = renderer->CreateVertexBuffer(true, true); + SetVertexBuffer(renderer->GetType(), vbuf); + + FModelVertex* vertptr = vbuf->LockVertexBuffer(Vertices.Size()); + memcpy(vertptr, Vertices.Data(), Vertices.Size() * sizeof(FModelVertex)); + vbuf->UnlockVertexBuffer(); + + unsigned int* indxptr = vbuf->LockIndexBuffer(Triangles.Size() * 3); + memcpy(indxptr, Triangles.Data(), Triangles.Size() * sizeof(unsigned int) * 3); + vbuf->UnlockIndexBuffer(); + + UnloadGeometry(); + } +} + +void IQMModel::AddSkins(uint8_t* hitlist, const FTextureID* surfaceskinids) +{ + for (int i = 0; i < Meshes.Size(); i++) + { + if (surfaceskinids && surfaceskinids[i].isValid()) + hitlist[surfaceskinids[i].GetIndex()] |= FTextureManager::HIT_Flat; + } +} + +const TArray* IQMModel::AttachAnimationData() +{ + return &FrameTransforms; +} diff --git a/src/common/models/models_md2.cpp b/src/common/models/models_md2.cpp index 81d1c36668..6a84b4e469 100644 --- a/src/common/models/models_md2.cpp +++ b/src/common/models/models_md2.cpp @@ -348,7 +348,7 @@ void FDMDModel::AddSkins(uint8_t *hitlist, const FTextureID*) // FDMDModel::FindFrame // //=========================================================================== -int FDMDModel::FindFrame(const char * name, bool nodefault) +int FDMDModel::FindFrame(const char* name, bool nodefault) { for (int i=0;i& animationData) { if (frameno >= info.numFrames || frameno2 >= info.numFrames) return; @@ -376,11 +376,22 @@ void FDMDModel::RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int f renderer->SetInterpolation(inter); renderer->SetMaterial(skin, false, translation); - renderer->SetupFrame(this, frames[frameno].vindex, frames[frameno2].vindex, lodInfo[0].numTriangles * 3); + renderer->SetupFrame(this, frames[frameno].vindex, frames[frameno2].vindex, lodInfo[0].numTriangles * 3, {}); renderer->DrawArrays(0, lodInfo[0].numTriangles * 3); renderer->SetInterpolation(0.f); } +//=========================================================================== +// +// Pointless for this format +// +//=========================================================================== + +const TArray* FDMDModel::AttachAnimationData() +{ + return {}; +} + //=========================================================================== @@ -552,4 +563,3 @@ void FMD2Model::LoadGeometry() FMD2Model::~FMD2Model() { } - diff --git a/src/common/models/models_md3.cpp b/src/common/models/models_md3.cpp index 95ac491673..b9a3ceeb36 100644 --- a/src/common/models/models_md3.cpp +++ b/src/common/models/models_md3.cpp @@ -328,13 +328,13 @@ void FMD3Model::AddSkins(uint8_t *hitlist, const FTextureID* surfaceskinids) // //=========================================================================== -int FMD3Model::FindFrame(const char * name, bool nodefault) +int FMD3Model::FindFrame(const char* name, bool nodefault) { for (unsigned i = 0; i < Frames.Size(); i++) { if (!stricmp(name, Frames[i].Name)) return i; } - return FErr_NotFound; + return -1; } //=========================================================================== @@ -343,7 +343,7 @@ int FMD3Model::FindFrame(const char * name, bool nodefault) // //=========================================================================== -void FMD3Model::RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frameno, int frameno2, double inter, int translation, const FTextureID* surfaceskinids) +void FMD3Model::RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frameno, int frameno2, double inter, int translation, const FTextureID* surfaceskinids, const TArray& animationData) { if ((unsigned)frameno >= Frames.Size() || (unsigned)frameno2 >= Frames.Size()) return; @@ -373,9 +373,19 @@ void FMD3Model::RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int f } renderer->SetMaterial(surfaceSkin, false, translation); - renderer->SetupFrame(this, surf->vindex + frameno * surf->numVertices, surf->vindex + frameno2 * surf->numVertices, surf->numVertices); + renderer->SetupFrame(this, surf->vindex + frameno * surf->numVertices, surf->vindex + frameno2 * surf->numVertices, surf->numVertices, {}); renderer->DrawElements(surf->numTriangles * 3, surf->iindex * sizeof(unsigned int)); } renderer->SetInterpolation(0.f); } +//=========================================================================== +// +// +// +//=========================================================================== + +const TArray* FMD3Model::AttachAnimationData() +{ + return {}; +} diff --git a/src/common/models/models_obj.cpp b/src/common/models/models_obj.cpp index d804ffff6a..6f93061706 100644 --- a/src/common/models/models_obj.cpp +++ b/src/common/models/models_obj.cpp @@ -615,7 +615,7 @@ FVector3 FOBJModel::CalculateNormalSmooth(unsigned int vidx, unsigned int smooth */ int FOBJModel::FindFrame(const char* name, bool nodefault) { - return nodefault? FErr_Singleframe : 0; // OBJs are not animated. + return nodefault ? FErr_Singleframe : 0; // OBJs are not animated. } /** @@ -628,7 +628,7 @@ int FOBJModel::FindFrame(const char* name, bool nodefault) * @param inter The amount to interpolate the two frames. * @param translation The translation for the skin */ -void FOBJModel::RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frameno, int frameno2, double inter, int translation, const FTextureID* surfaceskinids) +void FOBJModel::RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frameno, int frameno2, double inter, int translation, const FTextureID* surfaceskinids, const TArray& animationData) { // Prevent the model from rendering if the frame number is < 0 if (frameno < 0 || frameno2 < 0) return; @@ -657,7 +657,7 @@ void FOBJModel::RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int f } renderer->SetMaterial(userSkin, false, translation); - renderer->SetupFrame(this, surf->vbStart, surf->vbStart, surf->numTris * 3); + renderer->SetupFrame(this, surf->vbStart, surf->vbStart, surf->numTris * 3, {}); renderer->DrawArrays(0, surf->numTris * 3); } } @@ -700,3 +700,14 @@ FOBJModel::~FOBJModel() faces.Clear(); surfaces.Clear(); } + +//=========================================================================== +// +// +// +//=========================================================================== + +const TArray* FOBJModel::AttachAnimationData() +{ + return {}; +} diff --git a/src/common/models/models_ue1.cpp b/src/common/models/models_ue1.cpp index 3e47d87e0b..04450a1d3d 100644 --- a/src/common/models/models_ue1.cpp +++ b/src/common/models/models_ue1.cpp @@ -221,7 +221,7 @@ void FUE1Model::UnloadGeometry() groups.Reset(); } -int FUE1Model::FindFrame( const char *name, bool nodefault ) +int FUE1Model::FindFrame(const char* name, bool nodefault) { // there are no named frames, but we need something here to properly interface with it. So just treat the string as an index number. auto index = strtol(name, nullptr, 0); @@ -229,7 +229,7 @@ int FUE1Model::FindFrame( const char *name, bool nodefault ) return index; } -void FUE1Model::RenderFrame( FModelRenderer *renderer, FGameTexture *skin, int frame, int frame2, double inter, int translation, const FTextureID* surfaceskinids) +void FUE1Model::RenderFrame( FModelRenderer *renderer, FGameTexture *skin, int frame, int frame2, double inter, int translation, const FTextureID* surfaceskinids, const TArray& animationData) { // the moment of magic if ( (frame < 0) || (frame2 < 0) || (frame >= numFrames) || (frame2 >= numFrames) ) return; @@ -260,7 +260,7 @@ void FUE1Model::RenderFrame( FModelRenderer *renderer, FGameTexture *skin, int f // TODO: Handle per-group render styles and other flags once functions for it are implemented // Future note: poly renderstyles should always be enforced unless the actor itself has a style other than Normal renderer->SetMaterial(sskin,false,translation); - renderer->SetupFrame(this, vofs+frame*fsize,vofs+frame2*fsize,vsize); + renderer->SetupFrame(this, vofs + frame * fsize, vofs + frame2 * fsize, vsize, {}); renderer->DrawArrays(0,vsize); vofs += vsize; } @@ -321,3 +321,8 @@ FUE1Model::~FUE1Model() { UnloadGeometry(); } + +const TArray* FUE1Model::AttachAnimationData() +{ + return {}; +} diff --git a/src/common/models/models_voxel.cpp b/src/common/models/models_voxel.cpp index 41f5e854af..2e6ca4bd65 100644 --- a/src/common/models/models_voxel.cpp +++ b/src/common/models/models_voxel.cpp @@ -378,9 +378,9 @@ bool FVoxelModel::Load(const char * fn, int lumpnum, const char * buffer, int le // //=========================================================================== -int FVoxelModel::FindFrame(const char * name, bool nodefault) +int FVoxelModel::FindFrame(const char* name, bool nodefault) { - return nodefault? FErr_Voxel : 0; // -2, not -1 because voxels are special. + return nodefault ? FErr_Voxel : 0; // -2, not -1 because voxels are special. } //=========================================================================== @@ -400,10 +400,21 @@ float FVoxelModel::getAspectFactor(float stretch) // //=========================================================================== -void FVoxelModel::RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation, const FTextureID*) +void FVoxelModel::RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, int translation, const FTextureID*, const TArray& animationData) { renderer->SetMaterial(skin, true, translation); - renderer->SetupFrame(this, 0, 0, 0); + renderer->SetupFrame(this, 0, 0, 0, {}); renderer->DrawElements(mNumIndices, 0); } +//=========================================================================== +// +// Voxels don't use bones +// +//=========================================================================== + +const TArray* FVoxelModel::AttachAnimationData() +{ + return {}; +} + diff --git a/src/common/rendering/gl/gl_buffers.cpp b/src/common/rendering/gl/gl_buffers.cpp index 5439319a9a..a576b64315 100644 --- a/src/common/rendering/gl/gl_buffers.cpp +++ b/src/common/rendering/gl/gl_buffers.cpp @@ -211,8 +211,10 @@ void GLBuffer::GPUWaitSync() void GLVertexBuffer::SetFormat(int numBindingPoints, int numAttributes, size_t stride, const FVertexBufferAttribute *attrs) { - static int VFmtToGLFmt[] = { GL_FLOAT, GL_FLOAT, GL_FLOAT, GL_FLOAT, GL_UNSIGNED_BYTE, GL_INT_2_10_10_10_REV }; - static uint8_t VFmtToSize[] = {4, 3, 2, 1, 4, 4}; + static int VFmtToGLFmt[] = { GL_FLOAT, GL_FLOAT, GL_FLOAT, GL_FLOAT, GL_UNSIGNED_BYTE, GL_INT_2_10_10_10_REV, GL_UNSIGNED_BYTE }; + static uint8_t VFmtToSize[] = { 4, 3, 2, 1, 4, 4, 4 }; + static bool VFmtToNormalize[] = { false, false, false, false, true, true, false }; + static bool VFmtToIntegerType[] = { false, false, false, false, false, false, true }; mStride = stride; mNumBindingPoints = numBindingPoints; @@ -226,6 +228,8 @@ void GLVertexBuffer::SetFormat(int numBindingPoints, int numAttributes, size_t s attrinf.size = VFmtToSize[attrs[i].format]; attrinf.offset = attrs[i].offset; attrinf.bindingpoint = attrs[i].binding; + attrinf.normalize = VFmtToNormalize[attrs[i].format]; + attrinf.integerType = VFmtToIntegerType[attrs[i].format]; } } } @@ -246,7 +250,10 @@ void GLVertexBuffer::Bind(int *offsets) { glEnableVertexAttribArray(i); size_t ofs = offsets == nullptr ? attrinf.offset : attrinf.offset + mStride * offsets[attrinf.bindingpoint]; - glVertexAttribPointer(i, attrinf.size, attrinf.format, attrinf.format != GL_FLOAT, (GLsizei)mStride, (void*)(intptr_t)ofs); + if (!attrinf.integerType) + glVertexAttribPointer(i, attrinf.size, attrinf.format, attrinf.normalize, (GLsizei)mStride, (void*)(intptr_t)ofs); + else + glVertexAttribIPointer(i, attrinf.size, attrinf.format, (GLsizei)mStride, (void*)(intptr_t)ofs); } i++; } diff --git a/src/common/rendering/gl/gl_buffers.h b/src/common/rendering/gl/gl_buffers.h index d0c2b62c81..9bca819a12 100644 --- a/src/common/rendering/gl/gl_buffers.h +++ b/src/common/rendering/gl/gl_buffers.h @@ -46,6 +46,8 @@ class GLVertexBuffer : public IVertexBuffer, public GLBuffer { int bindingpoint; int format; + bool normalize; + bool integerType; int size; int offset; }; diff --git a/src/common/rendering/gl/gl_framebuffer.cpp b/src/common/rendering/gl/gl_framebuffer.cpp index 3913a21e13..6ea31e5bda 100644 --- a/src/common/rendering/gl/gl_framebuffer.cpp +++ b/src/common/rendering/gl/gl_framebuffer.cpp @@ -49,6 +49,7 @@ #include "hw_skydome.h" #include "hw_viewpointbuffer.h" #include "hw_lightbuffer.h" +#include "hw_bonebuffer.h" #include "gl_shaderprogram.h" #include "gl_debug.h" #include "r_videoscale.h" @@ -104,6 +105,7 @@ OpenGLFrameBuffer::~OpenGLFrameBuffer() if (mSkyData != nullptr) delete mSkyData; if (mViewpoints != nullptr) delete mViewpoints; if (mLights != nullptr) delete mLights; + if (mBones != nullptr) delete mBones; mShadowMap.Reset(); if (GLRenderer) @@ -171,9 +173,11 @@ void OpenGLFrameBuffer::InitializeState() mSkyData = new FSkyVertexBuffer; mViewpoints = new HWViewpointBuffer(screen->mPipelineNbr); mLights = new FLightBuffer(screen->mPipelineNbr); + mBones = new BoneBuffer(screen->mPipelineNbr); GLRenderer = new FGLRenderer(this); GLRenderer->Initialize(GetWidth(), GetHeight()); static_cast(mLights->GetBuffer())->BindBase(); + static_cast(mBones->GetBuffer())->BindBase(); mDebug = std::make_unique(); mDebug->Update(); diff --git a/src/common/rendering/gl/gl_renderstate.cpp b/src/common/rendering/gl/gl_renderstate.cpp index 688e469046..5d7fd774bd 100644 --- a/src/common/rendering/gl/gl_renderstate.cpp +++ b/src/common/rendering/gl/gl_renderstate.cpp @@ -33,6 +33,7 @@ #include "gl_shader.h" #include "gl_renderer.h" #include "hw_lightbuffer.h" +#include "hw_bonebuffer.h" #include "gl_renderbuffers.h" #include "gl_hwtexture.h" #include "gl_buffers.h" @@ -133,6 +134,7 @@ bool FGLRenderState::ApplyShader() activeShader->muTimer.Set((double)(screen->FrameTime - firstFrame) * (double)mShaderTimer / 1000.); activeShader->muAlphaThreshold.Set(mAlphaThreshold); activeShader->muLightIndex.Set(-1); + activeShader->muBoneIndexBase.Set(-1); activeShader->muClipSplit.Set(mClipSplit); activeShader->muSpecularMaterial.Set(mGlossiness, mSpecularLevel); activeShader->muAddColor.Set(mStreamData.uAddColor); @@ -210,6 +212,21 @@ bool FGLRenderState::ApplyShader() } activeShader->muLightIndex.Set(index); + + index = mBoneIndexBase; + if (!screen->mBones->GetBufferType() && index >= 0) // Uniform buffer fallback support + { + size_t start, size; + index = screen->mBones->GetBinding(index, &start, &size); + + if (start != mLastMappedBoneIndexBase || screen->mPipelineNbr > 1) // If multiple buffers always bind + { + mLastMappedBoneIndexBase = start; + static_cast(screen->mBones->GetBuffer())->BindRange(nullptr, start, size); + } + } + activeShader->muBoneIndexBase.Set(index); + return true; } diff --git a/src/common/rendering/gl/gl_renderstate.h b/src/common/rendering/gl/gl_renderstate.h index 9cfe3d7a26..8b9bba25c2 100644 --- a/src/common/rendering/gl/gl_renderstate.h +++ b/src/common/rendering/gl/gl_renderstate.h @@ -66,6 +66,7 @@ class FGLRenderState final : public FRenderState int lastTranslation = 0; int maxBoundMaterial = -1; size_t mLastMappedLightIndex = SIZE_MAX; + size_t mLastMappedBoneIndexBase = SIZE_MAX; IVertexBuffer *mCurrentVertexBuffer; int mCurrentVertexOffsets[2]; // one per binding point diff --git a/src/common/rendering/gl/gl_shader.cpp b/src/common/rendering/gl/gl_shader.cpp index 80e6a0e8a8..c4e20f7ba5 100644 --- a/src/common/rendering/gl/gl_shader.cpp +++ b/src/common/rendering/gl/gl_shader.cpp @@ -38,6 +38,7 @@ #include "shaderuniforms.h" #include "hw_viewpointuniforms.h" #include "hw_lightbuffer.h" +#include "hw_bonebuffer.h" #include "i_specialpaths.h" #include "printf.h" #include "version.h" @@ -276,6 +277,9 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char * // dynamic lights uniform int uLightIndex; + // bone animation + uniform int uBoneIndexBase; + // Blinn glossiness and specular level uniform vec2 uSpecularMaterial; @@ -297,6 +301,19 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char * }; #endif + // bone matrix buffers + #ifdef SHADER_STORAGE_BONES + layout(std430, binding = 7) buffer BoneBufferSSO + { + mat4 bones[]; + }; + #elif defined NUM_UBO_BONES + uniform BoneBufferUBO + { + mat4 bones[NUM_UBO_BONES]; + }; + #endif + // textures uniform sampler2D tex; uniform sampler2D ShadowMap; @@ -369,20 +386,21 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char * FString vp_comb; assert(screen->mLights != NULL); + assert(screen->mBones != NULL); bool lightbuffertype = screen->mLights->GetBufferType(); unsigned int lightbuffersize = screen->mLights->GetBlockSize(); if (!lightbuffertype) { - vp_comb.Format("#version 330 core\n#define NUM_UBO_LIGHTS %d\n", lightbuffersize); + vp_comb.Format("#version 330 core\n#define NUM_UBO_LIGHTS %d\n#define NUM_UBO_BONES %d\n", lightbuffersize, screen->mBones->GetBlockSize()); } else { // This differentiation is for Intel which do not seem to expose the full extension, even if marked as required. if (gl.glslversion < 4.3f) - vp_comb = "#version 400 core\n#extension GL_ARB_shader_storage_buffer_object : require\n#define SHADER_STORAGE_LIGHTS\n"; + vp_comb = "#version 400 core\n#extension GL_ARB_shader_storage_buffer_object : require\n#define SHADER_STORAGE_LIGHTS\n#define SHADER_STORAGE_BONES\n"; else - vp_comb = "#version 430 core\n#define SHADER_STORAGE_LIGHTS\n"; + vp_comb = "#version 430 core\n#define SHADER_STORAGE_LIGHTS\n#define SHADER_STORAGE_BONES\n"; } if ((gl.flags & RFL_SHADER_STORAGE_BUFFER) && screen->allowSSBO()) @@ -576,6 +594,7 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char * muLightParms.Init(hShader, "uLightAttr"); muClipSplit.Init(hShader, "uClipSplit"); muLightIndex.Init(hShader, "uLightIndex"); + muBoneIndexBase.Init(hShader, "uBoneIndexBase"); muFogColor.Init(hShader, "uFogColor"); muDynLightColor.Init(hShader, "uDynLightColor"); muObjectColor.Init(hShader, "uObjectColor"); @@ -610,6 +629,9 @@ bool FShader::Load(const char * name, const char * vert_prog_lump, const char * { int tempindex = glGetUniformBlockIndex(hShader, "LightBufferUBO"); if (tempindex != -1) glUniformBlockBinding(hShader, tempindex, LIGHTBUF_BINDINGPOINT); + + tempindex = glGetUniformBlockIndex(hShader, "BoneBufferUBO"); + if (tempindex != -1) glUniformBlockBinding(hShader, tempindex, BONEBUF_BINDINGPOINT); } int tempindex = glGetUniformBlockIndex(hShader, "ViewpointUBO"); if (tempindex != -1) glUniformBlockBinding(hShader, tempindex, VIEWPOINT_BINDINGPOINT); diff --git a/src/common/rendering/gl/gl_shader.h b/src/common/rendering/gl/gl_shader.h index 4f7debda38..6eb2b22263 100644 --- a/src/common/rendering/gl/gl_shader.h +++ b/src/common/rendering/gl/gl_shader.h @@ -241,6 +241,7 @@ class FShader FBufferedUniform4f muLightParms; FBufferedUniform2f muClipSplit; FBufferedUniform1i muLightIndex; + FBufferedUniform1i muBoneIndexBase; FBufferedUniformPE muFogColor; FBufferedUniform4f muDynLightColor; FBufferedUniformPE muObjectColor; diff --git a/src/common/rendering/gles/gles_framebuffer.cpp b/src/common/rendering/gles/gles_framebuffer.cpp index db2a69902b..0d0a459c02 100644 --- a/src/common/rendering/gles/gles_framebuffer.cpp +++ b/src/common/rendering/gles/gles_framebuffer.cpp @@ -48,6 +48,7 @@ #include "hw_skydome.h" #include "hw_viewpointbuffer.h" #include "hw_lightbuffer.h" +#include "hw_bonebuffer.h" #include "gles_shaderprogram.h" #include "r_videoscale.h" #include "gles_buffers.h" @@ -101,6 +102,7 @@ OpenGLFrameBuffer::~OpenGLFrameBuffer() if (mSkyData != nullptr) delete mSkyData; if (mViewpoints != nullptr) delete mViewpoints; if (mLights != nullptr) delete mLights; + if (mBones != nullptr) delete mBones; mShadowMap.Reset(); if (GLRenderer) @@ -154,9 +156,11 @@ void OpenGLFrameBuffer::InitializeState() mSkyData = new FSkyVertexBuffer; mViewpoints = new HWViewpointBuffer(mPipelineNbr); mLights = new FLightBuffer(mPipelineNbr); + mBones = new BoneBuffer(mPipelineNbr); GLRenderer = new FGLRenderer(this); GLRenderer->Initialize(GetWidth(), GetHeight()); static_cast(mLights->GetBuffer())->BindBase(); + static_cast(mBones->GetBuffer())->BindBase(); } //========================================================================== diff --git a/src/common/rendering/gles/gles_renderer.h b/src/common/rendering/gles/gles_renderer.h index 3322e06bf9..b1e7d23467 100644 --- a/src/common/rendering/gles/gles_renderer.h +++ b/src/common/rendering/gles/gles_renderer.h @@ -17,6 +17,7 @@ class FFlatVertexBuffer; class FSkyVertexBuffer; class HWPortal; class FLightBuffer; +class BoneBuffer; class DPSprite; class FGLRenderBuffers; class FGL2DDrawer; diff --git a/src/common/rendering/hwrenderer/data/buffers.h b/src/common/rendering/hwrenderer/data/buffers.h index 0a914bb9ce..5e2c027284 100644 --- a/src/common/rendering/hwrenderer/data/buffers.h +++ b/src/common/rendering/hwrenderer/data/buffers.h @@ -25,6 +25,8 @@ enum VATTR_NORMAL, VATTR_NORMAL2, VATTR_LIGHTMAP, + VATTR_BONEWEIGHT, + VATTR_BONESELECTOR, VATTR_MAX }; @@ -36,6 +38,7 @@ enum EVertexAttributeFormat VFmt_Float, VFmt_Byte4, VFmt_Packed_A2R10G10B10, + VFmt_Byte4_UInt }; struct FVertexBufferAttribute diff --git a/src/common/rendering/hwrenderer/data/hw_bonebuffer.cpp b/src/common/rendering/hwrenderer/data/hw_bonebuffer.cpp new file mode 100644 index 0000000000..9d465d417a --- /dev/null +++ b/src/common/rendering/hwrenderer/data/hw_bonebuffer.cpp @@ -0,0 +1,112 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2014-2016 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// + +#include "hw_bonebuffer.h" +#include "hw_dynlightdata.h" +#include "shaderuniforms.h" + +static const int BONE_SIZE = (16*sizeof(float)); + +BoneBuffer::BoneBuffer(int pipelineNbr) : mPipelineNbr(pipelineNbr) +{ + int maxNumberOfBones = 80000; + + mBufferSize = maxNumberOfBones; + mByteSize = mBufferSize * BONE_SIZE; + + // Hack alert: On Intel's GL driver SSBO's perform quite worse than UBOs. + // We only want to disable using SSBOs for bones but not disable the feature entirely. + // Note that using an uniform buffer here will limit the number of bones per model so it isn't done for NVidia and AMD. + if (screen->IsVulkan() || screen->IsPoly() || ((screen->hwcaps & RFL_SHADER_STORAGE_BUFFER) && screen->allowSSBO() && !strstr(screen->vendorstring, "Intel"))) + { + mBufferType = true; + mBlockAlign = 0; + mBlockSize = mBufferSize; + mMaxUploadSize = mBlockSize; + } + else + { + mBufferType = false; + mBlockSize = screen->maxuniformblock / BONE_SIZE; + mBlockAlign = screen->uniformblockalignment / BONE_SIZE; + mMaxUploadSize = (mBlockSize - mBlockAlign); + } + + for (int n = 0; n < mPipelineNbr; n++) + { + mBufferPipeline[n] = screen->CreateDataBuffer(BONEBUF_BINDINGPOINT, mBufferType, false); + mBufferPipeline[n]->SetData(mByteSize, nullptr, BufferUsageType::Persistent); + } + + Clear(); +} + +BoneBuffer::~BoneBuffer() +{ + delete mBuffer; +} + +void BoneBuffer::Clear() +{ + mIndex = 0; + + mPipelinePos++; + mPipelinePos %= mPipelineNbr; + + mBuffer = mBufferPipeline[mPipelinePos]; +} + +int BoneBuffer::UploadBones(const TArray& bones) +{ + int totalsize = bones.Size(); + if (totalsize > (int)mMaxUploadSize) + { + totalsize = mMaxUploadSize; + } + + uint8_t *mBufferPointer = (uint8_t*)mBuffer->Memory(); + assert(mBufferPointer != nullptr); + if (mBufferPointer == nullptr) return -1; + if (totalsize <= 0) return -1; // there are no bones + + unsigned int thisindex = mIndex.fetch_add(totalsize); + + if (thisindex + totalsize <= mBufferSize) + { + memcpy(mBufferPointer + thisindex * BONE_SIZE, bones.Data(), totalsize * BONE_SIZE); + return thisindex; + } + else + { + return -1; // Buffer is full. Since it is being used live at the point of the upload we cannot do much here but to abort. + } +} + +int BoneBuffer::GetBinding(unsigned int index, size_t* pOffset, size_t* pSize) +{ + // this function will only get called if a uniform buffer is used. For a shader storage buffer we only need to bind the buffer once at the start. + unsigned int offset = (index / mBlockAlign) * mBlockAlign; + + *pOffset = offset * BONE_SIZE; + *pSize = mBlockSize * BONE_SIZE; + return (index - offset); +} diff --git a/src/common/rendering/hwrenderer/data/hw_bonebuffer.h b/src/common/rendering/hwrenderer/data/hw_bonebuffer.h new file mode 100644 index 0000000000..c4ad550c94 --- /dev/null +++ b/src/common/rendering/hwrenderer/data/hw_bonebuffer.h @@ -0,0 +1,43 @@ +#pragma once + +#include "tarray.h" +#include "hwrenderer/data/buffers.h" +#include "common/utility/matrix.h" +#include +#include + +class FRenderState; + +class BoneBuffer +{ + IDataBuffer *mBuffer; + IDataBuffer* mBufferPipeline[HW_MAX_PIPELINE_BUFFERS]; + int mPipelineNbr; + int mPipelinePos = 0; + + bool mBufferType; + std::atomic mIndex; + unsigned int mBlockAlign; + unsigned int mBlockSize; + unsigned int mBufferSize; + unsigned int mByteSize; + unsigned int mMaxUploadSize; + +public: + BoneBuffer(int pipelineNbr = 1); + ~BoneBuffer(); + + void Clear(); + int UploadBones(const TArray &bones); + void Map() { mBuffer->Map(); } + void Unmap() { mBuffer->Unmap(); } + unsigned int GetBlockSize() const { return mBlockSize; } + bool GetBufferType() const { return mBufferType; } + int GetBinding(unsigned int index, size_t* pOffset, size_t* pSize); + + // OpenGL needs the buffer to mess around with the binding. + IDataBuffer* GetBuffer() const + { + return mBuffer; + } +}; diff --git a/src/common/rendering/hwrenderer/data/hw_modelvertexbuffer.cpp b/src/common/rendering/hwrenderer/data/hw_modelvertexbuffer.cpp index da5b3be3c7..9dff499f94 100644 --- a/src/common/rendering/hwrenderer/data/hw_modelvertexbuffer.cpp +++ b/src/common/rendering/hwrenderer/data/hw_modelvertexbuffer.cpp @@ -47,10 +47,12 @@ FModelVertexBuffer::FModelVertexBuffer(bool needindex, bool singleframe) { 0, VATTR_TEXCOORD, VFmt_Float2, (int)myoffsetof(FModelVertex, u) }, { 0, VATTR_NORMAL, VFmt_Packed_A2R10G10B10, (int)myoffsetof(FModelVertex, packedNormal) }, { 0, VATTR_LIGHTMAP, VFmt_Float3, (int)myoffsetof(FModelVertex, lu) }, + { 0, VATTR_BONESELECTOR, VFmt_Byte4_UInt, (int)myoffsetof(FModelVertex, boneselector[0])}, + { 0, VATTR_BONEWEIGHT, VFmt_Byte4, (int)myoffsetof(FModelVertex, boneweight[0]) }, { 1, VATTR_VERTEX2, VFmt_Float3, (int)myoffsetof(FModelVertex, x) }, { 1, VATTR_NORMAL2, VFmt_Packed_A2R10G10B10, (int)myoffsetof(FModelVertex, packedNormal) } }; - mVertexBuffer->SetFormat(2, 6, sizeof(FModelVertex), format); + mVertexBuffer->SetFormat(2, 8, sizeof(FModelVertex), format); } //=========================================================================== diff --git a/src/common/rendering/hwrenderer/data/hw_renderstate.h b/src/common/rendering/hwrenderer/data/hw_renderstate.h index bd8fd4d9e4..2ff77c53d4 100644 --- a/src/common/rendering/hwrenderer/data/hw_renderstate.h +++ b/src/common/rendering/hwrenderer/data/hw_renderstate.h @@ -217,6 +217,7 @@ protected: uint8_t mBrightmapEnabled : 1; int mLightIndex; + int mBoneIndexBase; int mSpecialEffect; int mTextureMode; int mTextureClamp; @@ -278,6 +279,7 @@ public: mLightParms[3] = -1.f; mSpecialEffect = EFF_NONE; mLightIndex = -1; + mBoneIndexBase = -1; mStreamData.uInterpolationFactor = 0; mRenderStyle = DefaultRenderStyle(); mMaterial.Reset(); @@ -568,6 +570,11 @@ public: mLightIndex = index; } + void SetBoneIndexBase(int index) + { + mBoneIndexBase = index; + } + void SetRenderStyle(FRenderStyle rs) { mRenderStyle = rs; diff --git a/src/common/rendering/hwrenderer/data/shaderuniforms.h b/src/common/rendering/hwrenderer/data/shaderuniforms.h index 92995fcaa7..4b11a3755a 100644 --- a/src/common/rendering/hwrenderer/data/shaderuniforms.h +++ b/src/common/rendering/hwrenderer/data/shaderuniforms.h @@ -11,7 +11,8 @@ enum VIEWPOINT_BINDINGPOINT = 3, LIGHTNODES_BINDINGPOINT = 4, LIGHTLINES_BINDINGPOINT = 5, - LIGHTLIST_BINDINGPOINT = 6 + LIGHTLIST_BINDINGPOINT = 6, + BONEBUF_BINDINGPOINT = 7 }; enum class UniformType diff --git a/src/common/rendering/i_modelvertexbuffer.h b/src/common/rendering/i_modelvertexbuffer.h index bd3df37671..f68d4e1179 100644 --- a/src/common/rendering/i_modelvertexbuffer.h +++ b/src/common/rendering/i_modelvertexbuffer.h @@ -9,6 +9,8 @@ struct FModelVertex unsigned packedNormal; // normal vector as GL_INT_2_10_10_10_REV. float lu, lv; // lightmap texture coordinates float lindex; // lightmap texture index + uint8_t boneselector[4]; + uint8_t boneweight[4]; void Set(float xx, float yy, float zz, float uu, float vv) { @@ -30,6 +32,22 @@ struct FModelVertex int inw = 0; packedNormal = (inw << 30) | ((inz & 1023) << 20) | ((iny & 1023) << 10) | (inx & 1023); } + + void SetBoneSelector(int x, int y, int z, int w) + { + boneselector[0] = x; + boneselector[1] = y; + boneselector[2] = z; + boneselector[3] = w; + } + + void SetBoneWeight(int x, int y, int z, int w) + { + boneweight[0] = x; + boneweight[1] = y; + boneweight[2] = z; + boneweight[3] = w; + } }; #define VMO ((FModelVertex*)nullptr) diff --git a/src/common/rendering/v_video.h b/src/common/rendering/v_video.h index aff93435e9..3be892efe7 100644 --- a/src/common/rendering/v_video.h +++ b/src/common/rendering/v_video.h @@ -59,6 +59,7 @@ struct HWDrawInfo; class FMaterial; class FGameTexture; class FRenderState; +class BoneBuffer; enum EHWCaps { @@ -143,6 +144,7 @@ public: FFlatVertexBuffer *mVertexData = nullptr; // Global vertex data HWViewpointBuffer *mViewpoints = nullptr; // Viewpoint render data. FLightBuffer *mLights = nullptr; // Dynamic lights + BoneBuffer* mBones = nullptr; // Model bones IShadowMap mShadowMap; IntRect mScreenViewport; diff --git a/src/common/rendering/vulkan/renderer/vk_descriptorset.cpp b/src/common/rendering/vulkan/renderer/vk_descriptorset.cpp index 0f74124a0f..ef6646580a 100644 --- a/src/common/rendering/vulkan/renderer/vk_descriptorset.cpp +++ b/src/common/rendering/vulkan/renderer/vk_descriptorset.cpp @@ -85,6 +85,7 @@ void VkDescriptorSetManager::UpdateHWBufferSet() .AddBuffer(HWBufferSet.get(), 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, fb->GetBufferManager()->MatrixBuffer->UniformBuffer->mBuffer.get(), 0, sizeof(MatricesUBO)) .AddBuffer(HWBufferSet.get(), 2, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, fb->GetBufferManager()->StreamBuffer->UniformBuffer->mBuffer.get(), 0, sizeof(StreamUBO)) .AddBuffer(HWBufferSet.get(), 3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, fb->GetBufferManager()->LightBufferSSO->mBuffer.get()) + .AddBuffer(HWBufferSet.get(), 4, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, fb->GetBufferManager()->BoneBufferSSO->mBuffer.get()) .Execute(fb->device); } @@ -252,6 +253,7 @@ void VkDescriptorSetManager::CreateHWBufferSetLayout() .AddBinding(1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT) .AddBinding(2, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT) .AddBinding(3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT) + .AddBinding(4, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT) .DebugName("VkDescriptorSetManager.HWBufferSetLayout") .Create(fb->device); } @@ -271,7 +273,7 @@ void VkDescriptorSetManager::CreateHWBufferPool() { HWBufferDescriptorPool = DescriptorPoolBuilder() .AddPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 3 * maxSets) - .AddPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1 * maxSets) + .AddPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2 * maxSets) .MaxSets(maxSets) .DebugName("VkDescriptorSetManager.HWBufferDescriptorPool") .Create(fb->device); diff --git a/src/common/rendering/vulkan/renderer/vk_renderpass.cpp b/src/common/rendering/vulkan/renderer/vk_renderpass.cpp index 083745a91c..4a7531b746 100644 --- a/src/common/rendering/vulkan/renderer/vk_renderpass.cpp +++ b/src/common/rendering/vulkan/renderer/vk_renderpass.cpp @@ -230,10 +230,11 @@ std::unique_ptr VkRenderPassSetup::CreatePipeline(const VkPipeli VK_FORMAT_R32G32_SFLOAT, VK_FORMAT_R32_SFLOAT, VK_FORMAT_R8G8B8A8_UNORM, - VK_FORMAT_A2B10G10R10_SNORM_PACK32 + VK_FORMAT_A2B10G10R10_SNORM_PACK32, + VK_FORMAT_R8G8B8A8_UINT }; - bool inputLocations[7] = { false, false, false, false, false, false, false }; + bool inputLocations[VATTR_MAX] = {}; for (size_t i = 0; i < vfmt.Attrs.size(); i++) { @@ -243,10 +244,10 @@ std::unique_ptr VkRenderPassSetup::CreatePipeline(const VkPipeli } // Vulkan requires an attribute binding for each location specified in the shader - for (int i = 0; i < 7; i++) + for (int i = 0; i < VATTR_MAX; i++) { if (!inputLocations[i]) - builder.AddVertexAttribute(i, 0, VK_FORMAT_R32G32B32_SFLOAT, 0); + builder.AddVertexAttribute(i, 0, i != 8 ? VK_FORMAT_R32G32B32_SFLOAT : VK_FORMAT_R8G8B8A8_UINT, 0); } builder.AddDynamicState(VK_DYNAMIC_STATE_VIEWPORT); diff --git a/src/common/rendering/vulkan/renderer/vk_renderstate.cpp b/src/common/rendering/vulkan/renderer/vk_renderstate.cpp index d2a68d9519..cb26679202 100644 --- a/src/common/rendering/vulkan/renderer/vk_renderstate.cpp +++ b/src/common/rendering/vulkan/renderer/vk_renderstate.cpp @@ -391,6 +391,7 @@ void VkRenderState::ApplyPushConstants() } mPushConstants.uLightIndex = mLightIndex; + mPushConstants.uBoneIndexBase = mBoneIndexBase; mPushConstants.uDataIndex = mStreamBufferWriter.DataIndex(); auto passManager = fb->GetRenderPassManager(); diff --git a/src/common/rendering/vulkan/shaders/vk_shader.cpp b/src/common/rendering/vulkan/shaders/vk_shader.cpp index c223cafa0e..44c419dda8 100644 --- a/src/common/rendering/vulkan/shaders/vk_shader.cpp +++ b/src/common/rendering/vulkan/shaders/vk_shader.cpp @@ -229,6 +229,12 @@ static const char *shaderBindings = R"( vec4 lights[]; }; + // bone matrix buffers + layout(set = 1, binding = 4, std430) buffer BoneBufferSSO + { + mat4 bones[]; + }; + // textures layout(set = 2, binding = 0) uniform sampler2D tex; layout(set = 2, binding = 1) uniform sampler2D texture2; @@ -262,8 +268,11 @@ static const char *shaderBindings = R"( // Blinn glossiness and specular level vec2 uSpecularMaterial; + // bone animation + int uBoneIndexBase; + int uDataIndex; - int padding1, padding2, padding3; + int padding2, padding3; }; // material types diff --git a/src/common/rendering/vulkan/shaders/vk_shader.h b/src/common/rendering/vulkan/shaders/vk_shader.h index 11682dae99..ba13726a6c 100644 --- a/src/common/rendering/vulkan/shaders/vk_shader.h +++ b/src/common/rendering/vulkan/shaders/vk_shader.h @@ -50,6 +50,9 @@ struct PushConstants // Blinn glossiness and specular level FVector2 uSpecularMaterial; + // bone animation + int uBoneIndexBase; + int uDataIndex; int padding1, padding2, padding3; }; diff --git a/src/common/rendering/vulkan/system/vk_buffer.cpp b/src/common/rendering/vulkan/system/vk_buffer.cpp index db7bc4ea82..2a45cc73c7 100644 --- a/src/common/rendering/vulkan/system/vk_buffer.cpp +++ b/src/common/rendering/vulkan/system/vk_buffer.cpp @@ -58,7 +58,7 @@ void VkBufferManager::RemoveBuffer(VkHardwareBuffer* buffer) buffer->fb = nullptr; Buffers.erase(buffer->it); - for (VkHardwareDataBuffer** knownbuf : { &ViewpointUBO, &LightBufferSSO, &LightNodes, &LightLines, &LightList }) + for (VkHardwareDataBuffer** knownbuf : { &ViewpointUBO, &LightBufferSSO, &LightNodes, &LightLines, &LightList, &BoneBufferSSO }) { if (buffer == *knownbuf) *knownbuf = nullptr; } @@ -85,6 +85,7 @@ IDataBuffer* VkBufferManager::CreateDataBuffer(int bindingpoint, bool ssbo, bool case LIGHTNODES_BINDINGPOINT: LightNodes = buffer; break; case LIGHTLINES_BINDINGPOINT: LightLines = buffer; break; case LIGHTLIST_BINDINGPOINT: LightList = buffer; break; + case BONEBUF_BINDINGPOINT: BoneBufferSSO = buffer; break; case POSTPROCESS_BINDINGPOINT: break; default: break; } diff --git a/src/common/rendering/vulkan/system/vk_buffer.h b/src/common/rendering/vulkan/system/vk_buffer.h index 39cc1ce15e..c54d60aac9 100644 --- a/src/common/rendering/vulkan/system/vk_buffer.h +++ b/src/common/rendering/vulkan/system/vk_buffer.h @@ -33,6 +33,7 @@ public: VkHardwareDataBuffer* LightNodes = nullptr; VkHardwareDataBuffer* LightLines = nullptr; VkHardwareDataBuffer* LightList = nullptr; + VkHardwareDataBuffer* BoneBufferSSO = nullptr; std::unique_ptr MatrixBuffer; std::unique_ptr StreamBuffer; diff --git a/src/common/rendering/vulkan/system/vk_framebuffer.cpp b/src/common/rendering/vulkan/system/vk_framebuffer.cpp index b93e597849..b5925ad534 100644 --- a/src/common/rendering/vulkan/system/vk_framebuffer.cpp +++ b/src/common/rendering/vulkan/system/vk_framebuffer.cpp @@ -41,6 +41,7 @@ #include "flatvertices.h" #include "hwrenderer/data/shaderuniforms.h" #include "hw_lightbuffer.h" +#include "hw_bonebuffer.h" #include "vk_framebuffer.h" #include "vk_hwbuffer.h" @@ -98,6 +99,7 @@ VulkanFrameBuffer::~VulkanFrameBuffer() delete mSkyData; delete mViewpoints; delete mLights; + delete mBones; mShadowMap.Reset(); if (mDescriptorSetManager) @@ -155,6 +157,7 @@ void VulkanFrameBuffer::InitializeState() mSkyData = new FSkyVertexBuffer; mViewpoints = new HWViewpointBuffer; mLights = new FLightBuffer(); + mBones = new BoneBuffer(); mShaderManager.reset(new VkShaderManager(this)); mDescriptorSetManager->Init(); diff --git a/src/common/utility/matrix.cpp b/src/common/utility/matrix.cpp index 476d09b604..3d84d48e70 100644 --- a/src/common/utility/matrix.cpp +++ b/src/common/utility/matrix.cpp @@ -98,6 +98,21 @@ VSMatrix::multMatrix(const float *aMatrix) } #endif +void VSMatrix::multQuaternion(const TVector4& q) +{ + FLOATTYPE m[16] = { FLOATTYPE(0.0) }; + m[0 * 4 + 0] = FLOATTYPE(1.0) - FLOATTYPE(2.0) * q.Y * q.Y - FLOATTYPE(2.0) * q.Z * q.Z; + m[1 * 4 + 0] = FLOATTYPE(2.0) * q.X * q.Y - FLOATTYPE(2.0) * q.W * q.Z; + m[2 * 4 + 0] = FLOATTYPE(2.0) * q.X * q.Z + FLOATTYPE(2.0) * q.W * q.Y; + m[0 * 4 + 1] = FLOATTYPE(2.0) * q.X * q.Y + FLOATTYPE(2.0) * q.W * q.Z; + m[1 * 4 + 1] = FLOATTYPE(1.0) - FLOATTYPE(2.0) * q.X * q.X - FLOATTYPE(2.0) * q.Z * q.Z; + m[2 * 4 + 1] = FLOATTYPE(2.0) * q.Y * q.Z - FLOATTYPE(2.0) * q.W * q.X; + m[0 * 4 + 2] = FLOATTYPE(2.0) * q.X * q.Z - FLOATTYPE(2.0) * q.W * q.Y; + m[1 * 4 + 2] = FLOATTYPE(2.0) * q.Y * q.Z + FLOATTYPE(2.0) * q.W * q.X; + m[2 * 4 + 2] = FLOATTYPE(1.0) - FLOATTYPE(2.0) * q.X * q.X - FLOATTYPE(2.0) * q.Y * q.Y; + m[3 * 4 + 3] = FLOATTYPE(1.0); + multMatrix(m); +} // gl LoadMatrix implementation @@ -129,6 +144,29 @@ VSMatrix::translate(FLOATTYPE x, FLOATTYPE y, FLOATTYPE z) mMatrix[14] = mMatrix[2] * x + mMatrix[6] * y + mMatrix[10] * z + mMatrix[14]; } +void VSMatrix::transpose() +{ + FLOATTYPE original[16]; + for (int cnt = 0; cnt < 16; cnt++) + original[cnt] = mMatrix[cnt]; + + mMatrix[0] = original[0]; + mMatrix[1] = original[4]; + mMatrix[2] = original[8]; + mMatrix[3] = original[12]; + mMatrix[4] = original[1]; + mMatrix[5] = original[5]; + mMatrix[6] = original[9]; + mMatrix[7] = original[13]; + mMatrix[8] = original[2]; + mMatrix[9] = original[6]; + mMatrix[10] = original[10]; + mMatrix[11] = original[14]; + mMatrix[12] = original[3]; + mMatrix[13] = original[7]; + mMatrix[14] = original[11]; + mMatrix[15] = original[15]; +} // gl Scale implementation void diff --git a/src/common/utility/matrix.h b/src/common/utility/matrix.h index c1c7c873c9..265981e1bc 100644 --- a/src/common/utility/matrix.h +++ b/src/common/utility/matrix.h @@ -53,6 +53,7 @@ class VSMatrix { { multMatrix(aMatrix.mMatrix); } + void multQuaternion(const TVector4& q); void loadMatrix(const FLOATTYPE *aMatrix); #ifdef USE_DOUBLE void loadMatrix(const float *aMatrix); diff --git a/src/r_data/models.cpp b/src/r_data/models.cpp index d7c6e10b92..4b6c70d536 100644 --- a/src/r_data/models.cpp +++ b/src/r_data/models.cpp @@ -343,10 +343,18 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr auto& ssids = surfaceskinids.Size() > 0 ? surfaceskinids : smf->surfaceskinIDs; auto ssidp = (unsigned)(i * MD3_MAX_SURFACES) < ssids.Size() ? &ssids[i * MD3_MAX_SURFACES] : nullptr; + const TArray* animationData = {}; + + if (smf->animationIDs[i] >= 0) + { + FModel* animation = Models[smf->animationIDs[i]]; + animationData = animation->AttachAnimationData(); + } + if (smfNext && modelframe != modelframenext) - mdl->RenderFrame(renderer, tex, modelframe, modelframenext, inter, translation, ssidp); + mdl->RenderFrame(renderer, tex, modelframe, modelframenext, inter, translation, ssidp, *animationData); else - mdl->RenderFrame(renderer, tex, modelframe, modelframe, 0.f, translation, ssidp); + mdl->RenderFrame(renderer, tex, modelframe, modelframe, 0.f, translation, ssidp, *animationData); } } } @@ -491,6 +499,7 @@ static void ParseModelDefLump(int Lump) initArray(smf.modelIDs, smf.modelsAmount, -1); initArray(smf.skinIDs, smf.modelsAmount, FNullTextureID()); initArray(smf.surfaceskinIDs, smf.modelsAmount * MD3_MAX_SURFACES, FNullTextureID()); + initArray(smf.animationIDs, smf.modelsAmount, -1); initArray(smf.modelframes, smf.modelsAmount, 0); sc.RestorePos(scPos); @@ -525,6 +534,26 @@ static void ParseModelDefLump(int Lump) Printf("%s: model not found in %s\n", sc.String, path.GetChars()); } } + else if (sc.Compare("animation")) + { + sc.MustGetNumber(); + index = sc.Number; + if (index < 0) + { + sc.ScriptError("Animation index must be 0 or greater in %s", type->TypeName.GetChars()); + } + else if (index >= smf.modelsAmount) + { + sc.ScriptError("Too many models in %s", type->TypeName.GetChars()); + } + sc.MustGetString(); + FixPathSeperator(sc.String); + smf.animationIDs[index] = FindModel(path.GetChars(), sc.String); + if (smf.animationIDs[index] == -1) + { + Printf("%s: animation model not found in %s\n", sc.String, path.GetChars()); + } + } else if (sc.Compare("scale")) { sc.MustGetFloat(); diff --git a/src/rendering/hwrenderer/hw_entrypoint.cpp b/src/rendering/hwrenderer/hw_entrypoint.cpp index eb171f51af..da63685830 100644 --- a/src/rendering/hwrenderer/hw_entrypoint.cpp +++ b/src/rendering/hwrenderer/hw_entrypoint.cpp @@ -44,6 +44,7 @@ #include "v_draw.h" #include "hw_lightbuffer.h" +#include "hw_bonebuffer.h" #include "hw_cvars.h" #include "hwrenderer/data/hw_viewpointbuffer.h" #include "hwrenderer/scene/hw_fakeflat.h" @@ -276,6 +277,7 @@ void WriteSavePic(player_t* player, FileWriter* file, int width, int height) screen->mVertexData->Reset(); RenderState.SetVertexBuffer(screen->mVertexData); screen->mLights->Clear(); + screen->mBones->Clear(); screen->mViewpoints->Clear(); // This shouldn't overwrite the global viewpoint even for a short time. @@ -340,6 +342,7 @@ sector_t* RenderView(player_t* player) else r_viewpoint.TicFrac = I_GetTimeFrac(); screen->mLights->Clear(); + screen->mBones->Clear(); screen->mViewpoints->Clear(); // NoInterpolateView should have no bearing on camera textures, but needs to be preserved for the main view below. diff --git a/src/rendering/hwrenderer/hw_models.cpp b/src/rendering/hwrenderer/hw_models.cpp index ff99702f01..ff9a953cfd 100644 --- a/src/rendering/hwrenderer/hw_models.cpp +++ b/src/rendering/hwrenderer/hw_models.cpp @@ -41,6 +41,7 @@ #include "hwrenderer/scene/hw_drawinfo.h" #include "hw_renderstate.h" #include "hwrenderer/scene/hw_portal.h" +#include "hw_bonebuffer.h" #include "hw_models.h" CVAR(Bool, gl_light_models, true, CVAR_ARCHIVE) @@ -71,6 +72,7 @@ void FHWModelRenderer::BeginDrawModel(FRenderStyle style, FSpriteModelFrame *smf void FHWModelRenderer::EndDrawModel(FRenderStyle style, FSpriteModelFrame *smf) { + state.SetBoneIndexBase(-1); state.EnableModelMatrix(false); state.SetDepthFunc(DF_Less); if (!(style == DefaultRenderStyle()) && !(smf->flags & MDL_DONTCULLBACKFACES)) @@ -134,9 +136,10 @@ void FHWModelRenderer::DrawElements(int numIndices, size_t offset) // //=========================================================================== -void FHWModelRenderer::SetupFrame(FModel *model, unsigned int frame1, unsigned int frame2, unsigned int size) +void FHWModelRenderer::SetupFrame(FModel *model, unsigned int frame1, unsigned int frame2, unsigned int size, const TArray& bones) { auto mdbuff = static_cast(model->GetVertexBuffer(GetType())); + state.SetBoneIndexBase(screen->mBones->UploadBones(bones)); state.SetVertexBuffer(mdbuff->vertexBuffer(), frame1, frame2); if (mdbuff->indexBuffer()) state.SetIndexBuffer(mdbuff->indexBuffer()); } diff --git a/src/rendering/hwrenderer/hw_models.h b/src/rendering/hwrenderer/hw_models.h index 28026905e0..0af0e0ceb2 100644 --- a/src/rendering/hwrenderer/hw_models.h +++ b/src/rendering/hwrenderer/hw_models.h @@ -55,7 +55,7 @@ public: void SetMaterial(FGameTexture *skin, bool clampNoFilter, int translation) override; void DrawArrays(int start, int count) override; void DrawElements(int numIndices, size_t offset) override; - void SetupFrame(FModel *model, unsigned int frame1, unsigned int frame2, unsigned int size) override; + void SetupFrame(FModel *model, unsigned int frame1, unsigned int frame2, unsigned int size, const TArray& bones) override; }; diff --git a/src/rendering/hwrenderer/scene/hw_drawinfo.cpp b/src/rendering/hwrenderer/scene/hw_drawinfo.cpp index 52a91a569b..85c872b67a 100644 --- a/src/rendering/hwrenderer/scene/hw_drawinfo.cpp +++ b/src/rendering/hwrenderer/scene/hw_drawinfo.cpp @@ -41,6 +41,7 @@ #include "hw_viewpointbuffer.h" #include "flatvertices.h" #include "hw_lightbuffer.h" +#include "hw_bonebuffer.h" #include "hw_vrmodes.h" #include "hw_clipper.h" #include "v_draw.h" @@ -444,6 +445,7 @@ void HWDrawInfo::CreateScene(bool drawpsprites) // clip the scene and fill the drawlists screen->mVertexData->Map(); screen->mLights->Map(); + screen->mBones->Map(); RenderBSP(Level->HeadNode(), drawpsprites); @@ -456,6 +458,7 @@ void HWDrawInfo::CreateScene(bool drawpsprites) PrepareUnhandledMissingTextures(); DispatchRenderHacks(); screen->mLights->Unmap(); + screen->mBones->Unmap(); screen->mVertexData->Unmap(); ProcessAll.Unclock(); @@ -696,6 +699,7 @@ void HWDrawInfo::DrawCoronas(FRenderState& state) state.SetDepthMask(true); } + //----------------------------------------------------------------------------- // // Draws player sprites and color blend diff --git a/wadsrc/static/shaders/glsl/main.vp b/wadsrc/static/shaders/glsl/main.vp index ef28da1fee..e41152065c 100644 --- a/wadsrc/static/shaders/glsl/main.vp +++ b/wadsrc/static/shaders/glsl/main.vp @@ -12,6 +12,8 @@ layout(location = 3) in vec4 aVertex2; layout(location = 4) in vec4 aNormal; layout(location = 5) in vec4 aNormal2; layout(location = 6) in vec3 aLightmap; +layout(location = 7) in vec4 aBoneWeight; +layout(location = 8) in uvec4 aBoneSelector; layout(location = 2) out vec4 pixelpos; layout(location = 3) out vec3 glowdist; @@ -25,15 +27,25 @@ layout(location = 7) out vec4 ClipDistanceA; layout(location = 8) out vec4 ClipDistanceB; #endif +struct BonesResult +{ + vec3 Normal; + vec4 Position; +}; + +BonesResult ApplyBones(); + void main() { float ClipDistance0, ClipDistance1, ClipDistance2, ClipDistance3, ClipDistance4; vec2 parmTexCoord; vec4 parmPosition; - + + BonesResult bones = ApplyBones(); + parmTexCoord = aTexCoord; - parmPosition = aPosition; + parmPosition = bones.Position; #ifndef SIMPLE vec4 worldcoord = ModelMatrix * mix(parmPosition, aVertex2, uInterpolationFactor); @@ -82,14 +94,7 @@ void main() ClipDistance4 = worldcoord.y - ((uSplitBottomPlane.w + uSplitBottomPlane.x * worldcoord.x + uSplitBottomPlane.y * worldcoord.z) * uSplitBottomPlane.z); } - #ifdef HAS_UNIFORM_VERTEX_DATA - if ((useVertexData & 2) == 0) - vWorldNormal = NormalModelMatrix * vec4(normalize(uVertexNormal.xyz), 1.0); - else - vWorldNormal = NormalModelMatrix * vec4(normalize(mix(aNormal.xyz, aNormal2.xyz, uInterpolationFactor)), 1.0); - #else - vWorldNormal = NormalModelMatrix * vec4(normalize(mix(aNormal.xyz, aNormal2.xyz, uInterpolationFactor)), 1.0); - #endif + vWorldNormal = NormalModelMatrix * vec4(normalize(bones.Normal), 1.0); vEyeNormal = NormalViewMatrix * vec4(normalize(vWorldNormal.xyz), 1.0); #endif @@ -146,3 +151,66 @@ void main() gl_PointSize = 1.0; } + +#if !defined(SIMPLE) +vec3 GetAttrNormal() +{ + #ifdef HAS_UNIFORM_VERTEX_DATA + if ((useVertexData & 2) == 0) + return uVertexNormal.xyz; + else + return mix(aNormal.xyz, aNormal2.xyz, uInterpolationFactor); + #else + return mix(aNormal.xyz, aNormal2.xyz, uInterpolationFactor); + #endif +} + +void AddWeightedBone(uint boneIndex, float weight, inout vec4 position, inout vec3 normal) +{ + if (weight != 0.0) + { + mat4 transform = bones[uBoneIndexBase + boneIndex]; + mat3 rotation = mat3(transform); + position += (transform * aPosition) * weight; + normal += (rotation * aNormal.xyz) * weight; + } +} + +BonesResult ApplyBones() +{ + BonesResult result; + if (uBoneIndexBase >= 0 && aBoneWeight != vec4(0.0)) + { + result.Position = vec4(0.0); + result.Normal = vec3(0.0); + + // We use low precision input for our bone weights. Rescale so the sum still is 1.0 + float totalWeight = aBoneWeight.x + aBoneWeight.y + aBoneWeight.z + aBoneWeight.w; + float weightMultiplier = 1.0 / totalWeight; + vec4 boneWeight = aBoneWeight * weightMultiplier; + + AddWeightedBone(aBoneSelector.x, boneWeight.x, result.Position, result.Normal); + AddWeightedBone(aBoneSelector.y, boneWeight.y, result.Position, result.Normal); + AddWeightedBone(aBoneSelector.z, boneWeight.z, result.Position, result.Normal); + AddWeightedBone(aBoneSelector.w, boneWeight.w, result.Position, result.Normal); + + result.Position.w = 1.0; // For numerical stability + } + else + { + result.Position = aPosition; + result.Normal = GetAttrNormal(); + } + return result; +} + +#else + +BonesResult ApplyBones() +{ + BonesResult result; + result.Position = aPosition; + return result; +} + +#endif