From c6e2f19e10bd840bbdf6ea4d277f498390629a14 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Sat, 13 Jun 2026 10:34:19 -0700 Subject: [PATCH] Enable GPU skinning in the default build. Have the model animation update functions check the current shader to see if they are going to do GPU skinning or not. Lazy load the CPU animation buffers so they don't get allocated if the user is doing GPU skinning. This change allows both GPU and CPU skinning in the same program. --- src/config.h | 2 +- src/raylib.h | 2 + src/rmodels.c | 159 +++++++++++++++++++++++++++++--------------------- 3 files changed, 94 insertions(+), 69 deletions(-) diff --git a/src/config.h b/src/config.h index 97f5ba544..71fa11354 100644 --- a/src/config.h +++ b/src/config.h @@ -322,7 +322,7 @@ #endif #ifndef SUPPORT_GPU_SKINNING // GPU skinning disabled by default, some GPUs do not support more than 8 VBOs - #define SUPPORT_GPU_SKINNING 0 + #define SUPPORT_GPU_SKINNING 1 #endif //------------------------------------------------------------------------------------ diff --git a/src/raylib.h b/src/raylib.h index e49d6b2f7..f774fb898 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1649,6 +1649,8 @@ RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount); // Load model animations from file RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, float frame); // Update model animation pose (vertex buffers and bone matrices) RLAPI void UpdateModelAnimationEx(Model model, ModelAnimation animA, float frameA, ModelAnimation animB, float frameB, float blend); // Update model animation pose, blending two animations +RLAPI void UpdateModelAnimationBones(Model model, ModelAnimation anim, float frame); // Update model animation pose (bones only) +RLAPI void UpdateModelAnimationBonesEx(Model model, ModelAnimation animA, float frameA, ModelAnimation animB, float frameB, float blend); // Update model animation bones to pose, blending two animations RLAPI void UnloadModelAnimations(ModelAnimation *animations, int animCount); // Unload animation array data RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match diff --git a/src/rmodels.c b/src/rmodels.c index 21f69c92e..355e3dfd8 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -182,6 +182,21 @@ static void ProcessMaterialsOBJ(Material *rayMaterials, tinyobj_material_t *mate // Update model vertex data (positions and normals) static void UpdateModelAnimationVertexBuffers(Model model); +// Lazy allocation of CPU animation buffers for vertex positions and normals, used for software skinning +static void AllocateMeshCPUAnimBuffers(Mesh* mesh) +{ + if (mesh == NULL || mesh->animVertices != NULL || mesh->animNormals != NULL) return; // Buffers already allocated + + mesh->animVertices = (float*)RL_CALLOC(mesh->vertexCount * 3, sizeof(float)); + memcpy(mesh->animVertices, mesh->vertices, mesh->vertexCount * 3 * sizeof(float)); + + if (mesh->normals != NULL) + { + mesh->animNormals = (float*)RL_CALLOC(mesh->vertexCount * 3, sizeof(float)); + memcpy(mesh->animNormals, mesh->normals, mesh->vertexCount * 3 * sizeof(float)); + } +} + //---------------------------------------------------------------------------------- // Module Functions Definition //---------------------------------------------------------------------------------- @@ -2295,11 +2310,9 @@ ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount) return animations; } -// Update model animation data (vertex buffers / bone matrices) for a specific pose +// Update model animation data (bone matrices) for a specific pose // NOTE 1: Request frame could be fractional, using a lerp interpolation between two frames -// NOTE 2: Updated vertex animation data is uploaded to GPU in case of CPU skinning, -// for GPU skinning, bone matrices are uploaded to shader on DrawModelEx() -void UpdateModelAnimation(Model model, ModelAnimation anim, float frame) +void UpdateModelAnimationBones(Model model, ModelAnimation anim, float frame) { if (model.boneMatrices == NULL) return; @@ -2313,8 +2326,8 @@ void UpdateModelAnimation(Model model, ModelAnimation anim, float frame) int nextFrame = currentFrame + 1; float blend = frame - currentFrame; blend = Clamp(blend, 0.0f, 1.0f); - if (currentFrame >= anim.keyframeCount) currentFrame = currentFrame%anim.keyframeCount; - if (nextFrame >= anim.keyframeCount) nextFrame = nextFrame%anim.keyframeCount; + if (currentFrame >= anim.keyframeCount) currentFrame = currentFrame % anim.keyframeCount; + if (nextFrame >= anim.keyframeCount) nextFrame = nextFrame % anim.keyframeCount; Matrix bindPoseMatrix = { 0 }; Matrix currentPoseMatrix = { 0 }; @@ -2336,13 +2349,13 @@ void UpdateModelAnimation(Model model, ModelAnimation anim, float frame) // Compute runtime bone matrix from model current pose //----------------------------------------------------------------------------------- - Transform *bindPoseTransform = &model.skeleton.bindPose[boneIndex]; + Transform* bindPoseTransform = &model.skeleton.bindPose[boneIndex]; bindPoseMatrix = MatrixMultiply( MatrixMultiply(MatrixScale(bindPoseTransform->scale.x, bindPoseTransform->scale.y, bindPoseTransform->scale.z), QuaternionToMatrix(bindPoseTransform->rotation)), MatrixTranslate(bindPoseTransform->translation.x, bindPoseTransform->translation.y, bindPoseTransform->translation.z)); - Transform *currentPoseTransform = &model.currentPose[boneIndex]; + Transform* currentPoseTransform = &model.currentPose[boneIndex]; currentPoseMatrix = MatrixMultiply( MatrixMultiply(MatrixScale(currentPoseTransform->scale.x, currentPoseTransform->scale.y, currentPoseTransform->scale.z), QuaternionToMatrix(currentPoseTransform->rotation)), @@ -2351,6 +2364,18 @@ void UpdateModelAnimation(Model model, ModelAnimation anim, float frame) model.boneMatrices[boneIndex] = MatrixMultiply(MatrixInvert(bindPoseMatrix), currentPoseMatrix); //----------------------------------------------------------------------------------- } + } +} + +// Update model animation data (vertex buffers / bone matrices) for a specific pose +// NOTE 1: Request frame could be fractional, using a lerp interpolation between two frames +// NOTE 2: Updated vertex animation data is uploaded to GPU in case of CPU skinning, +// for GPU skinning, bone matrices are uploaded to shader on DrawModelEx() +void UpdateModelAnimation(Model model, ModelAnimation anim, float frame) +{ + if ((anim.keyframeCount > 0) && (model.skeleton.bones != NULL) && (anim.keyframePoses != NULL)) + { + UpdateModelAnimationBones(model, anim, frame); // CPU skinning, updates CPU buffers and uploads them to GPU // NOTE: On GPU skinning not supported, use CPU skinning @@ -2358,12 +2383,10 @@ void UpdateModelAnimation(Model model, ModelAnimation anim, float frame) } } -// Update model animation data (vertex buffers / bone matrices) for a specific pose, +// Update model animation data (bone matrices) for a specific pose, // defined by two different animations at specific frames blended together // NOTE 1: Request frames could be fractional, using a lerp interpolation between two frames -// NOTE 2: Updated vertex animation data is uploaded to GPU in case of CPU skinning, -// for GPU skinning, bone matrices are uploaded to shader on DrawModelEx() -void UpdateModelAnimationEx(Model model, ModelAnimation animA, float frameA, ModelAnimation animB, float frameB, float blend) +void UpdateModelAnimationBonesEx(Model model, ModelAnimation animA, float frameA, ModelAnimation animB, float frameB, float blend) { if (model.boneMatrices == NULL) return; @@ -2372,20 +2395,20 @@ void UpdateModelAnimationEx(Model model, ModelAnimation animA, float frameA, Mod (blend >= 0.0f) && (blend <= 1.0f)) { // Inter-frame interpolation values for first animation - int currentFrameA = (int)frameA%animA.keyframeCount; + int currentFrameA = (int)frameA % animA.keyframeCount; int nextFrameA = currentFrameA + 1; float blendA = frameA - currentFrameA; blendA = Clamp(blendA, 0.0f, 1.0f); - if (currentFrameA >= animA.keyframeCount) currentFrameA = currentFrameA%animA.keyframeCount; - if (nextFrameA >= animA.keyframeCount) nextFrameA = nextFrameA%animA.keyframeCount; + if (currentFrameA >= animA.keyframeCount) currentFrameA = currentFrameA % animA.keyframeCount; + if (nextFrameA >= animA.keyframeCount) nextFrameA = nextFrameA % animA.keyframeCount; // Inter-frame interpolation values for second animation - int currentFrameB = (int)frameB%animB.keyframeCount; + int currentFrameB = (int)frameB % animB.keyframeCount; int nextFrameB = currentFrameB + 1; float blendB = frameB - currentFrameB; blendB = Clamp(blendB, 0.0f, 1.0f); - if (currentFrameB >= animB.keyframeCount) currentFrameB = currentFrameB%animB.keyframeCount; - if (nextFrameB >= animB.keyframeCount) nextFrameB = nextFrameB%animB.keyframeCount; + if (currentFrameB >= animB.keyframeCount) currentFrameB = currentFrameB % animB.keyframeCount; + if (nextFrameB >= animB.keyframeCount) nextFrameB = nextFrameB % animB.keyframeCount; Matrix bindPoseMatrix = { 0 }; Matrix currentPoseMatrix = { 0 }; @@ -2422,13 +2445,13 @@ void UpdateModelAnimationEx(Model model, ModelAnimation animA, float frameA, Mod // Compute runtime bone matrix from model current pose //----------------------------------------------------------------------------------- - Transform *bindPoseTransform = &model.skeleton.bindPose[boneIndex]; + Transform* bindPoseTransform = &model.skeleton.bindPose[boneIndex]; bindPoseMatrix = MatrixMultiply( MatrixMultiply(MatrixScale(bindPoseTransform->scale.x, bindPoseTransform->scale.y, bindPoseTransform->scale.z), QuaternionToMatrix(bindPoseTransform->rotation)), MatrixTranslate(bindPoseTransform->translation.x, bindPoseTransform->translation.y, bindPoseTransform->translation.z)); - Transform *currentPoseTransform = &model.currentPose[boneIndex]; + Transform* currentPoseTransform = &model.currentPose[boneIndex]; currentPoseMatrix = MatrixMultiply( MatrixMultiply(MatrixScale(currentPoseTransform->scale.x, currentPoseTransform->scale.y, currentPoseTransform->scale.z), QuaternionToMatrix(currentPoseTransform->rotation)), @@ -2466,6 +2489,23 @@ void UpdateModelAnimationEx(Model model, ModelAnimation animA, float frameA, Mod MatrixScale(boneScale.x, boneScale.y, boneScale.z)); */ } + } +} + +// Update model animation data (vertex buffers / bone matrices) for a specific pose, +// defined by two different animations at specific frames blended together +// NOTE 1: Request frames could be fractional, using a lerp interpolation between two frames +// NOTE 2: Updated vertex animation data is uploaded to GPU in case of CPU skinning, +// for GPU skinning, bone matrices are uploaded to shader on DrawModelEx() +void UpdateModelAnimationEx(Model model, ModelAnimation animA, float frameA, ModelAnimation animB, float frameB, float blend) +{ + if (model.boneMatrices == NULL) return; + + if ((animA.keyframeCount > 0) && (animA.keyframePoses != NULL) && + (animB.keyframeCount > 0) && (animB.keyframePoses != NULL) && + (blend >= 0.0f) && (blend <= 1.0f)) + { + UpdateModelAnimationBonesEx(model, animA, frameA, animB, frameB, blend); // CPU skinning, updates CPU buffers and uploads them to GPU (if available) // NOTE: Fallback in case GPU skinning is not supported or enabled @@ -2479,10 +2519,10 @@ static void UpdateModelAnimationVertexBuffers(Model model) { for (int m = 0; m < model.meshCount; m++) { - Mesh mesh = model.meshes[m]; + Mesh* mesh = model.meshes + m; // don't copy the mesh, we may need to allocate buffers Vector3 animVertex = { 0 }; Vector3 animNormal = { 0 }; - const int vertexValuesCount = mesh.vertexCount*3; + const int vertexValuesCount = mesh->vertexCount*3; int boneIndex = 0; int boneCounter = 0; @@ -2490,45 +2530,52 @@ static void UpdateModelAnimationVertexBuffers(Model model) bool bufferUpdateRequired = false; // Flag to check when anim vertex information is updated // Skip if missing bone data or missing anim buffers initialization - if ((mesh.boneWeights == NULL) || (mesh.boneIndices == NULL) || - (mesh.animVertices == NULL) || (mesh.animNormals == NULL)) continue; + if ((mesh->boneWeights == NULL) || (mesh->boneIndices == NULL)) continue; + + // Skip if the shader is using the bones directly +#if defined (SUPPORT_GPU_SKINNING) + Material material = model.materials[model.meshMaterial[m]]; + if ((material.shader.locs[SHADER_LOC_VERTEX_BONEIDS] != -1) ) continue; +#endif + + if (mesh->animVertices == NULL) AllocateMeshCPUAnimBuffers(mesh); for (int vCounter = 0; vCounter < vertexValuesCount; vCounter += 3) { - mesh.animVertices[vCounter] = 0; - mesh.animVertices[vCounter + 1] = 0; - mesh.animVertices[vCounter + 2] = 0; - if (mesh.animNormals != NULL) + mesh->animVertices[vCounter] = 0; + mesh->animVertices[vCounter + 1] = 0; + mesh->animVertices[vCounter + 2] = 0; + if (mesh->animNormals != NULL) { - mesh.animNormals[vCounter] = 0; - mesh.animNormals[vCounter + 1] = 0; - mesh.animNormals[vCounter + 2] = 0; + mesh->animNormals[vCounter] = 0; + mesh->animNormals[vCounter + 1] = 0; + mesh->animNormals[vCounter + 2] = 0; } // Iterates over 4 bones per vertex for (int j = 0; j < 4; j++, boneCounter++) { - boneWeight = mesh.boneWeights[boneCounter]; - boneIndex = mesh.boneIndices[boneCounter]; + boneWeight = mesh->boneWeights[boneCounter]; + boneIndex = mesh->boneIndices[boneCounter]; // Early stop when no transformation will be applied if (boneWeight == 0.0f) continue; - animVertex = (Vector3){ mesh.vertices[vCounter], mesh.vertices[vCounter + 1], mesh.vertices[vCounter + 2] }; + animVertex = (Vector3){ mesh->vertices[vCounter], mesh->vertices[vCounter + 1], mesh->vertices[vCounter + 2] }; animVertex = Vector3Transform(animVertex, model.boneMatrices[boneIndex]); - mesh.animVertices[vCounter] += animVertex.x*boneWeight; - mesh.animVertices[vCounter + 1] += animVertex.y*boneWeight; - mesh.animVertices[vCounter + 2] += animVertex.z*boneWeight; + mesh->animVertices[vCounter] += animVertex.x*boneWeight; + mesh->animVertices[vCounter + 1] += animVertex.y*boneWeight; + mesh->animVertices[vCounter + 2] += animVertex.z*boneWeight; bufferUpdateRequired = true; // Normals processing // NOTE: Using meshes.baseNormals (default normal) to calculate meshes.normals (animated normals) - if ((mesh.normals != NULL) && (mesh.animNormals != NULL )) + if ((mesh->normals != NULL) && (mesh->animNormals != NULL )) { - animNormal = (Vector3){ mesh.normals[vCounter], mesh.normals[vCounter + 1], mesh.normals[vCounter + 2] }; + animNormal = (Vector3){ mesh->normals[vCounter], mesh->normals[vCounter + 1], mesh->normals[vCounter + 2] }; animNormal = Vector3Transform(animNormal, MatrixTranspose(MatrixInvert(model.boneMatrices[boneIndex]))); - mesh.animNormals[vCounter] += animNormal.x*boneWeight; - mesh.animNormals[vCounter + 1] += animNormal.y*boneWeight; - mesh.animNormals[vCounter + 2] += animNormal.z*boneWeight; + mesh->animNormals[vCounter] += animNormal.x*boneWeight; + mesh->animNormals[vCounter + 1] += animNormal.y*boneWeight; + mesh->animNormals[vCounter + 2] += animNormal.z*boneWeight; } } } @@ -2536,8 +2583,8 @@ static void UpdateModelAnimationVertexBuffers(Model model) if (bufferUpdateRequired) { // Update GPU vertex buffers with updated data (position + normals) - rlUpdateVertexBuffer(mesh.vboId[SHADER_LOC_VERTEX_POSITION], mesh.animVertices, mesh.vertexCount*3*sizeof(float), 0); - if (mesh.normals != NULL) rlUpdateVertexBuffer(mesh.vboId[SHADER_LOC_VERTEX_NORMAL], mesh.animNormals, mesh.vertexCount*3*sizeof(float), 0); + rlUpdateVertexBuffer(mesh->vboId[SHADER_LOC_VERTEX_POSITION], mesh->animVertices, mesh->vertexCount*3*sizeof(float), 0); + if (mesh->normals != NULL) rlUpdateVertexBuffer(mesh->vboId[SHADER_LOC_VERTEX_NORMAL], mesh->animNormals, mesh->vertexCount*3*sizeof(float), 0); } } } @@ -4823,13 +4870,6 @@ static Model LoadIQM(const char *fileName) model.meshes[i].triangleCount = imesh[i].num_triangles; model.meshes[i].indices = (unsigned short *)RL_CALLOC(model.meshes[i].triangleCount*3, sizeof(unsigned short)); - -#if !SUPPORT_GPU_SKINNING - // Animated vertex data, processed for rendering - // NOTE: Animated vertex should be re-uploaded to GPU (if not using GPU skinning) - model.meshes[i].animVertices = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); - model.meshes[i].animNormals = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); -#endif } // Triangles data processing @@ -6329,13 +6369,6 @@ static Model LoadGLTF(const char *fileName) } } -#if !SUPPORT_GPU_SKINNING - // Animated vertex data (CPU skinning) - model.meshes[meshIndex].animVertices = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*3, sizeof(float)); - memcpy(model.meshes[meshIndex].animVertices, model.meshes[meshIndex].vertices, model.meshes[meshIndex].vertexCount*3*sizeof(float)); - model.meshes[meshIndex].animNormals = (float *)RL_CALLOC(model.meshes[meshIndex].vertexCount*3, sizeof(float)); - if (model.meshes[meshIndex].normals != NULL) memcpy(model.meshes[meshIndex].animNormals, model.meshes[meshIndex].normals, model.meshes[meshIndex].vertexCount*3*sizeof(float)); -#endif model.meshes[meshIndex].boneCount = model.skeleton.boneCount; meshIndex++; // Move to next mesh @@ -6985,10 +7018,6 @@ static Model LoadM3D(const char *fileName) { model.meshes[k].boneIndices = (unsigned char *)RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(unsigned char)); model.meshes[k].boneWeights = (float *)RL_CALLOC(model.meshes[k].vertexCount*4, sizeof(float)); -#if !SUPPORT_GPU_SKINNING - model.meshes[k].animVertices = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float)); - model.meshes[k].animNormals = (float *)RL_CALLOC(model.meshes[k].vertexCount*3, sizeof(float)); -#endif } model.meshMaterial[k] = mi + 1; @@ -7191,12 +7220,6 @@ static Model LoadM3D(const char *fileName) for (i = 0; i < model.meshCount; i++) { model.meshes[i].boneCount = model.skeleton.boneCount; - -#if !SUPPORT_GPU_SKINNING - // Initialize vertex buffers for CPU skinning - memcpy(model.meshes[i].animVertices, model.meshes[i].vertices, model.meshes[i].vertexCount*3*sizeof(float)); - memcpy(model.meshes[i].animNormals, model.meshes[i].normals, model.meshes[i].vertexCount*3*sizeof(float)); -#endif } // Initialize runtime animation data: current pose and bone matrices